diff --git a/.changeset/large-monkeys-dress.md b/.changeset/large-monkeys-dress.md new file mode 100644 index 0000000000..369a3994c5 --- /dev/null +++ b/.changeset/large-monkeys-dress.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +Added OpenTelemetry support to Scaffolder metrics diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index a8413f9931..45d4c33eb9 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -86,6 +86,7 @@ "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", "@backstage/types": "workspace:^", + "@opentelemetry/api": "^1.3.0", "@types/express": "^4.17.6", "@types/luxon": "^3.0.0", "concat-stream": "^2.0.0", diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 3b88fd0f10..3d7d8b8b1d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -26,6 +26,7 @@ import { PassThrough } from 'stream'; import { generateExampleOutput, isTruthy } from './helper'; import { validate as validateJsonSchema } from 'jsonschema'; import { TemplateActionRegistry } from '../actions'; +import { metrics } from '@opentelemetry/api'; import { SecureTemplater, SecureTemplateRenderer, @@ -520,36 +521,62 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { } function scaffoldingTracker() { - const taskCount = createCounterMetric({ + // prom-client metrics are deprecated in favour of OpenTelemetry metrics. + const promTaskCount = createCounterMetric({ name: 'scaffolder_task_count', help: 'Count of task runs', labelNames: ['template', 'user', 'result'], }); - const taskDuration = createHistogramMetric({ + const promTaskDuration = createHistogramMetric({ name: 'scaffolder_task_duration', help: 'Duration of a task run', labelNames: ['template', 'result'], }); - const stepCount = createCounterMetric({ + const promtStepCount = createCounterMetric({ name: 'scaffolder_step_count', help: 'Count of step runs', labelNames: ['template', 'step', 'result'], }); - const stepDuration = createHistogramMetric({ + const promStepDuration = createHistogramMetric({ name: 'scaffolder_step_duration', help: 'Duration of a step runs', labelNames: ['template', 'step', 'result'], }); + const meter = metrics.getMeter('default'); + const taskCount = meter.createCounter('scaffolder.task.count', { + description: 'Count of task runs', + }); + + const taskDuration = meter.createHistogram('scaffolder.task.duration', { + description: 'Duration of a task run', + unit: 'seconds', + }); + + const stepCount = meter.createCounter('scaffolder.step.count', { + description: 'Count of step runs', + }); + + const stepDuration = meter.createHistogram('scaffolder.step.duration', { + description: 'Duration of a step runs', + unit: 'seconds', + }); + async function taskStart(task: TaskContext) { await task.emitLog(`Starting up task with ${task.spec.steps.length} steps`); const template = task.spec.templateInfo?.entityRef || ''; const user = task.spec.user?.ref || ''; - const taskTimer = taskDuration.startTimer({ + const startTime = process.hrtime(); + const taskTimer = promTaskDuration.startTimer({ template, }); + function endTime() { + const delta = process.hrtime(startTime); + return delta[0] + delta[1] / 1e9; + } + async function skipDryRun( step: TaskStep, action: TemplateAction, @@ -561,12 +588,17 @@ function scaffoldingTracker() { } async function markSuccessful() { - taskCount.inc({ + promTaskCount.inc({ template, user, result: 'ok', }); taskTimer({ result: 'ok' }); + + taskCount.add(1, { template, user, result: 'ok' }); + taskDuration.record(endTime(), { + result: 'ok', + }); } async function markFailed(step: TaskStep, err: Error) { @@ -574,12 +606,17 @@ function scaffoldingTracker() { stepId: step.id, status: 'failed', }); - taskCount.inc({ + promTaskCount.inc({ template, user, result: 'failed', }); taskTimer({ result: 'failed' }); + + taskCount.add(1, { template, user, result: 'failed' }); + taskDuration.record(endTime(), { + result: 'failed', + }); } async function markCancelled(step: TaskStep) { @@ -587,12 +624,17 @@ function scaffoldingTracker() { stepId: step.id, status: 'cancelled', }); - taskCount.inc({ + promTaskCount.inc({ template, user, result: 'cancelled', }); taskTimer({ result: 'cancelled' }); + + taskCount.add(1, { template, user, result: 'cancelled' }); + taskDuration.record(endTime(), { + result: 'cancelled', + }); } return { @@ -610,40 +652,61 @@ function scaffoldingTracker() { }); const template = task.spec.templateInfo?.entityRef || ''; - const stepTimer = stepDuration.startTimer({ + const startTime = process.hrtime(); + const stepTimer = promStepDuration.startTimer({ template, step: step.name, }); + function endTime() { + const delta = process.hrtime(startTime); + return delta[0] + delta[1] / 1e9; + } + async function markSuccessful() { await task.emitLog(`Finished step ${step.name}`, { stepId: step.id, status: 'completed', }); - stepCount.inc({ + promtStepCount.inc({ template, step: step.name, result: 'ok', }); stepTimer({ result: 'ok' }); + + stepCount.add(1, { template, step: step.name, result: 'ok' }); + stepDuration.record(endTime(), { + result: 'ok', + }); } async function markCancelled() { - stepCount.inc({ + promtStepCount.inc({ template, step: step.name, result: 'cancelled', }); stepTimer({ result: 'cancelled' }); + + stepCount.add(1, { template, step: step.name, result: 'cancelled' }); + stepDuration.record(endTime(), { + result: 'cancelled', + }); } async function markFailed() { - stepCount.inc({ + promtStepCount.inc({ template, step: step.name, result: 'failed', }); stepTimer({ result: 'failed' }); + + stepCount.add(1, { template, step: step.name, result: 'failed' }); + stepDuration.record(endTime(), { + result: 'failed', + }); } async function skipFalsy() { @@ -652,6 +715,11 @@ function scaffoldingTracker() { { stepId: step.id, status: 'skipped' }, ); stepTimer({ result: 'skipped' }); + + stepCount.add(1, { template, step: step.name, result: 'skipped' }); + stepDuration.record(endTime(), { + result: 'skipped', + }); } return { diff --git a/yarn.lock b/yarn.lock index fe8c64dd81..293b532159 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7209,6 +7209,7 @@ __metadata: "@backstage/plugin-scaffolder-node": "workspace:^" "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" "@backstage/types": "workspace:^" + "@opentelemetry/api": ^1.3.0 "@types/express": ^4.17.6 "@types/fs-extra": ^11.0.0 "@types/luxon": ^3.0.0