How we built a Grafana to SigNoz compiler that checks every panel three ways.
We moved a Grafana dashboard to SigNoz, and instead of trusting that the panels came out right, we made the tool prove it, chart by chart.
One panel came back wrong, and the check told us exactly which layer was at fault—the translation, not the data.
That is the whole idea.
A migration you can measure beats a migration you have to hope about.
Why we built this
Teams stay on Prometheus and Grafana for one reason: the rewrite.
A real setup has hundreds of PromQL queries and panels behind it. SigNoz has an importer, so getting the dashboard across is not the hard part.
The hard part is trust.
After conversion, nobody can tell you whether panel 34 still means the same thing it did before. A chart can render perfectly and still show the wrong number, and you only find out when someone makes a decision based on bad data.
So we built Migration Proof for the Agents of SigNoz Hackathon.
It reads a Grafana dashboard JSON, extracts every panel and its PromQL, compiles each query into a SigNoz Query Builder query, and then verifies every panel against live data.
The result is a simple scorecard:
- ✅ Pass
- ❌ Mismatch
- ⚠️ Flagged for review
SigNoz sits at the center of this project in a role it wasn't specifically designed for.
We use it as a query engine and compare its answers directly against Prometheus.
The three-way check
This is the part that makes the verification honest.
If you compare only two things—our translated query against Prometheus—a mismatch has two possible causes:
- the data never landed correctly in SigNoz
- our translation is wrong
One diff hides two different bugs.
So instead we compare three systems.
- Prometheus is the source of truth.
- SigNoz PromQL proves that the data landed correctly.
- SigNoz Query Builder proves that our translation is correct.
Now the failure modes separate naturally.
If:
- Prometheus == SigNoz PromQL
- but SigNoz Query Builder != Prometheus
then the compiler is wrong.
If:
- SigNoz PromQL != Prometheus
then the ingestion pipeline is wrong.
Instead of simply telling you that a panel failed, the verification tells you which layer to fix.
This only works because SigNoz supports both native PromQL execution and Query Builder queries against the same underlying data.
That single capability makes fault isolation possible.
- Prometheus (Truth)
- SigNoz PromQL (Ingestion Validation)
- SigNoz Query Builder (Translation Validation)
What the compiler actually does
The translator is written in Python.
We parse PromQL using promql-parser, a Rust-backed parser that produces a complete syntax tree.
That syntax tree becomes a typed intermediate representation before finally being emitted as SigNoz Query Builder JSON.
The compiler currently has 64 unit tests.
Instead of trying to support every corner of PromQL, we intentionally support a bounded subset based on what appears most often in production dashboards.
That includes:
- label matchers
- rate()
- increase()
- aggregations with by/without
- histogram_quantile()
- arithmetic expressions
Anything outside that subset is never guessed.
It is flagged for manual review instead.
This is one of the most important design decisions in the project.
A wrong conversion that appears successful is far worse than refusing to translate something.
Whenever the compiler is unsure, it tells you.
The demo, and the verification loop
On a clean run the parity scorecard reports:
6 out of 7 panels verified successfully.
For every successful panel, all three result lines overlap perfectly:
- Prometheus
- SigNoz PromQL
- SigNoz Query Builder
The remaining panel is intentionally flagged.
It contains topk(), which is currently outside the supported subset.
Rather than generating an incorrect conversion, the compiler surfaces it for review.
Next we demonstrate the verification loop.
We intentionally inject a compiler bug.
Instead of translating:
rate(http_requests_total[5m])
the compiler accidentally emits a query against the raw counter.
The resulting chart still looks believable.
It is also completely wrong.
After re-running verification:
- Prometheus still matches SigNoz PromQL.
- The Query Builder result diverges.
The scorecard immediately localizes the problem.
The ingestion is correct.
The compiler is not.
We fix the bug, run verification again, and the panel returns to green.
Detect.
Localize.
Fix.
Verify.
Alerts: where importing really bites
Dashboards are only half of a migration.
Alerts are where things become dangerous.
SigNoz currently has a known issue where an alert rule can save successfully yet never actually fire.
That means:
"Alert imported successfully"
does not necessarily mean
"Alert actually works."
To verify alerts, we translate the same threshold alert into both systems and evaluate them against identical data.
We observe both state machines.
Prometheus transitions:
Pending → Firing
SigNoz transitions:
Pending → Firing
Both systems fire.
Not imported.
Proven.
What we learned about SigNoz by verifying against it
Building against a live SigNoz deployment surfaced issues that a simple converter would never notice.
We only discovered them because every translated panel was continuously compared against Prometheus.
OpenTelemetry relabeling
The OpenTelemetry Collector renames several Prometheus labels during ingestion.
Without explicitly mapping those names, comparisons never line up correctly.
Counter temporality
Counter metrics return empty results unless cumulative temporality is enabled.
Without that flag, the query looks like there is simply no data, which sends debugging in completely the wrong direction.
Rate differences
SigNoz Query Builder computes rate() slightly differently from native PromQL because of different window calculations.
The difference is only a few percent, but it is measurable.
Instead of pretending the values are identical, we measured the deviation and introduced a documented tolerance.
Eventual consistency
The hardest issue appeared while stabilizing the demo.
SigNoz Query Builder ingests the newest data roughly ninety seconds behind real time.
Under database load, queries against "now" can return incomplete points.
A parity check that compares the most recent timestamps will randomly fail even when both systems agree.
The correct solution is simple.
End the comparison window a few minutes in the past and compare only stable data.
That is how a production-grade parity checker should behave anyway.
None of these observations are complaints.
They are the kinds of engineering details you only discover by building real systems against production software and validating every number.
For SigNoz, they are useful implementation details.
For anyone migrating dashboards, they are valuable lessons learned ahead of time.
The point
Parity you can measure beats conversion you have to trust.
Our compiler moves a Grafana dashboard into SigNoz and then proves every translated panel.
For every chart we can demonstrate one of two outcomes:
- SigNoz returns the same values as Prometheus.
- Or we show exactly where the disagreement occurs and which layer caused it.
That transforms dashboard migration from a leap of faith into a repeatable engineering process.
And that is what gives teams the confidence to actually migrate.
The repository includes:
- the PromQL compiler
- the bounded translation subset
- the three-way parity verifier
- the reproducible demo environment
If you're migrating dashboards from Grafana to SigNoz, or building tooling around observability, we'd love your feedback and contributions.




Top comments (0)