Guide

Profiling with watch

An application is slow and you do not know where. Profilers give you a snapshot to read; watch gives you tables to interrogate — in the same database as the code, so the slow span and the method behind it are one join apart.

1 · Run it under the collector

The call returns as soon as the process starts, with a run id and the schema to query. The application keeps running while you work.

watch(executable="dotnet", arguments=["run"])

If the application has its own telemetry behind a switch, turn it on here rather than editing configuration — environment is merged over the inherited environment.

2 · Find where the time goes

SELECT name, count(*) AS calls, avg(duration_ms) AS avg_ms FROM run_a91f.spans GROUP BY name ORDER BY avg_ms * calls DESC LIMIT 10;

Ordering by total cost rather than by mean is usually the right first cut — the expensive thing is often cheap per call and made a great many times.

3 · Walk the residue

When a number looks like victory, the unexplained remainder is where the real problem lives. Take the slowest few and look at them individually rather than in aggregate.

SELECT trace_id, name, duration_ms, attributes FROM run_a91f.spans WHERE duration_ms > 1000 ORDER BY duration_ms DESC;

4 · Land on the code

You have a span name; it maps to a method. Address it directly and read the body, then its history.

read("file:///src/**/*.cs#symbol=*.HandleBatchAsync", 1500)

5 · Measure the fix the same way

Run it again after the change. The previous run's tables are still there, so the comparison is a query rather than a memory.

SELECT 'before' AS run, avg(duration_ms) FROM run_a91f.spans WHERE name = 'HandleBatch' UNION ALL SELECT 'after', avg(duration_ms) FROM run_b03c.spans WHERE name = 'HandleBatch';

This page names what exists. The depth behind every name — the bounds, the failure modes, how they compose — ships inside the binary at help:///, and answers to explore and read exactly like your code does. Install it, and your agent has the manual.