ui: Show log results in our dashboards

This commit is contained in:
Jeremy Wall 2024-03-04 20:52:36 -05:00
parent 17e6ae81a0
commit f69ea6d6fa
3 changed files with 113 additions and 21 deletions

View File

@ -22,7 +22,7 @@ use tracing::{debug, error};
use super::{LogLine, QueryResult, QueryType, TimeSpan};
// TODO(jwall): Should I allow non stream returns?
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub enum ResultType {
/// Returned by query endpoints
#[serde(rename = "vector")]
@ -85,8 +85,10 @@ pub fn loki_to_sample(data: LokiData) -> QueryResult {
}
QueryResult::StreamInstant(values)
}
// Stream types are nanoseconds. // Matrix types are seconds
ResultType::Matrix | ResultType::Streams => {
let mut values = Vec::with_capacity(data.result.len());
let multiple = (if data.result_type == ResultType::Matrix { 1000000 } else { 1 }) as f64;
for result in data.result {
if let Some(value) = result.values {
values.push((
@ -94,7 +96,7 @@ pub fn loki_to_sample(data: LokiData) -> QueryResult {
value
.into_iter()
.map(|(timestamp, line)| LogLine {
timestamp: timestamp.parse::<f64>().expect("Invalid f64 type"),
timestamp: multiple * timestamp.parse::<f64>().expect("Invalid f64 type"),
line,
})
.collect(),

View File

@ -26,7 +26,7 @@ use serde::{Deserialize, Serialize};
use tracing::debug;
use crate::dashboard::{
loki_query_data, prom_query_data, AxisDefinition, Dashboard, Graph, GraphSpan, Orientation,
loki_query_data, prom_query_data, AxisDefinition, Dashboard, Graph, GraphSpan, Orientation, LogStream,
};
use crate::query::QueryResult;
@ -120,6 +120,18 @@ pub fn mk_api_routes(config: Arc<Vec<Dashboard>>) -> Router<Config> {
)
}
pub fn log_component(dash_idx: usize, log_idx: usize, log: &LogStream) -> Markup {
let log_id = format!("log-{}-{}", dash_idx, log_idx);
let log_data_uri = format!("/api/dash/{}/log/{}", dash_idx, log_idx);
let log_embed_uri = format!("/embed/dash/{}/log/{}", dash_idx, log_idx);
html! {
div {
h2 { (log.title) " - " a href=(log_embed_uri) { "embed url" } }
graph-plot uri=(log_data_uri) id=(log_id) { }
}
}
}
pub fn graph_component(dash_idx: usize, graph_idx: usize, graph: &Graph) -> Markup {
let graph_id = format!("graph-{}-{}", dash_idx, graph_idx);
let graph_data_uri = format!("/api/dash/{}/graph/{}", dash_idx, graph_idx);
@ -160,19 +172,35 @@ fn dash_elements(config: State<Arc<Vec<Dashboard>>>, dash_idx: usize) -> maud::P
let dash = config
.get(dash_idx)
.expect(&format!("No such dashboard {}", dash_idx));
let graph_iter = dash
let graph_components = if let Some(graphs) = dash
.graphs
.as_ref()
.expect("No graphs in this dashboard")
.iter()
.as_ref() {
let graph_iter = graphs.iter()
.enumerate()
.collect::<Vec<(usize, &Graph)>>();
Some(html! {
@for (idx, graph) in &graph_iter {
(graph_component(dash_idx, *idx, *graph))
}
})
} else {
None
};
let log_components = if let Some(logs) = dash.logs.as_ref() {
let log_iter = logs.iter().enumerate().collect::<Vec<(usize, &LogStream)>>();
Some(html! {
@for (idx, log) in &log_iter {
(log_component(dash_idx, *idx, *log))
}
})
} else {
None
};
html!(
h1 { (dash.title) }
span-selector class="row-flex" {}
@for (idx, graph) in &graph_iter {
(graph_component(dash_idx, *idx, *graph))
}
@if graph_components.is_some() { (graph_components.unwrap()) }
@if log_components.is_some() { (log_components.unwrap()) }
)
}

View File

@ -15,10 +15,10 @@
/**
* @typedef PlotList
* @type {object}
* @property {?Array} Series
* @property {?Array} Scalar
* @property {?Array} StreamInstant
* @property {?Array} Stream
* @property {Array=} Series
* @property {Array=} Scalar
* @property {Array<{timestamp: string, line: string}>=} StreamInstant - Timestamps are in seconds
* @property {Array<{timestamp: string, line: string}>=} Stream - Timestamps are in nanoseconds
*/
/**
@ -30,15 +30,42 @@
*/
/**
* @typedef PlotTrace
* @typedef HeaderOrCell
* @type {object}
* @property {array} values
* @property {string=} fill
* @property {{width: number, color: string}=} line
* @property {{family: string, size: number, color: string }=} font
*/
/**
* @typedef TableTrace
* @type {object}
* @property {string=} name
* @property type {string}
* @property {string=} mode
* @property {HeaderOrCell} headers
* @property {HeaderOrCell} cells - An Array of columns for the table.
* @property {string=} xaxis
* @property {string=} yaxis
*/
/**
* @typedef GraphTrace
* @type {object}
* @property {string=} name
* @property {string=} fill
* @property type {string}
* @property {string=} mode
* @property {Array} x
* @property {Array} y
* @peroperty {string=} xaxis
* @peroperty {string=} yaxis
* @property {string=} xaxis
* @property {string=} yaxis
*/
/**
* @typedef PlotTrace
* @type {(TableTrace|GraphTrace)}
*/
/**
@ -401,7 +428,7 @@ export class GraphPlot extends HTMLElement {
var yaxis = meta.yaxis || "y";
// https://plotly.com/javascript/reference/layout/yaxis/
const series = triple[2];
const trace = {
const trace = /** @type GraphTrace */({
type: "scatter",
mode: "lines+text",
x: [],
@ -410,7 +437,7 @@ export class GraphPlot extends HTMLElement {
xaxis: "x",
yaxis: yaxis,
//yhoverformat: yaxis.tickformat,
};
});
if (meta.fill) {
trace.fill = meta.fill;
}
@ -434,7 +461,7 @@ export class GraphPlot extends HTMLElement {
}
const meta = triple[1];
const series = triple[2];
const trace = /** @type PlotTrace */({
const trace = /** @type GraphTrace */({
type: "bar",
x: [],
y: [],
@ -446,7 +473,42 @@ export class GraphPlot extends HTMLElement {
trace.x.push(trace.name);
traces.push(trace);
}
} // TODO(zaphar): subplot.Stream // log lines!!!
} else if (subplot.Stream) {
// TODO(zaphar): subplot.Stream // log lines!!!
const trace = /** @type TableTrace */({
type: "table",
headers: {
align: "left",
values: ["Timestamp", "Log"]
},
cells: {
align: "left",
values: []
},
});
const dateColumn = [];
const logColumn = [];
loopStream: for (const pair of subplot.Stream) {
const labels = pair[0];
for (var label in labels) {
var show = this.#filteredLabelSets[label];
if (show && !show.includes(labels[label])) {
continue loopStream;
}
}
const lines = pair[1];
// TODO(jwall): Headers
for (const line of lines) {
// For streams the timestamps are in nanoseconds
dateColumn.push(new Date(line.timestamp / 1000000));
logColumn.push(line.line);
}
}
trace.cells.values.push(dateColumn);
trace.cells.values.push(logColumn);
traces.push(trace);
}
}
// https://plotly.com/javascript/plotlyjs-function-reference/#plotlyreact
// @ts-ignore