Identify hosts in multi-node panels - #63
Open
kapantzak wants to merge 5 commits into
Open
Conversation
Series were named from result.labels, which carries machine-readable ids,
with a node lookup that only applied when grouping by node. Grouping by a
label produced the same legend entry for every host (e.g. "/"), so a panel
covering several nodes could not be read.
- name series from view.dimensions.names, the human-readable counterpart
that is index-aligned with result.labels
- attach Grafana field labels from view.dimensions.labels plus the node
identity, so {{node}} legend overrides and transformations work
- request the group-by-labels option, without which those labels are absent
from the response
- allow several groupings in one query, so a single query can separate
series per node and per label at the same time
- keep accepting the single-string groupBy of already saved dashboards
- scope the chart-data event per query, so sibling editor rows stop
overwriting each other's dimension and filter options
Grouping by dimension leaves the node out of the series id, so a query pinned to a single node produced a legend with no way to tell which host it came from - two such queries in one panel were identical. Fall back to the only node in summary.nodes when the id carries none: with one node in scope every series belongs to it, while several nodes mean the series really is a cross-node aggregate and stays unlabelled. summary.nodes is the only valid source - agents names whichever agent served the query, which is routinely a different node.
The series labels were only reachable through Grafana's Display name field
option and its ${__field.labels.x} syntax, which is obscure enough that the
labels were effectively unusable.
Add a Legend template to the query editor, the control users expect from
other Grafana data sources: {{node}} and any other label key resolve per
series, {{name}} is the name the query returned, and an unknown token
resolves to an empty string. The rendered value goes to displayNameFromDS,
so a panel-level Display name still overrides it.
An empty template leaves the field unnamed, keeping Grafana's own naming
and its disambiguation of series that share a name across queries.
Grafana keys plugin asset caching on the version, so every build shipping as 3.0.4 left browsers serving a stale module.js after a deploy, making it impossible to tell whether a change had actually landed.
The file was swept into the toolkit migration by accident and never maintained. eslint keys it on absolute paths, so the committed copy - whose entries point at a different working copy entirely - can never be a cache hit for anyone, while `yarn lint --cache` rewrites it on every run and dirties the tree. Untracked rather than deleted, so local lint caching keeps working. CI is unaffected; it runs the build, not lint.
kapantzak
requested review from
witalisoft
and removed request for
witalisoft
September 11, 2026 12:34
kapantzak
marked this pull request as ready for review
September 11, 2026 12:41
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A panel covering several nodes could not show which host each series belonged to. Reported in support ticket 835: disk usage of
/across 13 hosts produced 13 indistinguishable series.The series name came from
result.labels, which carries machine-readable ids, and the only translation was a machine-GUID lookup againstsummary.nodes:That lookup only matches when grouping by node (where the id is the machine GUID). Grouping by a label makes the id the label value, so every host came back as
/. On top of that,Grouping bywas single-select, so "per node and per mount point" was not expressible, and no Grafana field labels were emitted, so nothing could name a series by its host.What the API already provides
Verified against
netdata/cloud-charts-service(internal/model/data.go,internal/aggregation/data_v2_metadata.go) and a recorded response fixture in that repo:result.labels["time", ...ids]— machine-readableview.dimensions.names"time"view.dimensions.labels{ "<key>": [[values], ...] }, same alignment — only when the request sends thegroup-by-labelsoptionaggregations.metrics[].group_byaccepts combined values (["node","label"]) alongsidegroup_by_label.Changes
Naming and labels
view.dimensions.namesinstead of the raw ids (src/shared/utils/series.ts).Field.labelsfromview.dimensions.labelsplus the node identity.group-by-labelsoption, without which those labels are absent.summary.nodesis the only valid source —agentsnames whichever agent served the query, which is routinely a different node.Grouping
Grouping byis now a multi-select mapping togroup_by/group_by_label(src/shared/utils/grouping.ts), so one query can split per node and per label.groupByof already saved dashboards keeps working (normalizeGroupBy) — no dashboard migration needed.Legend
src/shared/utils/legend.ts):{{node}}, any other label key, and{{name}}for the name the query returned. Renders intoconfig.displayNameFromDS, so a panel-level Display name still overrides it. Empty leaves naming to Grafana, preserving its cross-query disambiguation.src/README.md.Unrelated defects fixed on the way
CHART_DATA.<refId>).3.0.4left browsers serving a stalemodule.jsafter a deploy, making it impossible to tell whether a change had landed.%VERSION%inplugin.jsonis substituted frompackage.jsonat build time, so the one bump also renames the CI artifact tonetdata-datasource-3.0.5.zip..eslintcacheand added it to.gitignore. It was swept into the toolkit migration (b441723) by accident; eslint keys it on absolute paths, so the committed copy — whose entries point at a different working copy entirely — could never be a cache hit for anyone, whileyarn lint --cacherewrote it on every run and dirtied the tree. Untracked rather than deleted, so local lint caching keeps working. CI runs the build, not lint, so it is unaffected.Implementation note
Netdata joins the group-by parts of an id with
,(and@forinstance@machine_guid) without escaping, so ids cannot be split back apart reliably. Label values are therefore read straight fromview.dimensions.labels, and the node is found by matching any separated part against a known machine GUID.Result
Verified against two real captured responses (single node per query, grouped by dimension — the shape that previously produced identical legends):
usedused{{node}}plaka-parentcosta-desktop{{node}} - {{mount_point}}plaka-parent - /costa-desktop - /And the intended single-query path — Nodes = all, Grouping by =
node+mount_point, Filtermount_point=/— names series by hostname directly.Verification
Notes for reviewers
CHANGELOG.mdholds only2.0.0and1.0.12— the whole 3.x line, including the current3.0.4, was never recorded. A lone3.0.5entry would be inconsistent with how the file has actually been maintained; reviving it properly means covering 3.0.0–3.0.5 as separate work.src/shared/hooks/useFetch*.test.tssuites fail withSyntaxError: Unexpected token 'export', a jest ESM-transform issue viamarked←@grafana/data. Left alone deliberately; it is whybuildGroupinglives in its own dependency-free module rather than being exported from the hook.Grouping byusesMultiSelectrather thanSelect isMulti, which is what typechecks correctly for a multi-value handler.