Skip to content

Perf: Dedupe tool-definition JSON across telemetry/OTel sites#318477

Merged
dmitrivMS merged 6 commits into
mainfrom
dev/dmitriv/perf-duplicate-json
May 27, 2026
Merged

Perf: Dedupe tool-definition JSON across telemetry/OTel sites#318477
dmitrivMS merged 6 commits into
mainfrom
dev/dmitriv/perf-duplicate-json

Conversation

@dmitrivMS
Copy link
Copy Markdown
Contributor

@dmitrivMS dmitrivMS commented May 27, 2026

Problem

Heap dumps of the extension host showed ~35 MB retained by ~10 duplicate copies of the same ~3.49 MB tool-definition JSON string. Each LLM round serialized the tool list multiple times — once for the OTel inference span, once for the OTel agent span, once for the tools_available event, once for the GH request.options.tools enhanced telemetry event, and once again from the per-key Object.entries(request) loop into request.option.tools. Each call produced an independent multi-MB string and was retained by buffered spans / telemetry queues.

Fix

Add two memoized stringifiers in messageFormatters and route every producer through them:

  • stringifyToolDefinitionsForOTel(tools) — normalizes via toToolDefinitions then JSON.stringify; used for gen_ai.tool.definitions.
  • stringifyToolsRawForTelemetry(tools) — raw JSON.stringify(tools); used by legacy GH telemetry that needs exact request-shape preservation. Mirrors JSON.stringify semantics: returns undefined only when the input is undefined, and '[]' for an empty array.

Both use:

  1. WeakMap by-reference cache — repeated calls with the same array return the same string instance instantly.
  2. Single-slot content intern — if a freshly produced JSON equals the last produced JSON (very common across consecutive agent-loop rounds even when the array reference changes), return the previously interned instance so only one copy stays alive.

Wired sites:

  • chatMLFetcher.ts — both GH request.options.tools calls + both Object.entries(request) loops (helper drives the tools branch; ?? 'undefined' preserves prior byte-for-byte output).
  • toolCallingLoop.ts — agent-span gen_ai.tool.definitions + tools_available event.
  • genAiEvents.ts — inference details event.
  • byok/anthropicProvider.ts, byok/geminiNativeProvider.ts — per-request span attribute.

Behavioral notes

  • toolCallingLoop's tools_available event is now suppressed when no nameable tools are present (previously emitted with possibly nameless entries). This matches OTel spec and the other call sites.
  • Test in genAiEvents.spec.ts asserting attrs[TOOL_DEFINITIONS] === JSON.stringify(tools) still passes — the raw helper returns exactly that.

Tests

messageFormatters.spec.ts — added coverage for the helpers, including:

  • Empty input / undefined input parity with JSON.stringify.
  • By-reference identity (same array returns the same string instance).
  • Cross-reference content intern (distinct arrays with equal JSON share one interned string).
  • Spy-based assertions on JSON.stringify confirming repeated same-ref calls do not re-serialize, and equal-content distinct-ref calls re-serialize exactly once before interning.

All 65 tests in messageFormatters.spec.ts pass.

Each LLM round previously serialized the tool list multiple times (OTel inference span, OTel agent span, 'tools_available' event, GH 'request.options.tools' enhanced telemetry, and per-key 'request.option.tools' property), producing several independent multi-MB strings retained by buffered spans and telemetry queues.

Add stringifyToolDefinitionsForOTel and stringifyToolsRawForTelemetry in messageFormatters: a WeakMap by-reference cache plus a single-slot content intern so identical tool lists across consecutive agent rounds collapse to one string instance. Wire all producer sites (chatMLFetcher GH telemetry + Object.entries loops, toolCallingLoop agent span and tools_available event, anthropic/gemini byok providers, genAiEvents inference event) through the helpers.
Copilot AI review requested due to automatic review settings May 27, 2026 00:57
@dmitrivMS dmitrivMS marked this pull request as draft May 27, 2026 00:59
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR reduces extension host heap retention by deduplicating multi-megabyte tool-definition JSON strings across OTel spans and telemetry events in the Copilot extension. It introduces memoized stringifiers and routes multiple call sites through them to avoid repeated JSON.stringify(...) allocations of identical content.

Changes:

  • Added stringifyToolDefinitionsForOTel and stringifyToolsRawForTelemetry with WeakMap memoization and single-slot content interning.
  • Updated OTel/telemetry producers (inference/agent spans, tools_available, and GH telemetry events) to use the new helpers.
  • Added unit tests for the new helpers.
Show a summary per file
File Description
extensions/copilot/src/platform/otel/common/messageFormatters.ts Adds memoized tool JSON stringifiers (OTel-normalized + raw telemetry).
extensions/copilot/src/platform/otel/common/test/messageFormatters.spec.ts Adds tests for the new stringifiers.
extensions/copilot/src/platform/otel/common/index.ts Re-exports the new stringifier helpers.
extensions/copilot/src/platform/otel/common/genAiEvents.ts Uses raw stringifier for inference-details tool payload capture.
extensions/copilot/src/extension/prompt/node/chatMLFetcher.ts Routes OTel span attrs + GH telemetry tool payloads through the new helpers.
extensions/copilot/src/extension/intents/node/toolCallingLoop.ts Uses normalized stringifier for agent-span tool definitions and tools_available.
extensions/copilot/src/extension/byok/vscode-node/geminiNativeProvider.ts Uses normalized stringifier for tool defs span attribute.
extensions/copilot/src/extension/byok/vscode-node/anthropicProvider.ts Uses normalized stringifier for tool defs span attribute.

Copilot's findings

  • Files reviewed: 8/8 changed files
  • Comments generated: 2

Comment thread extensions/copilot/src/platform/otel/common/messageFormatters.ts Outdated
Comment thread extensions/copilot/src/platform/otel/common/test/messageFormatters.spec.ts Outdated
dmitrivMS added 3 commits May 26, 2026 18:06
Those properties are sent synchronously and released; they weren't among the retained copies. The other helper sites still dedupe the long-lived strings.
…h JSON.stringify fallback to preserve empty-array byte equality
@dmitrivMS dmitrivMS requested a review from zhichli May 27, 2026 01:16
@dmitrivMS dmitrivMS changed the title perf(copilot): dedupe tool-definition JSON across telemetry/OTel sites perf: dedupe tool-definition JSON across telemetry/OTel sites May 27, 2026
@dmitrivMS dmitrivMS self-assigned this May 27, 2026
@dmitrivMS dmitrivMS changed the title perf: dedupe tool-definition JSON across telemetry/OTel sites Perf: Dedupe tool-definition JSON across telemetry/OTel sites May 27, 2026
@dmitrivMS dmitrivMS marked this pull request as ready for review May 27, 2026 04:37
@dmitrivMS dmitrivMS merged commit 9254a74 into main May 27, 2026
25 checks passed
@dmitrivMS dmitrivMS deleted the dev/dmitriv/perf-duplicate-json branch May 27, 2026 05:56
@vs-code-engineering vs-code-engineering Bot added this to the 1.123.0 milestone May 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants