Back to Netdata

Designing the dashboard the profile creates

.agents/skills/project-prometheus-profiles/chart-design.md

2.11.032.7 KB
Original Source

Designing the dashboard the profile creates

A profile is an information architecture. The YAML hierarchy becomes navigation, labels become identity/filtering/dimensions, chart composition determines what can be compared, and the UI determines presentation order. Design the profile-owned UX consequences deliberately.

Contents

Start with operator expectations

Begin with:

What do operators expect to see about this entity, module, or operation?

Then ask the diagnostic questions at that owner:

  • Is it doing useful work?
  • Are its outcomes failing or degraded?
  • Where does its work wait?
  • Which resource or limit constrains it?
  • Is the condition transient, growing, or stuck?

For every proposed chart, be able to finish this sentence:

When an operator sees this chart change, it helps answer ___ by comparing ___.

If the sentence has several unrelated answers, split or redesign the chart.

Make flat metrics self-describing

Prometheus exposition provides flat labeled time series. A separately authored dashboard can supply relationships by placing particular queries and panels together.

Netdata's generic dashboards instead organize charts from the metadata created by the profile:

  • families are navigation;
  • contexts are semantic chart types;
  • instances are monitored entities;
  • dimensions are bounded comparisons;
  • labels are filtering/explanatory metadata.

The profile must therefore make the relationships reusable and self-describing. It is not enough that one reviewer can infer a dashboard from metric names.

This difference is especially important for common protocols. Two metrics can both be HTTP request rates without belonging to one Netdata context:

  • If endpoint/module/path X and Y are distinct operator-managed entities or processing functions, give them distinct entity instances or semantic families.
  • If method and status are bounded aspects of one endpoint and one operator question, they can be chart dimensions.
  • Do not mix X and Y merely because both render as requests/s; the shared unit permits comparison only after ownership and entity scope agree.

Model entities, modules, and operations

Operators commonly understand an application through a combination of three structures:

  1. Entities and containment: service → server → database → table → index, or cluster → workload → pod → container.
  2. Modules and capabilities: listener, scheduler, cache, router, storage engine, backend pool, executor.
  3. Operations and processing stages: receive → authenticate → queue → process → persist → respond, or accept → route → forward → retry.

NIDL families should express those structures in the vocabulary operators use. For each proposed first- or second-level family, ask:

  • What entity, module, operation, or stage does it represent?
  • What work/state does it own?
  • What does it receive, process, store, or hand off?
  • What condition can it cause?
  • At which entity level should dashboard filtering apply?

Then place workload, outcomes, errors, latency, saturation, capacity, and resource pressure under that owner. These signal roles form a holistic diagnostic view of the owner; they are not normally application-wide navigation.

Use the closest defensible owner:

  • stage-local signals stay with the stage that produces, consumes, queues, or controls them;
  • end-to-end signals stay with the operation or nearest lifecycle owner they span;
  • requested limits/options stay with admission or the operation they shape;
  • resource pressure stays with its consumer unless the resource is itself an operator-managed entity/subsystem; and
  • the same data object can belong to different stages when it is received, cached, transformed, persisted, or emitted.

A small service-impact overview can answer “are users affected?” across owners. It must not become a dumping ground for every workload, latency, error, or resource metric.

Labels help reveal entities, but do not define them automatically. Check source semantics, observed combinations, cardinality, and stability. A database, backend, pod, or handler label may be entity identity. A status, method, reason, or operation label may be a bounded dimension. The name alone is insufficient.

Learn from common operator models

These examples illustrate the reasoning; they are not fixed trees.

Database server

Operators may need a containment lattice such as:

text
server:   {server}
database: {server, database}
table:    {server, database, table}
index:    {server, database, table, index}

Place each signal at the entity that owns it:

  • server-wide connections, process resources, and a shared buffer pool remain server-level;
  • per-database transactions, locks, failures, and cache behavior belong to the database when the exporter actually labels them that way;
  • table reads, writes, scans, latency, and storage belong to the table;
  • index lookups, misses, maintenance, and size belong to the index.

“Connections” or “cache” does not have one universal level. Ownership and labels decide whether it is server-wide, database-specific, or table-specific.

