Blog · 2026-07-02

Traces that split your code from their API

Every request gets a trace split into code time vs external I/O, plus p50, p95, p99 and error rate per project. Prove it is their API, not your code.

Every request to a DenkOps app gets a trace, and every trace is split into two numbers: time spent in your code, and time spent waiting on external I/O. That split is the argument-settler. "My code: 5 ms. Their API: 250 ms." is a sentence that ends debates which "it feels slow" starts. Tracing is injected automatically when you deploy, so there is no instrumentation code to write.

What do the traces actually show?

Two levels. Per project, a rolling summary over the last 24 hours: request count, error rate (the share of responses that were 5xx), and latency percentiles p50, p95 and p99, alongside average code time versus average I/O time. Per request, the individual trace: total duration, status code, a bar showing the code versus I/O split, and the spans underneath it as a waterfall, so you can see which call inside the request took the time. You can filter the trace list by minimum duration, which is usually the first move when hunting a slow path.

The percentiles matter more than the average. A connector that is fine at p50 and terrible at p99 is a connector that annoys one user in a hundred, and that user is the one who files the report.

A debugging story: the slow tool call

Here is the shape this takes in practice, with a connector like the one from deploying your first MCP server.

A teammate says the search_invoices tool "hangs for a second" in their assistant. Your handler is thirty lines, it cannot possibly be slow, but feelings are not data. So: open the project's Traces page, set min duration to 200 ms, filter.

There they are. A cluster of POST /mcp traces around 260 ms. Open one. Total: 261 ms. Code: 6 ms. I/O: 255 ms. The waterfall shows one span dominating the request: the outbound call your handler makes to the invoicing vendor's API. Your thirty lines run in single-digit milliseconds; the other 97 percent of the request is waiting for someone else's server.

That one trace changes the conversation three ways:

  • You stop optimizing your own code, because the numbers say it is not the problem.
  • You can go to the vendor with evidence: timestamps, durations, request counts, not vibes.
  • You can decide, with data, whether the dependency is worth wrapping in a cache. A TTL entry in denkops.store that keeps results for a few minutes would turn most of those 255 ms waits into local reads.

Without the split, every one of those steps starts with "add some timing logs, redeploy, wait for it to happen again". With it, the answer was already recorded when the complaint arrived.

Why split code vs I/O instead of just totals?

Because a total tells you that a request was slow, not who to blame or what to do. High p95 with high code time means your handler needs work: profile it, precompute, ship a fix. High p95 with high I/O time means an external dependency needs managing: cache it, add a timeout, or escalate to whoever runs it. Same symptom, opposite responses, and only the split tells you which world you are in.

It also gives deploys a before-and-after. Ship a change, watch p95 and error rate for the project, and if the code side of the split jumps, roll back with one click while you figure out why. Regressions become measurements instead of anecdotes.

FAQ

Do I need to add instrumentation code to get traces on DenkOps?

No. Tracing is injected automatically when your app is deployed. Every request produces a trace with a code time versus external I/O split, and the project dashboard aggregates p50, p95, p99 and error rate over the last 24 hours.

What does the code vs I/O split in a trace mean?

Code time is time your handler spent executing. I/O time is time the request spent waiting on external calls, like a third-party API. A request with 6 ms code and 255 ms I/O is slow because of the dependency, not your code.

How do I find slow requests in a project?

Open the project's Traces page and filter by minimum duration, for example 200 ms. Matching traces list duration, status and the code versus I/O split, and opening one shows a waterfall of the spans inside it so you can see exactly which call took the time.

Ship it yourself: bunx denkops deploy or say "deploy on DenkOps" from your coding agent.

Start on DenkOps →

← Put your MCP connector on your own domain · Deploy history and one-click rollback →