feat(dashboard_gen): make unit an enum

This commit is contained in:
Daniil Polyakov 2026-07-28 02:15:43 +03:00
parent ea36fa2e93
commit ec53979442
5 changed files with 130 additions and 16 deletions

View File

@ -10,7 +10,7 @@
)]
use dashboard_gen::{
Color, Dashboard, FieldOverride, Panel, Target, avg, percentiles, percentiles_labeled,
Color, Dashboard, FieldOverride, Panel, Target, Unit, avg, percentiles, percentiles_labeled,
rate_per_min,
};
use json_pretty_compact::PrettyCompactFormatter;
@ -26,7 +26,7 @@ fn sequencer_dashboard() -> Dashboard {
[
Panel::stat("Chain height")
.width(6)
.unit("short")
.unit(Unit::Short)
.decimals(0)
.color(Color::fixed("blue"))
.target(
@ -34,7 +34,7 @@ fn sequencer_dashboard() -> Dashboard {
),
Panel::timeseries("Block production rate")
.width(18)
.unit("short")
.unit(Unit::Short)
.target(rate_per_min(
sequencer_core_metrics::names::BLOCK_COUNT,
"blocks/min",
@ -45,7 +45,7 @@ fn sequencer_dashboard() -> Dashboard {
9,
[Panel::timeseries("Block creation time")
.width(24)
.unit("s")
.unit(Unit::Seconds)
.targets(percentiles(
sequencer_core_metrics::names::BLOCK_CREATION_TIME,
PERCENTILES,
@ -62,7 +62,7 @@ fn sequencer_dashboard() -> Dashboard {
[
Panel::timeseries("Transaction application time")
.width(12)
.unit("s")
.unit(Unit::Seconds)
.targets(percentiles_labeled(
sequencer_core_metrics::names::MEMPOOL_TRANSACTION_APPLICATION_TIME,
PERCENTILES,
@ -70,7 +70,7 @@ fn sequencer_dashboard() -> Dashboard {
)),
Panel::timeseries("Mempool size")
.width(12)
.unit("short")
.unit(Unit::Short)
.span_nulls()
.target(
Target::new(sequencer_core_metrics::names::MEMPOOL_SIZE)
@ -83,7 +83,7 @@ fn sequencer_dashboard() -> Dashboard {
[
Panel::timeseries("Transactions per block")
.width(12)
.unit("short")
.unit(Unit::Short)
.targets(percentiles(
sequencer_core_metrics::names::TRANSACTIONS_PER_BLOCK,
PERCENTILES,
@ -96,7 +96,7 @@ fn sequencer_dashboard() -> Dashboard {
),
Panel::timeseries("Transaction throughput (per minute)")
.width(12)
.unit("short")
.unit(Unit::Short)
.target(rate_per_min(
sequencer_service_metrics::names::SUBMITTED_TRANSACTION_COUNT,
"submitted",

View File

@ -10,6 +10,7 @@
use std::fmt::Write as _;
use crate::{
Unit,
input::PanelInput,
schema::{
AxisPlacement, Color, GradientMode, LineInterpolation, PanelType, ShowPoints, StackingMode,
@ -38,7 +39,11 @@ fn panel_expr_inner(panel: &PanelInput) -> Result<String, std::fmt::Error> {
let defaults = &panel.field_config.defaults;
// `short` is the builder's own default unit, so it round-trips without a call.
if let Some(unit) = defaults.unit.as_deref().filter(|u| *u != "short") {
write!(expr, "\n .unit({unit:?})")?;
write!(
expr,
"\n .unit({})",
Unit::from_id(unit).to_rust_source()
)?;
}
if let Some(decimals) = defaults.decimals {
write!(expr, "\n .decimals({decimals})")?;

View File

@ -22,11 +22,13 @@ use schema::{
Stacking, StatColorMode, StatOptions, TimeRange, TimeSeriesOptions, Tooltip, TooltipMode,
};
use serde::Serialize;
pub use unit::Unit;
mod codegen;
mod input;
mod schema;
mod styling;
mod unit;
/// Datasource uid every panel/target points at. Dashboards stay portable across
/// environments because they reference the datasource by this stable uid rather
@ -114,7 +116,7 @@ pub struct Panel {
kind: Kind,
targets: Vec<Target>,
width: u32,
unit: Option<String>,
unit: Option<Unit>,
decimals: Option<u32>,
color: Option<Color>,
span_nulls: bool,
@ -168,8 +170,8 @@ impl Panel {
}
#[must_use]
pub fn unit(mut self, unit: impl Into<String>) -> Self {
self.unit = Some(unit.into());
pub fn unit(mut self, unit: Unit) -> Self {
self.unit = Some(unit);
self
}
@ -439,8 +441,8 @@ pub fn rate_per_min(metric: &str, legend: &str) -> Target {
Target::new(format!("rate({metric}[1m]) * 60")).legend(legend)
}
fn default_unit() -> String {
"short".to_owned()
const fn default_unit() -> Unit {
Unit::Short
}
fn ref_letter(index: usize) -> String {

View File

@ -8,7 +8,7 @@
use serde::{Deserialize, Serialize};
use crate::{DATASOURCE_UID, FieldOverride, Target};
use crate::{DATASOURCE_UID, FieldOverride, Target, unit::Unit};
#[derive(Clone, Copy, Serialize)]
#[serde(rename_all = "lowercase")]
@ -245,7 +245,7 @@ pub struct Defaults {
pub color: Option<Color>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub custom: Option<Custom>,
pub unit: String,
pub unit: Unit,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub decimals: Option<u32>,
}

View File

@ -0,0 +1,107 @@
//! Panel value units.
//!
//! Grafana identifies a field's unit by a short id string (`"s"`, `"bytes"`,
//! `"reqps"`, …). We model the handful we actually use as named variants and
//! fall back to [`Unit::custom`] for anything else, so the value always
//! serializes to the exact id Grafana expects.
use serde::{Serialize, Serializer};
/// A panel value unit. Serializes to Grafana's unit id string.
///
/// Only the most common units are named; [`Unit::custom`] carries any other
/// Grafana unit id verbatim.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Unit {
/// Plain number, SI-abbreviated (`short`). Grafana's default.
Short,
/// Percentage on a 0100 scale (`percent`).
Percent,
/// Percentage on a 0.01.0 scale (`percentunit`).
PercentUnit,
/// Seconds (`s`).
Seconds,
/// Milliseconds (`ms`).
Milliseconds,
/// Nanoseconds (`ns`).
Nanoseconds,
/// Bytes, IEC/binary (`bytes`).
Bytes,
/// Bytes per second, SI (`Bps`).
BytesPerSec,
/// Requests per second (`reqps`).
RequestsPerSec,
/// Operations per second (`ops`).
OpsPerSec,
/// Any other Grafana unit id, kept verbatim.
Custom(String),
}
impl Unit {
/// Wrap an arbitrary Grafana unit id (e.g. `"dtdurationms"`, `"celsius"`).
#[must_use]
pub fn custom(id: impl Into<String>) -> Self {
Self::Custom(id.into())
}
/// The Grafana unit id this value serializes to.
fn as_id(&self) -> &str {
match self {
Self::Short => "short",
Self::Percent => "percent",
Self::PercentUnit => "percentunit",
Self::Seconds => "s",
Self::Milliseconds => "ms",
Self::Nanoseconds => "ns",
Self::Bytes => "bytes",
Self::BytesPerSec => "Bps",
Self::RequestsPerSec => "reqps",
Self::OpsPerSec => "ops",
Self::Custom(id) => id,
}
}
/// Reverse of [`Self::as_id`]: map a Grafana unit id back to a `Unit`,
/// falling back to [`Self::Custom`] for ids we don't name. Used by the
/// panel→Rust transpiler.
#[must_use]
pub(crate) fn from_id(id: &str) -> Self {
match id {
"short" => Self::Short,
"percent" => Self::Percent,
"percentunit" => Self::PercentUnit,
"s" => Self::Seconds,
"ms" => Self::Milliseconds,
"ns" => Self::Nanoseconds,
"bytes" => Self::Bytes,
"Bps" => Self::BytesPerSec,
"reqps" => Self::RequestsPerSec,
"ops" => Self::OpsPerSec,
other => Self::custom(other),
}
}
/// The Rust builder expression that reconstructs this unit, for codegen.
#[must_use]
pub(crate) fn to_rust_source(&self) -> String {
match self {
Self::Custom(id) => format!("Unit::custom({id:?})"),
named @ (Self::Short
| Self::Percent
| Self::PercentUnit
| Self::Seconds
| Self::Milliseconds
| Self::Nanoseconds
| Self::Bytes
| Self::BytesPerSec
| Self::RequestsPerSec
| Self::OpsPerSec) => format!("Unit::{named:?}"),
}
}
}
impl Serialize for Unit {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_id())
}
}