Proxy

Operators may think in terms of:

text
proxy service → frontend/listener → route or backend pool → backend server
  • Frontend acceptance, client-facing outcomes, and client latency belong near the frontend/listener.
  • Routing decisions, misses, and reroutes belong to routing.
  • Upstream connections, retries, failures, and response latency belong to the backend pool/server that causes them.

A global Latency family mixing client, routing, connection, and upstream timings destroys that causal path even though every chart uses seconds.

Kubernetes-hosted microservice

Two structures can coexist:

text
platform entities: cluster → namespace → workload → pod → container
application flow:  API → queue → worker → datastore
  • Container CPU/memory belongs to the container or workload entity represented by the labels.
  • API request outcomes and latency belong to the API operation/module.
  • Queue depth and wait time belong to the queue.
  • Worker throughput, retries, and processing latency belong to the worker stage.
  • Datastore calls belong to the datastore/client operation, not a global application latency section.

Pod names may be short-lived while workload identity is stable. Do not turn every Kubernetes label into chart identity; choose the entity level operators expect to filter and whose history should remain meaningful.

Build a monotonic identity lattice

Treat each entity type as an identity-label set. A narrower descendant retains the labels that identify its parent and adds only what identifies the narrower entity. For example:

text
service:  {}
server:   {server}
database: {server, database}
table:    {server, database, table}
index:    {server, database, table, index}

The names are illustrative; derive the real entity types and stable labels from the exporter. The structural property is the subset relationship:

text
parent identity ⊆ child identity

This matters because a filter applied at the parent should continue to select every descendant. If a database child drops server, or an index child drops database, the hierarchy may look nested while filtering no longer describes one coherent entity path.

Each displayed leaf family should contain charts for one effective entity identity. Do not place a global chart, a per-server chart, and a per-table chart under the same displayed leaf merely because they explain the same capability. Move them to explicit entity-level branches, or choose a common identity only when every selected series truly carries it.

Group chart_defaults.instances.by_labels can make an entity boundary explicit and reduce repetition. A child override replaces rather than extends the parent default, so repeat the parent labels when adding a narrower entity label. Never add a label that the selected series does not actually carry merely to make the lattice look consistent.

Shared identity across sibling families

Sibling second-level families often participate in one section-wide filter. In that case their charts should share the parent entity identity labels. If one subtree is per-database and another is global, a database filter cannot behave uniformly.

That mismatch can be intentional. Resolve it explicitly:

  • move global charts to a service-level sibling;
  • add the common parent identity when the series truly carries it;
  • nest table-specific charts under the database-level section;
  • retain the mismatch only when the UI boundary is meant to change entity level, and explain it.

The validator fails when a selected writer series lacks a label required by the chart's effective instance identity: that chart cannot materialize at the declared entity level. It also reports mixed leaf identities, descendant loss of an explicitly declared parent identity, and siblings with no common explicit identity as review prompts. The tool can prove structural contradictions; it cannot decide which valid entity boundary matches the operator's mental model.

Assign labels by role

A label can play several roles, but each role has different UX and cardinality:

RoleMechanismEffectGood candidates
Entity identityinstances.by_labelscreates separate chart instances and filter identityserver, database, table, device
Ownership pathpromoted label and, when needed for uniqueness, instances.by_labelsplaces the entity in its containing hierarchy without changing its leaf typecluster, database, pool, namespace
Comparable aspectname_from_label or selector-specific static namescreates dimensions within one chartstatus class, method, bounded phase
Descriptive detaillabel_promotionadds filter/group metadata without splitting identityserial number, display name, version
Routing constraintselector label matchincludes only the intended seriesrole, operation, state
Collection transformationrelabelingchanges/drops names or labels before writingnormalization backed by a clear contract

Ask what a label means, not merely whether it exists.

Identity labels

Use the smallest stable set that uniquely identifies the entity the operator selects. Too few labels merge different entities. Too many labels fragment one entity whenever incidental metadata changes.

Distinguish the semantic instance type from the complete key needed to identify one instance. A database label remains the owner of a table, but {database, table} may both be required in instances.by_labels when table names repeat across databases. The context still represents table instances; the ownership path makes each table unambiguous and preserves the identity lattice.

