The flow tables already line up
TMA1’s tma1_token_usage_1m, cost_1m, latency_1m, and status_1m flow tables derive from span_attributes.gen_ai.*, which this plugin populates by convention. Nothing else to configure.
DSH OTel
No collector. No sidecar. No fork of DSH. It installs as an ordinary DeepSeek Harness plugin, and every turn, model call, and tool execution becomes a row in GreptimeDB you can query.
INSTALL
dsh plugin --profile web add @tma1-ai/dsh-plugin-greptimedb
The package ships a bundle patch, so that one command is the whole install. Requires pnpm 10 or newer. The defaults already point at a local GreptimeDB.
Chat spans and tool spans share a trace and a dsh.step, so correlating them is a plain SQL join. Timestamps come from the session events themselves, not from when the plugin handled them.
Traces, metrics, and logs. signals takes any subset — a disabled signal builds no exporter at all.
The default content: none exports structure and accounting only. No prompts, no messages, no tool arguments, no tool results.
Bad configuration fails when the plugin loads, with the offending field named — not silently at the first export.
Signals
Traces for shape, metrics for long retention and sampling-proof percentiles, logs for the raw session events.
Turn spans are roots. Chat and tool spans hang off them as siblings, correlated by dsh.step. Every chat span gets a real end time, including the crash case.
DSH’s counts are disjoint: inputTokens is uncached input alone, cache reads and writes are separate fields. gen_ai.usage.input_tokens is the billed total, so the plugin sums them.
Overview, Cost, Sessions, Agent loop, Trace explorer, Log explorer, Metrics. They ship in grafana/ with a compose stack that brings up GreptimeDB and Grafana together, and every panel query is checked against a live database in CI.

Every table links onward: a trace id opens that turn’s waterfall, a session id jumps between the trace and log views.

Session, event type, turn, and step are real columns, so filtering a session does not mean unpacking JSON.
Metrics
The same activity as the traces, through PromQL — for longer retention and percentiles that survive sampling.
| Instrument | Type | Dimensions |
|---|---|---|
gen_ai.client.token.usage | Histogram | gen_ai.token.type (input/output only), model, provider |
gen_ai.client.operation.duration | Histogram | gen_ai.operation.name, model, provider |
gen_ai.invoke_agent.duration | Histogram | gen_ai.operation.name |
gen_ai.execute_tool.duration | Histogram | gen_ai.operation.name, gen_ai.tool.name |
dsh.token.detail | Histogram | dsh.token.detail_kind (cache_read/cache_write/reasoning) |
dsh.tool.invocations | Counter | gen_ai.tool.name, dsh.tool.outcome |
dsh.turns / dsh.steps | Counter | — |
Configuration
A profile patch replaces the row’s whole config instead of merging into it, so restate every field you want to keep.
| Key | Default | Notes |
|---|---|---|
endpoint | required | OTLP base URL. The plugin appends the /v1/{traces,metrics,logs} suffix itself. |
database | public | Sent as X-Greptime-DB-Name. |
username / password | none | Basic auth. Both or neither. |
signals | all three | Any subset of traces, metrics, logs. |
content | none | How much payload may leave the process. |
ttl | 180d | Retention for the tables the plugin creates. Also accepts forever. |
Batching, timeouts, service name, and table overrides have sensible defaults; the full table is in the README.
What leaves the machine
content decides thisThe default withholds all payloads. Raise it deliberately, per profile.
| Mode | Exported |
|---|---|
none (default) | Structure and accounting: event types, turn and step numbers, token counts, tool names, durations, outcomes, error name and code. |
full | Adds user and assistant message content, tool arguments, tool results. |
full+prompt | Adds request/header: the complete system prompt and every tool schema. |
Whatever the mode, a tool’s private meta payload and the message and stack of a failed request never leave. The projection is an allowlist, so an event type the plugin does not know exports its identity and nothing else.
With TMA1
TMA1 proxies OTLP into a GreptimeDB it manages. Change one line and DSH shows up in its OTel GenAI view.
TMA1’s tma1_token_usage_1m, cost_1m, latency_1m, and status_1m flow tables derive from span_attributes.gen_ai.*, which this plugin populates by convention. Nothing else to configure.
Known limitations
The full list is in the README.
It renames and repackages freely before its first tagged release, so the peer range is pinned to the version CI runs against.
Attribute names come from @opentelemetry/semantic-conventions/incubating and move with it.
ttl does not reach metric tablesOn the metric engine, retention belongs to the physical table, so set it there yourself (greptimedb#8951).
There is no per-turn flush, and records still in flight at shutdown can be lost.