Skip to content

Repository files navigation

Span Link Budget Lab

An executable GNU M4 lab showing how a successful batch consumer can lose most of its asynchronous trace graph when OpenTelemetry's span-link budget is set below the batch size.

The default scenario runs:

  • 100 consumer batches.
  • 40 messages per batch.
  • 4,000 successfully processed messages.
  • A custom LinkCountLimit of 8.

Every message arrives with a valid producer span context. Processing succeeds. But each consumer span can record only eight of the forty attempted links:

Signal Result
Message-processing success 100%
Producer links recorded 800
Producer links dropped 3,200
Causal coverage 20%

This is not a context-propagation failure. The contexts cross the queue and reach the consumer instrumentation. They are discarded later, when the SDK enforces a deliberately low link-count limit.

Why span links matter

A batch receive or process operation has many upstream message contexts but only one consumer span. OpenTelemetry messaging conventions represent those many-to-one relationships with span links.

The tracing SDK specification defines LinkCountLimit, with a default of 128. SDKs may discard links beyond the configured limit and must make the dropped count available to exporters. OTLP carries that evidence on the span as droppedLinksCount in JSON or dropped_links_count in protobuf.

This lab intentionally configures a limit of eight. It models an SDK that keeps the first eight links offered and drops subsequent links. Retention order is a lab assumption; the portable signal is the non-zero dropped count.

Two trace graphs

The generated payload compares:

  • Low link budget: eight recorded links and droppedLinksCount: 32 on each representative 40-message batch.
  • Batch-sized budget: forty recorded links and droppedLinksCount: 0.

Both consumer spans have status OK. Both process the same messages. Only the second preserves every producer trace as a queryable causal predecessor.

What telemetry.sh reveals

The OTLP-shaped payload includes:

  • Forty representative producer spans with unique trace contexts.
  • Capped and preserved consumer spans with their actual link arrays.
  • lab.span_link.messages.processed.
  • lab.span_link.links.attempted, links.recorded, and links.dropped.
  • lab.span_link.spans_with_drops.
  • lab.span_link.causal_coverage.
  • A warning log carrying otel.span.dropped_links_count.

That lets telemetry.sh compare application success with trace completeness, find consumer spans that admit missing links, quantify orphaned producers, and pivot across the causal edges that survived.

Run it

Requirements:

  • Node.js 20+
  • GNU M4
npm ci
npm start

Open http://localhost:8080.

Or run the clean Alpine Linux container:

docker compose up --build

Use the M4 model directly

The model has no infrastructure side effects. Inputs are standard M4 macro definitions:

m4 \
  -DBATCHES=100 \
  -DMESSAGES_PER_BATCH=40 \
  -DLINK_COUNT_LIMIT=8 \
  model/main.m4

Convenience commands:

npm run simulate
npm run simulate -- --batches 12 --messages-per-batch 25 --link-count-limit 6
npm run telemetry

HTTP endpoints expose the same model:

curl 'http://localhost:8080/api/simulate?messagesPerBatch=40&linkCountLimit=8'
curl 'http://localhost:8080/api/telemetry?messagesPerBatch=40&linkCountLimit=8'

Runtime proof

Every model and telemetry response includes the GNU M4 runtime version, runtime platform, SHA-256 digest of model/main.m4, and model path. The Node server does not duplicate the link-budget calculation.

Verify

npm ci
npm run check

Tests cover the default visibility gap, adequate and zero link budgets, bounded inputs, OTLP link arrays and dropped counts, metrics, logs, runtime proof, and every HTTP endpoint. CI also builds the Alpine container.

References

License

MIT

About

Interactive GNU M4 lab exposing asynchronous trace causality lost to low OpenTelemetry span-link limits.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages