diff --git a/Cargo.lock b/Cargo.lock index 6eb08255..86d8102a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2296,6 +2296,7 @@ dependencies = [ name = "dashboard_gen" version = "0.1.0" dependencies = [ + "clap", "json-pretty-compact", "sequencer_core_metrics", "sequencer_service_metrics", diff --git a/Justfile b/Justfile index 021f6774..ed124de1 100644 --- a/Justfile +++ b/Justfile @@ -57,14 +57,8 @@ regenerate-test-fixture: # (tools/dashboard_gen) and commit the result. CI checks these are up to date. regenerate-dashboards: @echo "📊 Regenerating Grafana dashboards" - @cargo build -q -p dashboard_gen --bin gen_sequencer_dashboard - @cargo run -q -p dashboard_gen --bin gen_sequencer_dashboard > monitoring/grafana/dashboards/sequencer.json - -# Transpile a single Grafana panel JSON (stdin) — Inspect → Panel JSON — into a -# Rust builder expression (stdout), omitting Grafana defaults. Paste into a row. -# Usage: `just panel-to-rust < panel.json`. -panel-to-rust: - @cargo run -q -p dashboard_gen --bin panel_json_to_rust + @cargo build -q -p dashboard_gen + @cargo run -q -p dashboard_gen -- sequencer > monitoring/grafana/dashboards/sequencer.json # Run criterion benches: fast crypto primitives, then the slow PPE verify (real proving setup). bench: diff --git a/tools/dashboard_gen/Cargo.toml b/tools/dashboard_gen/Cargo.toml index 1a167db6..4bc73e86 100644 --- a/tools/dashboard_gen/Cargo.toml +++ b/tools/dashboard_gen/Cargo.toml @@ -11,6 +11,7 @@ workspace = true sequencer_core_metrics.workspace = true sequencer_service_metrics.workspace = true +clap = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive", "alloc"] } serde_json.workspace = true json-pretty-compact = "0.1.2" diff --git a/tools/dashboard_gen/src/bin/panel_json_to_rust.rs b/tools/dashboard_gen/src/bin/panel_json_to_rust.rs deleted file mode 100644 index 9e10f046..00000000 --- a/tools/dashboard_gen/src/bin/panel_json_to_rust.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! Reads a single Grafana panel JSON on stdin (Grafana → panel menu → Inspect → -//! Panel JSON) and prints the Rust `Panel::…` builder expression that rebuilds -//! it through `dashboard_gen`, omitting values Grafana supplies by default. -//! -//! Paste the result into a dashboard's `.row(…)` and run `cargo fmt`. - -#![expect( - clippy::print_stderr, - reason = "CLI tool: diagnostics on stderr are the deliverable" -)] -#![expect( - clippy::non_ascii_literal, - reason = "help text mirrors Grafana's `Inspect → Panel JSON` menu path" -)] - -use std::{ - io::{self, Read as _, Write as _}, - process::ExitCode, -}; - -fn main() -> ExitCode { - let mut input = String::new(); - if let Err(err) = io::stdin().read_to_string(&mut input) { - eprintln!("error: failed to read panel JSON from stdin: {err}"); - return ExitCode::FAILURE; - } - - match dashboard_gen::panel_to_rust_source(&input) { - Ok(source) => { - if let Err(err) = io::stdout().write_all(source.as_bytes()) { - eprintln!("error: failed to write generated source to stdout: {err}"); - return ExitCode::FAILURE; - } - ExitCode::SUCCESS - } - Err(err) => { - eprintln!("error: could not parse panel JSON: {err}"); - eprintln!( - "hint: paste one panel (Inspect → Panel JSON);\ - only stat and timeseries are supported." - ); - eprintln!( - "hint: this tool supports only the subset of Grafana's panel JSON, you might need \ - to manually implement support for new fields." - ); - - ExitCode::FAILURE - } - } -} diff --git a/tools/dashboard_gen/src/codegen.rs b/tools/dashboard_gen/src/codegen.rs deleted file mode 100644 index 4874f053..00000000 --- a/tools/dashboard_gen/src/codegen.rs +++ /dev/null @@ -1,242 +0,0 @@ -//! Reverse of the builder: turn a single parsed panel back into the Rust -//! `Panel::…` builder expression, omitting values Grafana supplies by default. -//! Backs the `panel_json_to_rust` binary. -//! -//! Input is parsed leniently (see [`crate::input`]) so a raw Grafana panel -//! export — full of fields and vocabularies we don't model — still works; -//! anything unrecognized is dropped. The emitted expression is valid but only -//! lightly formatted, so `cargo fmt` re-indents it once it lands in a file. - -use std::fmt::Write as _; - -use crate::{ - DEFAULT_FILL_OPACITY, Unit, - input::{Defaults, PanelInput}, - schema::{ - AxisPlacement, Color, GradientMode, LineInterpolation, PanelType, ShowPoints, StackingMode, - ThresholdMode, Thresholds, - }, -}; - -/// Parse one Grafana panel JSON (Inspect → Panel JSON) and emit the Rust -/// builder expression that reproduces it (minus Grafana defaults). -pub fn panel_to_rust_source(json: &str) -> serde_json::Result { - let panel: PanelInput = serde_json::from_str(json)?; - Ok(format!("{}\n", panel_expr(&panel))) -} - -/// A `Panel::…()` expression with one method call per line. -fn panel_expr(panel: &PanelInput) -> String { - panel_expr_inner(panel).expect("writing to a String never fails") -} - -fn panel_expr_inner(panel: &PanelInput) -> Result { - let mut expr = match panel.panel_type { - PanelType::Stat => format!("Panel::stat({:?})", panel.title), - PanelType::Timeseries => format!("Panel::timeseries({:?})", panel.title), - PanelType::Gauge => format!("Panel::gauge({:?})", panel.title), - }; - write!(expr, "\n .width({})", panel.grid_pos.w)?; - - let defaults = &panel.field_config.defaults; - write_defaults(&mut expr, defaults)?; - - if let Some(custom) = &defaults.custom { - // Emitted against the builder's default, not Grafana's (which is 0), so - // a genuinely unfilled panel still round-trips. - if let Some(opacity) = custom.fill_opacity.filter(|&o| o != DEFAULT_FILL_OPACITY) { - write!(expr, "\n .fill_opacity({opacity})")?; - } - if custom.span_nulls == Some(true) { - expr.push_str("\n .span_nulls()"); - } - // Each optional styling field is emitted only when it differs from the - // Grafana default (matching the setters' panic-on-default contract). - if let Some(value) = custom - .line_interpolation - .filter(|&v| v != LineInterpolation::Linear) - { - write!( - expr, - "\n .line_interpolation(LineInterpolation::{})", - line_interp(value) - )?; - } - if let Some(value) = custom.show_points.filter(|&v| v != ShowPoints::Auto) { - write!( - expr, - "\n .show_points(ShowPoints::{})", - show_points(value) - )?; - } - if let Some(value) = custom.gradient_mode.filter(|&v| v != GradientMode::None) { - write!( - expr, - "\n .gradient_mode(GradientMode::{})", - gradient_mode(value) - )?; - } - let stacking = custom.stacking.as_ref().and_then(|s| s.mode); - if let Some(mode) = stacking.filter(|&m| m != StackingMode::None) { - write!( - expr, - "\n .stacking(StackingMode::{})", - stacking_mode(mode) - )?; - } - if let Some(value) = custom.axis_placement.filter(|&v| v != AxisPlacement::Auto) { - write!( - expr, - "\n .axis_placement(AxisPlacement::{})", - axis_placement(value) - )?; - } - if let Some(label) = custom.axis_label.as_deref().filter(|l| !l.is_empty()) { - write!(expr, "\n .axis_label({label:?})")?; - } - } - - for over in &panel.field_config.overrides { - // Only `byName` matchers map to the builder; skip anything else. - let Some(name) = over.matcher.by_name() else { - continue; - }; - let mut calls = String::new(); - for property in &over.properties { - match property.id.as_str() { - "color" => { - if let Ok(Color::Fixed { fixed_color }) = - serde_json::from_value::(property.value.clone()) - { - write!(calls, ".color(Color::fixed({fixed_color:?}))")?; - } - } - "custom.lineStyle" => calls.push_str(".dashed_line()"), - _ => {} // property kind the builder can't express — drop it - } - } - // An override with nothing representable adds no information. - if !calls.is_empty() { - write!( - expr, - "\n .with_override(FieldOverride::by_name({name:?}){calls})" - )?; - } - } - - for target in &panel.targets { - if target.expr.is_empty() { - continue; - } - write!(expr, "\n .target(Target::new({:?})", target.expr)?; - // `__auto` is Grafana's "no explicit legend" sentinel, i.e. the default. - if !target.legend.is_empty() && target.legend != "__auto" { - write!(expr, ".legend({:?})", target.legend)?; - } - expr.push(')'); - } - - Ok(expr) -} - -/// The field-level setters — everything outside the `custom` styling block. -fn write_defaults(expr: &mut String, defaults: &Defaults) -> Result<(), std::fmt::Error> { - // `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::from_id(unit).to_rust_source() - )?; - } - if let Some(decimals) = defaults.decimals { - write!(expr, "\n .decimals({decimals})")?; - } - // Only a fixed color is worth emitting; `palette-classic` is Grafana's default. - if let Some(Color::Fixed { fixed_color }) = &defaults.color { - write!(expr, "\n .color(Color::fixed({fixed_color:?}))")?; - } - if let Some(min) = defaults.min { - write!(expr, "\n .min({min:?})")?; - } - if let Some(max) = defaults.max { - write!(expr, "\n .max({max:?})")?; - } - - let ladder = defaults - .thresholds - .as_ref() - .filter(|thresholds| is_expressible_ladder(thresholds)) - .and_then(|thresholds| thresholds.steps.split_first()); - if let Some((base, steps)) = ladder { - write!(expr, "\n .thresholds(Thresholds::base({:?})", base.color)?; - for step in steps { - // A non-base step without a value is nonsense Grafana wouldn't render. - if let Some(value) = step.value { - write!(expr, ".step({value:?}, {:?})", step.color)?; - } - } - expr.push(')'); - } - - Ok(()) -} - -/// Whether a ladder is worth emitting: `percentage` mode is beyond the builder, -/// and green/red-at-80 is the ladder Grafana attaches to every panel by default. -fn is_expressible_ladder(thresholds: &Thresholds) -> bool { - if thresholds.mode != ThresholdMode::Absolute { - return false; - } - !matches!( - thresholds.steps.as_slice(), - [base, red] - if base.color == "green" - && base.value.is_none() - && red.color == "red" - && red.value == Some(80.0) - ) -} - -const fn line_interp(value: LineInterpolation) -> &'static str { - match value { - LineInterpolation::Linear => "Linear", - LineInterpolation::Smooth => "Smooth", - LineInterpolation::StepBefore => "StepBefore", - LineInterpolation::StepAfter => "StepAfter", - } -} - -const fn show_points(value: ShowPoints) -> &'static str { - match value { - ShowPoints::Auto => "Auto", - ShowPoints::Never => "Never", - ShowPoints::Always => "Always", - } -} - -const fn gradient_mode(value: GradientMode) -> &'static str { - match value { - GradientMode::None => "None", - GradientMode::Opacity => "Opacity", - GradientMode::Hue => "Hue", - GradientMode::Scheme => "Scheme", - } -} - -const fn stacking_mode(value: StackingMode) -> &'static str { - match value { - StackingMode::None => "None", - StackingMode::Normal => "Normal", - StackingMode::Percent => "Percent", - } -} - -const fn axis_placement(value: AxisPlacement) -> &'static str { - match value { - AxisPlacement::Auto => "Auto", - AxisPlacement::Left => "Left", - AxisPlacement::Right => "Right", - AxisPlacement::Hidden => "Hidden", - } -} diff --git a/tools/dashboard_gen/src/dashboards.rs b/tools/dashboard_gen/src/dashboards.rs new file mode 100644 index 00000000..39ed51ab --- /dev/null +++ b/tools/dashboard_gen/src/dashboards.rs @@ -0,0 +1,3 @@ +//! One module per dashboard, each exposing a `dashboard()` builder. + +pub mod sequencer; diff --git a/tools/dashboard_gen/src/bin/gen_sequencer_dashboard.rs b/tools/dashboard_gen/src/dashboards/sequencer.rs similarity index 92% rename from tools/dashboard_gen/src/bin/gen_sequencer_dashboard.rs rename to tools/dashboard_gen/src/dashboards/sequencer.rs index e2f7df78..3c609841 100644 --- a/tools/dashboard_gen/src/bin/gen_sequencer_dashboard.rs +++ b/tools/dashboard_gen/src/dashboards/sequencer.rs @@ -1,9 +1,6 @@ -//! Generates the sequencer dashboard and prints it to stdout. +//! The sequencer dashboard: chain progress, block timings, mempool and +//! transaction outcomes. -#![expect( - clippy::print_stdout, - reason = "CLI tool: emitting the dashboard JSON on stdout is the deliverable" -)] #![expect( clippy::non_ascii_literal, reason = "legend separators use `·` intentionally, matching the rendered Grafana labels" @@ -13,13 +10,11 @@ use dashboard_gen::{ Color, Dashboard, FieldOverride, GradientMode, Panel, Target, Thresholds, Unit, avg, percentile_legend, percentile_variable, rate_per_min, selected_percentile, }; -use json_pretty_compact::PrettyCompactFormatter; -use serde::Serialize as _; const PERCENTILES: &[u32] = &[50, 90, 95, 99]; const DEFAULT_PERCENTILE: u32 = 95; -fn sequencer_dashboard() -> Dashboard { +pub fn dashboard() -> Dashboard { Dashboard::new("Sequencer", "sequencer") .tag("sequencer") .variable(percentile_variable(PERCENTILES, DEFAULT_PERCENTILE)) @@ -186,15 +181,3 @@ fn sequencer_dashboard() -> Dashboard { ], ) } - -fn main() { - let dashboard = sequencer_dashboard(); - - let formatter = PrettyCompactFormatter::new(); - let mut output = Vec::new(); - let mut ser = serde_json::Serializer::with_formatter(&mut output, formatter); - dashboard.serialize(&mut ser).unwrap(); - - let json = String::from_utf8(output).unwrap(); - println!("{json}"); -} diff --git a/tools/dashboard_gen/src/input.rs b/tools/dashboard_gen/src/input.rs deleted file mode 100644 index 9d69002c..00000000 --- a/tools/dashboard_gen/src/input.rs +++ /dev/null @@ -1,137 +0,0 @@ -//! Lenient, deserialize-only model of a Grafana panel, for the panel→Rust -//! transpiler (`codegen`). -//! -//! A real Grafana export is far wider than what we emit: `unit` may be absent, -//! `options.tooltip.sort` may be `"none"`, overrides carry property types we -//! don't model, and there are dozens of fields we ignore. So this model is -//! deliberately separate from the strict `schema` (sized for *output*): it -//! captures only what codegen reads, makes every field optional, and lets serde -//! drop everything else — including the whole `options` block, which codegen -//! reconstructs from the panel type rather than reading. - -use serde::Deserialize; -use serde_json::Value; - -use crate::schema::{ - AxisPlacement, Color, GradientMode, LineInterpolation, PanelType, ShowPoints, StackingMode, - Thresholds, -}; - -#[derive(Deserialize)] -pub struct PanelInput { - #[serde(rename = "type")] - pub panel_type: PanelType, - #[serde(default)] - pub title: String, - #[serde(rename = "gridPos", default)] - pub grid_pos: GridPos, - #[serde(rename = "fieldConfig", default)] - pub field_config: FieldConfig, - #[serde(default)] - pub targets: Vec, -} - -#[derive(Deserialize, Default)] -pub struct GridPos { - #[serde(default)] - pub w: u32, -} - -#[derive(Deserialize, Default)] -pub struct FieldConfig { - #[serde(default)] - pub defaults: Defaults, - #[serde(default)] - pub overrides: Vec, -} - -#[derive(Deserialize, Default)] -pub struct Defaults { - #[serde(default)] - pub unit: Option, - #[serde(default)] - pub decimals: Option, - #[serde(default)] - pub color: Option, - #[serde(default)] - pub custom: Option, - #[serde(default)] - pub min: Option, - #[serde(default)] - pub max: Option, - #[serde(default)] - pub thresholds: Option, -} - -#[derive(Deserialize, Default)] -#[serde(rename_all = "camelCase")] -pub struct Custom { - #[serde(default)] - pub fill_opacity: Option, - #[serde(default)] - pub span_nulls: Option, - #[serde(default)] - pub line_interpolation: Option, - #[serde(default)] - pub show_points: Option, - #[serde(default)] - pub gradient_mode: Option, - #[serde(default)] - pub stacking: Option, - #[serde(default)] - pub axis_placement: Option, - #[serde(default)] - pub axis_label: Option, -} - -#[derive(Deserialize, Default)] -pub struct Stacking { - #[serde(default)] - pub mode: Option, -} - -#[derive(Deserialize)] -pub struct Override { - #[serde(default)] - pub matcher: Matcher, - #[serde(default)] - pub properties: Vec, -} - -#[derive(Deserialize, Default)] -pub struct Matcher { - #[serde(default)] - pub id: String, - #[serde(default)] - pub options: Value, -} - -impl Matcher { - /// The series name for a `byName` matcher; `None` for matcher kinds the - /// builder can't express (which the caller skips). - pub fn by_name(&self) -> Option<&str> { - if self.id == "byName" { - self.options.as_str() - } else { - None - } - } -} - -/// One override property. `id`/`value` are kept raw so unknown kinds are simply -/// ignored by codegen rather than failing the whole parse. -#[derive(Deserialize)] -pub struct Property { - #[serde(default)] - pub id: String, - #[serde(default)] - pub value: Value, -} - -#[derive(Deserialize, Default)] -pub struct Target { - #[serde(default)] - pub expr: String, - #[serde(rename = "legendFormat", default)] - pub legend: String, -} diff --git a/tools/dashboard_gen/src/lib.rs b/tools/dashboard_gen/src/lib.rs index 5bbaa672..350188a2 100644 --- a/tools/dashboard_gen/src/lib.rs +++ b/tools/dashboard_gen/src/lib.rs @@ -13,7 +13,6 @@ // Styling vocabularies passed to the optional `styling` setters are part of the // public API (and are used by this module's `Panel` fields and `finalize`). -pub use codegen::panel_to_rust_source; pub use schema::{ AxisPlacement, Color, GradientMode, LineInterpolation, ShowPoints, StackingMode, Thresholds, Variable, @@ -28,8 +27,6 @@ use schema::{ use serde::Serialize; pub use unit::Unit; -mod codegen; -mod input; mod schema; mod styling; mod unit; diff --git a/tools/dashboard_gen/src/main.rs b/tools/dashboard_gen/src/main.rs new file mode 100644 index 00000000..b428817d --- /dev/null +++ b/tools/dashboard_gen/src/main.rs @@ -0,0 +1,45 @@ +//! Builds one of the Grafana dashboards and prints its JSON to stdout. + +#![expect( + clippy::print_stdout, + reason = "CLI tool: emitting the dashboard JSON on stdout is the deliverable" +)] + +use clap::{Parser, ValueEnum}; +use dashboard_gen::Dashboard; +use json_pretty_compact::PrettyCompactFormatter; +use serde::Serialize as _; + +mod dashboards; + +#[derive(Debug, Parser)] +#[clap(version)] +struct Args { + /// Which dashboard to build. + dashboard: DashboardKind, +} + +#[derive(Debug, Clone, Copy, ValueEnum)] +enum DashboardKind { + Sequencer, +} + +impl DashboardKind { + fn build(self) -> Dashboard { + match self { + Self::Sequencer => dashboards::sequencer::dashboard(), + } + } +} + +fn main() { + let Args { dashboard } = Args::parse(); + + let formatter = PrettyCompactFormatter::new(); + let mut output = Vec::new(); + let mut ser = serde_json::Serializer::with_formatter(&mut output, formatter); + dashboard.build().serialize(&mut ser).unwrap(); + + let json = String::from_utf8(output).unwrap(); + println!("{json}"); +} diff --git a/tools/dashboard_gen/src/schema.rs b/tools/dashboard_gen/src/schema.rs index 190c4606..d04b6bad 100644 --- a/tools/dashboard_gen/src/schema.rs +++ b/tools/dashboard_gen/src/schema.rs @@ -1,12 +1,9 @@ //! The serializable Grafana dashboard schema — the internal data model the //! public builders assemble into. //! -//! Most types are `Serialize`-only, sized for what we *emit*. A handful of -//! vocabularies (`PanelType`, `Color`, and the styling enums) additionally -//! derive `Deserialize` because the lenient `input` model — which backs the -//! panel→Rust transpiler — reuses them. +//! Every type here is `Serialize`-only: the model is sized for what we emit. -use serde::{Deserialize, Serialize}; +use serde::Serialize; use crate::{DATASOURCE_UID, FieldOverride, Target, unit::Unit}; @@ -88,8 +85,7 @@ pub enum SortOrder { Desc, } -// Reused by the `input` model, hence `Deserialize`. -#[derive(Clone, Copy, Serialize, Deserialize)] +#[derive(Clone, Copy, Serialize)] #[serde(rename_all = "lowercase")] pub enum PanelType { Stat, @@ -97,7 +93,7 @@ pub enum PanelType { Gauge, } -#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, PartialEq, Eq, Serialize)] #[serde(rename_all = "lowercase")] pub enum ThresholdMode { Absolute, @@ -108,14 +104,14 @@ pub enum ThresholdMode { /// One threshold step: the color values at or above `value` take. The base step /// carries `value: null` — Grafana's "everything below the first threshold". -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Serialize)] pub struct ThresholdStep { pub color: String, pub value: Option, } /// A threshold ladder, driving gauge/stat coloring. -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Serialize)] pub struct Thresholds { pub mode: ThresholdMode, pub steps: Vec, @@ -145,10 +141,9 @@ impl Thresholds { } // Optional timeseries styling vocabularies. Each derives `PartialEq` so the -// public setters can panic when handed the Grafana default (see `styling`), and -// `Deserialize` because the `input` model reuses them. +// public setters can panic when handed the Grafana default (see `styling`). -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] #[serde(rename_all = "camelCase")] pub enum LineInterpolation { Linear, @@ -157,7 +152,7 @@ pub enum LineInterpolation { StepAfter, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] #[serde(rename_all = "lowercase")] pub enum ShowPoints { Auto, @@ -165,7 +160,7 @@ pub enum ShowPoints { Always, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] #[serde(rename_all = "lowercase")] pub enum GradientMode { None, @@ -174,7 +169,7 @@ pub enum GradientMode { Scheme, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] #[serde(rename_all = "lowercase")] pub enum StackingMode { None, @@ -182,7 +177,7 @@ pub enum StackingMode { Percent, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] #[serde(rename_all = "lowercase")] pub enum AxisPlacement { Auto, @@ -237,8 +232,7 @@ impl Datasource { } } -// Reused by the `input` model, hence `Deserialize`. -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Serialize)] #[serde(rename_all = "kebab-case")] #[serde(tag = "mode")] pub enum Color { diff --git a/tools/dashboard_gen/src/unit.rs b/tools/dashboard_gen/src/unit.rs index adeebf3b..a91f302b 100644 --- a/tools/dashboard_gen/src/unit.rs +++ b/tools/dashboard_gen/src/unit.rs @@ -60,44 +60,6 @@ impl Unit { 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 {