From 4e2229752bf3122592afb6c93a7cce4e191dba72 Mon Sep 17 00:00:00 2001 From: Juuso Tapaninen Date: Tue, 3 Jun 2025 16:55:55 +0300 Subject: [PATCH] fix(core)!: Change last activity to use unix time (#15951) --- packages/cli/BREAKING-CHANGES.md | 14 ++++++++++ .../prometheus-metrics.service.test.ts | 3 +- .../src/metrics/prometheus-metrics.service.ts | 9 +++--- .../integration/prometheus-metrics.test.ts | 28 +++++++++++++------ 4 files changed, 39 insertions(+), 15 deletions(-) diff --git a/packages/cli/BREAKING-CHANGES.md b/packages/cli/BREAKING-CHANGES.md index fd43dba3e9..7cebc7dccf 100644 --- a/packages/cli/BREAKING-CHANGES.md +++ b/packages/cli/BREAKING-CHANGES.md @@ -2,6 +2,20 @@ This list shows all the versions which include breaking changes and how to upgrade. +## 1.98.0 + +### What changed? + +The `last_activity` metric included as a part of route metrics has been changed to output a Unix time in seconds from +the previous timestamp label approach. The labeling approach could result in high cardinality within Prometheus and +thus result in poorer performance. + +### When is action necessary? + +If you've been ingesting route metrics from your n8n instance (version 1.81.0 and newer), you should analyze +how the `last_activity` metric has affected your Prometheus instance and potentially clean up the old data. Future +metrics will also be served in a different format, which needs to be taken into account. + ## 1.83.0 ### What changed? diff --git a/packages/cli/src/metrics/__tests__/prometheus-metrics.service.test.ts b/packages/cli/src/metrics/__tests__/prometheus-metrics.service.test.ts index e6ca160d1f..64209bea72 100644 --- a/packages/cli/src/metrics/__tests__/prometheus-metrics.service.test.ts +++ b/packages/cli/src/metrics/__tests__/prometheus-metrics.service.test.ts @@ -161,8 +161,7 @@ describe('PrometheusMetricsService', () => { expect(promClient.Gauge).toHaveBeenNthCalledWith(2, { name: 'n8n_last_activity', - help: 'last instance activity (backend request).', - labelNames: ['timestamp'], + help: 'last instance activity (backend request) in Unix time (seconds).', }); expect(app.use).toHaveBeenCalledWith( diff --git a/packages/cli/src/metrics/prometheus-metrics.service.ts b/packages/cli/src/metrics/prometheus-metrics.service.ts index 7dc1f46cf5..05eb8bc3fa 100644 --- a/packages/cli/src/metrics/prometheus-metrics.service.ts +++ b/packages/cli/src/metrics/prometheus-metrics.service.ts @@ -3,6 +3,7 @@ import { WorkflowRepository } from '@n8n/db'; import { Service } from '@n8n/di'; import type express from 'express'; import promBundle from 'express-prom-bundle'; +import { DateTime } from 'luxon'; import { InstanceSettings } from 'n8n-core'; import { EventMessageTypeNames } from 'n8n-workflow'; import promClient, { type Counter, type Gauge } from 'prom-client'; @@ -137,11 +138,10 @@ export class PrometheusMetricsService { const activityGauge = new promClient.Gauge({ name: this.prefix + 'last_activity', - help: 'last instance activity (backend request).', - labelNames: ['timestamp'], + help: 'last instance activity (backend request) in Unix time (seconds).', }); - activityGauge.set({ timestamp: new Date().toISOString() }, 1); + activityGauge.set(DateTime.now().toUnixInteger()); app.use( [ @@ -155,8 +155,7 @@ export class PrometheusMetricsService { `/${this.globalConfig.endpoints.formTest}/`, ], async (req, res, next) => { - activityGauge.reset(); - activityGauge.set({ timestamp: new Date().toISOString() }, 1); + activityGauge.set(DateTime.now().toUnixInteger()); await metricsMiddleware(req, res, next); }, diff --git a/packages/cli/test/integration/prometheus-metrics.test.ts b/packages/cli/test/integration/prometheus-metrics.test.ts index f1bf5f3765..2325a25f17 100644 --- a/packages/cli/test/integration/prometheus-metrics.test.ts +++ b/packages/cli/test/integration/prometheus-metrics.test.ts @@ -1,6 +1,7 @@ import { GlobalConfig } from '@n8n/config'; import { WorkflowRepository } from '@n8n/db'; import { Container } from '@n8n/di'; +import { DateTime } from 'luxon'; import { parse as semverParse } from 'semver'; import request, { type Response } from 'supertest'; @@ -53,6 +54,11 @@ describe('PrometheusMetricsService', () => { prometheusService.disableAllLabels(); }); + afterEach(() => { + // Make sure fake timers aren't in effect after a test + jest.useRealTimers(); + }); + it('should return n8n version', async () => { /** * Arrange @@ -211,9 +217,11 @@ describe('PrometheusMetricsService', () => { /** * Arrange */ + const startTime = DateTime.now().toUnixInteger(); + jest.useFakeTimers().setSystemTime(startTime * 1000); + prometheusService.enableMetric('routes'); await prometheusService.init(server.app); - await agent.get('/api/v1/workflows'); /** * Act @@ -230,26 +238,30 @@ describe('PrometheusMetricsService', () => { expect(lines).toContainEqual(expect.stringContaining('n8n_test_last_activity')); - const lastActivityLine = lines.find((line) => - line.startsWith('n8n_test_last_activity{timestamp='), - ); + const lastActivityLine = lines.find((line) => line.startsWith('n8n_test_last_activity')); expect(lastActivityLine).toBeDefined(); - expect(lastActivityLine?.endsWith('1')).toBe(true); + + const value = lastActivityLine!.split(' ')[1]; + + expect(parseInt(value, 10)).toBe(startTime); // Update last activity + jest.advanceTimersByTime(1000); await agent.get('/api/v1/workflows'); response = await agent.get('/metrics'); const updatedLines = toLines(response); const newLastActivityLine = updatedLines.find((line) => - line.startsWith('n8n_test_last_activity{timestamp='), + line.startsWith('n8n_test_last_activity'), ); expect(newLastActivityLine).toBeDefined(); - // Timestamp label should be different - expect(newLastActivityLine).not.toBe(lastActivityLine); + + const newValue = newLastActivityLine!.split(' ')[1]; + + expect(parseInt(newValue, 10)).toBe(startTime + 1); }); it('should return labels in route metrics if enabled', async () => {