instances.by_labels: ['*'] is open-ended: any future exporter label can change identity and multiply charts. Use it only when the exporter contract itself says all labels are identity. Explicit labels are easier to reason about and keep stable.

Dimension labels

A dimension label should have bounded values that are useful side by side. HTTP status class can be a good comparison; raw URL, request ID, user ID, or arbitrary error text usually creates an unreadable and unbounded chart.

If the values identify independently filterable entities, they normally belong in instances. If the values are aspects of one entity, dimensions are usually better. This distinction preserves both readability and filtering.

For example, raw disk-throughput series may identify {disk, operation}. If disk is the monitored entity and operation is the bounded read/write aspect, use one chart instance per disk and one dimension per operation. The dashboard then counts disks, not disk-operation pairs.

Distinguish a closed enum from a configuration-dependent set:

  • Use explicit selector/value dimensions when authoritative exporter semantics define a stable closed set and deliberate names/order matter.
  • Use name_from_label when values can grow with configuration, workload, or exporter version.
  • One dump showing two values does not prove a two-value enum. Hard-coding those values silently drops future values into autogen/unmatched behavior.

Optional dimension labels

An exporter may make a valid dimension label optional through configuration or version. Establish that optionality from authoritative configuration or source; name_from_label by itself is not evidence that the label can be absent. The label MAY still be a dimension when it remains a bounded aspect of the same entity, but it MUST NOT be the only route by which that metric can materialize in the chart.

Preserve one context and instance type with two mutually exclusive routes:

yaml
dimensions:
  - selector: 'disk_io_bytes_total{operation=~".*[^[:space:]].*"}'
    name_from_label: operation
  - selector: 'disk_io_bytes_total{operation!~".*[^[:space:]].*"}'
    name: unclassified
  • The positive route creates the dynamic dimensions when the label is present and contains at least one non-whitespace character.
  • The negative route catches missing, empty, or whitespace-only values and creates one fixed fallback dimension in the same chart.
  • The fallback name MUST describe missing classification, not total: in a mixed population it contains only the unlabeled subset.
  • The fallback name SHOULD be outside the label's authoritative value domain. A dynamic/static name collision maps both populations to one dimension.
  • Do not use .+ for the positive route. Chartengine trims and rejects whitespace-only dynamic names after selector matching, so that matcher would select a series that neither route can materialize.
  • Do not add a broad static selector beside the dynamic selector in the same chart. Labeled series would match both and the chart would double-count them.

If the optional label identifies an entity rather than an aspect, this pattern does not reconstruct that entity. Use instances.optional_by_labels when the same source legitimately has a base identity without the label and a refined, operator-useful identity when it is present. One series routes to one identity; do not duplicate base and detailed views merely to expose both levels. If the missing label would collapse non-additive gauge states, preserve a complete identity or use a source-defined aggregate; optional identity does not make an invalid reduction correct.

The design review must account for every observed label key, including labels intentionally aggregated away. Aggregation is a decision because it removes the ability to compare that label in the dashboard.

The validator warns when selected series carry a label that the chart does not use for identity, dimension naming, promoted metadata, selector routing, or an explicit by_labels exclusion. One observed value does not make the warning irrelevant: a later second value may be a distinct entity that would be merged. Resolve the warning by reasoning about the exporter's label contract and expected cardinality. Do not mechanically promote every label or add it to identity; intentional aggregation is valid when the lost comparison is stated and correct for the operator story.

Aggregation when labels are omitted

instances.by_labels and instances.optional_by_labels select the labels that form chart-instance identity. They do not relabel source series or delete other labels. When multiple selected series render to the same chart and dimension, the chart's aggregation reducer combines their raw values.

