If you run any real infrastructure, you've stared at a Grafana dashboard: a wall of graphs for CPU, memory, latency, and error rates. Each of those graphs is drawn by a small query you wrote in PromQL, the query language Prometheus uses, the rate(...) and sum(...) expressions that turn raw metrics into a line on a chart. A single dashboard can hold hundreds of them.
Now suppose you want to move to a different observability tool, maybe for better tracing, maybe to run one backend instead of five. Your metrics come across. Your logs come across. Your dashboards do not, and that is the part nobody warns you about.
You sit down in front of years of Grafana dashboards, hundreds of panels, and you rebuild every single one by hand. Panel by panel, PromQL query by PromQL query, retyped into the new tool and eyeballed until the graph looks about right.
That manual slog is a real reason teams never switch at all. It isn't that the new tool is worse. It's that nobody wants to spend two weeks re-creating dashboards and still not be sure they got them right. So I built something that does it for you. This post is why the move is worth making, the one detail that makes it tricky, what I built, and the thing I got wrong along the way.
Why anyone wants to leave the Grafana stack
For a decade, observability meant stitching tools together. Prometheus for metrics, Loki for logs, Tempo or Jaeger for traces, Grafana on top to draw all three. It works, but you're running four systems, each with its own storage and query language, and chasing a slow request from a trace to the metric that explains it means bouncing between tabs and hoping the timestamps line up.
OpenTelemetry changed the math. It's now the standard way to instrument code for traces, metrics, and logs, vendor-neutral and supported everywhere. Once your instrumentation is standard, the backend doesn't have to be four things. It can be one.
Why SigNoz is a good place to land
That one thing, for me, was SigNoz. If you haven't come across it: SigNoz is an open-source, OpenTelemetry-native observability platform that keeps logs, metrics, and traces in a single ClickHouse-backed store. You go from a slow trace straight to the metric and the log line behind it, in one place, no glue. You self-host it with Docker or Kubernetes, so your data stays yours. There's no proprietary agent to install because it speaks the OpenTelemetry your code already emits. And there's an MCP server, so a coding agent can debug against the same telemetry you're looking at.
The pitch is simple: collapse the Prometheus-plus-Loki-plus-Tempo-plus-Grafana stack into one tool. The pitch is also where you hit the wall, because your dashboards are still in Grafana.
The problem: your dashboards are hundreds of PromQL queries
A dashboard isn't one thing to move. Node Exporter Full, the one I tested against, is 140 panels and hundreds of PromQL queries. Moving it means translating every one of those queries into something SigNoz can run, and doing it in a way you can actually trust. The obvious approach, a converter that rewrites each PromQL string, or an LLM asked to "port this query," is a trap, and to see why you need one detail about how SigNoz asks questions.
Query Builder vs PromQL, the part worth understanding
In Grafana over Prometheus, every panel is a PromQL string. You write rate(...) and histogram_quantile(...) by hand, as text.
SigNoz gives you two ways to ask the same question. It will run raw PromQL directly, so a Grafana dashboard can come across as-is and render. But its first-class query type is the Query Builder: instead of a string, you pick a metric, an aggregation like avg or p99, a group-by, filters, and having and limit clauses, and SigNoz assembles the query. A Builder query is the native citizen. It stays editable in that visual builder, it powers click-to-filter drilldown, and it's the form alerts and the rest of the product are built around.
So every migrated panel is a fork in the road. Leave it as PromQL and it renders, but it's an opaque string you can't edit in the builder and can't drill into. Or translate the PromQL into a native Builder query and get the whole SigNoz experience back.
Translating PromQL into Builder queries is the move worth making, and it's the hard one, because PromQL does not mean the same thing once SigNoz's Builder engine is in the path. Same query text, same metric, and the number that comes out can be subtly different, because the two engines bucket and label time differently. A naive conversion looks perfect and is quietly, numerically wrong. Every panel renders. Some of them are lying, and you can't tell which by looking.
What I built
So I built the tool around distrust. It's called noz-in, it's open source and written in Go, and its whole job is to turn a Grafana dashboard's PromQL into native SigNoz Builder queries without ever claiming a conversion it can't prove.
The way it proves one is the important part. For each candidate, noz-in parses the PromQL with the real prometheus/promql/parser, never a regex, builds a Builder query, then runs both the Builder query and the original PromQL against your live SigNoz over the same window and compares the two result series point by point. It doesn't argue a conversion is correct. It measures it, on your data.
That measurement is where I learned I was doing it wrong.
What I got wrong while building it
My first version of the comparison did the obvious thing: check that the two series match in value, within 5%, with timestamps matched loosely to absorb jitter. I ran it on Node Exporter Full, and it happily promoted a handful of queries to native, each certified within 5%. Every number was green.
Then I plotted one of those "matching" panels against its PromQL source, and the SigNoz line was rendering every value one minute late. The whole way across. My own check had signed off on it.
The cause isn't a bug in SigNoz. Its Builder engine buckets time and labels each bucket at the bucket's start; PromQL evaluates at the step boundary. Both are internally consistent, they just disagree about which timestamp a value belongs to, so every moving series comes out shifted by exactly one step. I re-derived it through a separate code path to be sure I wasn't fooling myself twice:
builder[t] == promql[t+60s] for 10/10 points (0/10 match same-slot)
Ten out of ten points lined up one step over. Zero lined up in place. So why did a 5% check pass it? Because on a slow-moving gauge, one step of drift is a tiny change in value, about 0.006% for available memory, three orders of magnitude under the tolerance. The two series were nearly identical in value at every point and wrong in time at every point, and a value comparison can only see the first thing.
Here's the part that actually taught me something. My check hadn't been testing whether the translation was correct. It had been testing whether the data happened to be flat during my test window. Run it while the metric was steady and it passed; run it while the metric moved and it would have failed.
Correctness that depends on your test data being boring is not correctness.
The fix compares each point against the PromQL series at the same slot and one step on either side, and refuses to promote anything that only lines up when shifted. About forty lines, with both failure modes pinned as regression tests so I can't quietly bring them back.
A constant series came back ADOPTED fidelity=exact maxRelErr=0.0000, every point identical. A moving one diverged and came back REJECTED. The tool keeps what it can prove and refuses what it can't, with the receipt attached to each.
How it works now
Two rules hold the whole thing together.
The floor is that every query migrates, no exceptions. Nothing gets dropped because it was too hard to convert. The invariant is that nothing is called native without passing that live check first. Every query lands on one of three verdicts:
-
native— a Builder query proven equivalent on your live data, fully editable in SigNoz. -
passthrough— the original PromQL, verified to run, that SigNoz executes as-is so the panel still renders. -
needs_review— noz-in isn't sure, so it flags the query with a reason code instead of guessing.
Here's the run I'd want a skeptic to see. Fixed tool, pointed at Node Exporter Full, live:
All 140 panels accounted for and rendering in SigNoz. Zero forced to native, on purpose. Every panel came across as verified passthrough, and the queries that could have been promoted but carried time-bucketing semantics I couldn't fully prove stayed at review instead. "100% native" was never the goal; some PromQL, like topk and subqueries, has no Builder equivalent and stays passthrough forever, correctly. The dashboard works. That's the goal.
That's the same Node Exporter Full dashboard from the top of this post, running in SigNoz on live data, that I did not rebuild by hand.
Try it on your worst dashboard
It's one command: point noz-in at a Grafana dashboard JSON and your live SigNoz, and it writes the migrated dashboard plus a report of exactly what it proved and what it refused. There's an MCP server and an agent skill too, so an AI agent can propose a Builder query for anything stuck at review, but that proposal is adopted only if it passes the same live check as everything else. The agent proposes, the tool verifies. Nothing skips the gate.
If you've been putting off leaving Grafana because of the dashboard rebuild, that's the part I automated. And the lesson underneath it holds for any migration, not just mine: a conversion that checks equivalence on values alone will certify panels that are wrong in time, and you won't find out until an alert fires a minute late during an incident. Make it prove the time too.
mansiverma897993
/
noz-in
Migrate every Grafana dashboard to SigNoz immediately through safe PromQL passthrough then automatically promote only the queries proven equivalent to native SigNoz Builder queries.
noz-in
Migrate every Grafana dashboard to SigNoz immediately through safe PromQL passthrough then automatically promote only the queries proven equivalent to native SigNoz Builder queries.
noz-in is a deterministic query-compatibility and migration engine that
moves observability estates into SigNoz. It ships as the promcast CLI
which converts Grafana dashboards and Prometheus alerting rules into SigNoz
artifacts, validates the exact target queries against live SigNoz APIs, and
explains every compatibility decision in JSON and self-contained HTML.
This is an independent community project, not affiliated with or endorsed by SigNoz, Inc. The code is organized so proven adapters or compatibility rules can be proposed to SigNoz later without coupling its core model to Grafana internals.
How it works
Every migration rests on one floor and one invariant:
- The floor: every query always migrates. When a PromQL query cannot be proven equivalent to a SigNoz Builder query, the verbatim PromQL is emitted and…






Top comments (0)