@flarelabs-net/workers-observability-utils
Version:
A collection of Utilities for Capturing Logs and Metrics from Cloudflare Workers
122 lines (121 loc) • 4.44 kB
JavaScript
import { calculateHistogramValue, calculatePercentile } from "./utils/maths";
import { MetricType, } from "./types";
function serializeTags(tags) {
return Object.entries(tags)
.sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
.map(([key, value]) => `${key}:${value}`)
.join(",");
}
export class MetricsDb {
metrics = new Map();
getMetricKey(metric) {
const tagKey = serializeTags(metric.tags);
return `${metric.name}:${metric.type}:${tagKey}`;
}
storeMetric(metric) {
const key = this.getMetricKey(metric);
const existingMetric = this.metrics.get(key);
switch (metric.type) {
case MetricType.COUNT: {
const newValue = existingMetric
? existingMetric.value + Number(metric.value)
: Number(metric.value);
this.metrics.set(key, {
type: metric.type,
name: metric.name,
tags: metric.tags,
value: newValue,
lastUpdated: metric.timestamp,
});
break;
}
case MetricType.GAUGE: {
this.metrics.set(key, {
type: metric.type,
name: metric.name,
tags: metric.tags,
value: Number(metric.value),
lastUpdated: metric.timestamp,
});
break;
}
case MetricType.HISTOGRAM: {
const existingValue = existingMetric
? existingMetric.value
: [];
this.metrics.set(key, {
type: metric.type,
name: metric.name,
tags: metric.tags,
percentiles: metric.options?.percentiles,
aggregates: metric.options?.aggregates,
value: [...existingValue, metric.value],
lastUpdated: metric.timestamp,
});
}
}
}
storeMetrics(metrics) {
for (const metric of metrics) {
this.storeMetric(metric);
}
}
/**
* Get all stored metrics
*/
getAllMetrics() {
return Array.from(this.metrics.values());
}
clearAll() {
this.metrics.clear();
}
getMetricCount() {
return this.metrics.size;
}
/**
* Get the Metrics in a format ready to export to various different sinks
* @param flushWindowS
*/
toMetricPayloads() {
const payloads = [];
const flushTimestamp = Date.now();
for (const metric of this.metrics.values()) {
switch (metric.type) {
case MetricType.COUNT:
case MetricType.GAUGE:
payloads.push({
type: metric.type,
name: metric.name,
value: metric.value,
tags: metric.tags,
timestamp: flushTimestamp,
});
break;
case MetricType.HISTOGRAM: {
const sortedArray = [...metric.value].sort();
for (const percentile of metric.percentiles || []) {
const value = calculatePercentile(sortedArray, percentile);
payloads.push({
type: MetricType.GAUGE,
name: `${metric.name}.p${Math.round(percentile * 100)}`,
value: value,
tags: metric.tags,
timestamp: flushTimestamp,
});
}
for (const aggregate of metric.aggregates || []) {
const value = calculateHistogramValue(aggregate, metric.value);
payloads.push({
type: aggregate === "count" ? MetricType.COUNT : MetricType.GAUGE,
name: `${metric.name}.${aggregate}`,
value: value,
tags: metric.tags,
timestamp: flushTimestamp,
});
}
}
}
}
return payloads;
}
}