Bugfix/Prevent duplicated metrics on prometheus and opentel (#4269)

prevent duplicated metrics on prometheus and opentel
This commit is contained in:
Henry Heng
2025-04-08 17:01:26 +08:00
committed by GitHub
parent f963e5aa48
commit 3098c8e75f
2 changed files with 287 additions and 129 deletions
+91 -33
View File
@@ -12,6 +12,9 @@ export class Prometheus implements IMetricsProvider {
constructor(app: express.Application) {
this.app = app
// Clear any existing default registry metrics to avoid conflicts
promClient.register.clear()
// Create a separate registry for our metrics
this.register = new promClient.Registry()
}
@@ -27,48 +30,87 @@ export class Prometheus implements IMetricsProvider {
// look at the FLOWISE_COUNTER enum in Interface.Metrics.ts and get all values
// for each counter in the enum, create a new promClient.Counter and add it to the registry
this.counters = new Map<string, promClient.Counter<string>>()
this.counters = new Map<string, promClient.Counter<string> | promClient.Gauge<string> | promClient.Histogram<string>>()
const enumEntries = Object.entries(FLOWISE_METRIC_COUNTERS)
enumEntries.forEach(([name, value]) => {
// derive proper counter name from the enum value (chatflow_created = Chatflow Created)
const properCounterName: string = name.replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase())
this.counters.set(
value,
new promClient.Counter({
name: value,
help: `Total number of ${properCounterName}`,
labelNames: ['status']
})
)
try {
this.counters.set(
value,
new promClient.Counter({
name: value,
help: `Total number of ${properCounterName}`,
labelNames: ['status'],
registers: [this.register] // Explicitly set the registry
})
)
} catch (error) {
// If metric already exists, get it from the registry instead
const existingMetrics = this.register.getSingleMetric(value)
if (existingMetrics) {
this.counters.set(value, existingMetrics as promClient.Counter<string>)
}
}
})
// in addition to the enum counters, add a few more custom counters
// version, http_request_duration_ms, http_requests_total
const versionGaugeCounter = new promClient.Gauge({
name: 'flowise_version_info',
help: 'Flowise version info.',
labelNames: ['version']
})
try {
const versionGaugeCounter = new promClient.Gauge({
name: 'flowise_version_info',
help: 'Flowise version info.',
labelNames: ['version'],
registers: [this.register] // Explicitly set the registry
})
const { version } = await getVersion()
versionGaugeCounter.set({ version: 'v' + version }, 1)
this.counters.set('flowise_version', versionGaugeCounter)
const { version } = await getVersion()
versionGaugeCounter.set({ version: 'v' + version }, 1)
this.counters.set('flowise_version', versionGaugeCounter)
} catch (error) {
// If metric already exists, get it from the registry
const existingMetric = this.register.getSingleMetric('flowise_version')
if (existingMetric) {
this.counters.set('flowise_version', existingMetric as promClient.Gauge<string>)
}
}
this.httpRequestDurationMicroseconds = new promClient.Histogram({
name: 'http_request_duration_ms',
help: 'Duration of HTTP requests in ms',
labelNames: ['method', 'route', 'code'],
buckets: [1, 5, 15, 50, 100, 200, 300, 400, 500] // buckets for response time from 0.1ms to 500ms
})
this.counters.set('http_request_duration_ms', this.httpRequestDurationMicroseconds)
try {
this.httpRequestDurationMicroseconds = new promClient.Histogram({
name: 'http_request_duration_ms',
help: 'Duration of HTTP requests in ms',
labelNames: ['method', 'route', 'code'],
buckets: [1, 5, 15, 50, 100, 200, 300, 400, 500], // buckets for response time from 0.1ms to 500ms
registers: [this.register] // Explicitly set the registry
})
this.counters.set('http_request_duration_ms', this.httpRequestDurationMicroseconds)
} catch (error) {
// If metric already exists, get it from the registry
const existingMetric = this.register.getSingleMetric('http_request_duration_ms')
if (existingMetric) {
this.httpRequestDurationMicroseconds = existingMetric as Histogram<string>
this.counters.set('http_request_duration_ms', this.httpRequestDurationMicroseconds)
}
}
this.requestCounter = new Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'path', 'status']
})
this.counters.set('http_requests_total', this.requestCounter)
try {
this.requestCounter = new Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'path', 'status'],
registers: [this.register] // Explicitly set the registry
})
this.counters.set('http_requests_total', this.requestCounter)
} catch (error) {
// If metric already exists, get it from the registry
const existingMetric = this.register.getSingleMetric('http_requests_total')
if (existingMetric) {
this.requestCounter = existingMetric as Counter<string>
this.counters.set('http_requests_total', this.requestCounter)
}
}
// Only register metrics that aren't already in the registry
this.registerMetrics()
await this.setupMetricsEndpoint()
}
@@ -111,12 +153,28 @@ export class Prometheus implements IMetricsProvider {
private registerMetrics() {
if (process.env.METRICS_INCLUDE_NODE_METRICS !== 'false') {
// Clear any existing default metrics to avoid conflicts
promClient.register.clear()
// enable default metrics like CPU usage, memory usage, etc.
promClient.collectDefaultMetrics({ register: this.register })
// and ensure they're only registered with our custom registry
promClient.collectDefaultMetrics({
register: this.register,
prefix: 'flowise_' // Add a prefix to avoid conflicts
})
}
// Add our custom metrics to the registry
// Add only the custom metrics that haven't been registered yet
for (const counter of this.counters.values()) {
this.register.registerMetric(counter)
try {
// Type assertion to access the name property
const metricName = (counter as any).name
if (!this.register.getSingleMetric(metricName)) {
this.register.registerMetric(counter)
}
} catch (error) {
// If we can't register the metric, it probably already exists
// Just continue with the next one
}
}
}
}