Use that behavior deliberately:

  • Counters and histogram components: sum is the correct rollup across disjoint populations of the same counted or measured quantity. Keep the chart algorithm incremental by omitting the override; the Netdata Agent, not the collector or profile, computes rates and handles counter resets.
  • Point-in-time gauges: sum is correct only for a source-defined additive stock. Use avg only when an unweighted typical value is meaningful, and min/max only for an extremum or deliberate state reduction. Otherwise preserve the complete semantic identity.
  • Status-like gauges: min and max can answer “all”/“any” questions for source-proven 0/1 states. Status categories themselves remain dimensions.
  • Summary quantiles: No supported reducer creates a global quantile from per-source quantiles. Preserve the source identity.
  • No collector-side deltas: A profile or collector MUST NOT precompute counter deltas, rates, or reset adjustments to make aggregation appear safe. That duplicates and conflicts with the Agent's incremental algorithm.
  • No meaning-based censorship: Whether a label names a user, tenant, API key, endpoint, route, or another deployment-owned entity does not decide whether it is retained. Source semantics, mathematical correctness, operator usefulness, stability, cardinality, lifecycle, and resource cost do.

For exporters with configurable label contracts:

  1. Use required identity for labels that must exist to define the chosen entity.
  2. Use optional identity when one view should refine from a base chart to a stable per-value chart only when the label exists.
  3. When an optional label is a bounded dimension, use complementary dynamic and unclassified routes in the same chart.
  4. Keep non-additive gauges at their complete emitted identity when no reducer preserves meaning.
  5. Do not duplicate a coarser chart solely because Netdata can already obtain that view by grouping the finer chart.

Record every intentional aggregation in the operator model and prove it with a fixture containing at least two series that differ only on an omitted label. The expected chart must contain the selected reducer's result. Also prove representative gauges remain separate when any identity label differs.

Promotion is metadata, not identity. Use it for stable attributes that help the operator filter or explain a chart. It does not make a missing identity label available, and it cannot recover labels from writer-skipped _info metrics.

Ownership and descriptive-detail labels are valid promoted metadata only when they are functionally stable for the chosen instance. If one purported serial number, database owner, or display name varies across the dimensions of one instance, the source evidence contradicts that classification: refine the identity/aspect model instead of promoting an arbitrary value.

Compose charts around one comparison

Metrics belong together only when all of these are true:

  1. They answer one operator question.
  2. Their complete rendered units mean the same thing, including the counted or measured object.
  3. Their chart algorithm is compatible.
  4. Their scale allows each signal to remain visible.
  5. Their dimension cardinality remains readable.

Shared units are necessary, not sufficient. Requests/s and errors/s are related, but a tiny error rate can disappear under large traffic. Used bytes and queue bytes are both bytes, but they may describe unrelated resources.

Do not use an umbrella noun to make unlike units appear compatible. Batches/s, records/s, retries/s, and bytes/s do not become a common events/s or operations/s axis. The noun identifies the observation population and is part of dimensional correctness. If the exact noun is unknown, research it; if the nouns differ, split the chart even when the counters advance together.

An operation counter is not the same unit as a counter of objects produced by that operation. Combining them under the object's unit silently assumes one object per operation; combining them under events/s hides the same false assumption. Keep both near their causal owner, but on honest axes.

A useful composition is often a bounded breakdown of one whole: response classes, cache hit/miss outcomes, pipeline phases, input/output directions, or queue states. The comparison should reduce diagnostic time rather than save chart count.

Do not optimize for few charts. Optimize for a coherent operator scan with no important signal hidden.

Coverage is not one metric per chart

Objective coverage asks whether every writer-surviving flattened series reaches an authored dimension. It does not prescribe a chart count or a chart boundary.

  • Route every gauge, counter, bucket, quantile, count, and sum role.
  • Compose compatible same-unit roles from related metric families when one chart creates a useful comparison.
  • Split when the operator question, event population, rendered meaning, scale, algorithm, or cardinality differs.
  • Do not create generic metric-type sections merely to prove that each histogram produced separate bucket/count/sum charts.

This separation preserves both completeness and design judgment: coverage prevents silent evidence loss, while composition determines whether the dashboard teaches the application and shortens diagnosis.

Before merging dimensions, state in one sentence what comparison the chart answers and what one unit of every dimension represents. A long legend can be correct, but it is a warning sign when the sentence needs several unrelated clauses, when actual work is mixed with requested limits, or when a total is presented as one of its own phases. Move secondary comparisons into the causal family that owns them rather than turning Overview into a coverage inventory.

Resolve common design conflicts

Split them into adjacent charts under the same family and order. Relationship is preserved through navigation and proximity; visibility is preserved through separate axes.

