diff --git a/.changeset/healthy-waves-compare.md b/.changeset/healthy-waves-compare.md new file mode 100644 index 0000000000..cdd19d1450 --- /dev/null +++ b/.changeset/healthy-waves-compare.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +Deprecated Prometheus metrics in favour of OpenTelemtry metrics. diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index fe8ccc609d..e29c1d3c9a 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -47,6 +47,7 @@ "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/plugin-search-common": "workspace:^", "@backstage/types": "workspace:^", + "@opentelemetry/api": "^1.3.0", "@types/express": "^4.17.6", "codeowners-utils": "^1.0.2", "core-js": "^3.6.5", diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts index bef2878099..a27e5a0ce4 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts @@ -23,6 +23,7 @@ import { assertError, serializeError, stringifyError } from '@backstage/errors'; import { Hash } from 'crypto'; import stableStringify from 'fast-json-stable-stringify'; import { Logger } from 'winston'; +import { metrics } from '@opentelemetry/api'; import { ProcessingDatabase, RefreshStateItem } from '../database/types'; import { createCounterMetric, createSummaryMetric } from '../util/metrics'; import { @@ -257,62 +258,124 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { // Helps wrap the timing and logging behaviors function progressTracker() { - const stitchedEntities = createCounterMetric({ + // prom-client metrics are deprecated in favour of OpenTelemetry metrics. + const promStitchedEntities = createCounterMetric({ name: 'catalog_stitched_entities_count', - help: 'Amount of entities stitched', + help: 'Amount of entities stitched. DEPRECATED, use OpenTelemetry metrics instead', }); - const processedEntities = createCounterMetric({ + const promProcessedEntities = createCounterMetric({ name: 'catalog_processed_entities_count', - help: 'Amount of entities processed', + help: 'Amount of entities processed, DEPRECATED, use OpenTelemetry metrics instead', labelNames: ['result'], }); - const processingDuration = createSummaryMetric({ + const promProcessingDuration = createSummaryMetric({ name: 'catalog_processing_duration_seconds', - help: 'Time spent executing the full processing flow', + help: 'Time spent executing the full processing flow, DEPRECATED, use OpenTelemetry metrics instead', labelNames: ['result'], }); - const processorsDuration = createSummaryMetric({ + const promProcessorsDuration = createSummaryMetric({ name: 'catalog_processors_duration_seconds', - help: 'Time spent executing catalog processors', + help: 'Time spent executing catalog processors, DEPRECATED, use OpenTelemetry metrics instead', labelNames: ['result'], }); - const processingQueueDelay = createSummaryMetric({ + const promProcessingQueueDelay = createSummaryMetric({ name: 'catalog_processing_queue_delay_seconds', - help: 'The amount of delay between being scheduled for processing, and the start of actually being processed', + help: 'The amount of delay between being scheduled for processing, and the start of actually being processed, DEPRECATED, use OpenTelemetry metrics instead', }); + const meter = metrics.getMeter('default'); + const stitchedEntities = meter.createCounter( + 'catalog.stitched.entities.count', + { + description: 'Amount of entities stitched', + }, + ); + + const processedEntities = meter.createCounter( + 'catalog.stitched.entities.count', + { description: 'Amount of entities processed' }, + ); + + const processingDuration = meter.createHistogram( + 'catalog.processing.duration', + { + description: 'Time spent executing the full processing flow', + unit: 'seconds', + }, + ); + + const processorsDuration = meter.createHistogram( + 'catalog.processors.duration', + { + description: 'Time spent executing catalog processors', + unit: 'seconds', + }, + ); + + const processingQueueDelay = meter.createHistogram( + 'catalog.processing.queue.delay', + { + description: + 'The amount of delay between being scheduled for processing, and the start of actually being processed', + unit: 'seconds', + }, + ); + function processStart(item: RefreshStateItem, logger: Logger) { + const startTime = process.hrtime(); + const endOverallTimer = promProcessingDuration.startTimer(); + const endProcessorsTimer = promProcessorsDuration.startTimer(); + logger.debug(`Processing ${item.entityRef}`); if (item.nextUpdateAt) { - processingQueueDelay.observe(-item.nextUpdateAt.diffNow().as('seconds')); + promProcessingQueueDelay.observe( + -item.nextUpdateAt.diffNow().as('seconds'), + ); + processingQueueDelay.record(-item.nextUpdateAt.diffNow().as('seconds')); } - const endOverallTimer = processingDuration.startTimer(); - const endProcessorsTimer = processorsDuration.startTimer(); + function endTime() { + const delta = process.hrtime(startTime); + return delta[0] + delta[1] / 1e9; + } function markProcessorsCompleted(result: EntityProcessingResult) { endProcessorsTimer({ result: result.ok ? 'ok' : 'failed' }); + processorsDuration.record(endTime(), { + result: result.ok ? 'ok' : 'failed', + }); } function markSuccessfulWithNoChanges() { endOverallTimer({ result: 'unchanged' }); - processedEntities.inc({ result: 'unchanged' }, 1); + promProcessedEntities.inc({ result: 'unchanged' }, 1); + + processingDuration.record(endTime(), { result: 'unchanged' }); + processedEntities.add(1, { result: 'unchanged' }); } function markSuccessfulWithErrors() { endOverallTimer({ result: 'errors' }); - processedEntities.inc({ result: 'errors' }, 1); + promProcessedEntities.inc({ result: 'errors' }, 1); + + processingDuration.record(endTime(), { result: 'errors' }); + processedEntities.add(1, { result: 'errors' }); } function markSuccessfulWithChanges(stitchedCount: number) { endOverallTimer({ result: 'changed' }); - stitchedEntities.inc(stitchedCount); - processedEntities.inc({ result: 'changed' }, 1); + promStitchedEntities.inc(stitchedCount); + promProcessedEntities.inc({ result: 'changed' }, 1); + + processingDuration.record(endTime(), { result: 'changed' }); + stitchedEntities.add(stitchedCount); + processedEntities.add(1, { result: 'changed' }); } function markFailed(error: Error) { - processedEntities.inc({ result: 'failed' }, 1); + promProcessedEntities.inc({ result: 'failed' }, 1); + processedEntities.add(1, { result: 'failed' }); logger.warn(`Processing of ${item.entityRef} failed`, error); } diff --git a/yarn.lock b/yarn.lock index 84aaed0d0c..5a4116ed74 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5215,6 +5215,7 @@ __metadata: "@backstage/plugin-search-backend-node": "workspace:^" "@backstage/plugin-search-common": "workspace:^" "@backstage/types": "workspace:^" + "@opentelemetry/api": ^1.3.0 "@types/core-js": ^2.5.4 "@types/express": ^4.17.6 "@types/git-url-parse": ^9.0.0 @@ -12218,10 +12219,66 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/api@npm:^1.0.1": - version: 1.0.4 - resolution: "@opentelemetry/api@npm:1.0.4" - checksum: 793e9b5c21666b647a60c58c46c3e00ad1dac38505102b026ad0ef617571d637aca54a18533a73c1e288c95b5ac77e2db17f96467f11833ac1165338e1184260 +"@opentelemetry/api@npm:^1.0.1, @opentelemetry/api@npm:^1.3.0": + version: 1.3.0 + resolution: "@opentelemetry/api@npm:1.3.0" + checksum: 33d284b67b6fab20ff72961d289c6487d3cb27caf7489f0231d7030551f82871e081e744b0390751d8aef3bf1614bd79f854788901a354e15274f552581fb374 + languageName: node + linkType: hard + +"@opentelemetry/core@npm:1.8.0": + version: 1.8.0 + resolution: "@opentelemetry/core@npm:1.8.0" + dependencies: + "@opentelemetry/semantic-conventions": 1.8.0 + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.4.0" + checksum: 09cd58ec764f97b175af47fbaff335f06a31914fcb59b30c4f96f6ba69391ce4e59e3da46a95fd15a7d0e8fc5c638195aba95ca1a916127207481f1283991c97 + languageName: node + linkType: hard + +"@opentelemetry/exporter-prometheus@npm:^0.34.0": + version: 0.34.0 + resolution: "@opentelemetry/exporter-prometheus@npm:0.34.0" + dependencies: + "@opentelemetry/core": 1.8.0 + "@opentelemetry/resources": 1.8.0 + "@opentelemetry/sdk-metrics": 1.8.0 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 6c17d5ec1c638fb5f561230f706d84866d504aa2805ed74ab2174dae9a1c9d39ce909b61ba9796d38fbcdef85f76805e23d5c80d762231e08384e032beb38b65 + languageName: node + linkType: hard + +"@opentelemetry/resources@npm:1.8.0": + version: 1.8.0 + resolution: "@opentelemetry/resources@npm:1.8.0" + dependencies: + "@opentelemetry/core": 1.8.0 + "@opentelemetry/semantic-conventions": 1.8.0 + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.4.0" + checksum: eeea7864c486d31679dbae3c31e9badf277ece24ba8bd063e2bcc34b2d7240f543678b8106743aff30c6b0f028f2bed286d64cfd7b5cde6f9a0f6a9271a3fce1 + languageName: node + linkType: hard + +"@opentelemetry/sdk-metrics@npm:1.8.0, @opentelemetry/sdk-metrics@npm:^1.8.0": + version: 1.8.0 + resolution: "@opentelemetry/sdk-metrics@npm:1.8.0" + dependencies: + "@opentelemetry/core": 1.8.0 + "@opentelemetry/resources": 1.8.0 + lodash.merge: 4.6.2 + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.4.0" + checksum: 8dfb82e70b14fe2e95ce3f3d0b18e42981bfabe64c63b7f8c429aed197815356836a0350ad5cf38696f561c616ff5572f2ca63f4f968ae1367ed7180f730cad7 + languageName: node + linkType: hard + +"@opentelemetry/semantic-conventions@npm:1.8.0": + version: 1.8.0 + resolution: "@opentelemetry/semantic-conventions@npm:1.8.0" + checksum: df30ad9486b6c611c4110fab80815301a7cc9cb320983d6c5792a1b411dc4e4f04c489b2abfdea0da7f7bbb27b9f3c456764ba07f23432900a9bbcbc5f98ff58 languageName: node linkType: hard @@ -22007,6 +22064,9 @@ __metadata: "@backstage/plugin-todo-backend": "workspace:^" "@gitbeaker/node": ^35.1.0 "@octokit/rest": ^19.0.3 + "@opentelemetry/api": ^1.3.0 + "@opentelemetry/exporter-prometheus": ^0.34.0 + "@opentelemetry/sdk-metrics": ^1.8.0 "@types/dockerode": ^3.3.0 "@types/express": ^4.17.6 "@types/express-serve-static-core": ^4.17.5 @@ -27627,7 +27687,7 @@ __metadata: languageName: node linkType: hard -"lodash.merge@npm:^4.6.2": +"lodash.merge@npm:4.6.2, lodash.merge@npm:^4.6.2": version: 4.6.2 resolution: "lodash.merge@npm:4.6.2" checksum: ad580b4bdbb7ca1f7abf7e1bce63a9a0b98e370cf40194b03380a46b4ed799c9573029599caebc1b14e3f24b111aef72b96674a56cfa105e0f5ac70546cdc005