feat: Better y axes definition

This commit is contained in:
Jeremy Wall 2024-02-24 19:53:25 -05:00
parent 0af85229c2
commit 557d704a1b
6 changed files with 113 additions and 61 deletions

View File

@ -3,15 +3,18 @@
graphs: # Each Dashboard can have 1 or more graphs in it.
- title: Node cpu # Graphs have titles
query_type: Range # The type of graph. Range for timeseries and Scalar for point in time
d3_tick_format: "~s" # Default tick format for the graph y axis
d3_tickformat: "~s" # Default tick format for the graph y axis
yaxes:
- anchor: "y"
# overlaying: "y"
side: left
tickformat: "~%"
plots: # List of pluts to show on the graph
- source: http://heimdall:9001 # Prometheus source uri for this plot
query: 'sum by (instance)(irate(node_cpu_seconds_total{job="nodestats"}[5m]))' # The PromQL query for this plot
meta: # metadata for this plot
name_format: "`${labels.instance}`" # javascript template literal to format the trace name
fill: tozeroy
#d3_tick_format: "~%" # d3 tick format override for this plot's yaxis
#named_axis: "y" # yaxis name to use for this subplots traces
span: # The span for this range query
end: now # Where the span ends. RFC3339 format with special handling for the now keyword
duration: 1d # duration of the span. Uses SI formatting for duration amounts.
@ -23,25 +26,32 @@
step_duration: 1 minute
graphs:
- title: Node cpu percent
d3_tick_format: "~%"
d3_tickformat: "~%"
query_type: Range
yaxes:
- anchor: "y" # This axis is y
tickformat: "~%"
- overlaying: "y" # This axis is y2 but overlays axis y
side: right # show this axis on the right side instead of the left
tickformat: "~%"
plots:
- source: http://heimdall:9001
query: |
sum by (instance)(irate(node_cpu_seconds_total{mode="system",job="nodestats"}[5m])) / sum by (instance)(irate(node_cpu_seconds_total{job="nodestats"}[5m]))
meta:
d3_tick_format: "~%"
name_format: "`${labels.instance} system`"
named_axis: "y"
yaxis: "y"
- source: http://heimdall:9001
query: |
sum by (instance)(irate(node_cpu_seconds_total{mode="user",job="nodestats"}[5m])) / sum by (instance)(irate(node_cpu_seconds_total{job="nodestats"}[5m]))
meta:
d3_tick_format: "~%"
name_format: "`${labels.instance} user`"
named_axis: "y"
yaxis: "y2"
- title: Node memory
query_type: Scalar
yaxes:
- anchor: "y"
tickformat: "~s"
plots:
- source: http://heimdall:9001
query: 'node_memory_MemFree_bytes{job="nodestats"}'

View File

@ -77,6 +77,11 @@
query_type = "Range";
# yaxis formatting default for this graph
d3_tick_format = "~s";
yaxes = [
{
tickformat = "~s";
}
];
plots = [
{
source = "http://heimdall:9001";
@ -85,9 +90,8 @@
\'\';
meta = {
name_function = "''${labels.instance}";
named_axis = "y";
# yaxis formatting for this subplot
d3_tick_format = "~s";
# yaxis to use for this plot
yaxis = "y";
};
}
];

View File

