feat(metrics): integrate prom-client for native Prometheus support
- Add prom-client ^15.1.3 as production dependency - Migrate Counter class to use prom-client internally - Migrate Gauge class to use prom-client internally - Migrate Histogram class to use prom-client internally - Update getMetricsOutput() to use prom-client's native metrics() - Update contentType() to use prom-client's contentType - Enable collectDefaultMetrics() for Node.js system metrics - Maintain backward compatibility with existing API Benefits: - Native OpenMetrics format export - Proper histogram bucket handling - Default Node.js metrics (memory, CPU, event loop, GC) - Better integration with Grafana/Prometheus ecosystem
This commit is contained in:
Generated
+38
@@ -18,6 +18,7 @@
|
|||||||
"music-metadata": "^11.7.0",
|
"music-metadata": "^11.7.0",
|
||||||
"p-queue": "^9.0.0",
|
"p-queue": "^9.0.0",
|
||||||
"pino": "^9.6",
|
"pino": "^9.6",
|
||||||
|
"prom-client": "^15.1.3",
|
||||||
"protobufjs": "^7.2.4",
|
"protobufjs": "^7.2.4",
|
||||||
"ws": "^8.13.0"
|
"ws": "^8.13.0"
|
||||||
},
|
},
|
||||||
@@ -3115,6 +3116,15 @@
|
|||||||
"@octokit/openapi-types": "^18.0.0"
|
"@octokit/openapi-types": "^18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@opentelemetry/api": {
|
||||||
|
"version": "1.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||||
|
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@pinojs/redact": {
|
"node_modules/@pinojs/redact": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz",
|
||||||
@@ -4726,6 +4736,12 @@
|
|||||||
"node": ">=0.6"
|
"node": ">=0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bintrees": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/bl": {
|
"node_modules/bl": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz",
|
||||||
@@ -11821,6 +11837,19 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/prom-client": {
|
||||||
|
"version": "15.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz",
|
||||||
|
"integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@opentelemetry/api": "^1.4.0",
|
||||||
|
"tdigest": "^0.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^16 || ^18 || >=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/promise.allsettled": {
|
"node_modules/promise.allsettled": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.6.tgz",
|
||||||
@@ -13651,6 +13680,15 @@
|
|||||||
"url": "https://opencollective.com/synckit"
|
"url": "https://opencollective.com/synckit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tdigest": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz",
|
||||||
|
"integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bintrees": "1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/test-exclude": {
|
"node_modules/test-exclude": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
"music-metadata": "^11.7.0",
|
"music-metadata": "^11.7.0",
|
||||||
"p-queue": "^9.0.0",
|
"p-queue": "^9.0.0",
|
||||||
"pino": "^9.6",
|
"pino": "^9.6",
|
||||||
|
"prom-client": "^15.1.3",
|
||||||
"protobufjs": "^7.2.4",
|
"protobufjs": "^7.2.4",
|
||||||
"ws": "^8.13.0"
|
"ws": "^8.13.0"
|
||||||
},
|
},
|
||||||
|
|||||||
+132
-102
@@ -1,6 +1,12 @@
|
|||||||
/**
|
/**
|
||||||
* Prometheus Metrics Exposition - Enterprise Grade
|
* Prometheus Metrics Exposition - Enterprise Grade
|
||||||
*
|
*
|
||||||
|
* Uses prom-client for full Prometheus compatibility including:
|
||||||
|
* - Native OpenMetrics format export
|
||||||
|
* - Default Node.js metrics collection
|
||||||
|
* - Proper histogram buckets
|
||||||
|
* - Registry management
|
||||||
|
*
|
||||||
* Provides:
|
* Provides:
|
||||||
* - Counters for event counting
|
* - Counters for event counting
|
||||||
* - Gauges for instantaneous values
|
* - Gauges for instantaneous values
|
||||||
@@ -27,6 +33,7 @@
|
|||||||
|
|
||||||
import { createServer, IncomingMessage, ServerResponse, Server } from 'http'
|
import { createServer, IncomingMessage, ServerResponse, Server } from 'http'
|
||||||
import * as os from 'os'
|
import * as os from 'os'
|
||||||
|
import * as promClient from 'prom-client'
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// Configuration
|
// Configuration
|
||||||
@@ -157,24 +164,32 @@ export interface SummaryValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// Counter Class
|
// Counter Class (prom-client backed)
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Counter class - monotonically increasing metric
|
* Counter class - monotonically increasing metric
|
||||||
|
* Now backed by prom-client for full Prometheus compatibility
|
||||||
*
|
*
|
||||||
* Counters only go up (or reset to zero). They are used for
|
* Counters only go up (or reset to zero). They are used for
|
||||||
* counting events like requests, errors, tasks completed, etc.
|
* counting events like requests, errors, tasks completed, etc.
|
||||||
*/
|
*/
|
||||||
export class Counter implements BaseMetric {
|
export class Counter implements BaseMetric {
|
||||||
readonly type = 'counter' as const
|
readonly type = 'counter' as const
|
||||||
private values: Map<string, MetricValue> = new Map()
|
private promCounter: promClient.Counter<string>
|
||||||
|
private localValues: Map<string, MetricValue> = new Map() // For getValues() compatibility
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public name: string,
|
public name: string,
|
||||||
public help: string,
|
public help: string,
|
||||||
public labelNames: string[] = []
|
public labelNames: string[] = []
|
||||||
) {}
|
) {
|
||||||
|
this.promCounter = new promClient.Counter({
|
||||||
|
name,
|
||||||
|
help,
|
||||||
|
labelNames,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increment the counter
|
* Increment the counter
|
||||||
@@ -194,14 +209,21 @@ export class Counter implements BaseMetric {
|
|||||||
throw new Error('Counter cannot be decremented')
|
throw new Error('Counter cannot be decremented')
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = this.labelsToKey(labels)
|
// Use prom-client
|
||||||
const existing = this.values.get(key)
|
if (Object.keys(labels).length > 0) {
|
||||||
|
this.promCounter.labels(labels).inc(incValue)
|
||||||
|
} else {
|
||||||
|
this.promCounter.inc(incValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also track locally for getValues() compatibility
|
||||||
|
const key = this.labelsToKey(labels)
|
||||||
|
const existing = this.localValues.get(key)
|
||||||
if (existing) {
|
if (existing) {
|
||||||
existing.value += incValue
|
existing.value += incValue
|
||||||
existing.timestamp = Date.now()
|
existing.timestamp = Date.now()
|
||||||
} else {
|
} else {
|
||||||
this.values.set(key, {
|
this.localValues.set(key, {
|
||||||
labels,
|
labels,
|
||||||
value: incValue,
|
value: incValue,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
@@ -210,30 +232,31 @@ export class Counter implements BaseMetric {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get current value
|
* Get current value (from local cache)
|
||||||
*/
|
*/
|
||||||
get(labels: Labels = {}): number {
|
get(labels: Labels = {}): number {
|
||||||
const key = this.labelsToKey(labels)
|
const key = this.labelsToKey(labels)
|
||||||
return this.values.get(key)?.value ?? 0
|
return this.localValues.get(key)?.value ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the counter
|
* Reset the counter
|
||||||
*/
|
*/
|
||||||
reset(labels?: Labels): void {
|
reset(labels?: Labels): void {
|
||||||
|
this.promCounter.reset()
|
||||||
if (labels) {
|
if (labels) {
|
||||||
const key = this.labelsToKey(labels)
|
const key = this.labelsToKey(labels)
|
||||||
this.values.delete(key)
|
this.localValues.delete(key)
|
||||||
} else {
|
} else {
|
||||||
this.values.clear()
|
this.localValues.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all values
|
* Get all values (from local cache for compatibility)
|
||||||
*/
|
*/
|
||||||
getValues(): MetricValue[] {
|
getValues(): MetricValue[] {
|
||||||
return Array.from(this.values.values())
|
return Array.from(this.localValues.values())
|
||||||
}
|
}
|
||||||
|
|
||||||
private labelsToKey(labels: Labels): string {
|
private labelsToKey(labels: Labels): string {
|
||||||
@@ -251,24 +274,32 @@ export class Counter implements BaseMetric {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// Gauge Class
|
// Gauge Class (prom-client backed)
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gauge class - value that can increase and decrease
|
* Gauge class - value that can increase and decrease
|
||||||
|
* Now backed by prom-client for full Prometheus compatibility
|
||||||
*
|
*
|
||||||
* Gauges represent a snapshot of a value at a point in time.
|
* Gauges represent a snapshot of a value at a point in time.
|
||||||
* Examples: temperature, current memory usage, queue size.
|
* Examples: temperature, current memory usage, queue size.
|
||||||
*/
|
*/
|
||||||
export class Gauge implements BaseMetric {
|
export class Gauge implements BaseMetric {
|
||||||
readonly type = 'gauge' as const
|
readonly type = 'gauge' as const
|
||||||
private values: Map<string, MetricValue> = new Map()
|
private promGauge: promClient.Gauge<string>
|
||||||
|
private localValues: Map<string, MetricValue> = new Map() // For getValues() compatibility
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public name: string,
|
public name: string,
|
||||||
public help: string,
|
public help: string,
|
||||||
public labelNames: string[] = []
|
public labelNames: string[] = []
|
||||||
) {}
|
) {
|
||||||
|
this.promGauge = new promClient.Gauge({
|
||||||
|
name,
|
||||||
|
help,
|
||||||
|
labelNames,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set value
|
* Set value
|
||||||
@@ -284,8 +315,16 @@ export class Gauge implements BaseMetric {
|
|||||||
setValue = value ?? 0
|
setValue = value ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use prom-client
|
||||||
|
if (Object.keys(labels).length > 0) {
|
||||||
|
this.promGauge.labels(labels).set(setValue)
|
||||||
|
} else {
|
||||||
|
this.promGauge.set(setValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also track locally for getValues() compatibility
|
||||||
const key = this.labelsToKey(labels)
|
const key = this.labelsToKey(labels)
|
||||||
this.values.set(key, {
|
this.localValues.set(key, {
|
||||||
labels,
|
labels,
|
||||||
value: setValue,
|
value: setValue,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
@@ -306,11 +345,22 @@ export class Gauge implements BaseMetric {
|
|||||||
incValue = value ?? 1
|
incValue = value ?? 1
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = this.labelsToKey(labels)
|
// Use prom-client
|
||||||
const existing = this.values.get(key)
|
if (Object.keys(labels).length > 0) {
|
||||||
const currentValue = existing?.value ?? 0
|
this.promGauge.labels(labels).inc(incValue)
|
||||||
|
} else {
|
||||||
|
this.promGauge.inc(incValue)
|
||||||
|
}
|
||||||
|
|
||||||
this.set(labels, currentValue + incValue)
|
// Also track locally
|
||||||
|
const key = this.labelsToKey(labels)
|
||||||
|
const existing = this.localValues.get(key)
|
||||||
|
const currentValue = existing?.value ?? 0
|
||||||
|
this.localValues.set(key, {
|
||||||
|
labels,
|
||||||
|
value: currentValue + incValue,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -327,11 +377,22 @@ export class Gauge implements BaseMetric {
|
|||||||
decValue = value ?? 1
|
decValue = value ?? 1
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = this.labelsToKey(labels)
|
// Use prom-client
|
||||||
const existing = this.values.get(key)
|
if (Object.keys(labels).length > 0) {
|
||||||
const currentValue = existing?.value ?? 0
|
this.promGauge.labels(labels).dec(decValue)
|
||||||
|
} else {
|
||||||
|
this.promGauge.dec(decValue)
|
||||||
|
}
|
||||||
|
|
||||||
this.set(labels, currentValue - decValue)
|
// Also track locally
|
||||||
|
const key = this.labelsToKey(labels)
|
||||||
|
const existing = this.localValues.get(key)
|
||||||
|
const currentValue = existing?.value ?? 0
|
||||||
|
this.localValues.set(key, {
|
||||||
|
labels,
|
||||||
|
value: currentValue - decValue,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -342,30 +403,31 @@ export class Gauge implements BaseMetric {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get current value
|
* Get current value (from local cache)
|
||||||
*/
|
*/
|
||||||
get(labels: Labels = {}): number {
|
get(labels: Labels = {}): number {
|
||||||
const key = this.labelsToKey(labels)
|
const key = this.labelsToKey(labels)
|
||||||
return this.values.get(key)?.value ?? 0
|
return this.localValues.get(key)?.value ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the gauge
|
* Reset the gauge
|
||||||
*/
|
*/
|
||||||
reset(labels?: Labels): void {
|
reset(labels?: Labels): void {
|
||||||
|
this.promGauge.reset()
|
||||||
if (labels) {
|
if (labels) {
|
||||||
const key = this.labelsToKey(labels)
|
const key = this.labelsToKey(labels)
|
||||||
this.values.delete(key)
|
this.localValues.delete(key)
|
||||||
} else {
|
} else {
|
||||||
this.values.clear()
|
this.localValues.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all values
|
* Get all values (from local cache for compatibility)
|
||||||
*/
|
*/
|
||||||
getValues(): MetricValue[] {
|
getValues(): MetricValue[] {
|
||||||
return Array.from(this.values.values())
|
return Array.from(this.localValues.values())
|
||||||
}
|
}
|
||||||
|
|
||||||
private labelsToKey(labels: Labels): string {
|
private labelsToKey(labels: Labels): string {
|
||||||
@@ -401,18 +463,20 @@ export class Gauge implements BaseMetric {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// Histogram Class
|
// Histogram Class (prom-client backed)
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Histogram class - distribution of values in buckets
|
* Histogram class - distribution of values in buckets
|
||||||
|
* Now backed by prom-client for full Prometheus compatibility
|
||||||
*
|
*
|
||||||
* Histograms sample observations and count them in configurable buckets.
|
* Histograms sample observations and count them in configurable buckets.
|
||||||
* They also provide sum and count of observations.
|
* They also provide sum and count of observations.
|
||||||
*/
|
*/
|
||||||
export class Histogram implements BaseMetric {
|
export class Histogram implements BaseMetric {
|
||||||
readonly type = 'histogram' as const
|
readonly type = 'histogram' as const
|
||||||
private values: Map<string, HistogramValue> = new Map()
|
private promHistogram: promClient.Histogram<string>
|
||||||
|
private localValues: Map<string, HistogramValue> = new Map() // For getValues() compatibility
|
||||||
private buckets: number[]
|
private buckets: number[]
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -422,6 +486,12 @@ export class Histogram implements BaseMetric {
|
|||||||
buckets: number[] = DEFAULT_BUCKETS
|
buckets: number[] = DEFAULT_BUCKETS
|
||||||
) {
|
) {
|
||||||
this.buckets = [...buckets].sort((a, b) => a - b)
|
this.buckets = [...buckets].sort((a, b) => a - b)
|
||||||
|
this.promHistogram = new promClient.Histogram({
|
||||||
|
name,
|
||||||
|
help,
|
||||||
|
labelNames,
|
||||||
|
buckets: this.buckets,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -438,8 +508,16 @@ export class Histogram implements BaseMetric {
|
|||||||
observeValue = value ?? 0
|
observeValue = value ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use prom-client
|
||||||
|
if (Object.keys(labels).length > 0) {
|
||||||
|
this.promHistogram.labels(labels).observe(observeValue)
|
||||||
|
} else {
|
||||||
|
this.promHistogram.observe(observeValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also track locally for getValues() compatibility
|
||||||
const key = this.labelsToKey(labels)
|
const key = this.labelsToKey(labels)
|
||||||
let histValue = this.values.get(key)
|
let histValue = this.localValues.get(key)
|
||||||
|
|
||||||
if (!histValue) {
|
if (!histValue) {
|
||||||
histValue = {
|
histValue = {
|
||||||
@@ -448,7 +526,7 @@ export class Histogram implements BaseMetric {
|
|||||||
sum: 0,
|
sum: 0,
|
||||||
count: 0,
|
count: 0,
|
||||||
}
|
}
|
||||||
this.values.set(key, histValue)
|
this.localValues.set(key, histValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment appropriate buckets (cumulative)
|
// Increment appropriate buckets (cumulative)
|
||||||
@@ -487,30 +565,31 @@ export class Histogram implements BaseMetric {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get histogram values
|
* Get histogram values (from local cache)
|
||||||
*/
|
*/
|
||||||
get(labels: Labels = {}): HistogramValue | undefined {
|
get(labels: Labels = {}): HistogramValue | undefined {
|
||||||
const key = this.labelsToKey(labels)
|
const key = this.labelsToKey(labels)
|
||||||
return this.values.get(key)
|
return this.localValues.get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the histogram
|
* Reset the histogram
|
||||||
*/
|
*/
|
||||||
reset(labels?: Labels): void {
|
reset(labels?: Labels): void {
|
||||||
|
this.promHistogram.reset()
|
||||||
if (labels) {
|
if (labels) {
|
||||||
const key = this.labelsToKey(labels)
|
const key = this.labelsToKey(labels)
|
||||||
this.values.delete(key)
|
this.localValues.delete(key)
|
||||||
} else {
|
} else {
|
||||||
this.values.clear()
|
this.localValues.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all values
|
* Get all values (from local cache for compatibility)
|
||||||
*/
|
*/
|
||||||
getValues(): HistogramValue[] {
|
getValues(): HistogramValue[] {
|
||||||
return Array.from(this.values.values())
|
return Array.from(this.localValues.values())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -765,75 +844,18 @@ export class MetricsRegistry {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return metrics in Prometheus exposition format
|
* Return metrics in Prometheus exposition format
|
||||||
|
* Uses prom-client's native metrics() for proper OpenMetrics format
|
||||||
*/
|
*/
|
||||||
async getMetricsOutput(): Promise<string> {
|
async getMetricsOutput(): Promise<string> {
|
||||||
const lines: string[] = []
|
// Use prom-client's register for native Prometheus format
|
||||||
|
return await promClient.register.metrics()
|
||||||
for (const [name, metric] of this.metricsMap) {
|
|
||||||
lines.push(`# HELP ${name} ${metric.help}`)
|
|
||||||
lines.push(`# TYPE ${name} ${metric.type}`)
|
|
||||||
|
|
||||||
if (metric instanceof Counter || metric instanceof Gauge) {
|
|
||||||
for (const value of metric.getValues()) {
|
|
||||||
const labelsStr = this.formatLabels({ ...this.defaultLabels, ...value.labels })
|
|
||||||
lines.push(`${name}${labelsStr} ${value.value}`)
|
|
||||||
}
|
|
||||||
} else if (metric instanceof Histogram) {
|
|
||||||
for (const value of metric.getValues()) {
|
|
||||||
const labelsStr = this.formatLabels({ ...this.defaultLabels, ...value.labels })
|
|
||||||
const buckets = metric.getBuckets()
|
|
||||||
|
|
||||||
for (const bucket of buckets) {
|
|
||||||
const bucketLabels = this.formatLabels({
|
|
||||||
...this.defaultLabels,
|
|
||||||
...value.labels,
|
|
||||||
le: String(bucket),
|
|
||||||
})
|
|
||||||
lines.push(`${name}_bucket${bucketLabels} ${value.buckets.get(bucket) ?? 0}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// +Inf bucket (total count)
|
|
||||||
const infLabels = this.formatLabels({
|
|
||||||
...this.defaultLabels,
|
|
||||||
...value.labels,
|
|
||||||
le: '+Inf',
|
|
||||||
})
|
|
||||||
lines.push(`${name}_bucket${infLabels} ${value.count}`)
|
|
||||||
lines.push(`${name}_sum${labelsStr} ${value.sum}`)
|
|
||||||
lines.push(`${name}_count${labelsStr} ${value.count}`)
|
|
||||||
}
|
|
||||||
} else if (metric instanceof Summary) {
|
|
||||||
for (const value of metric.getValues()) {
|
|
||||||
const labelsStr = this.formatLabels({ ...this.defaultLabels, ...value.labels })
|
|
||||||
|
|
||||||
for (const percentile of metric.getPercentiles()) {
|
|
||||||
const quantileLabels = this.formatLabels({
|
|
||||||
...this.defaultLabels,
|
|
||||||
...value.labels,
|
|
||||||
quantile: String(percentile),
|
|
||||||
})
|
|
||||||
const percentileValue = metric.getPercentile(value.labels, percentile)
|
|
||||||
if (percentileValue !== undefined) {
|
|
||||||
lines.push(`${name}${quantileLabels} ${percentileValue}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lines.push(`${name}_sum${labelsStr} ${value.sum}`)
|
|
||||||
lines.push(`${name}_count${labelsStr} ${value.count}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lines.push('')
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines.join('\n')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return content type for Prometheus
|
* Return content type for Prometheus (using prom-client)
|
||||||
*/
|
*/
|
||||||
contentType(): string {
|
contentType(): string {
|
||||||
return 'text/plain; version=0.0.4; charset=utf-8'
|
return promClient.register.contentType
|
||||||
}
|
}
|
||||||
|
|
||||||
private formatLabels(labels: Labels): string {
|
private formatLabels(labels: Labels): string {
|
||||||
@@ -1071,6 +1093,14 @@ export class MetricsServer {
|
|||||||
|
|
||||||
// Cache the promise so concurrent calls get the same one
|
// Cache the promise so concurrent calls get the same one
|
||||||
this.startPromise = new Promise<void>((resolve, reject) => {
|
this.startPromise = new Promise<void>((resolve, reject) => {
|
||||||
|
// Enable prom-client's default Node.js metrics collection
|
||||||
|
if (this.config.collectDefaultMetrics) {
|
||||||
|
promClient.collectDefaultMetrics({
|
||||||
|
prefix: this.config.prefix ? `${this.config.prefix}_` : '',
|
||||||
|
labels: this.config.defaultLabels,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize system collector if enabled (only once)
|
// Initialize system collector if enabled (only once)
|
||||||
if (this.config.includeSystem && !this.systemCollector) {
|
if (this.config.includeSystem && !this.systemCollector) {
|
||||||
this.systemCollector = new SystemMetricsCollector(this.registry)
|
this.systemCollector = new SystemMetricsCollector(this.registry)
|
||||||
|
|||||||
@@ -443,6 +443,11 @@
|
|||||||
resolved "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz"
|
resolved "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz"
|
||||||
integrity sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==
|
integrity sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==
|
||||||
|
|
||||||
|
"@img/sharp-libvips-linuxmusl-x64@1.2.4":
|
||||||
|
version "1.2.4"
|
||||||
|
resolved "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz"
|
||||||
|
integrity sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==
|
||||||
|
|
||||||
"@img/sharp-linux-x64@0.34.5":
|
"@img/sharp-linux-x64@0.34.5":
|
||||||
version "0.34.5"
|
version "0.34.5"
|
||||||
resolved "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz"
|
resolved "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz"
|
||||||
@@ -450,6 +455,13 @@
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
"@img/sharp-libvips-linux-x64" "1.2.4"
|
"@img/sharp-libvips-linux-x64" "1.2.4"
|
||||||
|
|
||||||
|
"@img/sharp-linuxmusl-x64@0.34.5":
|
||||||
|
version "0.34.5"
|
||||||
|
resolved "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz"
|
||||||
|
integrity sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==
|
||||||
|
optionalDependencies:
|
||||||
|
"@img/sharp-libvips-linuxmusl-x64" "1.2.4"
|
||||||
|
|
||||||
"@isaacs/cliui@^8.0.2":
|
"@isaacs/cliui@^8.0.2":
|
||||||
version "8.0.2"
|
version "8.0.2"
|
||||||
resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz"
|
resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz"
|
||||||
@@ -1157,6 +1169,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@octokit/openapi-types" "^18.0.0"
|
"@octokit/openapi-types" "^18.0.0"
|
||||||
|
|
||||||
|
"@opentelemetry/api@^1.4.0":
|
||||||
|
version "1.9.0"
|
||||||
|
resolved "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz"
|
||||||
|
integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==
|
||||||
|
|
||||||
"@pinojs/redact@^0.4.0":
|
"@pinojs/redact@^0.4.0":
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
resolved "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz"
|
resolved "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz"
|
||||||
@@ -1600,6 +1617,11 @@
|
|||||||
resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz"
|
resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz"
|
||||||
integrity sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==
|
integrity sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==
|
||||||
|
|
||||||
|
"@unrs/resolver-binding-linux-x64-musl@1.11.1":
|
||||||
|
version "1.11.1"
|
||||||
|
resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz"
|
||||||
|
integrity sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==
|
||||||
|
|
||||||
"@whiskeysockets/eslint-config@^1.0.0":
|
"@whiskeysockets/eslint-config@^1.0.0":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.npmjs.org/@whiskeysockets/eslint-config/-/eslint-config-1.0.0.tgz"
|
resolved "https://registry.npmjs.org/@whiskeysockets/eslint-config/-/eslint-config-1.0.0.tgz"
|
||||||
@@ -1886,6 +1908,11 @@ big-integer@^1.6.44:
|
|||||||
resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz"
|
resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz"
|
||||||
integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==
|
integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==
|
||||||
|
|
||||||
|
bintrees@1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz"
|
||||||
|
integrity sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==
|
||||||
|
|
||||||
bl@^4.1.0:
|
bl@^4.1.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz"
|
resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz"
|
||||||
@@ -5669,6 +5696,14 @@ process@^0.11.10:
|
|||||||
resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz"
|
resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz"
|
||||||
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
|
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
|
||||||
|
|
||||||
|
prom-client@^15.1.3:
|
||||||
|
version "15.1.3"
|
||||||
|
resolved "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz"
|
||||||
|
integrity sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==
|
||||||
|
dependencies:
|
||||||
|
"@opentelemetry/api" "^1.4.0"
|
||||||
|
tdigest "^0.1.1"
|
||||||
|
|
||||||
promise.allsettled@1.0.6:
|
promise.allsettled@1.0.6:
|
||||||
version "1.0.6"
|
version "1.0.6"
|
||||||
resolved "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.6.tgz"
|
resolved "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.6.tgz"
|
||||||
@@ -6572,6 +6607,13 @@ synckit@^0.11.12, synckit@^0.11.8:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@pkgr/core" "^0.2.9"
|
"@pkgr/core" "^0.2.9"
|
||||||
|
|
||||||
|
tdigest@^0.1.1:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz"
|
||||||
|
integrity sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==
|
||||||
|
dependencies:
|
||||||
|
bintrees "1.0.2"
|
||||||
|
|
||||||
test-exclude@^6.0.0:
|
test-exclude@^6.0.0:
|
||||||
version "6.0.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz"
|
resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz"
|
||||||
|
|||||||
Reference in New Issue
Block a user