mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-17 10:09:05 +02:00
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
214 lines
9.1 KiB
Jsonnet
214 lines
9.1 KiB
Jsonnet
local g = import 'github.com/grafana/grafonnet/gen/grafonnet-v13.0.0/main.libsonnet';
|
|
local v2 = g.apps.dashboard.v2;
|
|
local styles = import 'styles.libsonnet';
|
|
local grid = v2.spec.layout.GridLayoutKind;
|
|
local rows = v2.spec.layout.RowsLayoutKind;
|
|
local tabs = v2.spec.layout.TabsLayoutKind;
|
|
local item = grid.spec.items;
|
|
local row = rows.spec.rows;
|
|
local tab = tabs.spec.tabs;
|
|
local prometheus = g.query.prometheus;
|
|
|
|
// Unlike std.mergePatch, this keeps explicit nulls (notably threshold steps).
|
|
local merge(base, patch) = base + {
|
|
[key]: if std.objectHas(base, key) && std.isObject(base[key]) && std.isObject(patch[key])
|
|
then merge(base[key], patch[key]) else patch[key]
|
|
for key in std.objectFields(patch)
|
|
};
|
|
|
|
{
|
|
dashboard: v2,
|
|
|
|
// Grafonnet's v2 dashboard accepts elements but has no Panel/QueryGroup constructor.
|
|
// Keep that small schema boundary here; tab sources use the same additive builders.
|
|
panel: {
|
|
new(id, title, visualization): {
|
|
kind: 'Panel',
|
|
spec: {
|
|
id: id,
|
|
title: title,
|
|
description: '',
|
|
links: [],
|
|
data: {
|
|
kind: 'QueryGroup',
|
|
spec: { queries: [], queryOptions: {}, transformations: [] },
|
|
},
|
|
vizConfig: {
|
|
kind: 'VizConfig',
|
|
group: visualization,
|
|
version: '',
|
|
spec: {
|
|
fieldConfig: { defaults: styles[visualization].defaults, overrides: [] },
|
|
options: styles[visualization].options,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
withDescription(value): { spec+: { description: value } },
|
|
withLinks(value): { spec+: { links: value } },
|
|
withQueries(value): { spec+: { data+: { spec+: { queries: value } } } },
|
|
withQueryOptions(value): { spec+: { data+: { spec+: { queryOptions: value } } } },
|
|
withTransformations(value): { spec+: { data+: { spec+: { transformations: value } } } },
|
|
withDefaults(value): {
|
|
spec+: { vizConfig+: { spec+: { fieldConfig+: { defaults: merge(super.defaults, value) } } } },
|
|
},
|
|
withOptions(value): { spec+: { vizConfig+: { spec+: { options: merge(super.options, value) } } } },
|
|
withOverrides(value): { spec+: { vizConfig+: { spec+: { fieldConfig+: { overrides: value } } } } },
|
|
},
|
|
|
|
// Green below warn, orange from warn and red from crit.
|
|
levels(warn, crit): [
|
|
{ color: 'green', value: null },
|
|
{ color: 'orange', value: warn },
|
|
{ color: 'red', value: crit },
|
|
],
|
|
|
|
// Current value with the time range drawn as a sparkline behind it. Only
|
|
// threshold steps add color, so green, orange and red always mean health;
|
|
// informational values stay in the text color.
|
|
stat(id, title, description, expr, unit='short', steps=null):
|
|
$.panel.new(id, title, 'stat')
|
|
+ $.panel.withDescription(description)
|
|
+ $.panel.withQueries([$.query.new(expr)])
|
|
+ $.panel.withQueryOptions({ maxDataPoints: 100 })
|
|
+ $.panel.withDefaults(
|
|
{ unit: unit }
|
|
+ if steps == null
|
|
then { color: { fixedColor: 'text', mode: 'fixed' } }
|
|
else { thresholds: { steps: steps } }
|
|
)
|
|
+ $.panel.withOptions({ graphMode: 'area' }),
|
|
|
|
// Time series of [expr, legend] queries. Series whose name matches below are
|
|
// drawn below the axis, for example reads against writes.
|
|
graph(id, title, description, unit, queries, below=null):
|
|
$.panel.new(id, title, 'timeseries')
|
|
+ $.panel.withDescription(description)
|
|
+ $.panel.withQueries([
|
|
$.query.new(queries[i][0], std.char(65 + i)) + $.query.withLegendFormat(queries[i][1])
|
|
for i in std.range(0, std.length(queries) - 1)
|
|
])
|
|
+ $.panel.withDefaults({ unit: unit } + if below == null then { min: 0 } else {})
|
|
+ $.panel.withOverrides(if below == null then [] else [{
|
|
matcher: { id: 'byRegexp', options: below },
|
|
properties: [{ id: 'custom.transform', value: 'negative-Y' }],
|
|
}]),
|
|
|
|
// Table of instant queries joined by a label. columns lists [field, title]
|
|
// pairs in display order; the other fields are hidden.
|
|
joinedTable(id, title, description, byField, queries, columns, overrides=[]):
|
|
local refIds = [std.char(65 + i) for i in std.range(0, std.length(queries) - 1)];
|
|
local shown = [column[0] for column in columns];
|
|
$.panel.new(id, title, 'table')
|
|
+ $.panel.withDescription(description)
|
|
+ $.panel.withQueries([$.query.new(queries[i], refIds[i]) + $.query.withInstant() + $.query.withFormat('table') for i in std.range(0, std.length(queries) - 1)])
|
|
+ $.panel.withTransformations([
|
|
{ group: 'joinByField', kind: 'Transformation', spec: { options: { byField: byField, mode: 'outer' } } },
|
|
{
|
|
group: 'organize',
|
|
kind: 'Transformation',
|
|
spec: {
|
|
options: {
|
|
excludeByName: { [name]: true for name in ['Time'] + ['Time ' + i for i in std.range(1, std.length(queries))] + ['Value #' + refId for refId in refIds] if !std.member(shown, name) },
|
|
includeByName: {},
|
|
indexByName: { [columns[i][0]]: i for i in std.range(0, std.length(columns) - 1) },
|
|
renameByName: { [column[0]]: column[1] for column in columns },
|
|
},
|
|
},
|
|
},
|
|
])
|
|
+ $.panel.withDefaults({ unit: 'short' })
|
|
+ $.panel.withOptions({ sortBy: [{ desc: false, displayName: columns[0][1] }] })
|
|
+ $.panel.withOverrides(overrides),
|
|
|
|
dataQuery(spec, group='prometheus', datasource='${datasource}'): {
|
|
kind: 'DataQuery',
|
|
group: group,
|
|
version: 'v0',
|
|
datasource: { name: datasource },
|
|
spec: spec,
|
|
},
|
|
|
|
query: {
|
|
new(expr, refId='A'): {
|
|
kind: 'PanelQuery',
|
|
spec: {
|
|
hidden: false,
|
|
refId: refId,
|
|
query: $.dataQuery(
|
|
prometheus.withExpr(expr)
|
|
+ prometheus.withEditorMode('code')
|
|
+ prometheus.withInstant(false)
|
|
+ prometheus.withRange(true)
|
|
+ prometheus.withLegendFormat('__auto')
|
|
),
|
|
},
|
|
},
|
|
withLegendFormat(value): {
|
|
spec+: { query+: { spec+: prometheus.withLegendFormat(value) } },
|
|
},
|
|
withInstant(value=true): {
|
|
spec+: { query+: { spec+: prometheus.withInstant(value) + prometheus.withRange(!value) } },
|
|
},
|
|
withRange(value): { spec+: { query+: { spec+: prometheus.withRange(value) } } },
|
|
withFormat(value): { spec+: { query+: { spec+: prometheus.withFormat(value) } } },
|
|
},
|
|
|
|
grid(items): (grid.withKind() + grid.spec.withItems(items)).spec.layout,
|
|
// Rows of disabled collectors are null, and rows without panels are dropped.
|
|
rows(rows_): (rows.withKind() + rows.spec.withRows([
|
|
row_
|
|
for row_ in rows_
|
|
if row_ != null && std.length(row_.spec.layout.spec.items) > 0
|
|
])).spec.layout,
|
|
tabs(tabs_): (tabs.withKind() + tabs.spec.withTabs(tabs_)).spec.layout,
|
|
place(id, x, y, width, height):
|
|
item.withKind()
|
|
+ item.spec.withElement({ kind: 'ElementReference', name: 'panel-' + id })
|
|
+ item.spec.withX(x)
|
|
+ item.spec.withY(y)
|
|
+ item.spec.withWidth(width)
|
|
+ item.spec.withHeight(height),
|
|
// Places stats side by side across the full row. When the stat count does not
|
|
// divide the 24 columns, the first stats are one column wider.
|
|
statRow(ids, y=0, height=4):
|
|
local present = [id for id in ids if id != null];
|
|
local n = std.length(present);
|
|
local width(i) = std.floor(24 / n) + (if i < 24 % n then 1 else 0);
|
|
local x(i) = std.foldl(function(sum, j) sum + width(j), std.range(0, i - 1), 0);
|
|
[$.place(present[i], x(i), y, width(i), height) for i in std.range(0, n - 1)],
|
|
// Grid of [id, width, height] panels placed left to right, wrapping at 24
|
|
// columns. Panels of disabled collectors are null and take no space.
|
|
flow(panels_):
|
|
local panels = [panel for panel in panels_ if panel != null];
|
|
local step(acc, panel) =
|
|
local wrap = acc.x + panel[1] > 24;
|
|
local x = if wrap then 0 else acc.x;
|
|
local y = if wrap then acc.y + acc.height else acc.y;
|
|
{
|
|
x: x + panel[1],
|
|
y: y,
|
|
height: if wrap then panel[2] else std.max(acc.height, panel[2]),
|
|
items: acc.items + [$.place(panel[0], x, y, panel[1], panel[2])],
|
|
};
|
|
$.grid(std.foldl(step, panels, { x: 0, y: 0, height: 0, items: [] }).items),
|
|
// First row of each tab: the stat row of current values.
|
|
summary(ids): $.row('Summary', $.grid($.statRow(ids))),
|
|
row(title, layout): row.withKind() + row.withSpec({ title: title, collapse: false, layout: layout }),
|
|
// Only panels placed in the layout become dashboard elements, so a layout
|
|
// without the rows of disabled collectors also drops their panels.
|
|
tab(title, layout, panels):
|
|
local placed(layout_) =
|
|
if layout_.kind == 'RowsLayout'
|
|
then std.flattenArrays([placed(row_.spec.layout) for row_ in layout_.spec.rows])
|
|
else [item_.spec.element.name for item_ in layout_.spec.items];
|
|
local names = std.set(placed(layout));
|
|
local elements = { ['panel-' + panel.spec.id]: panel for panel in panels };
|
|
assert std.length(std.setDiff(names, std.objectFields(elements))) == 0 : title + ' places undefined panels: ' + std.join(', ', std.setDiff(names, std.objectFields(elements)));
|
|
{
|
|
elements: { [name]: elements[name] for name in names },
|
|
layout: tab.withKind() + tab.withSpec({ title: title, layout: layout }),
|
|
empty: std.length(names) == 0,
|
|
},
|
|
}
|