Skip to content

Commit 18231e1

Browse files
committed
Grafana dashboard generator tool
Initial commit for the `generate-rust-dashboards` tool. This auto-generates Grafana dashboards for teams that own Rust Components. There's still a lot of work to do, but this feels like a good start.
1 parent 1db4410 commit 18231e1

File tree

20 files changed

+2494
-0
lines changed

20 files changed

+2494
-0
lines changed

.cargo/config.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[alias]
2+
generate-rust-dashboards = ["run", "-p", "generate-rust-dashboards"]
23
regen-protobufs = ["run", "--bin", "protobuf-gen", "tools/protobuf_files.toml"]
34
uniffi-bindgen = ["run", "--package", "embedded-uniffi-bindgen", "--"]
45
uniffi-bindgen-library-mode = ["run", "--package", "uniffi-bindgen-library-mode", "--"]

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ members = [
6161
"megazords/ios-rust",
6262
"megazords/ios-rust/focus",
6363
"tools/embedded-uniffi-bindgen",
64+
"tools/generate-rust-dashboards",
6465
"tools/start-bindings",
6566
"tools/uniffi-bindgen-library-mode",
6667
"automation/swift-components-docs",
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "generate-rust-dashboards"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
anyhow = "1"
8+
camino = "1"
9+
clap = {version = "4.2", default-features = false, features = ["std", "derive"]}
10+
serde = { version = "1", features = ["derive"] }
11+
serde_json = "1"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Dashboard Generator
2+
3+
Use this tool to create Grafana dashboards for your team's components.
4+
5+
## Setup
6+
7+
Ensure you have a yardstick account by going to https://yardstick.mozilla.org/ and logging in using Mozilla SSO.
8+
You should have “editor” access and can create, edit, and delete dashboards and alerts. If not, go
9+
to https://mozilla-hub.atlassian.net/wiki/spaces/SRE/pages/886866077/Yardstick+Grafana+Service+User+Guide
10+
for help.
11+
12+
## Configuration
13+
14+
Edit `src/component_config.rs` add a `Component` variant for each of your team's components.
15+
Edit `src/team_config.rs` and add an entry for your team.
16+
Feel free to copy the and paste other team's configurations to get started.
17+
18+
## Running
19+
20+
Run `cargo generate-rust-dashboards [team-name] [output-directory]` and follow the instructions.
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
use crate::config::{Application, Application::*};
6+
7+
/// Enumeration containing all Rust components.
8+
/// When adding new variants, make sure to also update the impl block below
9+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10+
pub enum Component {
11+
Autofill,
12+
Fxa,
13+
Logins,
14+
Places,
15+
RemoteSettings,
16+
Suggest,
17+
Tabs,
18+
}
19+
20+
impl Component {
21+
/// Unique name for the component in slug format (lower-case letters + dashes).
22+
pub fn slug(&self) -> &'static str {
23+
match self {
24+
Self::Autofill => "autofill",
25+
Self::Fxa => "fxa",
26+
Self::Logins => "logins",
27+
Self::Places => "places",
28+
Self::RemoteSettings => "remote-settings",
29+
Self::Suggest => "suggest",
30+
Self::Tabs => "tabs",
31+
}
32+
}
33+
34+
/// Applications your component ships on
35+
pub fn applications(&self) -> &[Application] {
36+
match self {
37+
Self::Autofill => &[Android, Ios],
38+
Self::Fxa => &[Android, Ios],
39+
Self::Logins => &[Desktop, Android, Ios],
40+
Self::Places => &[Android, Ios],
41+
Self::RemoteSettings => &[Desktop, Android, Ios],
42+
Self::Suggest => &[Desktop, Android, Ios],
43+
Self::Tabs => &[Desktop, Android, Ios],
44+
}
45+
}
46+
47+
/// Prefix for error strings.
48+
///
49+
/// This is the common prefix for strings sent to the `error_support`. You can usually find it
50+
/// by going to `error.rs` for your component and looking at the `report_error` calls.
51+
pub fn error_prefix(&self) -> &'static str {
52+
match self {
53+
Self::Autofill => "autofill-",
54+
Self::Fxa => "fxa-client-",
55+
Self::Logins => "logins-",
56+
Self::Places => "places-",
57+
Self::RemoteSettings => "remote-settings-",
58+
Self::Suggest => "suggest-",
59+
Self::Tabs => "tabs-",
60+
}
61+
}
62+
63+
/// Sync engine names
64+
///
65+
/// These represent 2 things:
66+
/// - The Glean pings for the component without the `-sync` suffix.
67+
/// - The `engine.name` value for the legacy `telemetry.sync` table.
68+
pub fn sync_engines(&self) -> &[&'static str] {
69+
match self {
70+
Self::Autofill => &["addresses", "creditcards"],
71+
Self::Fxa => &[],
72+
Self::Logins => &["logins"],
73+
Self::Places => &["bookmarks", "history"],
74+
Self::RemoteSettings => &[],
75+
Self::Suggest => &[],
76+
Self::Tabs => &["tabs"],
77+
}
78+
}
79+
}
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
use std::{collections::BTreeSet, fmt};
6+
7+
pub use crate::component_config::Component;
8+
use crate::util::slug;
9+
10+
/// Dashboard configuration for a team
11+
pub struct TeamConfig {
12+
/// Display name for the team.
13+
///
14+
/// This is what shows up on your dashboard titles. Spell it out however you want.
15+
pub team_name: &'static str,
16+
17+
/// Components that your team manages.
18+
pub components: Vec<Component>,
19+
20+
/// Track component errors
21+
///
22+
/// This adds a panel to the main dashboard as well as creates a extra dashboard for error
23+
/// details.
24+
pub component_errors: bool,
25+
26+
/// Track sync metrics
27+
///
28+
/// This adds a panel to the main dashboard as well as creates a extra dashboard for sync
29+
/// errors.
30+
pub sync_metrics: bool,
31+
32+
/// Metric to include on your main dashboard
33+
pub main_dashboard_metrics: Vec<Metric>,
34+
35+
/// Extra dashboards to generate
36+
pub extra_dashboards: Vec<ExtraDashboard>,
37+
}
38+
39+
/// Extra dashboard to generate for a team
40+
pub struct ExtraDashboard {
41+
pub name: &'static str,
42+
/// Metrics to include in the dashboard
43+
pub metrics: Vec<Metric>,
44+
}
45+
46+
/// Metric to add to your team dashboard
47+
///
48+
/// Metrics will add panels to your overview dashboard and/or create secondary dashboards.
49+
pub enum Metric {
50+
Counter(CounterMetric),
51+
LabeledCounter(LabeledCounterMetric),
52+
Distribution(DistributionMetric),
53+
LabeledDistribution(LabeledDistributionMetric),
54+
}
55+
56+
/// Glean counter
57+
///
58+
/// This will create time-series panels for the counter
59+
pub struct CounterMetric {
60+
/// Name to display on the dashboard
61+
pub display_name: &'static str,
62+
/// Name of the ping ("metrics" by default)
63+
pub ping: &'static str,
64+
/// Category name (top-level key in metrics.yaml)
65+
pub category: &'static str,
66+
/// Metric name (key for the metric)
67+
pub metric: &'static str,
68+
// Which applications report this metric
69+
pub applications: Vec<Application>,
70+
}
71+
72+
/// Glean labeled counter
73+
///
74+
/// This will create time-series panels for the counter, partitioned by the label
75+
pub struct LabeledCounterMetric {
76+
/// Name to display on the dashboard
77+
pub display_name: &'static str,
78+
/// Name of the ping ("metrics" by default)
79+
pub ping: &'static str,
80+
/// Category name (top-level key in metrics.yaml)
81+
pub category: &'static str,
82+
/// Metric name (key for the metric)
83+
pub metric: &'static str,
84+
// Which applications report this metric
85+
pub applications: Vec<Application>,
86+
}
87+
88+
/// Glean timing/memory distribution
89+
///
90+
/// This will create time-series panels for the 5th, 50th and 95th percentile.
91+
pub struct DistributionMetric {
92+
pub kind: DistributionMetricKind,
93+
/// Name to display on the dashboard
94+
pub display_name: &'static str,
95+
/// Label describing what we're measure, including units
96+
pub axis_label: &'static str,
97+
/// Name of the ping ("metrics" by default)
98+
pub ping: &'static str,
99+
/// Category name (top-level key in metrics.yaml)
100+
pub category: &'static str,
101+
/// Metric name (key for the metric)
102+
pub metric: &'static str,
103+
// Which applications report this metric
104+
pub applications: Vec<Application>,
105+
// Divide each value by this amount
106+
//
107+
// Note:
108+
// * Timing distributions are always stored in nanoseconds, regardless of the unit listed in
109+
// `metrics.yaml`
110+
// * Memory distributions are always stored in bytes, regardless of the unit listed in
111+
// `metrics.yaml`
112+
pub value_divisor: Option<u64>,
113+
// Filter out values lower than this amount (takes effect before the divisor)
114+
pub value_filter: Option<u64>,
115+
// Link to an extra dashboard, the inner value is the name of the dashboard
116+
pub link_to: Option<&'static str>,
117+
}
118+
119+
/// Glean labeled timing/memory distribution
120+
///
121+
/// This will create time-series panels for the 5th, 50th and 95th percentile.
122+
/// Percentiles will be partitioned by the metric label.
123+
pub struct LabeledDistributionMetric {
124+
pub kind: DistributionMetricKind,
125+
/// Name to display on the dashboard
126+
pub display_name: &'static str,
127+
/// Label describing what we're measure, including units
128+
pub axis_label: &'static str,
129+
/// Name of the ping ("metrics" by default)
130+
pub ping: &'static str,
131+
/// Category name (top-level key in metrics.yaml)
132+
pub category: &'static str,
133+
/// Metric name (key for the metric)
134+
pub metric: &'static str,
135+
// Which applications report this metric
136+
pub applications: Vec<Application>,
137+
// Divide each value by this amount
138+
//
139+
// Note:
140+
// * Timing distributions are always stored in nanoseconds, regardless of the unit listed in
141+
// `metrics.yaml`
142+
// * Memory distributions are always stored in bytes, regardless of the unit listed in
143+
// `metrics.yaml`
144+
pub value_divisor: Option<u64>,
145+
// Filter out values lower than this amount (takes effect before the divisor)
146+
pub value_filter: Option<u64>,
147+
}
148+
149+
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
150+
pub enum DistributionMetricKind {
151+
Memory,
152+
Timing,
153+
Custom,
154+
}
155+
156+
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
157+
pub enum Application {
158+
Android,
159+
Ios,
160+
Desktop,
161+
}
162+
163+
#[derive(Clone, Copy, PartialEq, Eq)]
164+
pub enum ReleaseChannel {
165+
Nightly,
166+
Beta,
167+
Release,
168+
}
169+
170+
impl TeamConfig {
171+
pub fn applications(&self) -> BTreeSet<Application> {
172+
self.components
173+
.iter()
174+
.flat_map(Component::applications)
175+
.cloned()
176+
.collect()
177+
}
178+
179+
pub fn team_slug(&self) -> String {
180+
slug(self.team_name)
181+
}
182+
}
183+
184+
impl Application {
185+
pub fn slug(&self) -> &'static str {
186+
match self {
187+
Self::Android => "android",
188+
Self::Ios => "ios",
189+
Self::Desktop => "desktop",
190+
}
191+
}
192+
193+
pub fn bigquery_dataset(&self) -> &'static str {
194+
// There's a few datasets we can use, these were chosen because they seem to include
195+
// release, beta, and nightly data
196+
match self {
197+
Self::Android => "fenix",
198+
Self::Ios => "firefox_ios",
199+
Self::Desktop => "firefox_desktop",
200+
}
201+
}
202+
203+
pub fn display_name(&self, channel: ReleaseChannel) -> String {
204+
format!("{self} ({channel})")
205+
}
206+
}
207+
208+
impl fmt::Display for Application {
209+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210+
match self {
211+
Self::Android => write!(f, "Android"),
212+
Self::Ios => write!(f, "iOS"),
213+
Self::Desktop => write!(f, "Desktop"),
214+
}
215+
}
216+
}
217+
218+
impl fmt::Display for ReleaseChannel {
219+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220+
match self {
221+
Self::Nightly => write!(f, "nightly"),
222+
Self::Beta => write!(f, "beta"),
223+
Self::Release => write!(f, "release"),
224+
}
225+
}
226+
}
227+
228+
impl From<CounterMetric> for Metric {
229+
fn from(m: CounterMetric) -> Self {
230+
Self::Counter(m)
231+
}
232+
}
233+
234+
impl From<LabeledCounterMetric> for Metric {
235+
fn from(m: LabeledCounterMetric) -> Self {
236+
Self::LabeledCounter(m)
237+
}
238+
}
239+
240+
impl From<DistributionMetric> for Metric {
241+
fn from(m: DistributionMetric) -> Self {
242+
Self::Distribution(m)
243+
}
244+
}
245+
246+
impl From<LabeledDistributionMetric> for Metric {
247+
fn from(m: LabeledDistributionMetric) -> Self {
248+
Self::LabeledDistribution(m)
249+
}
250+
}

0 commit comments

Comments
 (0)