@ -15,12 +15,52 @@ use std::path::Path;
use chrono::prelude::*;
use chrono::Duration;
use serde::Deserialize;
use serde::{Serialize, Deserialize};
use serde_yaml;
use tracing::{debug, error};
use anyhow::Result;
use crate::query::{QueryConn, QueryType, QueryResult, PlotMeta, to_samples};
use crate::query::{QueryConn, QueryType, QueryResult, to_samples};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PlotMeta {
name_format: Option<String>,
fill: Option<FillTypes>,
yaxis: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum FillTypes {
#[serde(rename = "tonexty")]
ToNextY,
#[serde(rename = "tozeroy")]
ToZeroY,
#[serde(rename = "tonextx")]
ToNextX,
#[serde(rename = "tozerox")]
ToZeroX,
#[serde(rename = "toself")]
ToSelf,
#[serde(rename = "tonext")]
ToNext,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum AxisSide {
#[serde(rename = "right")]
Right,
#[serde(rename = "left")]
Left,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AxisDefinition {
anchor: Option<String>,
overlaying: Option<String>,
side: Option<AxisSide>,
#[serde(rename = "tickformat")]
tick_format: Option<String>,
}
#[derive(Deserialize, Debug)]
pub struct GraphSpan {
@ -47,6 +87,7 @@ pub struct SubPlot {
#[derive(Deserialize)]
pub struct Graph {
pub title: String,
pub yaxes: Vec<AxisDefinition>,
pub plots: Vec<SubPlot>,
pub span: Option<GraphSpan>,
pub query_type: QueryType,

View File

@ -1,3 +1,5 @@
use std::collections::HashMap;
// Copyright 2023 Jeremy Wall
//
// Licensed under the Apache License, Version 2.0 (the "License");
@ -11,8 +13,6 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::collections::HashMap;
use chrono::prelude::*;
use prometheus_http_query::{
response::{Data, PromqlResult},
@ -21,6 +21,8 @@ use prometheus_http_query::{
use serde::{Deserialize, Serialize};
use tracing::debug;
use crate::dashboard::PlotMeta;
#[derive(Deserialize, Clone, Debug)]
pub enum QueryType {
Range,
@ -117,30 +119,6 @@ pub struct DataPoint {
value: f64,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum FillTypes {
#[serde(rename = "tonexty")]
ToNextY,
#[serde(rename = "tozeroy")]
ToZeroY,
#[serde(rename = "tonextx")]
ToNextX,
#[serde(rename = "tozerox")]
ToZeroX,
#[serde(rename = "toself")]
ToSelf,
#[serde(rename = "tonext")]
ToNext,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PlotMeta {
name_format: Option<String>,
named_axis: Option<String>,
fill: Option<FillTypes>,
d3_tick_format: Option<String>,
}
#[derive(Serialize, Deserialize)]
pub enum QueryResult {
Series(Vec<(HashMap<String, String>, PlotMeta, Vec<DataPoint>)>),

View File

@ -22,18 +22,25 @@ use axum::{
// https://maud.lambda.xyz/getting-started.html
use maud::{html, Markup};
use serde::{Serialize, Deserialize};
use tracing::debug;
use crate::dashboard::{Dashboard, Graph, GraphSpan, query_data};
use crate::query::{to_samples, QueryResult};
use crate::dashboard::{Dashboard, Graph, GraphSpan, AxisDefinition, query_data};
use crate::query::QueryResult;
type Config = State<Arc<Vec<Dashboard>>>;
#[derive(Serialize, Deserialize)]
pub struct GraphPayload {
pub yaxes: Vec<AxisDefinition>,
pub plots: Vec<QueryResult>,
}
pub async fn graph_query(
State(config): Config,
Path((dash_idx, graph_idx)): Path<(usize, usize)>,
Query(query): Query<HashMap<String, String>>,
) -> Json<Vec<QueryResult>> {
) -> Json<GraphPayload> {
debug!("Getting data for query");
let dash = config.get(dash_idx).expect("No such dashboard index");
let graph = dash
@ -54,8 +61,8 @@ pub async fn graph_query(
None
}
};
let data = query_data(graph, dash, query_span).await.expect("Unable to get query results");
Json(data)
let plots = query_data(graph, dash, query_span).await.expect("Unable to get query results");
Json(GraphPayload{yaxes: graph.yaxes.clone(), plots})
}
pub fn mk_api_routes(config: Arc<Vec<Dashboard>>) -> Router<Config> {

View File

@ -214,7 +214,8 @@ class TimeseriesGraph extends HTMLElement {
this.#menuContainer.replaceChildren(...children);
}
getLabelsForData(data) {
getLabelsForData(graph) {
const data = graph.plots;
for (var subplot of data) {
if (subplot.Series) {
for (const triple of subplot.Series) {
@ -231,11 +232,25 @@ class TimeseriesGraph extends HTMLElement {
}
}
async updateGraph(maybeData) {
var data = maybeData;
if (!data) {
data = await this.fetchData();
yaxisNameGenerator() {
var counter = 1;
return function() {
var name = "yaxis";
if (counter != 1) {
name = "yaxis" + counter ;
}
counter++;
return name;
};
}
async updateGraph(maybeGraph) {
var graph = maybeGraph;
if (!graph) {
graph = await this.fetchData();
}
var data = graph.plots;
var yaxes = graph.yaxes;
const config = {
legend: {
orientation: 'h'
@ -253,11 +268,17 @@ class TimeseriesGraph extends HTMLElement {
gridcolor: getCssVariableValue("--accent-color")
}
};
var nextYaxis = this.yaxisNameGenerator();
for (const yaxis of yaxes) {
yaxis.tickformat = yaxis.tickformat || this.#d3TickFormat;
yaxis.gridColor = getCssVariableValue("--accent-color");
layout[nextYaxis()] = yaxis;
}
var traces = [];
for (var subplot_idx in data) {
const subplot = data[subplot_idx];
const subplotCount = Number(subplot_idx) + 1;
const default_yaxis = "y" + subplotCount
var nextYaxis = this.yaxisNameGenerator();
if (subplot.Series) {
// https://plotly.com/javascript/reference/scatter/
loopSeries: for (const triple of subplot.Series) {
@ -269,13 +290,8 @@ class TimeseriesGraph extends HTMLElement {
}
}
const meta = triple[1];
const yaxis = meta["named_axis"] || default_yaxis;
var yaxis = meta.yaxis || "y";
// https://plotly.com/javascript/reference/layout/yaxis/
layout["yaxis" + subplotCount] = {
anchor: yaxis,
gridcolor: getCssVariableValue("--accent-color"),
tickformat: meta["d3_tick_format"] || this.#d3TickFormat
};
const series = triple[2];
var trace = {
type: "scatter",
@ -285,7 +301,7 @@ class TimeseriesGraph extends HTMLElement {
// We always share the x axis for timeseries graphs.
xaxis: "x",
yaxis: yaxis,
yhoverformat: meta["d3_tick_format"],
//yhoverformat: yaxis.tickformat,
};
if (meta.fill) {
trace.fill = meta.fill;
@ -300,10 +316,6 @@ class TimeseriesGraph extends HTMLElement {
}
} else if (subplot.Scalar) {
// https://plotly.com/javascript/reference/bar/
layout["yaxis"] = {
tickformat: this.#d3TickFormat,
gridcolor: getCssVariableValue("--accent-color")
};
loopScalar: for (const triple of subplot.Scalar) {
const labels = triple[0];
for (var label in labels) {