From 2b9e7310c09b630da39e5cd1d018757158eb85fa Mon Sep 17 00:00:00 2001 From: Tolyan Korniltsev Date: Fri, 26 Jun 2026 14:17:50 +0800 Subject: [PATCH 1/3] feat: add otel.scope.name otel.scope.version process.runtime.name process.runtime.version labels --- .github/workflows/test.yml | 2 ++ package.json | 8 ++++---- release-please-config.json | 1 + src/profilers/pyroscope-profiler.ts | 21 ++++++++++++++----- src/version.ts | 2 ++ test/profiler.test.ts | 32 +++++++++++++++++++++++++---- tools/check-version.js | 21 +++++++++++++++++++ tools/generate-version.js | 12 +++++++++++ tools/packagejson.js | 25 ---------------------- yarn.lock | 2 +- 10 files changed, 87 insertions(+), 39 deletions(-) create mode 100644 src/version.ts create mode 100644 tools/check-version.js create mode 100644 tools/generate-version.js delete mode 100644 tools/packagejson.js diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5c026255..04cfeb26 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,6 +39,8 @@ jobs: run: corepack enable - name: Install modules run: yarn + - name: Check version.ts is in sync with package.json + run: yarn check:version - name: Lint code base run: yarn lint test: diff --git a/package.json b/package.json index 7c2658d6..a4eaafb0 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "scripts": { "prepublishOnly": "pinst --disable", "postpublish": "pinst --enable", - "build": "yarn build:cjs && yarn build:esm", + "build": "node tools/generate-version.js && yarn build:cjs && yarn build:esm", "build:cjs": "node tools/cleanup cjs && tsc -p config/tsconfig.cjs.json", "build:esm": "node tools/cleanup esm && tsc -p config/tsconfig.esm.json && echo '{\"type\":\"module\"}' >./dist/esm/package.json", "clean": "node tools/cleanup", @@ -20,10 +20,10 @@ "format:fix": "prettier --write .", "lint": "eslint --cache --ext .js,.jsx,.ts,.tsx .", "lint:fix": "yarn lint --fix", - "build:test": "node tools/cleanup dist-test && tsc -p config/tsconfig.test.json && echo '{\"type\":\"module\"}' >./dist-test/package.json", + "check:version": "node tools/generate-version.js && node tools/check-version.js", + "build:test": "node tools/generate-version.js && node tools/cleanup dist-test && tsc -p config/tsconfig.test.json && echo '{\"type\":\"module\"}' >./dist-test/package.json", "test": "yarn build:test && node --test dist-test/test/*.test.js", - "test:cov": "yarn build:test && node --test --experimental-test-coverage dist-test/test/*.test.js", - "addscope": "node tools/packagejson name @pyroscope" + "test:cov": "yarn build:test && node --test --experimental-test-coverage dist-test/test/*.test.js" }, "publishConfig": { "access": "public" diff --git a/release-please-config.json b/release-please-config.json index 3d8e67be..6e4a5388 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -9,6 +9,7 @@ ".": { "release-type": "node", "package-name": "@pyroscope/nodejs", + "extra-files": ["src/version.ts"], "bump-minor-pre-major": true, "changelog-sections": [ {"type": "feat", "section": "Features"}, diff --git a/src/profilers/pyroscope-profiler.ts b/src/profilers/pyroscope-profiler.ts index 9e14782d..65ec0890 100644 --- a/src/profilers/pyroscope-profiler.ts +++ b/src/profilers/pyroscope-profiler.ts @@ -6,6 +6,8 @@ import { SourceMapper } from '../sourcemapper.js'; import { ContinuousProfiler } from './continuous-profiler.js'; import { HeapProfiler, HeapProfilerStartArgs } from './heap-profiler.js'; import { WallProfiler, WallProfilerStartArgs } from './wall-profiler.js'; +import process from 'node:process'; +import { VERSION } from '../version.js'; const MICROS_PER_SECOND = 1e6; const MS_PER_SECOND = 1e3; @@ -43,11 +45,20 @@ export class PyroscopeProfiler { private buildApplicationName(config: PyroscopeConfig): string { const appName: string = config.appName ?? DEFAULT_APP_NAME; - const tagsStringified: string = Object.entries(config.tags ?? {}) - .map( - ([tagKey, tagValue]: [string, number | string]) => - `${tagKey}=${tagValue}` - ) + const tags = config.tags ?? {}; + const stringify = ([tagKey, tagValue]: [string, number | string]) => { + return `${tagKey}=${tagValue}`; + }; + + const tagsStringified = Object.entries({ + 'otel.scope.name': 'com.grafana.pyroscope/nodejs', + 'otel.scope.version': VERSION, + 'process.runtime.name': 'nodejs', + 'process.runtime.version': process.versions.node, + }) + .filter((e: [string, string]) => !(e[0] in tags)) + .map(stringify) + .concat(Object.entries(tags).map(stringify)) .join(','); return `${appName}{${tagsStringified}}`; diff --git a/src/version.ts b/src/version.ts new file mode 100644 index 00000000..6e388e2d --- /dev/null +++ b/src/version.ts @@ -0,0 +1,2 @@ +// Generated by tools/generate-version.js — do not edit. +export const VERSION = '0.4.13'; diff --git a/test/profiler.test.ts b/test/profiler.test.ts index 33d64bf0..15611a3b 100644 --- a/test/profiler.test.ts +++ b/test/profiler.test.ts @@ -1,12 +1,31 @@ import { describe, it, type TestContext } from 'node:test'; import { strict as assert } from 'node:assert'; +import process from 'node:process'; import Pyroscope from '../src/index.js'; +import { VERSION } from '../src/version.js'; import express from 'express'; import busboy from 'busboy'; import { Profile } from 'pprof-format'; import zlib from 'zlib'; +function assertAppNameIncludes(name: unknown, ...parts: string[]): void { + const value = String(name); + for (const part of parts) { + assert.ok( + value.includes(part), + `expected app name to include ${JSON.stringify(part)}, got ${JSON.stringify(value)}` + ); + } +} + +const defaultSemconvTags = [ + 'otel.scope.name=com.grafana.pyroscope/nodejs', + `otel.scope.version=${VERSION}`, + 'process.runtime.name=nodejs', + `process.runtime.version=${process.versions.node}`, +]; + // createBackend creates an Express server with an /ingest endpoint and returns a promise // that resolves to the HTTP port the server is listening on const createBackend = ( @@ -87,7 +106,7 @@ describe('common behaviour of profilers', () => { const req = await firstRequest; await Pyroscope.stopWallProfiling(); assert.strictEqual(req.query.spyName, 'nodespy'); - assert.strictEqual(req.query.name, 'nodejs{}'); + assertAppNameIncludes(req.query.name, 'nodejs{', ...defaultSemconvTags); }); it('should call a server on startHeapProfiling and clear gracefully', async (t) => { @@ -138,7 +157,12 @@ describe('common behaviour of profilers', () => { const req = await firstRequest; await Pyroscope.stopHeapProfiling(); assert.strictEqual(req.query['spyName'], 'nodespy'); - assert.strictEqual(req.query['name'], 'nodejs{env=test env}'); + assertAppNameIncludes( + req.query['name'], + 'nodejs{', + ...defaultSemconvTags, + 'env=test env' + ); }); it('should allow to call start profiling twice', async (t) => { @@ -245,7 +269,7 @@ describe('common behaviour of profilers', () => { await Pyroscope.stopWallProfiling(); assert.strictEqual(req.query['spyName'], 'nodespy'); - assert.strictEqual(req.query['name'], 'nodejs{}'); + assertAppNameIncludes(req.query['name'], 'nodejs{', ...defaultSemconvTags); // ensure we contain everything expected const emptyLabels = JSON.stringify({}); @@ -305,7 +329,7 @@ describe('common behaviour of profilers', () => { await Pyroscope.stopWallProfiling(); assert.strictEqual(req.query['spyName'], 'nodespy'); - assert.strictEqual(req.query['name'], 'nodejs{}'); + assertAppNameIncludes(req.query['name'], 'nodejs{', ...defaultSemconvTags); // expect sample, wall and cpu types assert.deepStrictEqual(sampleType, [ 'samples=count', diff --git a/tools/check-version.js b/tools/check-version.js new file mode 100644 index 00000000..e8596c8a --- /dev/null +++ b/tools/check-version.js @@ -0,0 +1,21 @@ +/* eslint-disable */ +const { execSync } = require('child_process'); +/* eslint-enable */ + +try { + execSync('git ls-files --error-unmatch src/version.ts', { stdio: 'ignore' }); +} catch { + console.error( + 'src/version.ts is not tracked by git. Run node tools/generate-version.js and commit the file.' + ); + process.exit(1); +} + +try { + execSync('git diff --exit-code -- src/version.ts', { stdio: 'inherit' }); +} catch { + console.error( + 'src/version.ts is out of sync with package.json. Run: node tools/generate-version.js' + ); + process.exit(1); +} diff --git a/tools/generate-version.js b/tools/generate-version.js new file mode 100644 index 00000000..3eace86b --- /dev/null +++ b/tools/generate-version.js @@ -0,0 +1,12 @@ +/* eslint-disable */ +const fs = require('fs'); +const path = require('path'); +const { version } = require('../package.json'); + +const outPath = path.join(__dirname, '../src/version.ts'); +const contents = `// Generated by tools/generate-version.js — do not edit. +export const VERSION = '${version}'; +`; + +fs.writeFileSync(outPath, contents); +console.log(`Generated ${outPath} with VERSION=${version}`); diff --git a/tools/packagejson.js b/tools/packagejson.js deleted file mode 100644 index 54f2579d..00000000 --- a/tools/packagejson.js +++ /dev/null @@ -1,25 +0,0 @@ -/* eslint-disable */ -const fs = require('fs'); -const Path = require('path'); -const fileName = '../package.json'; -const file = require(fileName); -/* eslint-enable */ - -const args = process.argv.slice(2); - -for (let i = 0, l = args.length; i < l; i++) { - if (i % 2 === 0) { - file[args[i]] = args[i + 1]; - } -} - -fs.writeFile( - Path.join(__dirname, fileName), - JSON.stringify(file, null, 2), - (err) => { - if (err) { - return console.log(err); - } - console.log('Writing to ' + fileName); - } -); diff --git a/yarn.lock b/yarn.lock index dd654402..9492c202 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,7 +2,7 @@ # Manual changes might be lost - proceed with caution! __metadata: - version: 10 + version: 8 cacheKey: 10c0 "@datadog/pprof@npm:5.14.4": From 58fd8535a5c492f8da47534df734e8aabad3fc66 Mon Sep 17 00:00:00 2001 From: Tolyan Korniltsev Date: Fri, 26 Jun 2026 14:20:19 +0800 Subject: [PATCH 2/3] fix release-please --- release-please-config.json | 7 ++++++- src/version.ts | 2 +- tools/generate-version.js | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/release-please-config.json b/release-please-config.json index 6e4a5388..43a7d849 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -9,7 +9,12 @@ ".": { "release-type": "node", "package-name": "@pyroscope/nodejs", - "extra-files": ["src/version.ts"], + "extra-files": [ + { + "type": "generic", + "path": "src/version.ts" + } + ], "bump-minor-pre-major": true, "changelog-sections": [ {"type": "feat", "section": "Features"}, diff --git a/src/version.ts b/src/version.ts index 6e388e2d..88e7ecbf 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,2 +1,2 @@ // Generated by tools/generate-version.js — do not edit. -export const VERSION = '0.4.13'; +export const VERSION = '0.4.13'; // x-release-please-version diff --git a/tools/generate-version.js b/tools/generate-version.js index 3eace86b..00f44119 100644 --- a/tools/generate-version.js +++ b/tools/generate-version.js @@ -5,7 +5,7 @@ const { version } = require('../package.json'); const outPath = path.join(__dirname, '../src/version.ts'); const contents = `// Generated by tools/generate-version.js — do not edit. -export const VERSION = '${version}'; +export const VERSION = '${version}'; // x-release-please-version `; fs.writeFileSync(outPath, contents); From 3fc0150a5b1a5f83b930a09747a4648787a0e7e0 Mon Sep 17 00:00:00 2001 From: Tolyan Korniltsev Date: Fri, 26 Jun 2026 14:47:41 +0800 Subject: [PATCH 3/3] revert yarn.lock metadata change --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 9492c202..dd654402 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,7 +2,7 @@ # Manual changes might be lost - proceed with caution! __metadata: - version: 8 + version: 10 cacheKey: 10c0 "@datadog/pprof@npm:5.14.4":