Skip to content

Commit

Permalink
dynamically update Grafana dashboard time range
Browse files Browse the repository at this point in the history
  • Loading branch information
drink7036290 committed Dec 6, 2024
1 parent 5e7e584 commit 6771805
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 2 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ jobs:
- uses: Swatinem/rust-cache@v2

- name: Install Dependencies
run: sudo apt update && sudo apt install -y gnuplot
run: |
sudo apt update && sudo apt install -y gnuplot pkg-config libssl-dev
- name: Build and Run Benchmarks
run: cargo bench # -v will be too noisy
Expand All @@ -46,7 +47,7 @@ jobs:
with:
tool: cargo-llvm-cov

- name: Parse and Send Benchmark Data to InfluxDB, along with collecting coverage data
- name: Send Benchmark Data to InfluxDB, adjust Grafana dashboard's time range, along with collecting coverage data
run: |
cargo llvm-cov run --release -p bench_util --lcov --output-path lcov_bench_util.info
env:
Expand All @@ -56,6 +57,9 @@ jobs:
INFLUXDB_BUCKET: ${{ secrets.INFLUXDB_BUCKET }}
GIT_COMMIT_SHA: ${{ github.sha }}
GIT_BRANCH: ${{ github.ref_name }}
GRAFANA_URL: ${{ secrets.GRAFANA_URL }}
GRAFANA_SERVICE_ACCOUNT_TOKEN: ${{ secrets.GRAFANA_SERVICE_ACCOUNT_TOKEN }}
GRAFANA_DASHBOARD_UID: ${{ secrets.GRAFANA_DASHBOARD_UID }}

- name: Upload coverage data to codecov
uses: codecov/codecov-action@v5
Expand Down
11 changes: 11 additions & 0 deletions utilities/bench_util/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use dotenvy::dotenv;
mod modules;
use modules::dashboard;
use reqwest::blocking::Client;
use serde::Deserialize;
use std::env;
Expand Down Expand Up @@ -54,6 +56,7 @@ fn main() -> anyhow::Result<()> {

// Build the path to the target/criterion directory
let criterion_dir = workspace_root.join("target").join("criterion");
let mut db_updated = false;

// Iterate over all estimates.json files
for entry in WalkDir::new(criterion_dir)
Expand Down Expand Up @@ -153,6 +156,14 @@ fn main() -> anyhow::Result<()> {
response.text()?
);
}

db_updated = true;
}

if db_updated {
dashboard::update_dashboard_time_range()?;
} else {
println!("No new data to update the Grafana dashboard");
}

Ok(())
Expand Down
174 changes: 174 additions & 0 deletions utilities/bench_util/src/modules/dashboard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
use anyhow::{anyhow, Context, Result};
use chrono::{DateTime, Duration, Utc};
use dotenvy::dotenv;
use reqwest::blocking::Client;
use serde_json::Value;
use std::env;

fn get_time_start() -> Result<DateTime<Utc>> {
// Fetch environment variables
let influxdb_url = env::var("INFLUXDB_URL").context("INFLUXDB_URL not set")?;
let influxdb_token = env::var("INFLUXDB_TOKEN").context("INFLUXDB_TOKEN not set")?;
let influxdb_org = env::var("INFLUXDB_ORG").context("INFLUXDB_ORG not set")?;

let client = Client::new();

let query = r#"
// Flux query to get earliest data saved in InfluxDB instead of GitHub's earliest commit
// Adjust the query as needed
from(bucket: "leetcode-rs_bench")
|> range(start: -30d)
|> filter(fn: (r) =>
r._measurement == "benchmark" and
r.name == "q146" and
r._field == "slope_point_estimate"
)
|> sort(columns: ["_time"], desc: false)
|> limit(n: 1) // Retrieves the earliest record
|> map(fn: (r) => ({
text: string(v: r._time)
}))
"#;

let response = client
.post(format!("{}/api/v2/query", influxdb_url))
.header("Authorization", format!("Token {}", influxdb_token))
.header("Content-Type", "application/vnd.flux")
.query(&[("org", influxdb_org)])
.body(query)
.send()?;

let text = response.text()?;
let mut time_start = Utc::now() - Duration::days(1); // default time

// Parse CSV response and extract times
let lines: Vec<&str> = text.lines().take(2).collect();
if lines.len() < 2 {
// maybe influxdb has flushed all the data, return a default time
return Ok(time_start);
}

// Example response:
// ",result,table,text"
// ",_result,0,2024-12-05T01:42:34.008261000Z"

let titles = lines[0].split(',');
let values = lines[1].split(',');

for (title, value) in titles.zip(values) {
if title == "text" {
time_start = chrono::DateTime::parse_from_rfc3339(value)?.with_timezone(&chrono::Utc);
break;
}
}

Ok(time_start)
}

