How Do You Measure Instrumentation Overhead in OpenTelemetry?
A complete, plain-English guide to finding out what observability really costs your application in speed, processor time, memory and network, using fair experiments, honest statistics and a clear budget, so that you can keep the insight and control the price.
Imagine you are a doctor who wants to know how a heart behaves during a run. You attach sensors to the runner’s chest, wrists and ankles. The readings are wonderful. Then someone asks an awkward question: do the sensors, the wires and the little backpack of electronics slow the runner down? If they do, then the readings describe a runner who is a little slower than the real one, and every conclusion drawn from them is slightly off. The honest doctor does not guess. The doctor has the runner run the same track twice, once with sensors and once without, and compares the two.
Instrumentation in software is exactly this. When you add OpenTelemetry to an application, you attach sensors that record what the program does. Those sensors cost something: a little time on each request, a little processor power, some memory, some network traffic. Usually the cost is small. Sometimes it is not. The only way to know which is true for your system is to measure it carefully. This tutorial explains how, step by step, using ordinary language and everyday analogies, so that you can design an experiment, read the results, and make decisions you can defend.
1Why Measure Overhead at All?
Before learning how, let us be clear about why this question deserves a careful answer.
Observability tools exist to help you understand your system. But they run inside your system, using the same processors, the same memory and the same network as the program they are watching. Physicists call this the observer effect: the act of measuring can change what is being measured. In software, the effect is usually tiny, yet it is never exactly zero, and it can grow in surprising ways when instrumentation is added carelessly to the busiest parts of a program.
There are three good reasons to measure rather than assume. The first is trust: if you cannot say how much your telemetry costs, people who care about performance will, quite reasonably, resist adopting it. The second is cost control: processor time, memory and network traffic are paid for, and overhead multiplied by thousands of servers becomes real money. The third is safety: a badly configured instrumentation setup can cause slowdowns or memory pressure during exactly the moments when the system is already under stress.
Think of a smart watch that tracks your sleep. It is only useful if it is comfortable enough that you actually wear it all night. If it is so heavy that it wakes you up, it ruins the very sleep it was meant to measure. Instrumentation has to be light enough that the system it observes still behaves like itself.
The goal is not zero overhead, which is impossible. The goal is a known, acceptable overhead that you have measured, written down and agreed with the people who own performance.
2The Vocabulary You Need First
Each term below comes with what it is, why it matters for this topic, and a picture to hold in your head.
Overhead
The extra cost caused by adding instrumentation, measured in time, processor use, memory or network traffic. It is the difference between two runs.
Baseline
The reference run without instrumentation, or with the lightest possible setup. Everything is compared against it.
Latency
How long one request takes from start to finish. The time a customer actually waits.
Percentile
A way to describe the slow end. The 99th percentile, often written p99, is the time that 99 out of 100 requests beat.
Throughput
How many requests the system finishes each second. The size of the pipe, not the speed of one drop.
CPU time
How much processor effort a program consumes, usually shown as cores used. More cores used for the same work means more cost.
Allocation and garbage collection
Creating objects uses memory, and many runtimes later clean up unused objects in pauses or background work. More objects created means more cleanup.
Warm-up
The early period when a program is still loading code and optimising itself. Measurements taken during warm-up are not typical.
Load generator
A tool that sends artificial traffic to the system so the experiment is repeatable.
Noise
Random variation between runs that has nothing to do with instrumentation. Good experiments measure it and rise above it.
Auto-instrumentation
Instrumentation added without changing code, usually by an agent or library hook that watches common frameworks.
Exporter
The part of the SDK that sends finished telemetry out of the application, usually to a Collector.
Overhead is not the same as the time a span reports. A span says how long an operation took. Overhead is how much longer the whole request took because the span existed. They are different questions with different measurements.
3Where Overhead Actually Comes From
To measure a cost, you must know where it hides, so let us follow a request through an instrumented program.
Instrumentation work happens in several places. Some of it happens on the thread that is serving the customer, and that part adds directly to latency. Some happens on background threads, and that part adds to processor and memory use but not directly to latency. Some happens on the network and in other programs, such as the Collector. A complete measurement looks at all of these.
| Source | Where it runs | What it costs |
|---|---|---|
| Starting and ending a span | On the request thread | A little time and a few small objects per span |
| Setting attributes and adding events | On the request thread | Time and memory proportional to how many and how large |
| Context propagation | On the request thread, at every call between services | Reading and writing a few headers |
| Sampling decision | On the request thread | A small calculation per span |
| Queueing for export | Handoff from request thread to background | A small, brief synchronisation |
| Batching and serialising | Background thread | Processor time and temporary memory |
| Sending over the network | Background thread and network | Bandwidth and a little processor work |
| Cleaning up memory | Runtime garbage collection | Extra pressure from many short-lived objects |
| Agent loading and class modification | At start-up | Longer start time and some extra memory |
| Receiving and processing in the Collector | A separate program | Its own processor, memory and network cost |
Think of a restaurant that adds a feedback card to every meal. The waiter must hand out the card, which takes a few seconds at the table. Someone in the back must collect and sort the cards. A courier must deliver them to head office. The customer notices only the waiter’s few seconds, but the restaurant pays for all three jobs.
OpenTelemetry’s own specification says that normal telemetry operations should not block the application, and that background work should be asynchronous. Your measurements are, in effect, a check that this promise holds in your particular system.
4The Core Idea in One Page
Every sound overhead measurement is a variation on one simple experiment.
Choose what to compare
Decide on a baseline and a variant, for example the program without instrumentation and the same program with it.
Hold everything else still
Use the same machine type, the same version of the application, the same traffic and the same settings. Change exactly one thing.
Apply realistic load
Send repeatable traffic that looks like real usage, and wait until the system is warm and steady before recording.
Measure the five areas
Record latency, throughput, processor use, memory and network traffic for both runs.
Repeat and compare honestly
Run each variant several times, measure the natural noise, and report the difference only if it clearly rises above that noise.
Everything else in this tutorial adds detail to those five steps: how to build the ladder of variants, how to generate realistic load, how to read the numbers, how to measure noise, and what to do with the answer. Keep the five-step shape in mind and the details will fall into place.
5The Five Dimensions of Overhead
A cost can show up as slowness, as lost capacity, as extra processor use, as memory growth or as network traffic, and each needs its own measurement.
Latency
Does each request take longer? Measure typical and slow requests, using percentiles, not only averages.
Throughput
Does the system complete fewer requests per second at its limit? Measure how much load it can sustain.
Processor use
Does the same amount of work use more processor effort? Measure cores used at a fixed request rate.
Memory
Does the program hold more memory or create more garbage? Measure heap size, allocation rate and cleanup activity.
Network and downstream cost
How much data leaves the program, and how much work does it cause in the Collector and backend?
Start-up and size
How much longer does the program take to start, and how much larger is it on disk and in memory at rest?
| Dimension | Best measured with | Who feels it |
|---|---|---|
| Latency | Load test at a fixed request rate, response-time histograms | Customers and calling services |
| Throughput | Load test that increases traffic until the system saturates | Capacity planners |
| Processor use | Process-level processor counters at a fixed request rate, profilers | Finance and infrastructure teams |
| Memory | Heap and allocation metrics, garbage collection logs | Operations and reliability teams |
| Network and downstream | Counters on exporter bytes sent and Collector resource use | Network and observability platform teams |
| Start-up and size | Timed starts from a cold container, file and image sizes | Deployment and autoscaling |
Measuring only latency and declaring success. An application can show no change in latency while using noticeably more processor time and memory. If you pay per machine, those hidden costs are the ones you will see on the invoice.
6Overhead Is a Budget: Decide What Is Acceptable First
A number means nothing until you know what number would be too much.
Many overhead investigations go in circles because nobody agreed in advance what counts as acceptable. The measurement shows that processor use is up six percent, and one person calls that negligible while another calls it a scandal. The cure is to write the budget down before you run the test. A budget turns an argument about opinions into a check against a target.
A good budget has a few lines, each tied to something the business cares about. For a customer-facing service, the most important line is usually the slow tail of latency, because slow requests are what customers complain about. For a service that is cost-sensitive, processor and memory lines matter more. For a service with a strict capacity plan, the throughput line matters.
| Budget line (illustrative targets) | Example limit | Why this limit |
|---|---|---|
| Added latency at p50 | No more than about 1 millisecond | Typical requests should feel no difference |
| Added latency at p99 | No more than about 3 percent | Slow requests drive complaints and timeouts |
| Added processor use | No more than about 5 percent | Keeps cost growth predictable |
| Added memory | No more than a fixed amount of megabytes per instance | Fits within container limits with safe headroom |
| Maximum sustainable throughput | Drop of no more than about 3 percent | Protects the capacity plan |
| Start-up time | Increase of no more than a few seconds | Keeps scaling and deployments responsive |
They show the shape of a budget, not recommended values. Your limits should come from your own service-level goals and cost targets, agreed with the people responsible for them.
Think of a family budget. You decide in advance that streaming services may take at most a certain share of your income. Then, when a new subscription appears, the question is not whether it feels expensive, but whether it fits the budget. A written limit makes the answer easy and keeps the conversation friendly.
7Designing a Fair Experiment
Most bad overhead numbers come from unfair comparisons, not from bad tools.
A fair experiment changes only the thing you are studying. In practice, many other things can change by accident, and each one can hide or exaggerate the effect of instrumentation. A short checklist of controlled factors prevents most of these accidents.
Same machine type
Run both variants on identical hardware, ideally dedicated, so that neighbours on a shared machine do not add random noise.
Same build of the application
Use the same code version. The only difference should be whether and how instrumentation is enabled.
Same traffic, same pattern
Send identical request mixes at identical rates. A different mix changes the work done, not just the instrumentation.
Same settings and dependencies
Databases, caches and downstream services should behave the same way in both runs, or be replaced by consistent stand-ins.
Warm up before measuring
Let the program run under load until its behaviour stabilises, then record. The specification’s own benchmark guidance calls for a warm-up phase for languages that optimise code while running.
Alternate and repeat the runs
Run baseline, variant, baseline, variant, rather than all baselines first. This stops slow drifts, such as a machine heating up, from being mistaken for an effect.
Imagine testing whether a new pair of running shoes makes you faster. You would run the same route, at the same time of day, in the same weather, after the same breakfast. If you ran the old shoes on a cold morning and the new ones on a hot afternoon, the result would say more about the weather than the shoes.
Background jobs, automatic updates, other tenants on the same host, and power-saving modes can all change results between runs. If a number looks surprising, check whether something else changed before blaming the instrumentation.
8The Overhead Ladder: Isolating Each Layer
Comparing only the two extremes tells you that there is overhead, but not where it comes from; a ladder of variants tells you both.
Instead of two runs, design a ladder of several. Each step adds exactly one more piece of the instrumentation stack, so that the difference between neighbouring steps is the cost of that one piece. This is the single most useful technique for turning a vague “it seems slower” into a precise “this particular layer costs this much”.
flowchart TD
A["Step 0: no instrumentation at all"] --> B["Step 1: API only, no SDK installed"]
B --> C["Step 2: SDK installed, sampler drops everything"]
C --> D["Step 3: SDK samples, spans created, no export"]
D --> E["Step 4: spans exported to a local fast sink"]
E --> F["Step 5: spans exported to the real Collector"]
F --> G["Step 6: full production configuration"]
| Step | What is switched on | The gap to the previous step shows the cost of |
|---|---|---|
| 0 to 1 | The instrumentation interfaces, with no real implementation behind them | The calls themselves, which should be nearly free |
| 1 to 2 | The SDK, with a sampler that records nothing | Making sampling decisions and carrying context |
| 2 to 3 | Spans are really created and recorded | Creating spans, attributes and events |
| 3 to 4 | Spans are batched, serialised and sent to a trivial local receiver | Export work inside the application |
| 4 to 5 | The real Collector replaces the trivial receiver | Network realities and real back-pressure |
| 5 to 6 | Production settings such as sampling rate and limits | The effect of your real configuration choices |
Think of finding out why a car uses more fuel. You do not just compare the empty car to the fully loaded one. You add the roof rack, then the luggage, then the passengers, one at a time, and note the change after each addition. The step that costs the most is the one to fix first.
Steps one to three need no network and no Collector, so they are easy to run on a laptop or in a quick pipeline job. Run them first to find out whether overhead sits in your application code or further downstream.
9Designing a Realistic Workload
The traffic you send decides what you learn, so it must resemble the traffic your system really sees.
Instrumentation overhead depends heavily on what the application is doing. A request that makes a single quick database call creates a handful of spans. A request that fans out to twenty services creates dozens. If your test sends only simple requests, you will measure a very light overhead and be surprised in production. A good workload reflects the real mix: the common requests, the heavy ones, and the rare expensive ones.
Open loop versus closed loop
Load generators work in one of two ways. A closed loop generator has a fixed number of virtual users, and each user waits for its response before sending the next request. If the system slows down, the generator automatically sends fewer requests, which hides the slowdown. An open loop generator sends requests on a fixed schedule, regardless of whether earlier ones have finished, just as real customers do. Open loop generators give a much more honest picture of latency and of how the system behaves near its limits.
flowchart LR
subgraph CL["Closed loop"]
U1["Virtual user sends request"] --> W1["Waits for response"] --> U1
end
subgraph OL["Open loop"]
T["Timer fires on schedule"] --> S["Sends request"] --> T
S --> R["Response arrives whenever it arrives"]
end
When a closed loop generator waits during a slow period, it simply stops sending requests that would have been slow, and so never records them. The results look better than reality. This well-known trap makes overhead look smaller than it is, especially in the slow tail. Prefer open loop generators for latency work.
Realistic request mix
Use the same proportions of request types seen in production, taken from logs or existing metrics.
Realistic data
Use varied inputs. Identical requests can be unrealistically fast because everything is cached.
Steady phase
Include a long, constant-rate period after warm-up, because that is where the measurements are taken.
Several rates
Test at low, typical and high load. Overhead often becomes visible only when the system is busy.
10Measuring Latency the Right Way
Averages hide the very requests that matter most, so latency must be measured with percentiles.
Suppose a service answers ninety-nine requests in ten milliseconds and one request in a full second. The average is about twenty milliseconds, which sounds fine and describes nobody’s real experience. Customers see either ten milliseconds or one second. Percentiles describe this honestly: the 50th percentile, or median, is ten milliseconds, and the 99th percentile is a full second. Instrumentation overhead often shows up first in the slow tail, for example when a garbage collection pause coincides with a burst of span creation.
| Statistic | What it tells you | How overhead shows up in it |
|---|---|---|
| Median (p50) | The typical request | A small, steady shift if each request does extra work |
| p90 and p95 | The upper-normal range | Growth when queues or locks become busy |
| p99 | The slow tail that customers complain about | Jumps from pauses, contention and bursts |
| p99.9 and maximum | The worst cases | Rare pauses; useful but noisy, so interpret with care |
| Average | A single summary number | Easy to compute but easy to misread; never use it alone |
Record histograms, not just summary numbers
The best practice is to record the full distribution of response times as a histogram, then compare the two histograms. A histogram lets you see whether the whole curve shifted slightly, which suggests a steady per-request cost, or whether only the slow tail grew, which suggests pauses or contention. It also lets you compute any percentile later without rerunning the experiment.
Think of a bus route. The average journey time may be thirty minutes, but if one bus in a hundred breaks down and takes two hours, the people on that bus remember it for weeks. The average hides them. Percentiles tell you how often the bad days happen and how bad they are.
Overhead differs between request types. A cheap endpoint with a handful of spans may show a larger relative slowdown than an expensive one, because instrumentation is a fixed cost added to a small base. Compare each important endpoint separately, not just the overall mix.
11Throughput and the Saturation Point
Latency at a comfortable load can hide a cost that appears only when the system is pushed to its limit.
Every service has a point beyond which adding more traffic stops increasing the number of completed requests and only makes the waiting longer. This is the saturation point. Instrumentation uses a little processor time on every request, so it moves the saturation point slightly lower. To see that, you must push the system to saturation with and without instrumentation and compare where the knee of the curve appears.
Start at a low rate
Send a modest, steady rate and confirm that response times are low and stable.
Raise the rate in steps
Increase traffic by a fixed amount, hold each step long enough to settle, and record latency and errors at each level.
Find the knee
The knee is the level where latency starts rising sharply or errors begin. Record the highest rate that stays within your latency goal.
Repeat with the variant
Run exactly the same steps with instrumentation enabled and compare the two knee positions.
Picture a motorway. At low traffic, every car moves at full speed. As more cars enter, speeds hold up until a certain density, then suddenly everyone slows down. If a new toll booth takes a second from each car, the point where traffic jams begin moves a little earlier. Throughput testing finds where that point is, with and without the booth.
At low load, there is plenty of spare processor capacity to absorb background export work, and overhead is nearly invisible. The same instrumentation near saturation can show a clear effect. Always test at several loads, including one close to your real peak.
12Measuring Processor Use
Processor cost is the overhead that most directly becomes money, so measure it at a fixed amount of work.
The cleanest way to compare processor use is to hold the request rate constant, so both variants do the same work, and then compare how many processor cores each one uses. If the instrumented version uses a little more at the same rate, the difference is the cost of the instrumentation. Dividing the difference by the request rate gives a very useful number: the extra processor time spent on each request.
Process-level counters
Read the total processor time consumed by the application process at the start and end of a steady period, and divide by the length of the period.
Container or host metrics
Use the platform’s own processor metrics for the container or machine, which also capture helper processes such as an agent.
Sampling profilers
Profilers that periodically sample what the program is doing show which functions use the processor, so you can see how much is instrumentation code.
Continuous profiling
In production, low-overhead profiling over long periods can reveal instrumentation costs that short tests miss.
| Metric | How to compute | Why it is useful |
|---|---|---|
| Cores used | Processor seconds consumed divided by elapsed seconds | Directly comparable between variants |
| Processor time per request | Cores used divided by requests per second | Remains meaningful if the request rate differs slightly |
| Relative overhead | Variant minus baseline, divided by baseline | A single percentage to compare with the budget |
| Share of profile in instrumentation code | Profiler samples inside instrumentation divided by all samples | Shows exactly where the cost sits |
If instrumentation adds a separate process, such as a local Collector, include its processor use in the variant’s total. Otherwise the cost has merely been moved out of sight.
13Measuring Memory and Garbage Collection
Memory overhead has two faces: how much is held, and how much is created and thrown away.
Instrumentation holds memory in a few places. Spans that are still in progress live in memory until they end. Finished spans wait in a queue until they are exported. The SDK itself and any agent add a fixed amount. Because the specification asks implementations not to use unbounded memory, the queue has a limit, but that limit can still be a noticeable amount of memory when spans are large or numerous.
Instrumentation also creates many small, short-lived objects: spans, attribute lists, context objects, serialised buffers. In languages with automatic memory cleanup, these objects must later be collected. A high rate of object creation means more frequent cleanup, which can cause short pauses or extra background processor use. Often the allocation rate is a more telling number than the total memory held.
| What to record | What it shows | Warning sign |
|---|---|---|
| Steady-state heap size | Memory held after warm-up | A steady increase beyond what the budget allows |
| Peak heap size | Highest memory during the test | Peaks near the container limit |
| Allocation rate | How fast new objects are created | A large jump compared with the baseline |
| Cleanup frequency and pause time | How often and how long the runtime pauses to clean up | Longer or more frequent pauses at the same load |
| Resident memory of the process | What the operating system sees the program using | Growth that is not explained by the heap alone |
| Export queue depth | How many spans wait to be sent | A queue that sits near its limit |
Think of a kitchen. The pantry holds ingredients, which is the memory held. But the cooking also produces a constant flow of peelings and wrappers that must be cleared away, which is the allocation and cleanup. A kitchen can have a small pantry and still be overwhelmed by rubbish if it produces too much too quickly.
A short test can miss a memory problem that builds over hours. For anything that must run for days, include at least one long run and watch whether memory stabilises or keeps climbing.
14Start-Up Time and Footprint
Instrumentation can slow down the first seconds of a program’s life, which matters more than people expect.
Automatic instrumentation often works by inspecting and modifying program code as it loads. That takes time at start-up, and it keeps extra information in memory afterwards. In a world of containers that start and stop frequently, because of scaling, deployments and restarts, a few extra seconds at start-up can slow rollouts and delay new capacity during a traffic surge.
Start from cold
Measure start-up from a freshly created container or process, not a warm machine with cached files.
Define the finish line
Decide what ready means, for example the health check passing or the first request being answered, and time exactly that.
Repeat many times
Start-up times vary a lot. Run at least ten starts for each variant and compare the typical and the slowest.
Record footprint at rest
Compare memory use just after start-up, before traffic, and the size of the application package and image.
Start-up time matters most for short-lived jobs, serverless-style functions and fast-scaling services. For a long-running server that restarts once a month, a few seconds is usually irrelevant, so weigh this line of the budget accordingly.
Imagine a sprinter who must fit a heavy tracking vest before every race. Over a marathon the extra seconds at the start line are nothing. For a one-hundred-metre dash, they are the whole race. The shorter the lifetime of your program, the more the start-up cost counts.
15Network and Downstream Cost
The cost of telemetry does not stop at the edge of your application; it continues across the network and into the systems that receive it.
Every span that leaves the application becomes bytes on a network and work for a Collector and a backend. At small scale this is invisible. At large scale it is often the largest part of the bill. A full picture of overhead therefore includes how much data is sent and what it costs the receiving systems to handle it.
Bytes sent per request
Divide the bytes the exporter sends by the number of requests served. This shows how heavy each request’s telemetry is.
Spans per request
Count how many spans each kind of request creates. This number multiplies almost every other cost.
Export success and failure
Count exports that succeed, fail or are dropped. Rising failures mean data is being lost and queues are growing.
Collector resource use
Record the processor and memory the Collector uses at your traffic level, and divide by traffic to get a cost per span.
Larger batches and compressed transfers usually reduce network cost and per-item processing cost, at the price of slightly higher delay and a little processor time. Include these settings in your ladder so that you can see their effect.
Think of posting letters. Mailing each letter separately costs a stamp and a trip to the box every time. Gathering a week of letters into one envelope costs one stamp and one trip. The batch is a little late, but the total cost is far lower. Export batching trades a little delay for much lower cost.
16What OpenTelemetry Itself Suggests
The project publishes benchmark guidance that you can borrow as a starting recipe.
OpenTelemetry’s specification includes a short benchmark description for its tracing SDKs. It defines one standard test scenario: spans with no parent, default settings and always-on sampling, each carrying a numeric attribute and one event, and each ended immediately after creation. The guidance asks implementers to report three things for this scenario, and gives a few rules for how to run it.
| Measurement | What the guidance describes |
|---|---|
| Throughput | How many spans with ten attributes, each with short text values, can be created and exported per second per processor core |
| Processor use | Average and peak processor use of the SDK with the default simple and batching span processors and the standard exporter, at a stated rate, with ten thousand spans per second as the default |
| Memory | Heap use under the same conditions as the processor test |
Run long enough
Tests should last at least fifteen seconds so that short bursts do not dominate the result.
Repeat the runs
Repeat measurements at least ten times and report the combined result, which gives a feel for natural variation.
Warm up
For languages that optimise code while running, include a warm-up phase before measuring.
Use a cheap receiver
The receiving end should answer immediately without extra work, so the test measures the SDK and not the receiver.
Treat the specification’s scenario as the micro-level building block of your own ladder, steps three and four. Then add your own application-level tests on top, because a synthetic span test cannot tell you what your real request mix will cost.
A standard scenario is useful for comparing SDK versions or languages under identical conditions. It is not a prediction of your overhead, because your application has its own span counts, attribute sizes and traffic patterns.
17The SDK Settings That Move the Numbers
A few configuration choices have an outsized effect on overhead, so they belong on your ladder.
The tracing SDK’s batching span processor has four main settings, each with a documented default. The queue size, defaulting to two thousand and forty-eight spans, limits how many finished spans can wait for export. The schedule delay, defaulting to five seconds, controls how often a batch is sent. The export timeout, defaulting to thirty seconds, limits how long one export may take. The maximum batch size, defaulting to five hundred and twelve, limits how many spans go out in one request. When the queue is full, new spans are dropped rather than slowing the application.
| Setting | Documented default | Effect on overhead if you raise it | Effect if you lower it |
|---|---|---|---|
| Queue size | 2048 spans | More memory held, fewer dropped spans in bursts | Less memory, more drops under bursts |
| Schedule delay | 5 seconds | Larger, less frequent batches and fresher memory use, but later data | More frequent small exports, more network and processor work |
| Export timeout | 30 seconds | Slow exports tie up the background worker longer | Failed exports are abandoned sooner, risking loss |
| Maximum batch size | 512 spans | Fewer requests, larger payloads | More requests, smaller payloads |
Simple versus batching processors
A simple processor sends each span as soon as it finishes, which is easy to understand but costly because every span triggers its own export call. A batching processor groups spans and sends them together. For nearly all production use, batching is far lighter, which is why your ladder should compare both.
The values above come from the specification. Individual language SDKs can differ in details, and defaults can change between versions, so confirm the settings of the exact SDK you test and record them with your results.
A simple processor is like a courier who drives to the depot with every single parcel. A batching processor is like a courier who waits for a van-load. The van takes slightly longer to fill, but the fuel bill is far smaller.
18Sampling: The Biggest Lever You Can Test
Choosing how many traces to record changes almost every cost at once, so test it explicitly.
The SDK offers several standard samplers. The always-on sampler records every span. The always-off sampler records none. The ratio-based sampler records a chosen fraction of traces, deciding in a way that stays consistent across all services touched by one trace. The parent-based sampler follows the decision already made by the parent span, which keeps whole traces together. Each choice changes how many spans are fully recorded and exported, and therefore how much overhead the system carries.
| Sampler | What it does | Effect on cost |
|---|---|---|
| Always on | Records every span | Highest cost; useful as a worst-case baseline |
| Always off | Records no spans | Lowest SDK cost; shows the floor of the instrumentation layer |
| Ratio based | Records a fixed fraction of traces | Cost falls roughly in proportion to the fraction kept |
| Parent based | Follows the parent’s decision, with a chosen rule for root spans | Keeps traces complete; cost follows the root decision |
Not every cost shrinks with sampling
When a span is not sampled, most of the heavy work is skipped: recording attributes and events, queueing, serialising and sending. But some work remains for every request, such as creating the context, making the sampling decision and passing headers to the next service. This residual cost is exactly what steps one and two of your ladder measure. A good result is one where the always-off configuration is very close to the uninstrumented baseline.
flowchart LR
R["Request arrives"] --> S{"Sampling decision"}
S -->|"Not sampled"| N["Light path: context only, no recording, no export"]
S -->|"Sampled"| H["Heavy path: record, queue, batch, serialise, send"]
Run your ladder at a few sampling levels, for example everything, a tenth and a hundredth. Plot overhead against the sampling ratio. The curve usually shows a steady floor plus a part that grows with the ratio, and tells you which sampling level meets your budget.
Lower sampling reduces cost but also reduces visibility. Overhead measurement tells you the price; deciding how much visibility to buy is a separate, equally important decision.
19Attributes, Events and Limits
How much you attach to each span matters as much as how many spans you create.
A span can carry attributes, which are labelled facts such as the route name or a customer tier, and events, which are timestamped notes. Each attribute costs a little time to set and some memory to hold, and it adds bytes to every export. A span with three short attributes and a span with fifty long ones are very different loads, even though both count as one span.
The SDK specification therefore defines limits that cap how many attributes, events and links one span can carry, and how long values may be. When a limit is exceeded, extra data is discarded and a warning is logged. These limits are a safety net, but they are not a substitute for thoughtful instrumentation, because the discarded data has already cost some effort to produce.
Attribute count
More attributes per span mean more time and more bytes per span. Measure with your real attribute counts, not only minimal ones.
Attribute value size
Long text values, such as full query strings or message bodies, inflate memory and network cost quickly.
Event count
Adding an event inside a loop can create thousands of events on one span. Check the maximum in realistic worst cases.
Link count
Links join spans across traces. Batch jobs that link to many inputs can generate large numbers of links on a single span.
| Test variation | Question it answers |
|---|---|
| Minimal attributes versus typical attributes | How much of the cost is driven by what we attach? |
| Short values versus real values | Do large values dominate the export size? |
| Normal request versus worst-case request | What happens to the heaviest requests, not only the average ones? |
| Default limits versus tightened limits | Can limits cap the worst cases cheaply? |
Trimming oversized values and avoiding low-value attributes often cuts memory and network cost more than any tuning of queues or timers, and it makes traces easier to read as well.
20Instrumentation Style and Span Granularity
Where you place spans, and how many, usually matters more than any setting inside the SDK.
There are three broad ways to add instrumentation. Automatic instrumentation watches common frameworks and libraries for you, giving wide coverage with little effort. Library instrumentation is built into libraries by their authors. Manual instrumentation is code you add yourself, at places that matter to your business. Each style produces a different number of spans per request, and span count is the single biggest driver of cost.
| Style | Typical span volume | Overhead characteristic | What to test |
|---|---|---|---|
| Automatic | Many spans, wide coverage | Start-up cost plus per-call cost across many libraries | Spans per request and start-up time |
| Library built-in | Moderate, chosen by authors | Usually carefully tuned | Cost per common library call |
| Manual | As many as you write | Entirely under your control | Placement in hot code paths |
Problem
A span is created inside a tight loop or around a very small function that runs millions of times, such as every item in a large list.
Why It’s Harmful
The fixed cost of a span is small, but millions of them swamp the real work, create huge garbage, fill export queues and produce traces nobody can read.
Correct Approach
Create one span around the whole loop and record the item count as an attribute. Use spans for meaningful units of work, such as a call to another system, not for every function.
A tour guide who announces every single step the group takes would exhaust everybody. A good guide announces each landmark. Spans work best as landmarks, not as footsteps.
Before any timing test, simply count spans per request for each request type. If one type creates hundreds of spans, you have found your likely overhead problem without running a single benchmark.
21Metrics and Logs Have Their Own Costs
Tracing is only one signal, and the other two can be just as expensive in different ways.
Metrics are numbers aggregated over time. Recording a measurement is usually very cheap, because it only updates a running total. The danger lies elsewhere: every distinct combination of label values creates a separate series that must be stored and exported. A label with an unbounded number of possible values, such as a user identifier or a full web address, can create an enormous number of series. This is called a cardinality explosion, and it costs memory, network and storage far more than the act of recording ever did.
Logs are records of individual events. Their cost scales with volume, which can be enormous if applications log on every request. The specification itself notes that logging can use a great deal of memory by default and recommends giving applications ways to filter what is sent. Tests should include realistic log volumes, including bursts caused by error storms.
Cost of recording
Usually tiny per measurement. Test at your real measurement rate, especially for measurements recorded on every request.
Cost of series count
Measure how many distinct series the application produces and how that number grows with traffic and labels.
Cost of volume
Measure records per second, bytes per record and behaviour during error bursts.
Cost of correlation data
Attaching trace identifiers to logs is cheap, but check that extra fields are not inflating each record.
Putting identifiers with unbounded values, like a customer number, into metric labels. The recording looks cheap in a benchmark with ten customers and becomes a disaster in production with ten million.
Writing a number on a tally sheet is quick. But if you start a new sheet for every possible combination of customer, product and day, you soon have a warehouse full of paper. The act is cheap; the filing system is not.
22Context Propagation and Baggage
The small headers that travel with every call are cheap individually and noticeable in aggregate.
For a trace to span several services, each call carries a few headers with the trace identifier and the sampling decision. Reading and writing these headers costs a tiny amount of time on every outgoing and incoming call, whether or not the trace is sampled. For almost all systems this cost is very small, but it is worth confirming, particularly for services that make a very large number of internal calls per request.
Baggage is a separate feature that carries custom key-value pairs along with the trace context to every downstream service. It is useful, but because it is copied into the headers of every call, it can grow the size of each message and the work of reading and writing it. Many teams are surprised by how much baggage accumulates over time.
| Item | Where the cost lands | How to measure it |
|---|---|---|
| Trace context headers | Every call, sampled or not | Ladder step two: compare no instrumentation with sampler-off |
| Baggage entries | Every call, scaled by entry count and size | Compare header sizes and per-call time with and without baggage |
| Header size growth | Network and parsing in every service | Record average and maximum header size per call |
Put only a few short, genuinely needed values in baggage, and review it regularly. Large or numerous entries tax every service on the path, including ones that never use them.
Think of a school trip where every child wears a name badge. One small badge is nothing. If every teacher along the route adds another sticker, by the end each child is wearing a heavy coat of labels. Baggage behaves the same way.
23Micro-Benchmarks and Macro-Benchmarks
Small, focused tests and whole-system tests answer different questions, and you need both.
A micro-benchmark measures one tiny operation in isolation, such as the time to create and end a span. It is quick, repeatable and good at spotting changes between SDK versions. A macro-benchmark measures the whole application under realistic load, which captures interactions that no isolated test can, such as processor contention, memory pressure and garbage collection behaviour.
| Aspect | Micro-benchmark | Macro-benchmark |
|---|---|---|
| What it measures | One small operation | The whole application under load |
| Speed and cost to run | Fast and cheap | Slower and more expensive |
| Realism | Low, synthetic | High, close to production |
| Best for | Comparing SDK versions, spotting regressions in the instrumentation itself | Answering whether your service meets its budget |
| Main risk | Results that do not carry over to the real application | Noise from many moving parts |
Problem
A team runs only a micro-benchmark, finds that creating a span takes a few microseconds, and concludes that overhead is negligible for the whole service.
Why It’s Harmful
The micro-benchmark ignores how many spans real requests create, garbage collection effects, queue contention and network costs. The real overhead can be far larger.
Correct Approach
Use micro-benchmarks to understand and compare building blocks, and use macro-benchmarks with a realistic workload to judge the budget.
In micro-benchmarks, runtimes can optimise away work whose result is never used, producing absurdly fast numbers. Make sure the benchmarked code genuinely runs and that its result is consumed. Warm-up matters here too, as discussed earlier.
24Measuring in Production
Test environments never perfectly match reality, so the final proof of overhead comes from real systems.
Production measurement answers the question that test environments cannot: what does instrumentation cost with real traffic, real data and real neighbours? The safest way to measure it is a controlled comparison inside production, using a small group of instances with the variant and a matching group without it, both receiving the same live traffic.
Create two matching groups
Pick instances of the same type, same version and similar load. One group runs the instrumentation under test, the other runs the baseline.
Split traffic fairly
Send both groups random, comparable slices of live traffic, so neither receives an easier mix.
Compare the same metrics
Look at latency percentiles, error rates, processor use and memory for both groups over several hours or days.
Keep a quick off switch
Make sure the variant can be disabled or its sampling reduced instantly, using a setting rather than a new release.
Canary groups
A small share of instances run the new instrumentation first, and their numbers are compared with the rest.
Sampling-rate toggles
Raising or lowering the sampling rate on a group shows how overhead scales, without changing code.
Self-telemetry
SDKs and Collectors can report on their own queues, drops and exports, giving a direct view of instrumentation activity.
Continuous profiling
Low-overhead profiles taken over long periods show how much of the application’s time is spent in instrumentation code.
A production experiment must never put customers at risk. Start with a tiny group, watch closely, and stop at the first sign of harm. Overhead experiments are valuable, but not worth an outage.
25Statistics Without Tears: Noise and Confidence
A measured difference is only real if it is bigger than the random variation between identical runs.
No two runs of the same test give exactly the same numbers. Machines vary, networks vary, and the program itself varies. This natural variation is called noise. Suppose that two identical runs differ by two percent in processor use. If your instrumented variant uses three percent more than the baseline, then three percent is not clearly bigger than the noise, and you cannot honestly claim an effect. You need a way to find out how big the noise is.
The A/A test: measuring the noise floor
The simplest tool is an A/A test. Run the baseline against itself several times and look at how much the results vary. This shows the size of natural noise for your setup. Any difference between baseline and variant smaller than that noise should be treated as no detectable effect, not as zero effect.
Run the baseline several times
At least ten runs, as the specification’s benchmark guidance suggests for its own tests.
Note the spread
Record the lowest and highest values, and the typical value. The spread is your noise.
Run the variant the same number of times
Use identical conditions and alternate the runs with the baseline.
Compare the typical values and the ranges
If the ranges barely overlap and the typical values differ clearly, the effect is real. If the ranges overlap heavily, you need more runs or a quieter environment.
Weigh yourself five mornings in a row and the number will wobble by a few hundred grams even if nothing changed. If you then eat a large dinner and weigh two hundred grams more, you cannot say that dinner made a difference. The wobble is the noise floor, and only changes larger than the wobble mean anything.
Instead of saying that overhead is four percent, say that overhead was between three and five percent across ten runs. The range tells the reader how much to trust the result.
26The Experiment Flow from Start to Result
Let us follow one complete experiment through every component involved, to see how the pieces fit.
sequenceDiagram
participant L as Load generator
participant A as Application under test
participant S as SDK and exporter
participant C as Collector or local sink
participant M as Metrics store
L->>A: Send steady open-loop traffic
A->>S: Create spans and record data
S->>C: Export batches in the background
A-->>L: Responses with timings
A->>M: Report processor and memory use
C->>M: Report received and dropped counts
L->>M: Store latency histograms
M->>M: Compare baseline and variant runs
Latency histograms
Recorded by the load generator, showing the full distribution of response times for each run.
Resource metrics
Processor, memory and garbage collection numbers from the application’s host or container.
Telemetry counters
Spans created, exported, dropped and received, which prove that the variant really did the work it was supposed to.
Run records
Configuration, versions, machine type, start and end times, stored with each result so that it can be reproduced later.
Always check the span counters. A variant that quietly dropped every span, because of a misconfiguration, will look wonderfully cheap and teach you nothing. The count of spans actually exported is your proof that the instrumentation was active.
27Choosing Tools by Category
Many tools can do each job, so it is more useful to know the categories than to memorise names.
| Category | Job in the experiment | What to look for |
|---|---|---|
| Load generator | Sends repeatable traffic | Open-loop mode, realistic request mixes, histogram output |
| Benchmark harness | Runs micro-benchmarks reliably | Built-in warm-up, repeated runs, protection against over-optimisation |
| Profiler | Shows where processor time and memory go | Low overhead itself, flame graph views, allocation profiling |
| Host and container metrics | Report processor, memory and network use | Fine time resolution and per-process detail |
| Runtime diagnostics | Report heap, allocation and cleanup behaviour | Garbage collection pauses and allocation rates |
| Local telemetry sink | Receives exports instantly | Counts what it receives, adds almost no delay |
| Metrics store and dashboards | Keeps and compares results across runs | Tagging runs by configuration and version |
Keep the measuring tools light
A profiler or monitoring agent that itself uses a lot of processor time distorts the experiment. Use tools designed for low overhead, and apply the same tools to both variants so any distortion is shared equally.
Attaching a heavy profiler changes timings. Use profilers in separate runs to explain a result, and use light-touch measurement for the main comparison.
28A Worked Example With Illustrative Numbers
Numbers make ideas stick. The figures below are invented for teaching, so please do not treat them as a promise about any real system.
Imagine an online shop with a service that handles checkout. During a busy hour it receives about two thousand requests every second. The team wants to switch on tracing, metrics and logs through OpenTelemetry. Before they do, they set a budget: the instrumented service may add at most five percent to the slowest requests, at most ten percent more processor time, and at most fifteen percent more memory.
They run the experiment ladder from earlier chapters. The first run is the baseline with no instrumentation. The second run has the library installed but switched off. The third run has full tracing at one hundred percent sampling. The fourth run keeps tracing but samples only ten percent of requests. All four runs use the same machine type, the same replayed traffic and the same duration, and each is repeated several times.
| Run | Slowest one percent latency | Processor cores used | Memory in use | Data sent per second |
|---|---|---|---|---|
| Baseline, nothing installed | 40 ms | 6.0 | 800 MB | 0 |
| Installed but disabled | 40 ms | 6.0 | 810 MB | 0 |
| Full tracing, every request | 46 ms | 7.1 | 980 MB | High |
| Tracing, ten percent sampled | 41 ms | 6.3 | 870 MB | Low |
Now do the arithmetic. Full tracing raised the slow-request latency from forty to forty-six milliseconds, which is fifteen percent. It raised processor use from six to seven point one cores, about eighteen percent. Memory grew from eight hundred to nine hundred eighty megabytes, about twenty-two percent. All three exceed the budget. With ten percent sampling, latency rose by two and a half percent, processor use by five percent and memory by about nine percent. All three fit.
Think of a school bus carrying a few extra backpacks. One bag barely changes the trip. Every child carrying a heavy science kit slows the bus on every hill. The team found that carrying kits for only one child in ten kept the bus on schedule while still showing what the trip was like.
The disabled run matters. It shows that merely installing the library cost almost nothing, so the overhead came from creating and exporting data, not from the presence of the library.
29Interpreting Results Without Fooling Yourself
A result is only useful when you understand what it does and does not say.
The first lesson is to look at both absolute and relative change. A rise of six milliseconds sounds small, yet on a request that normally takes forty it is fifteen percent. A rise of two milliseconds on a request that takes two seconds is invisible. Always write down both numbers so that readers can judge for themselves.
The second lesson is to think in cost per request. If instrumentation adds a fixed amount of processor time to each request, then a service handling more requests pays more in total. A second way to say this is that overhead usually grows with the number of spans, metric updates and log lines per request, not with the number of requests alone. A request that touches twenty internal steps and records a span for each pays much more than one that records a single span.
The third lesson is about shape. Averages hide trouble. If most requests are unchanged but a few become much slower because an export buffer fills up, the average barely moves while the slow tail grows. Look at percentiles, and look at the whole distribution, not at one summary number.
Is the change bigger than the noise?
Compare the difference with the spread between repeated runs of the same setup. A difference smaller than the spread proves nothing.
Does it grow with load?
Overhead that is tiny at low traffic may explode near saturation. Check several load levels.
Does it grow with detail?
Double the spans or attributes and see whether cost doubles. This reveals what you are really paying for.
Does it hurt the tail?
Look at the slowest requests, because users feel those first.
30Ways to Reduce Overhead, Ordered by Impact
When the budget is exceeded, change the biggest levers first.
Many teams start by tuning small settings and are disappointed. The better habit is to work from the largest source of cost to the smallest. The timeline below follows that order.
Record less, by sampling
Keeping only a share of traces cuts creation, storage and export work for everything downstream. This is usually the largest single saving.
Remove low-value instrumentation
Spans around tiny internal functions rarely help anyone. Keep spans for meaningful boundaries such as incoming requests, outgoing calls and database work.
Control attributes and events
Fewer, smaller attributes and events mean less memory and less to send. Use the SDK limits to put a ceiling on size.
Control metric dimensions
Each distinct combination of attribute values creates a separate series to track. Avoid attributes with endless possible values, such as user identifiers.
Batch and compress exports
Sending data in larger groups, away from the request path, reduces network calls. Compression trades processor time for bandwidth, so measure it.
Move work off the application
Let a separate Collector perform heavy tasks such as filtering and enrichment, so the application only hands over raw data.
Tune the small settings last
Queue sizes, timers and buffer limits matter, but only after the bigger levers are set.
If a suitcase is too heavy, you do not start by trimming the zipper. You first take out the heavy shoes you will not wear. Sampling and removing needless spans are the heavy shoes; buffer tuning is the zipper.
Cutting data lowers cost but also lowers insight. Keep errors and unusually slow requests whenever your sampling approach allows it, so the data you keep is the data you most need.
31Measurement Mistakes That Produce False Answers
Bad measurements are worse than none, because they create confidence in the wrong answer.
Problem
The team compares one baseline run with one instrumented run, done on different days, on shared machines, with different traffic.
Why It’s Harmful
Neighbouring workloads, warm-up state and traffic differences all change results. The team then blames or excuses instrumentation based on noise.
Correct Approach
Run both setups on identical, quiet machines, interleave them, repeat several times, warm up before recording, and compare the spread between runs against the difference you found.
Problem
The team measures only the average response time and declares the overhead negligible.
Why It’s Harmful
The average hides the slow tail, where full queues and garbage collection pauses show up first. Users and service targets are usually tied to the tail.
Correct Approach
Record percentiles such as the median, the ninety-ninth and higher, alongside throughput, processor use, memory and garbage collection behaviour.
Problem
The test sends data to a fast local sink that discards everything immediately.
Why It’s Harmful
Real destinations have delays, limits and occasional failures. A perfect sink hides back-pressure, retries and dropped data, so the result is too optimistic.
Correct Approach
Use a destination that resembles production, and also test a slow or failing destination to see how the application behaves under stress.
Before trusting any result, ask whether you could repeat it tomorrow and get a similar answer. If not, the experiment, not the instrumentation, is the thing to fix.
32Best Practices and Common Mistakes
A short list of habits separates calm measurement from chaos.
Best practices
- Write the overhead budget before measuring.
- Change one thing at a time on the ladder.
- Warm up the service before recording.
- Repeat each run and report the spread.
- Use a realistic workload with realistic load levels.
- Record percentiles, processor use, memory and garbage collection.
- Test with a production-like destination.
- Save the configuration and versions with each result.
Common mistakes
- Measuring once and declaring victory.
- Using only averages.
- Testing at low load and assuming it scales.
- Leaving a heavy profiler attached for the main comparison.
- Ignoring the cost of the pipeline after the application.
- Forgetting that settings differ between languages.
- Never re-measuring after upgrades.
- Treating a number from another company as your own.
A good cook tastes the soup several times while it simmers, using a clean spoon each time. A careless cook tastes once, from the top, and serves it. The habits above are the clean spoon and the repeated tasting.
33Reporting Results So People Can Trust Them
A result that cannot be repeated by someone else is only an opinion.
A good report is short, honest and complete enough that a colleague could rerun the experiment. Start with the question and the budget. Then describe the setup: machine type, number of processor cores, language and runtime versions, OpenTelemetry library versions, configuration, sampling setting and the workload shape. After that show the results as a table with the spread between runs, followed by a plain sentence stating whether the budget was met.
Next, state the limits. Say what you did not test, such as other languages, much higher traffic or unusual failures. This is not weakness; it protects readers from using your result somewhere it does not apply. Finally, store the raw data and the exact configuration in a place the team can find later, because the question will come back after the next upgrade.
| Report section | What it contains | Why it matters |
|---|---|---|
| Question and budget | What you wanted to know and the limits you accepted | Makes the conclusion testable |
| Setup | Hardware, versions, settings, workload | Lets others repeat the test |
| Method | Ladder steps, warm-up, repeats, duration | Shows the test was fair |
| Results | Tables with percentiles, processor, memory and spread | Shows the evidence |
| Conclusion | Budget met or not, and why | Gives a decision |
| Limits | What was not tested | Prevents misuse of the result |
Over time, a folder of dated reports becomes a history. You can see whether each release made instrumentation heavier or lighter, and catch drift early.
34Catching Regressions Automatically
Overhead is not a one-time question. It can creep up every release unless something watches for it.
Because instrumentation changes with every new span, attribute and library upgrade, overhead tends to rise slowly, like weight gained a little at a time. The cure is to run a small, repeatable test inside your delivery pipeline. It does not need to be perfect; it needs to be consistent. If the result moves outside an agreed band compared with the last accepted result, the pipeline raises a warning or blocks the change.
flowchart LR
A["Code or library change"] --> B["Build service"]
B --> C["Run baseline and instrumented tests"]
C --> D["Compare with last accepted result"]
D --> E{"Within the budget band?"}
E -->|"Yes"| F["Accept and store the result"]
E -->|"No"| G["Warn or block and investigate"]
G --> H["Fix or update the budget with approval"]
H --> C
The diagram shows the loop. A change triggers a build. The pipeline runs the baseline and the instrumented versions under the same conditions, then compares the outcome with the last accepted result. If it fits, the new result becomes the reference. If not, a person investigates, and either fixes the cause or deliberately updates the budget with approval so that the decision is visible.
Machines shared with other jobs produce noisy numbers. Use dedicated or well-isolated machines for these tests, and set the allowed band wider than the usual noise so that the check does not cry wolf.
A bathroom scale that you step on every morning catches a slow gain long before your clothes feel tight. The pipeline check is that scale for your service.
35Design Patterns for Keeping Overhead Under Control
A few repeatable patterns make the whole subject easier to live with.
The overhead budget
Agree on written limits for latency, processor and memory before work begins, and treat them like any other service target.
The overhead ladder
Measure in steps, from nothing installed to full detail, so each cost has a name and a number.
The canary comparison
Run the instrumented version on a small share of production machines next to the old version and compare them live.
The tiered detail level
Give each service a default light level and a switch for deeper detail that can be turned on temporarily when investigating a problem.
The offload pipeline
Keep the application thin and let a Collector do filtering, enrichment and routing.
The regression gate
Automate a small overhead test in the delivery pipeline so every change is checked.
A household budget, a monthly weigh-in and a trial run of a new route to work are three ordinary habits for staying within limits. These patterns are the same habits applied to telemetry.
36Real-World Scenarios
Different kinds of systems feel overhead in different places.
A high-traffic web service
A service handling thousands of requests every second watches the slowest requests closely. The team uses head sampling at a modest percentage, keeps spans to important boundaries, and moves filtering to a Collector. The main measurements are tail latency and processor cores at peak traffic.
A short-lived serverless function
Functions start often and run briefly, so start-up time and the time to flush data before the function freezes matter most. The team measures cold start with and without the library, and keeps the setup light.
A background batch processor
A job that processes millions of records has no user waiting, but total run time and memory matter. The team avoids a span per record and records one span per batch, with counters for totals, which keeps both memory and run time steady.
A small device or edge node
Devices with limited memory and weak processors cannot afford heavy libraries. The team measures memory footprint and battery or power use, and sends data in larger, less frequent batches.
A latency-critical trading or gaming path
When every millisecond counts, teams instrument the edges of the path only, and use sampling or separate paths for detailed investigation, because even small additions are visible.
37Comparing Measurement Methods and Their Trade-offs
No single method answers every question, so choose by the question you are asking.
| Method | Strength | Weakness | Best used for |
|---|---|---|---|
| Micro-benchmark | Precise cost of one operation | Ignores real traffic and interaction | Comparing two ways to record a span or attribute |
| Load test in a lab | Controlled, repeatable, fair comparison | May not match real traffic | Answering the budget question before release |
| Canary in production | Real traffic and real destinations | Limited control, risk to users | Confirming lab results at small scale |
| Continuous monitoring | Catches drift over time | Hard to separate causes | Watching for regressions after release |
| Profiling | Shows where time and memory go | Itself changes timing | Explaining a result you already found |
Why combine methods
- Lab tests give fairness.
- Canaries give realism.
- Profilers give explanations.
- Monitoring gives memory over time.
Risk of using only one
- Only micro-benchmarks miss real interaction.
- Only production tests risk users.
- Only profilers distort what they observe.
- Only monitoring cannot isolate a cause.
38Troubleshooting Unexpected Results
When numbers surprise you, the cause is usually one of a small set of familiar things.
| Symptom | Likely cause | What to check |
|---|---|---|
| Instrumented run is faster than baseline | Noise, warm-up differences or machine differences | Repeat runs, interleave them, extend warm-up |
| Slow tail grows but average is steady | Full queues or garbage collection pauses | Queue metrics and memory behaviour |
| Memory keeps rising | Too many attributes, unbounded metric dimensions or stuck buffers | Attribute limits and series counts |
| Processor use jumps at high load | Per-request work grows with span count | Spans per request and sampling rate |
| Data is missing at the destination | Dropped data when queues overflow | Drop counters in the SDK and Collector |
| Results differ between languages | Different SDK designs and defaults | Measure each language separately |
| Start-up much slower | Heavy auto-instrumentation or loading many libraries | Disable unused instrumentation, measure start-up alone |
When debugging a surprise, resist fixing three things at once. One change and one measurement at a time is slower per step but faster overall, because you always know what caused what.
39Maturity, Ecosystem and What to Verify
OpenTelemetry gives guidance, but you remain responsible for your own numbers.
The project publishes specification material about performance and benchmarking, and the individual language projects often keep their own benchmark results and suggestions. These are helpful starting points, yet they come from the maintainers’ own machines, workloads and versions. They cannot tell you how your service behaves.
Remember too that implementations differ. A setting that exists in one language may be named differently, or may not exist, in another. Defaults also change from release to release. Whenever you upgrade a library, treat the previous measurements as out of date until you repeat them. Check the current documentation for your language before relying on any specific setting described in a general guide.
Published benchmark numbers are context, not conclusions. Use them to know what order of magnitude to expect, then confirm with your own measurements.
40Frequently Asked Questions
It is the extra time, processor work, memory, network traffic and start-up cost that your service pays because it creates and sends telemetry data.
No. The right limit depends on your service targets and business. Set your own budget in advance, then test against it.
Usually very little, but check. The disabled-library step on the ladder tells you exactly how much.
Typically creating many spans, attaching many attributes and exporting everything. Reducing the volume through sampling and careful choice of what to record usually helps most.
No. Unsampled requests may still pay a small decision cost. Sampling mostly removes the larger cost of recording and exporting the data.
Averages hide the slowest requests, which are the ones users notice and where queue and pause problems appear first.
Yes, carefully, using a canary that compares an instrumented share of machines with an uninstrumented share, ideally after lab tests have passed.
Each language library is built differently, with its own runtime, memory model and defaults, so each needs its own measurement.
Yes, but in a separate process. It moves work off the application and adds its own resource cost and network hop, which you should measure and plan for.
Before the first rollout, after each major upgrade or instrumentation change, and ideally automatically on every change using a small regression check.
They are a useful guide to what is typical, but your workload, hardware and settings are different, so measure your own service.
Work down the list of levers: sample less, remove low-value spans, limit attributes and dimensions, batch exports, and offload work to a Collector. Then measure again.
41Why Each Programming Language Behaves Differently
The same idea of a span costs different amounts in different languages, because the languages themselves work differently.
Some languages run inside a managed runtime that cleans up unused memory automatically through a process called garbage collection. Every span, attribute and event is a small object. Many small objects make the cleaner work harder, and the cleaner can briefly pause the program. In such languages, overhead often appears as extra pauses and a fatter slow tail.
Other languages manage memory directly, or through ownership rules. There the cost shows up more as processor work and copying, and less as pauses. Interpreted languages add their own cost, since each instruction takes longer to run, and instrumentation code is instructions too. Languages that run many tasks at once may lose time when instrumentation uses locks that tasks must queue for.
Carrying a parcel by bicycle, by car and by train costs different things. The bicycle rider tires, the car burns fuel and the train needs a timetable. The parcel is the same, but the price is paid in a different currency each time. Overhead has different currencies in different languages.
| Runtime style | Where overhead often shows | What to watch |
|---|---|---|
| Managed with garbage collection | Pauses and slow tail | Collection frequency and pause length |
| Direct memory control | Processor work and copying | Processor cores and allocation counts |
| Interpreted | Slower per-instruction cost | Throughput and processor use |
| Highly concurrent | Waiting on shared resources | Lock contention and queue depth |
If your organisation uses several languages, run the experiment for each one separately. Never copy a result from one language to another.
42Turning Overhead Into Capacity and Cost Plans
Overhead is not only a technical number; it becomes machines, bills and headroom.
Suppose full tracing adds eighteen percent processor use to a service. If the service runs on one hundred machines, the fleet needs roughly eighteen more machines to carry the same traffic at the same comfort level. That cost is real, and it belongs in the plan next to the cost of storing and viewing the data.
Capacity planning therefore asks three questions. How much extra resource does instrumentation need per unit of traffic? How much headroom do we keep so that a sudden spike does not push the service past its limit? And how will the overhead change as traffic and detail grow? Answering them early avoids unpleasant surprises, such as a service that was comfortable on Monday and struggling on Friday because a new release added many spans.
Measure cost per request
Use the lab experiment to find the extra processor time and memory for each request at your chosen detail level.
Multiply by expected traffic
Combine the per-request cost with your peak traffic forecast to see the total extra load.
Add headroom
Keep a margin for spikes, failures and growth, rather than planning to run exactly at the limit.
Include downstream cost
Remember the Collector machines, network traffic and storage for the telemetry itself.
43A Simple Roadmap for Your Own Team
You do not need to do everything at once. Grow the habit in small, sensible steps.
First look
Run a basic baseline versus instrumented test on one important service and write down the result and the budget.
Repeatable method
Adopt the ladder, warm-up, repeats and percentiles as the standard way your team measures.
Automation
Add a small regression check to the delivery pipeline so that every change is tested.
Production confirmation
Use canaries and live dashboards to confirm that lab results hold on real traffic.
Shared knowledge
Keep a results diary and shared guidance, so new engineers inherit the lessons instead of repeating the mistakes.
People learn fitness the same way. First they check their weight once, then they weigh themselves regularly, then they follow a plan, and finally they teach others. Each stage builds on the one before it.
A single careful measurement on the service that matters most teaches more than a shallow survey of fifty services.
44Measuring the Collector and the Path After the Application
Overhead does not stop at the edge of your application; the data still has to travel and be handled.
Many teams place a Collector between applications and the final destination. This moves heavy work out of the application, but it does not make the work disappear. The Collector uses processor time to receive, change and forward data, memory to hold queues, and network bandwidth in both directions. If you measure only the application, you will believe the cost is smaller than it truly is.
To measure the pipeline fairly, give the Collector the same treatment as the application. Run it on machines like those you will use in production. Send it realistic volumes of traces, metrics and logs. Record processor use, memory, queue depth, the number of items dropped, and the delay between arrival and delivery. Then raise the volume step by step until you find the point where it starts to fall behind. That point, with a safety margin, tells you how many Collector instances you need.
Queue depth
A queue that keeps growing means data arrives faster than it leaves.
Dropped items
Any drops mean visibility is being lost, so treat them as a serious signal.
Delivery delay
Long delays make data stale, which hurts live troubleshooting.
Resource use
Processor and memory trends show how close each instance is to its limit.
A post office sorting centre sits between the houses that send letters and the places that receive them. Houses may post their letters quickly, but if the centre is too small, letters pile up and arrive late. You must size the centre as carefully as you pack the letters.
Moving data between machines, and especially between zones or regions, can carry a cost and a delay. Include these in your plan, not only the processor and memory figures.
45Summary and Key Takeaways
Measuring overhead is a calm, repeatable habit, not a one-time scare.
You began with a doctor who must not disturb the patient while checking their health. You learned that instrumentation costs time, processor work, memory, network traffic and start-up speed, and that these costs come mainly from creating, enriching and exporting data. You learned to set a budget, build a fair experiment, climb an overhead ladder, use a realistic workload, and read percentiles rather than averages. You saw how sampling, careful instrumentation, bounded attributes and batching reduce cost, and how pipeline checks catch slow creeping increases.
Remember that overhead is a trade, not a sin. Telemetry buys you the ability to see failures, slow requests and strange behaviour that would otherwise stay hidden. The goal is never zero overhead; the goal is a known, agreed and affordable overhead that gives your team clear insight. When the cost is measured honestly and kept within a written budget, the debate stops being a matter of opinion and becomes a matter of evidence that anyone can inspect, repeat and improve over time.
Finally, share what you learn. A short note describing your budget, your method and your latest numbers helps neighbouring teams avoid repeating your mistakes, and invites them to correct yours.
Key Takeaways
- Decide your overhead budget before you measure, and write it down.
- Measure on a ladder, changing one thing at a time, from nothing installed to full detail.
- Use a realistic workload and a production-like destination, and warm up before recording.
- Look at percentiles, processor use, memory, garbage collection and network, not only the average.
- Reduce overhead by the biggest levers first: sampling, fewer spans, limited attributes and dimensions, then batching.
- Automate a small regression check so overhead cannot creep up unnoticed.
- Treat published numbers as hints, and always verify with your own service, language and versions.