diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml new file mode 100644 index 000000000..ca9d117ae --- /dev/null +++ b/.github/workflows/performance.yml @@ -0,0 +1,140 @@ +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 + # 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: '3' + PERF_DURATION: '5' + PERF_WARMUP: '1' + 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 ci + + - name: Install CANDIDATE applicationinsights into perf harness + working-directory: pr/test/performanceTests + 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 + 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 --no-package-lock "$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..757c537a8 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 3 --duration 5` + +#### 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..0ab57e3e7 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.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" @@ -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.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.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.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.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", @@ -2117,73 +1966,36 @@ "@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==", + "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.0.0 <1.10.0" + "@opentelemetry/api": ">=1.3.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/sdk-logs": { + "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/core": "2.1.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" }, - "peerDependencies": { - "@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==", - "dependencies": { - "@opentelemetry/api-logs": "0.52.1", - "@opentelemetry/core": "1.25.1", - "@opentelemetry/resources": "1.25.1" - }, - "engines": { - "node": ">=14" - }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } @@ -2235,15 +2047,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 +2143,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,28 +2160,14 @@ "@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==", - "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.1.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz", - "integrity": "sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==", + "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.1.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { @@ -2397,15 +2177,6 @@ "@opentelemetry/api": ">=1.3.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==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "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", @@ -2438,13 +2209,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 +2273,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 +2517,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 +2685,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 +2923,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 +3005,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..ccefbedde 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.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" @@ -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..a015eed8c --- /dev/null +++ b/test/performanceTests/runBenchmarks.mjs @@ -0,0 +1,151 @@ +#!/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.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}`); + } + 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..5c4d43020 --- /dev/null +++ b/test/performanceTests/test/azureMonitorLog.spec.ts @@ -0,0 +1,55 @@ +// 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. +// +// 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 { 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; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +let logger: any; +function ensureInit(): void { + if (initialized) return; + 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; +} + +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..dad8843b7 --- /dev/null +++ b/test/performanceTests/test/azureMonitorSpan.spec.ts @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Scenario: span emission via this package's modern `useAzureMonitor` API +// 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 { 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; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +let tracer: any; +function ensureInit(): void { + if (initialized) return; + 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; +} + +export class AzureMonitorSpanTest extends PerfTest { + public options: PerfOptionDictionary = {}; + + constructor() { + super(); + ensureInit(); + } + + async run(): Promise { + const span = tracer.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..e227b8539 100644 --- a/test/performanceTests/test/index.spec.ts +++ b/test/performanceTests/test/index.spec.ts @@ -4,60 +4,82 @@ 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"; +import { format } from "util"; -const json = JSON.parse(fs.readFileSync('package.json', 'utf8')); -let perfTestData: string = ""; +const json = JSON.parse(fs.readFileSync("package.json", "utf8")); +let perfTestData = ""; const originalConsole = console.log; -console.log = function(message: string) { - perfTestData += message; +// 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(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; + + // 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) { + 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..0cc97c38a --- /dev/null +++ b/test/performanceTests/test/otelLog.spec.ts @@ -0,0 +1,44 @@ +// 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. +// +// 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 type { Logger } from "@opentelemetry/api-logs"; +import { + LoggerProvider, + SimpleLogRecordProcessor, + InMemoryLogRecordExporter, +} from "@opentelemetry/sdk-logs"; + +type OtelLogOptions = Record; + +let logger: Logger | undefined; +function ensureProvider(): Logger { + if (logger) { + return logger; + } + const provider = new LoggerProvider({ + processors: [new SimpleLogRecordProcessor(new InMemoryLogRecordExporter())], + }); + logger = provider.getLogger("perf-otel-log"); + return logger; +} + +export class OtelLogTest extends PerfTest { + public options: PerfOptionDictionary = {}; + + constructor() { + super(); + ensureProvider(); + } + + async run(): Promise { + logger!.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..ee664b068 --- /dev/null +++ b/test/performanceTests/test/otelSpan.spec.ts @@ -0,0 +1,51 @@ +// 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. +// +// 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 type { Tracer } from "@opentelemetry/api"; +import { + BasicTracerProvider, + SimpleSpanProcessor, + InMemorySpanExporter, +} from "@opentelemetry/sdk-trace-base"; + +type OtelSpanOptions = Record; + +let tracer: Tracer | undefined; +function ensureProvider(): Tracer { + if (tracer) { + return tracer; + } + const provider = new BasicTracerProvider({ + spanProcessors: [new SimpleSpanProcessor(new InMemorySpanExporter())], + }); + tracer = provider.getTracer("perf-otel-span"); + return tracer; +} + +export class OtelSpanTest extends PerfTest { + public options: PerfOptionDictionary = {}; + + constructor() { + super(); + ensureProvider(); + } + + async run(): Promise { + const span = tracer!.startSpan("perf-span", { + attributes: { + "db.system": "zsql", + "db.statement": "SELECT * FROM Customers", + "peer.service": "http://dbname", + }, + }); + span.end(); + } +}