fn update_time_range(
dashboard_json: &mut Value,
time_start: DateTime<Utc>,
time_end: DateTime<Utc>,
) -> Result<()> {
let dashboard = dashboard_json
.get_mut("dashboard")
.ok_or_else(|| anyhow!("Missing 'dashboard' field"))?;

dashboard["time"]["from"] = Value::String(time_start.to_rfc3339());
dashboard["time"]["to"] = Value::String(time_end.to_rfc3339());

Ok(())
}

fn update_dashboard(time_start: DateTime<Utc>, time_end: DateTime<Utc>) -> Result<()> {
// Load environment variables
let grafana_url = env::var("GRAFANA_URL").context("GRAFANA_URL not set")?;
let grafana_token = env::var("GRAFANA_SERVICE_ACCOUNT_TOKEN")
.context("GRAFANA_SERVICE_ACCOUNT_TOKEN not set")?;
let dashboard_uid =
env::var("GRAFANA_DASHBOARD_UID").context("GRAFANA_DASHBOARD_UID not set")?;

// Fetch the current dashboard JSON
let client = Client::new();
let response = client
.get(format!(
"{}/api/dashboards/uid/{}",
grafana_url, dashboard_uid
))
.bearer_auth(&grafana_token)
.send()
.with_context(|| "Failed to send request to Grafana API")?;

if !response.status().is_success() {
let status = response.status();
let error_text = response
.text()
.unwrap_or_else(|_| "Unknown error".to_string());
return Err(anyhow!(
"Failed to fetch dashboard: {} - {}",
status,
error_text
));
}

let mut dashboard_json: Value = response
.json()
.with_context(|| "Failed to parse dashboard JSON")?;

// Update the time range
update_time_range(&mut dashboard_json, time_start, time_end)?;

// Prepare the payload
let payload = serde_json::json!({
"dashboard": dashboard_json["dashboard"],
"overwrite": true
});

// Update the dashboard
let response = client
.post(format!("{}/api/dashboards/db", grafana_url))
.bearer_auth(&grafana_token)
.json(&payload)
.send()
.with_context(|| "Failed to send update request to Grafana API")?;

if !response.status().is_success() {
let status = response.status();
let error_text = response
.text()
.unwrap_or_else(|_| "Unknown error".to_string());
return Err(anyhow!(
"Failed to update dashboard: {} - {}",
status,
error_text
));
}

println!("Dashboard time range updated successfully.");

Ok(())
}

pub fn update_dashboard_time_range() -> Result<()> {
// Load environment variables from .env file (optional)
dotenv().ok();

// Calculate desired time range
let mut time_end = Utc::now();
let mut time_start = get_time_start()?;

// Expand the time range by 10% on each side
let shift = (time_end - time_start).num_seconds() / 10;
time_start -= Duration::seconds(shift);
time_end += Duration::seconds(shift);

println!(
"Setting Dashboard Time Range from {} to {}",
time_start, time_end
);

// Update dashboard
update_dashboard(time_start, time_end)?;

Ok(())
}
1 change: 1 addition & 0 deletions utilities/bench_util/src/modules/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod dashboard;

0 comments on commit 6771805

Please sign in to comment.