From d6926f92274f95647235df22babbb12be8d8fe22 Mon Sep 17 00:00:00 2001 From: Jackson Weber Date: Thu, 21 May 2026 19:33:09 -0700 Subject: [PATCH 1/4] perf: expand benchmarks vs upstream OpenTelemetry + add CI regression gate Adds four new perf scenarios so we can measure overhead of this package against equivalent upstream OpenTelemetry calls: - AzureMonitorSpanTest / AzureMonitorLogTest (useAzureMonitor + direct OTel API) - OtelSpanTest / OtelLogTest (plain @opentelemetry/sdk-trace-base & sdk-logs reference, informational only) Introduces a deterministic benchmark runner (bench.mjs + runBenchmarks.mjs) that bypasses the @azure-tools/test-perf worker pool, runs each scenario in a fresh Node child process to avoid OTel global-state contamination, and emits structured JSON with median/mean/stdev across N samples. Adds .github/workflows/performance.yml: packs both PR and base branch as tarballs via npm pack, installs each in turn under the PR's perf harness, runs the benchmark suite, and fails the job (blocking merge when set as a required check) if any gating scenario regresses by more than PERF_REGRESSION_THRESHOLD percent (default 15%). Posts a sticky PR comment with the comparison table. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/performance.yml | 134 +++++ test/performanceTests/README.md | 52 +- test/performanceTests/bench.mjs | 109 ++++ test/performanceTests/comparePerf.mjs | 106 ++++ test/performanceTests/package-lock.json | 555 ++++++------------ test/performanceTests/package.json | 7 +- test/performanceTests/runBenchmarks.mjs | 143 +++++ .../test/appInsightsShim.spec.ts | 18 +- .../test/azureMonitorLog.spec.ts | 44 ++ .../test/azureMonitorSpan.spec.ts | 52 ++ test/performanceTests/test/index.spec.ts | 95 +-- test/performanceTests/test/otelLog.spec.ts | 43 ++ test/performanceTests/test/otelSpan.spec.ts | 52 ++ 13 files changed, 996 insertions(+), 414 deletions(-) create mode 100644 .github/workflows/performance.yml create mode 100644 test/performanceTests/bench.mjs create mode 100644 test/performanceTests/comparePerf.mjs create mode 100644 test/performanceTests/runBenchmarks.mjs create mode 100644 test/performanceTests/test/azureMonitorLog.spec.ts create mode 100644 test/performanceTests/test/azureMonitorSpan.spec.ts create mode 100644 test/performanceTests/test/otelLog.spec.ts create mode 100644 test/performanceTests/test/otelSpan.spec.ts diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml new file mode 100644 index 000000000..ddfd0a01c --- /dev/null +++ b/.github/workflows/performance.yml @@ -0,0 +1,134 @@ +name: Performance + +on: + pull_request: + branches: [ main ] + +# Cancel in-flight perf runs on the same PR to free runner capacity. +concurrency: + group: perf-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: write + +jobs: + perf-regression: + runs-on: ubuntu-latest + # 20 min is plenty for ~5 samples × ~6 scenarios × ~8s + builds. + timeout-minutes: 25 + env: + NODE_VERSION: '22.x' + PERF_SAMPLES: '5' + PERF_DURATION: '8' + PERF_WARMUP: '2' + PERF_REGRESSION_THRESHOLD: '15' + + steps: + - name: Checkout PR (candidate) + uses: actions/checkout@v4 + with: + path: pr + + - name: Checkout base branch (baseline) + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.base.ref }} + path: baseline + + - uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + # ---- Build & pack BOTH versions of the package up-front ---- + - name: Generate dummy TLS certs (PR) + working-directory: pr + run: openssl req -x509 -nodes -newkey rsa -keyout ./test/certs/server-key.pem -out ./test/certs/server-cert.pem -days 1 -subj "/C=CL/ST=RM/L=OpenTelemetryTest/O=Root/OU=Test/CN=ca" + + - name: Install + build + pack candidate + working-directory: pr + run: | + npm ci + npm run build + npm pack + mv applicationinsights-*.tgz "$GITHUB_WORKSPACE/candidate.tgz" + + - name: Generate dummy TLS certs (baseline) + working-directory: baseline + run: openssl req -x509 -nodes -newkey rsa -keyout ./test/certs/server-key.pem -out ./test/certs/server-cert.pem -days 1 -subj "/C=CL/ST=RM/L=OpenTelemetryTest/O=Root/OU=Test/CN=ca" + + - name: Install + build + pack baseline + working-directory: baseline + run: | + npm ci + npm run build + npm pack + mv applicationinsights-*.tgz "$GITHUB_WORKSPACE/baseline.tgz" + + # ---- Use the PR's perf harness for BOTH runs (consistent code) ---- + - name: Install perf harness deps (PR) + working-directory: pr/test/performanceTests + run: npm install + + - name: Install CANDIDATE applicationinsights into perf harness + working-directory: pr/test/performanceTests + run: npm install --no-save "$GITHUB_WORKSPACE/candidate.tgz" + + - name: Run candidate benchmarks + working-directory: pr/test/performanceTests + run: | + node runBenchmarks.mjs \ + --out "$GITHUB_WORKSPACE/candidate.json" \ + --samples "$PERF_SAMPLES" \ + --duration "$PERF_DURATION" \ + --warmup "$PERF_WARMUP" + + - name: Install BASELINE applicationinsights into perf harness + working-directory: pr/test/performanceTests + run: npm install --no-save "$GITHUB_WORKSPACE/baseline.tgz" + + - name: Run baseline benchmarks + working-directory: pr/test/performanceTests + run: | + node runBenchmarks.mjs \ + --out "$GITHUB_WORKSPACE/baseline.json" \ + --samples "$PERF_SAMPLES" \ + --duration "$PERF_DURATION" \ + --warmup "$PERF_WARMUP" + + # ---- Compare and publish ---- + - name: Compare results + id: compare + working-directory: pr/test/performanceTests + run: | + set +e + node comparePerf.mjs \ + "$GITHUB_WORKSPACE/baseline.json" \ + "$GITHUB_WORKSPACE/candidate.json" \ + "$GITHUB_WORKSPACE/perf-comparison.md" + echo "exit=$?" >> "$GITHUB_OUTPUT" + + - name: Upload raw results + if: always() + uses: actions/upload-artifact@v4 + with: + name: perf-results + path: | + baseline.json + candidate.json + perf-comparison.md + + - name: Comment on PR (best-effort) + if: always() && github.event.pull_request.head.repo.full_name == github.repository + uses: marocchino/sticky-pull-request-comment@v2 + continue-on-error: true + with: + header: perf-regression + path: perf-comparison.md + + - name: Fail job on gating regression + if: steps.compare.outputs.exit != '0' + run: | + echo "Performance regression beyond ${PERF_REGRESSION_THRESHOLD}% detected." >&2 + exit 1 diff --git a/test/performanceTests/README.md b/test/performanceTests/README.md index 5480f367d..434a3c98a 100644 --- a/test/performanceTests/README.md +++ b/test/performanceTests/README.md @@ -1,10 +1,46 @@ -### Guide +### Performance Tests -1. Copy the `sample.env` file and name it as `.env`. -2. Create an Application Insights resource and populate the `.env` file with connectionString. -3. Run the tests as follows (parameters can be modified to as appropriate): +The performance test harness measures throughput (ops/s) for hot-path APIs in +this package and reports them against an upstream-OpenTelemetry-only baseline. -- Tracking Dependencies (spans) - - `npm run perf-test:node -- TrackDependencyTest --warmup 1 --iterations 1 --parallel 2 --duration 15` -- Tracking Traces (logs) - - `npm run perf-test:node -- TrackTraceTest --warmup 1 --iterations 1 --parallel 2 --duration 15` +#### Manual run + +1. Copy `sample.env` to `.env` and set `APPLICATIONINSIGHTS_CONNECTION_STRING` + (any well-formed connection string works; the perf path never sends data + when an unreachable ingestion endpoint is configured). +2. Run a single scenario via the existing harness: + + - `npm run perf-test:node -- TrackDependencyTest --warmup 1 --iterations 1 --parallel 2 --duration 15` + - `npm run perf-test:node -- TrackTraceTest --warmup 1 --iterations 1 --parallel 2 --duration 15` + - `npm run perf-test:node -- AzureMonitorSpanTest --warmup 1 --iterations 1 --parallel 2 --duration 15` + - `npm run perf-test:node -- AzureMonitorLogTest --warmup 1 --iterations 1 --parallel 2 --duration 15` + - `npm run perf-test:node -- OtelSpanTest --warmup 1 --iterations 1 --parallel 2 --duration 15` + - `npm run perf-test:node -- OtelLogTest --warmup 1 --iterations 1 --parallel 2 --duration 15` + +3. Or run every scenario and produce a JSON summary suitable for comparison: + + `npm run perf:benchmark -- --out results.json --samples 5 --duration 8` + +#### Scenario tiers + +| Scenario | Tier | What it measures | +|---|---|---| +| `TrackDependencyTest` | gating | `appInsights.defaultClient.trackDependency()` via the v2 shim | +| `TrackTraceTest` | gating | `appInsights.defaultClient.trackTrace()` via the v2 shim | +| `AzureMonitorSpanTest` | gating | `useAzureMonitor()` + `tracer.startSpan()` | +| `AzureMonitorLogTest` | gating | `useAzureMonitor()` + `logger.emit()` | +| `OtelSpanTest` | informational | Upstream `@opentelemetry/sdk-trace-base` only | +| `OtelLogTest` | informational | Upstream `@opentelemetry/sdk-logs` only | + +Only **gating** scenarios block CI on regression. Upstream-OTel scenarios are +reported as a reference for like-for-like comparison and are not owned by this +repo, so they are never used for gate-fail decisions. + +#### Regression CI + +`.github/workflows/performance.yml` runs on every PR. It packs both the PR and +the base branch as tarballs, installs each in turn under the PR's perf harness, +runs the benchmark suite, and fails the job (blocking merge when set as a +required check) if any gating scenario regresses beyond +`PERF_REGRESSION_THRESHOLD` percent (default 15%). A sticky comment with the +full comparison table is posted to the PR. diff --git a/test/performanceTests/bench.mjs b/test/performanceTests/bench.mjs new file mode 100644 index 000000000..12c204a60 --- /dev/null +++ b/test/performanceTests/bench.mjs @@ -0,0 +1,109 @@ +#!/usr/bin/env node +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/* + * Measures throughput of a single scenario directly, without going through + * the @azure-tools/test-perf framework's worker pool. Running in a single + * process makes JSON output and result capture deterministic, and a fresh + * Node child per scenario keeps OpenTelemetry global state isolated. + * + * Usage: + * node bench.mjs --scenario --duration --warmup --out + */ + +import { writeFileSync } from "node:fs"; + +function parseArgs(argv) { + const a = { duration: 8, warmup: 2 }; + for (let i = 0; i < argv.length; i++) { + const k = argv[i]; + const v = () => argv[++i]; + if (k === "--scenario") a.scenario = v(); + else if (k === "--duration") a.duration = Number(v()); + else if (k === "--warmup") a.warmup = Number(v()); + else if (k === "--out") a.out = v(); + } + if (!a.scenario || !a.out) { + console.error("Required: --scenario --out "); + process.exit(2); + } + return a; +} + +const SCENARIO_MODULES = { + TrackDependencyTest: "./dist-esm/trackDependency.spec.js", + TrackTraceTest: "./dist-esm/trackTrace.spec.js", + AzureMonitorSpanTest: "./dist-esm/azureMonitorSpan.spec.js", + AzureMonitorLogTest: "./dist-esm/azureMonitorLog.spec.js", + OtelSpanTest: "./dist-esm/otelSpan.spec.js", + OtelLogTest: "./dist-esm/otelLog.spec.js", +}; + +async function runLoop(instance, durationMs) { + // Tight async loop. We rely on each .run() awaiting only synchronous-ish + // work (the scenarios under test do not perform real network I/O). The + // loop polls Date.now() infrequently (every BATCH iterations) to keep + // measurement overhead negligible. + const deadline = Date.now() + durationMs; + let ops = 0; + const BATCH = 256; + while (true) { + for (let i = 0; i < BATCH; i++) { + await instance.run(); + } + ops += BATCH; + if (Date.now() >= deadline) break; + } + return ops; +} + +async function main() { + const args = parseArgs(process.argv.slice(2)); + const modulePath = SCENARIO_MODULES[args.scenario]; + if (!modulePath) { + console.error(`Unknown scenario: ${args.scenario}`); + process.exit(2); + } + const mod = await import(modulePath); + const Cls = mod[args.scenario]; + if (!Cls) { + console.error(`Module ${modulePath} does not export ${args.scenario}`); + process.exit(2); + } + const instance = new Cls(); + + // Warmup (not counted) + if (args.warmup > 0) { + await runLoop(instance, args.warmup * 1000); + } + + const startWall = Date.now(); + const ops = await runLoop(instance, args.duration * 1000); + const elapsedMs = Date.now() - startWall; + const opsPerSec = (ops / elapsedMs) * 1000; + + writeFileSync( + args.out, + JSON.stringify( + { + scenario: args.scenario, + opsPerSec, + ops, + elapsedMs, + timestamp: new Date().toISOString(), + }, + null, + 2, + ), + ); + + console.log( + `[bench] ${args.scenario}: ${ops} ops in ${elapsedMs}ms => ${opsPerSec.toFixed(0)} ops/s`, + ); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/test/performanceTests/comparePerf.mjs b/test/performanceTests/comparePerf.mjs new file mode 100644 index 000000000..7cb5c82ec --- /dev/null +++ b/test/performanceTests/comparePerf.mjs @@ -0,0 +1,106 @@ +#!/usr/bin/env node +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/* + * Compares two perf result JSON files produced by runBenchmarks.mjs. + * + * Usage: + * node comparePerf.mjs baseline.json candidate.json [markdown.md] + * + * Environment: + * PERF_REGRESSION_THRESHOLD Percent regression that fails the gate (default 15) + * + * Exit codes: + * 0 no gating regression beyond threshold + * 1 one or more gating scenarios regressed beyond threshold + * 2 invalid input + */ + +import { readFileSync, writeFileSync } from "node:fs"; + +function loadResults(path) { + const data = JSON.parse(readFileSync(path, "utf8")); + const map = new Map(); + for (const r of data.results) { + map.set(r.name, r); + } + return { meta: data, map }; +} + +const [baselinePath, candidatePath, markdownPath] = process.argv.slice(2); +if (!baselinePath || !candidatePath) { + console.error("Usage: comparePerf.mjs [markdown.md]"); + process.exit(2); +} + +const threshold = Number(process.env.PERF_REGRESSION_THRESHOLD || "15"); + +const baseline = loadResults(baselinePath); +const candidate = loadResults(candidatePath); + +const rows = []; +const regressions = []; + +for (const [name, b] of baseline.map) { + const c = candidate.map.get(name); + if (!c) { + rows.push({ name, tier: b.tier, status: "missing in candidate" }); + continue; + } + // Positive deltaPct means improvement (more ops/s); negative means regression. + const deltaPct = ((c.median - b.median) / b.median) * 100; + const row = { + name, + tier: b.tier, + baselineMedian: b.median, + candidateMedian: c.median, + deltaPct, + }; + rows.push(row); + if (b.tier === "gating" && deltaPct < -threshold) { + regressions.push(row); + } +} + +// Scenarios present only in candidate (e.g. newly added) are informational. +for (const [name, c] of candidate.map) { + if (!baseline.map.has(name)) { + rows.push({ name, tier: c.tier, status: "new in candidate", candidateMedian: c.median }); + } +} + +const fmt = (n) => (Number.isFinite(n) ? n.toFixed(2) : "n/a"); +const arrow = (d) => (d > 0 ? "🟢" : d < 0 ? "🔴" : "⚪"); + +let md = `## Performance comparison\n\n`; +md += `Threshold for gating regression: **-${threshold}%** (median ops/s)\n\n`; +md += `| Scenario | Tier | Baseline (ops/s) | Candidate (ops/s) | Δ % |\n`; +md += `|---|---|---:|---:|---:|\n`; +for (const r of rows) { + if (r.status) { + md += `| \`${r.name}\` | ${r.tier} | ${r.status} | ${fmt(r.candidateMedian)} | n/a |\n`; + continue; + } + md += `| \`${r.name}\` | ${r.tier} | ${fmt(r.baselineMedian)} | ${fmt(r.candidateMedian)} | ${arrow(r.deltaPct)} ${fmt(r.deltaPct)}% |\n`; +} + +if (regressions.length) { + md += `\n### ❌ Gating regressions\n\n`; + for (const r of regressions) { + md += `- \`${r.name}\`: ${fmt(r.deltaPct)}% (baseline ${fmt(r.baselineMedian)} → candidate ${fmt(r.candidateMedian)} ops/s)\n`; + } +} else { + md += `\n### ✅ No gating regression beyond threshold.\n`; +} + +md += `\nbaseline: ${baseline.meta.generatedAt} (node ${baseline.meta.node}, ${baseline.meta.samples} samples × ${baseline.meta.duration}s)\n`; +md += `candidate: ${candidate.meta.generatedAt} (node ${candidate.meta.node}, ${candidate.meta.samples} samples × ${candidate.meta.duration}s)\n`; + +console.log(md); + +if (markdownPath) { + writeFileSync(markdownPath, md); +} + +process.exit(regressions.length ? 1 : 0); diff --git a/test/performanceTests/package-lock.json b/test/performanceTests/package-lock.json index 71c0f6426..a57c32906 100644 --- a/test/performanceTests/package-lock.json +++ b/test/performanceTests/package-lock.json @@ -11,8 +11,9 @@ "dependencies": { "@azure-tools/test-perf": "^1.0.0", "@opentelemetry/api": "^1.9.0", - "@opentelemetry/api-logs": "^0.52.1", - "@opentelemetry/sdk-logs": "^0.52.1", + "@opentelemetry/api-logs": "^0.208.0", + "@opentelemetry/sdk-logs": "^0.208.0", + "@opentelemetry/sdk-trace-base": "^2.2.0", "applicationinsights": "^3.2.2", "dotenv": "^16.4.5", "tslib": "^2.6.2" @@ -312,15 +313,6 @@ "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, - "node_modules/@azure/monitor-opentelemetry-exporter/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@azure/monitor-opentelemetry/node_modules/@opentelemetry/api-logs": { "version": "0.204.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.204.0.tgz", @@ -381,15 +373,6 @@ "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, - "node_modules/@azure/monitor-opentelemetry/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@azure/msal-browser": { "version": "4.24.0", "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-4.24.0.tgz", @@ -455,21 +438,6 @@ "node": ">=8.0.0" } }, - "node_modules/@azure/opentelemetry-instrumentation-azure-sdk/node_modules/@opentelemetry/core": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", - "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, "node_modules/@azure/opentelemetry-instrumentation-azure-sdk/node_modules/@opentelemetry/instrumentation": { "version": "0.200.0", "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.200.0.tgz", @@ -489,15 +457,6 @@ "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@azure/opentelemetry-instrumentation-azure-sdk/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@colors/colors": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", @@ -743,20 +702,20 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "peer": true, "engines": { "node": ">=8.0.0" } }, "node_modules/@opentelemetry/api-logs": { - "version": "0.52.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.52.1.tgz", - "integrity": "sha512-qnSqB2DQ9TPP96dl8cDubDvrUyWc0/sK81xHTK8eSUspzDM3bsewX903qclQFvVhgStjRWdC5bLb3kQqMkfV5A==", + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.208.0.tgz", + "integrity": "sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==", + "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api": "^1.0.0" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=14" + "node": ">=8.0.0" } }, "node_modules/@opentelemetry/context-async-hooks": { @@ -772,14 +731,15 @@ } }, "node_modules/@opentelemetry/core": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.25.1.tgz", - "integrity": "sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", "dependencies": { - "@opentelemetry/semantic-conventions": "1.25.1" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" @@ -865,15 +825,6 @@ "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, - "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/exporter-logs-otlp-http": { "version": "0.204.0", "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.204.0.tgz", @@ -953,15 +904,6 @@ "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, - "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/exporter-logs-otlp-proto": { "version": "0.204.0", "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.204.0.tgz", @@ -1043,13 +985,21 @@ "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, - "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.1.0.tgz", + "integrity": "sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==", "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { @@ -1105,15 +1055,6 @@ "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/exporter-metrics-otlp-http": { "version": "0.204.0", "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.204.0.tgz", @@ -1164,15 +1105,6 @@ "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/exporter-metrics-otlp-proto": { "version": "0.204.0", "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.204.0.tgz", @@ -1224,15 +1156,6 @@ "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/exporter-prometheus": { "version": "0.204.0", "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.204.0.tgz", @@ -1281,15 +1204,6 @@ "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { "version": "0.204.0", "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.204.0.tgz", @@ -1342,13 +1256,21 @@ "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", + "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.1.0.tgz", + "integrity": "sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==", "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "node_modules/@opentelemetry/exporter-trace-otlp-http": { @@ -1401,13 +1323,21 @@ "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", + "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.1.0.tgz", + "integrity": "sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==", "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "node_modules/@opentelemetry/exporter-trace-otlp-proto": { @@ -1460,13 +1390,21 @@ "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", + "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.1.0.tgz", + "integrity": "sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==", "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "node_modules/@opentelemetry/exporter-zipkin": { @@ -1518,13 +1456,21 @@ "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", + "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.1.0.tgz", + "integrity": "sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==", "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "node_modules/@opentelemetry/instrumentation": { @@ -1606,15 +1552,6 @@ "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/instrumentation-mongodb": { "version": "0.57.0", "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.57.0.tgz", @@ -1631,15 +1568,6 @@ "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/instrumentation-mongodb/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/instrumentation-mysql": { "version": "0.50.0", "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.50.0.tgz", @@ -1657,15 +1585,6 @@ "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/instrumentation-mysql/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/instrumentation-pg": { "version": "0.57.0", "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.57.0.tgz", @@ -1686,30 +1605,6 @@ "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/instrumentation-pg/node_modules/@opentelemetry/core": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", - "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/instrumentation-pg/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/instrumentation-redis": { "version": "0.53.0", "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.53.0.tgz", @@ -1783,24 +1678,6 @@ "node": "^18.19.0 || >=20.6.0" } }, - "node_modules/@opentelemetry/instrumentation-redis-4/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/instrumentation-redis/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/instrumentation-winston": { "version": "0.49.0", "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.49.0.tgz", @@ -1872,15 +1749,6 @@ "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/otlp-grpc-exporter-base": { "version": "0.204.0", "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.204.0.tgz", @@ -1914,15 +1782,6 @@ "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@opentelemetry/otlp-grpc-exporter-base/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/otlp-transformer": { "version": "0.204.0", "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.204.0.tgz", @@ -2004,13 +1863,21 @@ "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.1.0.tgz", + "integrity": "sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==", "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "node_modules/@opentelemetry/propagator-b3": { @@ -2043,15 +1910,6 @@ "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@opentelemetry/propagator-b3/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/propagator-jaeger": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.1.0.tgz", @@ -2082,15 +1940,6 @@ "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@opentelemetry/propagator-jaeger/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/redis-common": { "version": "0.38.0", "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.38.0.tgz", @@ -2132,13 +1981,13 @@ "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@opentelemetry/resource-detector-azure/node_modules/@opentelemetry/resources": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz", - "integrity": "sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==", + "node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.1.0", + "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { @@ -2148,41 +1997,18 @@ "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/resource-detector-azure/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/resources": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.25.1.tgz", - "integrity": "sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==", - "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/semantic-conventions": "1.25.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, "node_modules/@opentelemetry/sdk-logs": { - "version": "0.52.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.52.1.tgz", - "integrity": "sha512-MBYh+WcPPsN8YpRHRmK1Hsca9pVlyyKd4BxOC4SsgHACnl/bPp4Cri9hWhVm5+2tiQ9Zf4qSc1Jshw9tOLGWQA==", + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.208.0.tgz", + "integrity": "sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA==", + "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "0.52.1", - "@opentelemetry/core": "1.25.1", - "@opentelemetry/resources": "1.25.1" + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0" }, "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" @@ -2235,15 +2061,6 @@ "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/sdk-metrics/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/sdk-node": { "version": "0.204.0", "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.204.0.tgz", @@ -2340,16 +2157,7 @@ "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/sdk-trace-base": { + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-trace-base": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.1.0.tgz", "integrity": "sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==", @@ -2366,44 +2174,52 @@ "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/core": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", - "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", + "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", "license": "Apache-2.0", "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/resources": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz", - "integrity": "sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==", + "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.1.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", + "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "node_modules/@opentelemetry/sdk-trace-node": { @@ -2438,13 +2254,37 @@ "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", + "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/resources": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz", + "integrity": "sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==", "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.1.0.tgz", + "integrity": "sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "node_modules/@opentelemetry/sdk-trace-web": { @@ -2478,62 +2318,63 @@ "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@opentelemetry/sdk-trace-web/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", - "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/sql-common": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.41.0.tgz", - "integrity": "sha512-pmzXctVbEERbqSfiAgdes9Y63xjoOyXcD7B6IXBkVb+vbM7M9U98mn33nGXxPf4dfYR0M+vhcKRZmbSJ7HfqFA==", + "node_modules/@opentelemetry/sdk-trace-web/node_modules/@opentelemetry/resources": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz", + "integrity": "sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "^2.0.0" + "@opentelemetry/core": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "@opentelemetry/api": "^1.1.0" + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/sql-common/node_modules/@opentelemetry/core": { + "node_modules/@opentelemetry/sdk-trace-web/node_modules/@opentelemetry/sdk-trace-base": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", - "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.1.0.tgz", + "integrity": "sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==", "license": "Apache-2.0", "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/sql-common/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", + "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", "license": "Apache-2.0", "engines": { "node": ">=14" } }, + "node_modules/@opentelemetry/sql-common": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.41.0.tgz", + "integrity": "sha512-pmzXctVbEERbqSfiAgdes9Y63xjoOyXcD7B6IXBkVb+vbM7M9U98mn33nGXxPf4dfYR0M+vhcKRZmbSJ7HfqFA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0" + } + }, "node_modules/@opentelemetry/winston-transport": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/@opentelemetry/winston-transport/-/winston-transport-0.15.0.tgz", @@ -2721,7 +2562,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2890,15 +2730,6 @@ "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, - "node_modules/applicationinsights/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3137,7 +2968,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/diagnostic-channel/-/diagnostic-channel-1.1.1.tgz", "integrity": "sha512-r2HV5qFkUICyoaKlBEpLKHjxMXATUf/l+h8UZPGBHGLy4DDiY2sOLcIctax4eRnTw5wH2jTMExLntGPJ8eOJxw==", - "peer": true, "dependencies": { "semver": "^7.5.3" } @@ -3220,7 +3050,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", diff --git a/test/performanceTests/package.json b/test/performanceTests/package.json index 1190cf919..4a58ad2f6 100644 --- a/test/performanceTests/package.json +++ b/test/performanceTests/package.json @@ -11,8 +11,9 @@ "dependencies": { "@azure-tools/test-perf": "^1.0.0", "@opentelemetry/api": "^1.9.0", - "@opentelemetry/api-logs": "^0.52.1", - "@opentelemetry/sdk-logs": "^0.52.1", + "@opentelemetry/api-logs": "^0.208.0", + "@opentelemetry/sdk-logs": "^0.208.0", + "@opentelemetry/sdk-trace-base": "^2.2.0", "applicationinsights": "^3.2.2", "dotenv": "^16.4.5", "tslib": "^2.6.2" @@ -26,6 +27,8 @@ "private": true, "scripts": { "perf-test:node": "npm run build && node dist-esm/index.spec.js", + "perf:benchmark": "npm run build && node runBenchmarks.mjs", + "perf:compare": "node comparePerf.mjs", "audit": "node ../../../common/scripts/rush-audit.js && rimraf node_modules package-lock.json && npm i --package-lock-only 2>&1 && npm audit", "build": "npm run clean && tsc -p .", "build:samples": "echo skipped", diff --git a/test/performanceTests/runBenchmarks.mjs b/test/performanceTests/runBenchmarks.mjs new file mode 100644 index 000000000..1a9b37e9e --- /dev/null +++ b/test/performanceTests/runBenchmarks.mjs @@ -0,0 +1,143 @@ +#!/usr/bin/env node +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/* + * Runs each scenario via bench.mjs in a fresh Node child process to avoid + * OpenTelemetry global-state contamination across scenarios. Each scenario + * is sampled N times; median ops/s is recorded. + * + * Usage: + * node runBenchmarks.mjs --out results.json [--samples 5] [--duration 8] + * [--warmup 2] [--scenarios a,b,c] + */ + +import { spawnSync } from "node:child_process"; +import { readFileSync, writeFileSync, mkdtempSync, rmSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { tmpdir } from "node:os"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +// "gating" scenarios block the PR on regression; "informational" are reported +// but never fail the build. Upstream-OTel benchmarks are informational since +// regressions there are not owned by this repository. +const SCENARIOS = [ + { name: "TrackDependencyTest", tier: "gating" }, + { name: "TrackTraceTest", tier: "gating" }, + { name: "AzureMonitorSpanTest", tier: "gating" }, + { name: "AzureMonitorLogTest", tier: "gating" }, + { name: "OtelSpanTest", tier: "informational" }, + { name: "OtelLogTest", tier: "informational" }, +]; + +function parseArgs(argv) { + const args = { samples: 5, duration: 8, warmup: 2 }; + for (let i = 0; i < argv.length; i++) { + const a = argv[i]; + const next = () => argv[++i]; + if (a === "--out") args.out = next(); + else if (a === "--samples") args.samples = Number(next()); + else if (a === "--duration") args.duration = Number(next()); + else if (a === "--warmup") args.warmup = Number(next()); + else if (a === "--scenarios") args.scenarios = next().split(","); + } + if (!args.out) { + console.error("--out is required"); + process.exit(1); + } + return args; +} + +function median(nums) { + const sorted = [...nums].sort((a, b) => a - b); + const mid = Math.floor(sorted.length / 2); + return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2; +} +function mean(nums) { + return nums.reduce((a, b) => a + b, 0) / nums.length; +} +function stdev(nums) { + const m = mean(nums); + const variance = nums.reduce((sum, n) => sum + (n - m) ** 2, 0) / nums.length; + return Math.sqrt(variance); +} + +function runOne(scenario, args, outPath) { + const child = spawnSync( + process.execPath, + [ + join(__dirname, "bench.mjs"), + "--scenario", + scenario, + "--duration", + String(args.duration), + "--warmup", + String(args.warmup), + "--out", + outPath, + ], + { + cwd: __dirname, + stdio: ["ignore", "inherit", "inherit"], + }, + ); + if (child.status !== 0) { + throw new Error(`Scenario ${scenario} failed with exit code ${child.status}`); + } + const result = JSON.parse(readFileSync(outPath, "utf8")); + if (!isFinite(result.opsPerSec)) { + throw new Error(`Scenario ${scenario} produced no ops/s value`); + } + return result.opsPerSec; +} + +function main() { + const args = parseArgs(process.argv.slice(2)); + const selected = args.scenarios + ? SCENARIOS.filter((s) => args.scenarios.includes(s.name)) + : SCENARIOS; + + const tmp = mkdtempSync(join(tmpdir(), "perf-")); + const results = []; + + try { + for (const scenario of selected) { + const samples = []; + for (let i = 0; i < args.samples; i++) { + const out = join(tmp, `${scenario.name}-${i}.json`); + console.log(`\n[run] ${scenario.name} sample ${i + 1}/${args.samples}`); + const ops = runOne(scenario.name, args, out); + samples.push(ops); + } + results.push({ + name: scenario.name, + tier: scenario.tier, + samples, + median: median(samples), + mean: mean(samples), + stdev: stdev(samples), + }); + } + } finally { + try { + rmSync(tmp, { recursive: true, force: true }); + } catch { + /* noop */ + } + } + + const out = { + generatedAt: new Date().toISOString(), + node: process.version, + samples: args.samples, + duration: args.duration, + warmup: args.warmup, + results, + }; + writeFileSync(args.out, JSON.stringify(out, null, 2)); + console.log(`\nWrote ${args.out}`); +} + +main(); diff --git a/test/performanceTests/test/appInsightsShim.spec.ts b/test/performanceTests/test/appInsightsShim.spec.ts index 886ddc9c7..d0fe78608 100644 --- a/test/performanceTests/test/appInsightsShim.spec.ts +++ b/test/performanceTests/test/appInsightsShim.spec.ts @@ -6,11 +6,23 @@ import appInsights from "applicationinsights"; import dotenv from "dotenv"; dotenv.config(); +let started = false; +function ensureStarted(): void { + if (started) { + return; + } + appInsights + .setup( + process.env.APPLICATIONINSIGHTS_CONNECTION_STRING || + "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://localhost/", + ) + .start(); + started = true; +} + export abstract class ShimTest extends PerfTest { constructor() { super(); - appInsights - .setup(process.env.APPLICATIONINSIGHTS_CONNECTION_STRING || "") - .start(); + ensureStarted(); } } diff --git a/test/performanceTests/test/azureMonitorLog.spec.ts b/test/performanceTests/test/azureMonitorLog.spec.ts new file mode 100644 index 000000000..3f1a1b276 --- /dev/null +++ b/test/performanceTests/test/azureMonitorLog.spec.ts @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Scenario: log emission via this package's modern `useAzureMonitor` API +// plus the OpenTelemetry logs API. Gated for regression. + +import { PerfOptionDictionary, PerfTest } from "@azure-tools/test-perf"; +import { logs, Logger } from "@opentelemetry/api-logs"; +import { useAzureMonitor } from "applicationinsights"; +import dotenv from "dotenv"; +dotenv.config(); + +type AzureMonitorLogOptions = Record; + +let initialized = false; +let logger: Logger | undefined; +function ensureInit(): Logger { + if (initialized && logger) { + return logger; + } + useAzureMonitor({ + azureMonitorExporterOptions: { + connectionString: + process.env.APPLICATIONINSIGHTS_CONNECTION_STRING || + "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://localhost/", + }, + }); + logger = logs.getLogger("perf-azure-monitor-log"); + initialized = true; + return logger; +} + +export class AzureMonitorLogTest extends PerfTest { + public options: PerfOptionDictionary = {}; + + constructor() { + super(); + ensureInit(); + } + + async run(): Promise { + logger!.emit({ body: "trace message" }); + } +} diff --git a/test/performanceTests/test/azureMonitorSpan.spec.ts b/test/performanceTests/test/azureMonitorSpan.spec.ts new file mode 100644 index 000000000..f90ac65c2 --- /dev/null +++ b/test/performanceTests/test/azureMonitorSpan.spec.ts @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Scenario: span emission via this package's modern `useAzureMonitor` API +// plus the OpenTelemetry API. Gated for regression. + +import { PerfOptionDictionary, PerfTest } from "@azure-tools/test-perf"; +import { trace, Tracer } from "@opentelemetry/api"; +import { useAzureMonitor } from "applicationinsights"; +import dotenv from "dotenv"; +dotenv.config(); + +type AzureMonitorSpanOptions = Record; + +let initialized = false; +let tracer: Tracer | undefined; +function ensureInit(): Tracer { + if (initialized && tracer) { + return tracer; + } + useAzureMonitor({ + azureMonitorExporterOptions: { + connectionString: + process.env.APPLICATIONINSIGHTS_CONNECTION_STRING || + "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://localhost/", + }, + }); + tracer = trace.getTracer("perf-azure-monitor-span"); + initialized = true; + return tracer; +} + +export class AzureMonitorSpanTest extends PerfTest { + public options: PerfOptionDictionary = {}; + + constructor() { + super(); + ensureInit(); + } + + async run(): Promise { + const t = tracer!; + const span = t.startSpan("perf-span", { + attributes: { + "db.system": "zsql", + "db.statement": "SELECT * FROM Customers", + "peer.service": "http://dbname", + }, + }); + span.end(); + } +} diff --git a/test/performanceTests/test/index.spec.ts b/test/performanceTests/test/index.spec.ts index 033e63975..42e7ece01 100644 --- a/test/performanceTests/test/index.spec.ts +++ b/test/performanceTests/test/index.spec.ts @@ -4,60 +4,79 @@ import { createPerfProgram } from "@azure-tools/test-perf"; import { TrackDependencyTest } from "./trackDependency.spec.js"; import { TrackTraceTest } from "./trackTrace.spec.js"; +import { AzureMonitorSpanTest } from "./azureMonitorSpan.spec.js"; +import { AzureMonitorLogTest } from "./azureMonitorLog.spec.js"; +import { OtelSpanTest } from "./otelSpan.spec.js"; +import { OtelLogTest } from "./otelLog.spec.js"; import https from "https"; import fs from "fs"; -const json = JSON.parse(fs.readFileSync('package.json', 'utf8')); +const json = JSON.parse(fs.readFileSync("package.json", "utf8")); let perfTestData: string = ""; const originalConsole = console.log; -console.log = function(message: string) { - perfTestData += message; +console.log = function (message: string) { + perfTestData += message + "\n"; }; -const perfProgram = createPerfProgram(TrackDependencyTest, TrackTraceTest); +const perfProgram = createPerfProgram( + TrackDependencyTest, + TrackTraceTest, + AzureMonitorSpanTest, + AzureMonitorLogTest, + OtelSpanTest, + OtelLogTest, +); + +const scenarioFromArgv = process.argv + .slice(2) + .find((a) => !a.startsWith("-")) || "unknown"; + perfProgram.run().then(() => { console.log = originalConsole; + + // Only post telemetry when the Microsoft-internal CI secrets are present; + // local/CI-without-secrets runs simply re-emit captured output and exit. + process.stdout.write(perfTestData); + + const iKey = process.env.GENEVA_IKEY; + const apiKey = process.env.API_KEY; + if (!iKey || !apiKey) { + return; + } + + // Match the LAST occurrence of ops/s (skips the warmup line). + const allMatches = [...perfTestData.matchAll(/(\d{1,3}(?:,\d{3})*(?:\.\d+)?)\s*ops\/s/g)]; + if (allMatches.length === 0) { + console.error("Error: Could not find a performance value to report."); + return; + } + const lastMatch = allMatches[allMatches.length - 1]; + const value = Number(lastMatch[1].replace(/,/g, "")); + const time = new Date().toISOString(); const name = "SDKPerfTest"; - const iKey = process.env.GENEVA_IKEY; - let sku = ""; const ver = "4.0"; - const apiKey = process.env.API_KEY; const testName = "NodePerfTests"; const unit = "ops/sec"; const metric = "ops"; - const sdkVersion = json.dependencies['applicationinsights'].replace(/^\^/, ''); - let value = 0; - - if (perfTestData.includes("TrackDependencyTest")) { - sku = "TrackDependencyTest"; - } else if (perfTestData.includes("TrackTraceTest")) { - sku = "TrackTraceTest"; - } - - let regex = /(\d{1,3}(,\d{3})*) ops\/s/; - const match = perfTestData.match(regex); - console.log(`Match: ${match}`); - console.log(`perfTestData: `, perfTestData); - if (match) { - // Remove commas from the result - value = Number(match[1].replace(/,/g, '')); - console.log(`Value: ${value}`); - https.get(`https://browser.events.data.microsoft.com/OneCollector/1.0/t.js?qsp=true&name=%22${name}%22&time=%22${time}%22&ver=%22${ver}%22&iKey=%22${iKey}%22&apikey=${apiKey}&-testName=%22${testName}%22&-sku=%22${sku}%22&-version=%22${sdkVersion}%22&-unitOfMeasure=%22${unit}%22&-metric=%22${metric}%22&-value*6=${value}`, (res) => { - let data = ''; - - res.on('data', (chunk) => { - data += chunk; - }); - - res.on('end', () => { - console.log(data); - }); - }).on('error', (err) => { + const sdkVersion = (json.dependencies?.applicationinsights || "0.0.0").replace(/^\^/, ""); + const sku = scenarioFromArgv; + + https + .get( + `https://browser.events.data.microsoft.com/OneCollector/1.0/t.js?qsp=true&name=%22${name}%22&time=%22${time}%22&ver=%22${ver}%22&iKey=%22${iKey}%22&apikey=${apiKey}&-testName=%22${testName}%22&-sku=%22${sku}%22&-version=%22${sdkVersion}%22&-unitOfMeasure=%22${unit}%22&-metric=%22${metric}%22&-value*6=${value}`, + (res) => { + let data = ""; + res.on("data", (chunk) => { + data += chunk; + }); + res.on("end", () => { + console.log(data); + }); + }, + ) + .on("error", (err) => { console.error(err); }); - } else { - console.error("Error: Could not find a performance value to report."); - } }); \ No newline at end of file diff --git a/test/performanceTests/test/otelLog.spec.ts b/test/performanceTests/test/otelLog.spec.ts new file mode 100644 index 000000000..6bbb63d37 --- /dev/null +++ b/test/performanceTests/test/otelLog.spec.ts @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Reference scenario: log emission via plain upstream OpenTelemetry only. +// Reported as informational baseline; not used for PR-gating regression checks. + +import { PerfOptionDictionary, PerfTest } from "@azure-tools/test-perf"; +import { logs, Logger } from "@opentelemetry/api-logs"; +import { + LoggerProvider, + SimpleLogRecordProcessor, + InMemoryLogRecordExporter, +} from "@opentelemetry/sdk-logs"; + +type OtelLogOptions = Record; + +let initialized = false; +let logger: Logger | undefined; +function ensureProvider(): Logger { + if (initialized && logger) { + return logger; + } + const provider = new LoggerProvider({ + processors: [new SimpleLogRecordProcessor(new InMemoryLogRecordExporter())], + }); + logger = provider.getLogger("perf-otel-log"); + initialized = true; + return logger; +} + +export class OtelLogTest extends PerfTest { + public options: PerfOptionDictionary = {}; + + constructor() { + super(); + ensureProvider(); + } + + async run(): Promise { + const l = logger ?? logs.getLogger("perf-otel-log"); + l.emit({ body: "trace message" }); + } +} diff --git a/test/performanceTests/test/otelSpan.spec.ts b/test/performanceTests/test/otelSpan.spec.ts new file mode 100644 index 000000000..b63b4cd94 --- /dev/null +++ b/test/performanceTests/test/otelSpan.spec.ts @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Reference scenario: span creation via plain upstream OpenTelemetry only. +// Reported as informational baseline; not used for PR-gating regression checks. + +import { PerfOptionDictionary, PerfTest } from "@azure-tools/test-perf"; +import { trace, Tracer } from "@opentelemetry/api"; +import { + BasicTracerProvider, + SimpleSpanProcessor, + InMemorySpanExporter, +} from "@opentelemetry/sdk-trace-base"; + +type OtelSpanOptions = Record; + +let initialized = false; +let tracer: Tracer | undefined; +function ensureProvider(): Tracer { + if (initialized && tracer) { + return tracer; + } + const provider = new BasicTracerProvider({ + spanProcessors: [new SimpleSpanProcessor(new InMemorySpanExporter())], + }); + // Avoid setting the global provider — keeps state local so multiple + // scenarios can coexist if ever run in the same process. + tracer = provider.getTracer("perf-otel-span"); + initialized = true; + return tracer; +} + +export class OtelSpanTest extends PerfTest { + public options: PerfOptionDictionary = {}; + + constructor() { + super(); + ensureProvider(); + } + + async run(): Promise { + const t = tracer ?? trace.getTracer("perf-otel-span"); + const span = t.startSpan("perf-span", { + attributes: { + "db.system": "zsql", + "db.statement": "SELECT * FROM Customers", + "peer.service": "http://dbname", + }, + }); + span.end(); + } +} From 633c484f7e0d04b01d749717067aac560d3e0c1e Mon Sep 17 00:00:00 2001 From: Jackson Weber Date: Thu, 21 May 2026 21:36:33 -0700 Subject: [PATCH 2/4] perf: address PR feedback and fix CI build Fixes CI: add explicit npm run build of the perf harness before running benchmarks; previous run died with ERR_MODULE_NOT_FOUND because dist-esm was never produced. Review feedback: - workflow: switch perf harness install to npm ci; add --no-package-lock to tarball installs so the lockfile is not rewritten mid-run - AzureMonitor scenarios: acquire @opentelemetry/api and @opentelemetry/api-logs via createRequire resolved from the installed applicationinsights, so the Tracer/Logger we benchmark is backed by the SAME api / api-logs instance that useAzureMonitor() mutated (otherwise a duplicate hoisted copy at the harness level would yield a no-op proxy and we'd silently measure nothing) - OTel reference scenarios: use provider.getTracer / provider.getLogger directly instead of going through the global registry, eliminating dual-instance concerns for these - index.spec.ts: capture console.log with rest args + util.format so multi-arg / non-string calls are formatted the same way Node would print them - runBenchmarks.mjs: propagate child.error and child.signal in failure messages (spawnSync status can be null on spawn error or signal exit) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/performance.yml | 10 +++++-- test/performanceTests/runBenchmarks.mjs | 8 +++++ .../test/azureMonitorLog.spec.ts | 27 ++++++++++++----- .../test/azureMonitorSpan.spec.ts | 30 ++++++++++++------- test/performanceTests/test/index.spec.ts | 21 +++++++------ test/performanceTests/test/otelLog.spec.ts | 13 ++++---- test/performanceTests/test/otelSpan.spec.ts | 15 +++++----- 7 files changed, 80 insertions(+), 44 deletions(-) diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index ddfd0a01c..915925580 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -69,11 +69,15 @@ jobs: # ---- Use the PR's perf harness for BOTH runs (consistent code) ---- - name: Install perf harness deps (PR) working-directory: pr/test/performanceTests - run: npm install + run: npm ci - name: Install CANDIDATE applicationinsights into perf harness working-directory: pr/test/performanceTests - run: npm install --no-save "$GITHUB_WORKSPACE/candidate.tgz" + run: npm install --no-save --no-package-lock "$GITHUB_WORKSPACE/candidate.tgz" + + - name: Build perf harness (TS -> dist-esm) + working-directory: pr/test/performanceTests + run: npm run build - name: Run candidate benchmarks working-directory: pr/test/performanceTests @@ -86,7 +90,7 @@ jobs: - name: Install BASELINE applicationinsights into perf harness working-directory: pr/test/performanceTests - run: npm install --no-save "$GITHUB_WORKSPACE/baseline.tgz" + run: npm install --no-save --no-package-lock "$GITHUB_WORKSPACE/baseline.tgz" - name: Run baseline benchmarks working-directory: pr/test/performanceTests diff --git a/test/performanceTests/runBenchmarks.mjs b/test/performanceTests/runBenchmarks.mjs index 1a9b37e9e..a015eed8c 100644 --- a/test/performanceTests/runBenchmarks.mjs +++ b/test/performanceTests/runBenchmarks.mjs @@ -83,6 +83,14 @@ function runOne(scenario, args, outPath) { stdio: ["ignore", "inherit", "inherit"], }, ); + if (child.error) { + throw new Error( + `Scenario ${scenario} could not be spawned: ${child.error.message}`, + ); + } + if (child.signal) { + throw new Error(`Scenario ${scenario} terminated by signal ${child.signal}`); + } if (child.status !== 0) { throw new Error(`Scenario ${scenario} failed with exit code ${child.status}`); } diff --git a/test/performanceTests/test/azureMonitorLog.spec.ts b/test/performanceTests/test/azureMonitorLog.spec.ts index 3f1a1b276..5c4d43020 100644 --- a/test/performanceTests/test/azureMonitorLog.spec.ts +++ b/test/performanceTests/test/azureMonitorLog.spec.ts @@ -3,21 +3,33 @@ // Scenario: log emission via this package's modern `useAzureMonitor` API // plus the OpenTelemetry logs API. Gated for regression. +// +// We acquire `@opentelemetry/api-logs` via `createRequire` resolved from the +// installed `applicationinsights` package, so the Logger we benchmark is +// backed by the SAME api-logs instance that `useAzureMonitor()` registered +// its LoggerProvider on. Without this, if npm installs a duplicate copy of +// `@opentelemetry/api-logs` at the harness level, `logs.getLogger()` returns +// a no-op proxy and the benchmark silently measures nothing. +import { createRequire } from "node:module"; import { PerfOptionDictionary, PerfTest } from "@azure-tools/test-perf"; -import { logs, Logger } from "@opentelemetry/api-logs"; import { useAzureMonitor } from "applicationinsights"; import dotenv from "dotenv"; dotenv.config(); type AzureMonitorLogOptions = Record; +const harnessRequire = createRequire(import.meta.url); +const aiEntry = harnessRequire.resolve("applicationinsights"); +const aiRequire = createRequire(aiEntry); +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const { logs } = aiRequire("@opentelemetry/api-logs") as any; + let initialized = false; -let logger: Logger | undefined; -function ensureInit(): Logger { - if (initialized && logger) { - return logger; - } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +let logger: any; +function ensureInit(): void { + if (initialized) return; useAzureMonitor({ azureMonitorExporterOptions: { connectionString: @@ -27,7 +39,6 @@ function ensureInit(): Logger { }); logger = logs.getLogger("perf-azure-monitor-log"); initialized = true; - return logger; } export class AzureMonitorLogTest extends PerfTest { @@ -39,6 +50,6 @@ export class AzureMonitorLogTest extends PerfTest { } async run(): Promise { - logger!.emit({ body: "trace message" }); + logger.emit({ body: "trace message" }); } } diff --git a/test/performanceTests/test/azureMonitorSpan.spec.ts b/test/performanceTests/test/azureMonitorSpan.spec.ts index f90ac65c2..dad8843b7 100644 --- a/test/performanceTests/test/azureMonitorSpan.spec.ts +++ b/test/performanceTests/test/azureMonitorSpan.spec.ts @@ -2,22 +2,34 @@ // Licensed under the MIT License. // Scenario: span emission via this package's modern `useAzureMonitor` API -// plus the OpenTelemetry API. Gated for regression. +// plus the OpenTelemetry trace API. Gated for regression. +// +// We deliberately acquire `@opentelemetry/api` from inside +// `applicationinsights`'s module-resolution tree (via `createRequire`) +// so that the Tracer we benchmark is registered against the SAME api +// instance that `useAzureMonitor()` mutated. Otherwise — if npm happens +// to install a second hoisted copy of `@opentelemetry/api` at the perf +// harness level — we would measure a no-op proxy tracer. +import { createRequire } from "node:module"; import { PerfOptionDictionary, PerfTest } from "@azure-tools/test-perf"; -import { trace, Tracer } from "@opentelemetry/api"; import { useAzureMonitor } from "applicationinsights"; import dotenv from "dotenv"; dotenv.config(); type AzureMonitorSpanOptions = Record; +const harnessRequire = createRequire(import.meta.url); +const aiEntry = harnessRequire.resolve("applicationinsights"); +const aiRequire = createRequire(aiEntry); +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const { trace } = aiRequire("@opentelemetry/api") as any; + let initialized = false; -let tracer: Tracer | undefined; -function ensureInit(): Tracer { - if (initialized && tracer) { - return tracer; - } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +let tracer: any; +function ensureInit(): void { + if (initialized) return; useAzureMonitor({ azureMonitorExporterOptions: { connectionString: @@ -27,7 +39,6 @@ function ensureInit(): Tracer { }); tracer = trace.getTracer("perf-azure-monitor-span"); initialized = true; - return tracer; } export class AzureMonitorSpanTest extends PerfTest { @@ -39,8 +50,7 @@ export class AzureMonitorSpanTest extends PerfTest { } async run(): Promise { - const t = tracer!; - const span = t.startSpan("perf-span", { + const span = tracer.startSpan("perf-span", { attributes: { "db.system": "zsql", "db.statement": "SELECT * FROM Customers", diff --git a/test/performanceTests/test/index.spec.ts b/test/performanceTests/test/index.spec.ts index 42e7ece01..e227b8539 100644 --- a/test/performanceTests/test/index.spec.ts +++ b/test/performanceTests/test/index.spec.ts @@ -10,13 +10,16 @@ import { OtelSpanTest } from "./otelSpan.spec.js"; import { OtelLogTest } from "./otelLog.spec.js"; import https from "https"; import fs from "fs"; +import { format } from "util"; const json = JSON.parse(fs.readFileSync("package.json", "utf8")); -let perfTestData: string = ""; +let perfTestData = ""; const originalConsole = console.log; -console.log = function (message: string) { - perfTestData += message + "\n"; +// Preserve Node's default console.log formatting (multi-arg, non-string +// values) so downstream regex parsing matches what would have been printed. +console.log = function (...args: unknown[]): void { + perfTestData += format(...args) + "\n"; }; const perfProgram = createPerfProgram( @@ -28,17 +31,17 @@ const perfProgram = createPerfProgram( OtelLogTest, ); -const scenarioFromArgv = process.argv - .slice(2) - .find((a) => !a.startsWith("-")) || "unknown"; +const scenarioFromArgv = + process.argv.slice(2).find((a) => !a.startsWith("-")) || "unknown"; perfProgram.run().then(() => { console.log = originalConsole; - // Only post telemetry when the Microsoft-internal CI secrets are present; - // local/CI-without-secrets runs simply re-emit captured output and exit. + // Re-emit captured stdout so it remains visible to the parent driver. process.stdout.write(perfTestData); + // Only post telemetry when the Microsoft-internal CI secrets are present; + // local/CI-without-secrets runs simply re-emit captured output and exit. const iKey = process.env.GENEVA_IKEY; const apiKey = process.env.API_KEY; if (!iKey || !apiKey) { @@ -79,4 +82,4 @@ perfProgram.run().then(() => { .on("error", (err) => { console.error(err); }); -}); \ No newline at end of file +}); diff --git a/test/performanceTests/test/otelLog.spec.ts b/test/performanceTests/test/otelLog.spec.ts index 6bbb63d37..0cc97c38a 100644 --- a/test/performanceTests/test/otelLog.spec.ts +++ b/test/performanceTests/test/otelLog.spec.ts @@ -3,9 +3,13 @@ // Reference scenario: log emission via plain upstream OpenTelemetry only. // Reported as informational baseline; not used for PR-gating regression checks. +// +// Uses the provider directly (no global LoggerProvider registration) so this +// benchmark is unaffected by whether other scenarios in the same install share +// or duplicate the @opentelemetry/api-logs module instance. import { PerfOptionDictionary, PerfTest } from "@azure-tools/test-perf"; -import { logs, Logger } from "@opentelemetry/api-logs"; +import type { Logger } from "@opentelemetry/api-logs"; import { LoggerProvider, SimpleLogRecordProcessor, @@ -14,17 +18,15 @@ import { type OtelLogOptions = Record; -let initialized = false; let logger: Logger | undefined; function ensureProvider(): Logger { - if (initialized && logger) { + if (logger) { return logger; } const provider = new LoggerProvider({ processors: [new SimpleLogRecordProcessor(new InMemoryLogRecordExporter())], }); logger = provider.getLogger("perf-otel-log"); - initialized = true; return logger; } @@ -37,7 +39,6 @@ export class OtelLogTest extends PerfTest { } async run(): Promise { - const l = logger ?? logs.getLogger("perf-otel-log"); - l.emit({ body: "trace message" }); + logger!.emit({ body: "trace message" }); } } diff --git a/test/performanceTests/test/otelSpan.spec.ts b/test/performanceTests/test/otelSpan.spec.ts index b63b4cd94..ee664b068 100644 --- a/test/performanceTests/test/otelSpan.spec.ts +++ b/test/performanceTests/test/otelSpan.spec.ts @@ -3,9 +3,13 @@ // Reference scenario: span creation via plain upstream OpenTelemetry only. // Reported as informational baseline; not used for PR-gating regression checks. +// +// Uses the provider directly (no global TracerProvider registration) so this +// benchmark is unaffected by whether other scenarios in the same install share +// or duplicate the @opentelemetry/api module instance. import { PerfOptionDictionary, PerfTest } from "@azure-tools/test-perf"; -import { trace, Tracer } from "@opentelemetry/api"; +import type { Tracer } from "@opentelemetry/api"; import { BasicTracerProvider, SimpleSpanProcessor, @@ -14,19 +18,15 @@ import { type OtelSpanOptions = Record; -let initialized = false; let tracer: Tracer | undefined; function ensureProvider(): Tracer { - if (initialized && tracer) { + if (tracer) { return tracer; } const provider = new BasicTracerProvider({ spanProcessors: [new SimpleSpanProcessor(new InMemorySpanExporter())], }); - // Avoid setting the global provider — keeps state local so multiple - // scenarios can coexist if ever run in the same process. tracer = provider.getTracer("perf-otel-span"); - initialized = true; return tracer; } @@ -39,8 +39,7 @@ export class OtelSpanTest extends PerfTest { } async run(): Promise { - const t = tracer ?? trace.getTracer("perf-otel-span"); - const span = t.startSpan("perf-span", { + const span = tracer!.startSpan("perf-span", { attributes: { "db.system": "zsql", "db.statement": "SELECT * FROM Customers", From 6ba986e9217970953a7c42e4f0f719ba6e40450a Mon Sep 17 00:00:00 2001 From: Jackson Weber Date: Thu, 21 May 2026 22:12:24 -0700 Subject: [PATCH 3/4] perf(ci): reduce sample budget so the perf job fits in its timeout Previous run cancelled at 25min job timeout: candidate ran 12min (5 samples x 6 scenarios x ~24s/sample on CI runners; AzureMonitor scenarios pay a 5-10s SDK init cost per fresh child process), baseline got 12min in before cancellation. Cut samples 5 -> 3, duration 8s -> 5s, warmup 2s -> 1s. New estimate: ~5min per side, ~12min total with install/build. Bumped job timeout 25 -> 40 min for safety margin. Median of 3 samples is still robust to a single outlier (the main source of CI flake), and 5s is enough sustained measurement time for even the slowest scenario (AzureMonitorLog at ~12k ops/s yields ~60k ops per sample). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/performance.yml | 12 +++++++----- test/performanceTests/README.md | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index 915925580..ca9d117ae 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -16,13 +16,15 @@ permissions: jobs: perf-regression: runs-on: ubuntu-latest - # 20 min is plenty for ~5 samples × ~6 scenarios × ~8s + builds. - timeout-minutes: 25 + # Headroom for two full benchmark runs (candidate + baseline) plus npm + # installs and tarball packing. Each benchmark run is bounded by + # samples * scenarios * (warmup + duration + per-child init). + timeout-minutes: 40 env: NODE_VERSION: '22.x' - PERF_SAMPLES: '5' - PERF_DURATION: '8' - PERF_WARMUP: '2' + PERF_SAMPLES: '3' + PERF_DURATION: '5' + PERF_WARMUP: '1' PERF_REGRESSION_THRESHOLD: '15' steps: diff --git a/test/performanceTests/README.md b/test/performanceTests/README.md index 434a3c98a..757c537a8 100644 --- a/test/performanceTests/README.md +++ b/test/performanceTests/README.md @@ -19,7 +19,7 @@ this package and reports them against an upstream-OpenTelemetry-only baseline. 3. Or run every scenario and produce a JSON summary suitable for comparison: - `npm run perf:benchmark -- --out results.json --samples 5 --duration 8` + `npm run perf:benchmark -- --out results.json --samples 3 --duration 5` #### Scenario tiers From 7ad23830dfd8e4d75d36d71cc88040ea5a393b7a Mon Sep 17 00:00:00 2001 From: Jackson Weber Date: Fri, 22 May 2026 12:28:49 -0700 Subject: [PATCH 4/4] perf: align OTel package versions with applicationinsights to dedupe api-logs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- test/performanceTests/package-lock.json | 85 ++++++------------------- test/performanceTests/package.json | 6 +- 2 files changed, 23 insertions(+), 68 deletions(-) diff --git a/test/performanceTests/package-lock.json b/test/performanceTests/package-lock.json index a57c32906..0ab57e3e7 100644 --- a/test/performanceTests/package-lock.json +++ b/test/performanceTests/package-lock.json @@ -11,9 +11,9 @@ "dependencies": { "@azure-tools/test-perf": "^1.0.0", "@opentelemetry/api": "^1.9.0", - "@opentelemetry/api-logs": "^0.208.0", - "@opentelemetry/sdk-logs": "^0.208.0", - "@opentelemetry/sdk-trace-base": "^2.2.0", + "@opentelemetry/api-logs": "^0.217.0", + "@opentelemetry/sdk-logs": "^0.217.0", + "@opentelemetry/sdk-trace-base": "^2.7.1", "applicationinsights": "^3.2.2", "dotenv": "^16.4.5", "tslib": "^2.6.2" @@ -707,9 +707,9 @@ } }, "node_modules/@opentelemetry/api-logs": { - "version": "0.208.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.208.0.tgz", - "integrity": "sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==", + "version": "0.217.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.217.0.tgz", + "integrity": "sha512-Cdq0jW2lknrNfrAm92MyEAvpe2cRsKjdnQLHUL6xRA4IVUnsWx6P65E7NcUO0Y+L4w1Aee5iV8FvjSwd+lrs9A==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "^1.3.0" @@ -731,9 +731,9 @@ } }, "node_modules/@opentelemetry/core": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", - "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" @@ -1966,28 +1966,13 @@ "@opentelemetry/api": "^1.0.0" } }, - "node_modules/@opentelemetry/resource-detector-azure/node_modules/@opentelemetry/core": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", - "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, "node_modules/@opentelemetry/resources": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", - "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.2.0", + "@opentelemetry/core": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { @@ -1998,14 +1983,15 @@ } }, "node_modules/@opentelemetry/sdk-logs": { - "version": "0.208.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.208.0.tgz", - "integrity": "sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA==", + "version": "0.217.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.217.0.tgz", + "integrity": "sha512-BB+PcHItcZDL63dPMW+mJvwN9rk37wuIDjRxbVlg6pPDvDR/7GL7UJHbGsllgoggOoTimsKgENaWPoGch/oE1A==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "0.208.0", - "@opentelemetry/core": "2.2.0", - "@opentelemetry/resources": "2.2.0" + "@opentelemetry/api-logs": "0.217.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -2191,37 +2177,6 @@ "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/core": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", - "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/resources": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", - "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, "node_modules/@opentelemetry/sdk-trace-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.1.0.tgz", diff --git a/test/performanceTests/package.json b/test/performanceTests/package.json index 4a58ad2f6..ccefbedde 100644 --- a/test/performanceTests/package.json +++ b/test/performanceTests/package.json @@ -11,9 +11,9 @@ "dependencies": { "@azure-tools/test-perf": "^1.0.0", "@opentelemetry/api": "^1.9.0", - "@opentelemetry/api-logs": "^0.208.0", - "@opentelemetry/sdk-logs": "^0.208.0", - "@opentelemetry/sdk-trace-base": "^2.2.0", + "@opentelemetry/api-logs": "^0.217.0", + "@opentelemetry/sdk-logs": "^0.217.0", + "@opentelemetry/sdk-trace-base": "^2.7.1", "applicationinsights": "^3.2.2", "dotenv": "^16.4.5", "tslib": "^2.6.2"