Current-versus-capacity pairs deserve the same check. Open descriptors and descriptor limit share units and one resource story, but a very large limit can flatten all movement in the open count. If the schema cannot compute a useful utilization ratio, separate current and capacity charts rather than sacrificing the operational signal. The validator's observed-scale warning is evidence from one dump, not proof of every deployment.

“These values are conventionally shown together” does not resolve a scale warning. Relatedness explains why an operator may compare the signals; it does not make the smaller line visible. Keeping the shared chart requires evidence that the UI still exposes meaningful movement or that the deployment range keeps the ratio readable. Otherwise use adjacent charts so the relationship remains clear without flattening the actionable signal.

One signal spans several capabilities

Do not solve overlap by creating application-wide Latency, Throughput, Parameters, or Resources drawers. Place a stage-specific signal with that stage. Place a true end-to-end signal with the nearest common lifecycle owner. When one chart deliberately compares stages, put it at their nearest common domain owner and say what the comparison diagnoses.

Why: the operator usually starts from the affected operation, then follows its causes. A global signal-role branch reverses that reasoning and forces the operator to remember which lines belong to which stage.

Same metric, multiple entity levels

Do not force one identity policy across all uses. Place charts under the entity level they represent and scope defaults narrowly. A root chart_defaults identity is harmful when descendants include global and per-entity series.

Rich label, unbounded cardinality

Prefer a bounded aggregation already exported. Otherwise choose a truthful chart reducer or preserve identity when the detail is operator-useful. If the source-owned family has no useful bounded view, exclude it through source-backed profile relabeling and record the lost question. Job relabel/drop is reserved for deployment policy. Do not turn arbitrary values into dimensions and rely on lifecycle caps to make the chart usable.

Distribution shape versus volume

Keep histogram heatmap/summary quantiles close to count and semantic sum charts, but do not combine different units in one chart. Distribution answers “how is latency/size shaped?”; count answers “how much work?”; sum rate may answer “how much resource/throughput?”

Errors are both outcomes and diagnostics

A response-class chart can show error proportion, while a focused error-rate chart can keep rare failures visible. This is not duplication when the charts answer different questions. Avoid duplicating identical visualizations merely under multiple families.

Job policy versus profile coverage

A profile cannot chart a series filtered or rejected before metrix ingestion. Decide the job policy and profile together. An exclusion is correct only when losing that signal is intentional and explained; metrics: declaration is never a drop or coverage mechanism.

Choose chart types for visual meaning

Use chart type as visual semantics, not decoration:

  • line: rates, counts, latency/quantiles, ratios, state, and most time-varying signals. Lines preserve independent trends and crossings.
  • area: an intentionally filled magnitude where the area itself helps answer the operator question, including a deliberate mirrored in/out view.
  • stacked: dimensions that form an exact disjoint, exhaustive, additive partition of one whole. Additive categories are not automatically a useful composition view.
  • heatmap: histogram bucket distribution. Bucket charts are forced to heatmap by the compiler; omit the redundant type in stock YAML.

Use line by default. Units alone cannot establish whether fill or composition is truthful: request outcomes may form a valid partition while unrelated rates may share units. The semantic design must state the intended relationship. The validator warns on every explicit area/stacked choice so that relationship is reviewed rather than guessed from unit words.

A negative multiplier is a presentation convention, not negative data. Use it only when the below-zero direction communicates a real pair such as inbound and outbound traffic, and make the title/units clear.

Make titles, units, and conversions honest

A title is a promise about what the chart computes.

  • “by database” requires actual per-database instances or dimensions.
  • “by endpoint,” “per backend,” and similar titles require an instance label, dynamic dimension, or selector split that preserves that entity/aspect. Merely carrying the label in the source is insufficient: if it is used by none of those mechanisms, chartengine aggregates it away.
  • “average latency” requires an average; _sum rate alone is not an average.
  • “requests” versus “requests/s” must match the algorithm.
  • “utilization” should state percentage/ratio semantics rather than expose a raw limit or count.

Keep unit strings consistent within a chart. Use integer multiplier/divisor for supported conversions and verify dimensional analysis. For example, bytes multiplied by 8 and divided by 1000 becomes kilobits; a counter with incremental then becomes kilobits/s.

Perform the same dimensional analysis for time counters, then verify when the source contributes the time. Process CPU seconds accumulate while the process runs and therefore become used CPU cores. Histogram duration sums usually increase only when observations complete; their incremental value is completed work time in seconds/s, not the live population that was active during the interval. Use a current Gauge for titles such as “in progress” or “in flight.”

Avoid conversions chosen only to make lines look similar. If two dimensions need unrelated scale manipulation, they probably do not belong on one axis.

Order the operator journey

Prometheus profiles MUST omit priority. Every chart receives the same runtime default, and the UI owns presentation sorting. YAML order is useful for human review but does not promise runtime or UI order.

At the application level, order domain capabilities by the operator's causal journey. Within each capability, a useful local order is health/workload → failures/latency → queueing/resource pressure → detailed internals. Do not turn that local diagnostic sequence into application-wide Workload, Latency, or Resources branches.

Protect identity and cardinality

Context, chart ID, instance labels, and dimension names become durable runtime identity. Changes can create parallel charts and leave old metadata/history to expire.

Before choosing dynamic identity:

  • inventory observed cardinality and label combinations;
  • check whether label values are stable across scrapes/restarts;
  • consider optional future labels documented by the exporter;
  • validate values that normalize similarly, such as punctuation variants;
  • use lifecycle only with an understood loss/expiry policy.

A schema-valid collision can silently suppress or merge charts. The objective validator checks observed cross-template IDs, same-template instance collapse, lifecycle dimension loss, and public-wire normalization of chart IDs, contexts, and dimensions. Unseen future values remain a review risk.

Recognize bad patterns by their effects

  • Families named Gauges/Counters/Histograms: expose transport mechanics and force operators to reconstruct the application.
  • Application-wide Workload/Latency/Errors/Distributions/Parameters families: expose diagnostic lenses or observation form instead of the capability that owns the signal, forcing operators to join one causal story across multiple branches.
  • Repeated identical sibling family names: render as the same navigation path, so repetition does not express distinct semantic owners. Give the branches domain names or nest them under their actual capability.
  • One metric per chart by default: loses useful comparison and creates a long dashboard; separate only when meaning/scale requires it.
  • Everything on one chart: hides smaller signals, mixes questions, and produces dimension overload.
  • Every label in identity: creates unstable cardinality and fragmented history.
  • Every label as a dimension: turns entity cardinality into an unreadable legend.
  • Broad deny list: makes validation pass by deleting diagnostic surface instead of designing it.
  • Authored chart priority: claims UI ordering authority the Prometheus profile does not own and creates unnecessary maintenance.
  • Titles derived mechanically from metric names: repeat exporter syntax instead of explaining operational meaning.
  • Passing the validator as definition of quality: proves runtime behavior for evidence, not the semantic usefulness of the dashboard.

Semantic review

After objective validation passes, review the dashboard design:

  • Does the first screen answer the application's most urgent operator questions?
  • Does navigation express the entities/containment, modules/capabilities, and operations/processing stages that operators actually use?
  • For each first- and second-level family, is the answer to “what do operators expect to see about this?” coherent without relying on a shared signal role, metric form, parameter kind, or unit?
  • Does each owner keep its available workload, outcomes, errors, latency, saturation, capacity, and resources close enough for holistic diagnosis?
  • Is each context homogeneous in entity type?
  • Does each displayed leaf family contain one effective entity identity?
  • Does descendant identity retain its parent labels?
  • Are identity labels minimal, stable, and filterable?
  • Are dimensions bounded comparable aspects?
  • Do sibling families share the intended parent identity?
  • Does every chart satisfy question, units, algorithm, scale, and cardinality?
  • Does every shared axis preserve the exact counted/measured noun, rather than hiding unlike objects under events, operations, items, or observations?
  • Are rare failures visible beside high-volume traffic?
  • Are titles and units mathematically true?
  • Is each exclusion justified by the operator question lost?
  • Is file order coherent for review without being presented as UI ordering?

Warnings demand reasoning, not mechanical edits. Record why the design is intentional or change it when the UX consequence is wrong.