mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-17 10:09:05 +02:00
mixin: add a dashboard per tab next to the full dashboard (#2678)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b3d043d9dc
commit
1cdea1f42a
@@ -1,42 +1,34 @@
|
||||
# Keep exceptions scoped to the existing sample dashboard and panels.
|
||||
# Exceptions apply to all sample dashboards; entries without a dashboard
|
||||
# match every dashboard title.
|
||||
exclusions:
|
||||
target-promql-rule:
|
||||
reason: dashboard-linter 0.3.0's v2 adapter omits custom variable current values and options, so the numeric process_top value becomes a vector identifier. These queries are validated against live Prometheus with limits 5, 10 and 20.
|
||||
entries:
|
||||
- dashboard: Windows Exporter
|
||||
panel: Process CPU usage
|
||||
- dashboard: Windows Exporter
|
||||
panel: Process working set
|
||||
- dashboard: Windows Exporter
|
||||
panel: Process private bytes
|
||||
- dashboard: Windows Exporter
|
||||
panel: Process I/O throughput
|
||||
- dashboard: Windows Exporter
|
||||
panel: Process threads
|
||||
- dashboard: Windows Exporter
|
||||
panel: Process handles
|
||||
- dashboard: Windows Exporter
|
||||
panel: Process I/O operations
|
||||
- dashboard: Windows Exporter
|
||||
panel: Process page faults
|
||||
- panel: Process CPU usage
|
||||
- panel: Process working set
|
||||
- panel: Process private bytes
|
||||
- panel: Process I/O throughput
|
||||
- panel: Process threads
|
||||
- panel: Process handles
|
||||
- panel: Process I/O operations
|
||||
- panel: Process page faults
|
||||
target-instance-rule:
|
||||
reason: Fleet panels intentionally span instances; per-host panels use an exact matcher for the single-select instance variable.
|
||||
entries:
|
||||
- dashboard: Windows Exporter
|
||||
- {}
|
||||
template-instance-rule:
|
||||
reason: Per-host tabs require one instance, so the instance variable has neither multi-select nor an All value.
|
||||
entries:
|
||||
- dashboard: Windows Exporter
|
||||
- {}
|
||||
template-job-rule:
|
||||
reason: Grafana expands All to the discovered job values rather than using a custom .+ wildcard.
|
||||
entries:
|
||||
- dashboard: Windows Exporter
|
||||
- {}
|
||||
uneditable-dashboard:
|
||||
reason: This sample dashboard is editable so users can adapt it to their environment.
|
||||
entries:
|
||||
- dashboard: Windows Exporter
|
||||
- {}
|
||||
panel-units-rule:
|
||||
reason: This table displays service names and states; the numeric query value is hidden.
|
||||
entries:
|
||||
- dashboard: Windows Exporter
|
||||
panel: Automatic services not running
|
||||
- panel: Automatic services not running
|
||||
|
||||
+46
-24
@@ -202,16 +202,22 @@ Fork users should override `runbookURLPattern` to match their published URL.
|
||||
|
||||
## Dashboard
|
||||
|
||||
`grafanaDashboards` contains `windows-exporter.json`, the sample dashboard in
|
||||
[dashboard/windows-exporter-dashboard.json](../../dashboard/windows-exporter-dashboard.json).
|
||||
It is a Grafana v2 dashboard with tabs and needs Grafana 13 or later. See the
|
||||
[dashboard documentation](../../dashboard/README.md) for its content.
|
||||
`grafanaDashboards` contains `windows-exporter.json`, the full sample dashboard
|
||||
with all tabs in
|
||||
[dashboard/windows-exporter-dashboard.json](../../dashboard/windows-exporter-dashboard.json),
|
||||
and a shorter dashboard per tab, such as `windows-exporter-cpu.json`, in
|
||||
[dashboard](../../dashboard). They are Grafana v2 dashboards and need Grafana 13
|
||||
or later. See the [dashboard documentation](../../dashboard/README.md) for their
|
||||
content.
|
||||
|
||||
The dashboard uses additive builders with [grafonnet](https://github.com/grafana/grafonnet),
|
||||
pinned in `jsonnetfile.json`:
|
||||
|
||||
- `dashboards/windows-exporter.libsonnet` composes the dashboard, with one source
|
||||
file per tab for its panels, queries, rows, and grid positions.
|
||||
- `dashboards/windows-exporter.libsonnet` composes the full dashboard and the
|
||||
dashboard of each tab, with one source file per tab for its panels, queries,
|
||||
rows, and grid positions. A tab dashboard keeps only the variables its panels
|
||||
reference, directly or through other variables, and the annotations of those
|
||||
variables.
|
||||
- `dashboards/builders.libsonnet` uses Grafonnet's native `apps.dashboard.v2`
|
||||
builders for the dashboard and layout and wraps its Prometheus query builders
|
||||
into the v2 panel/query-group schema.
|
||||
@@ -242,41 +248,56 @@ b.panel.new(200, 'CPU usage', 'timeseries')
|
||||
+ b.panel.withDefaults({ unit: 'percentunit', min: 0, max: 1 })
|
||||
```
|
||||
|
||||
Regenerate the committed file after changing the dashboard source:
|
||||
Regenerate the committed files after changing the dashboard source:
|
||||
|
||||
```sh
|
||||
mise run dashboard
|
||||
```
|
||||
|
||||
This runs `jb install` and writes `../../dashboard/windows-exporter-dashboard.json`.
|
||||
This runs `jb install` and writes `../../dashboard/windows-exporter-*.json`.
|
||||
`mise run dashboards` is an alias. `mise run generate` builds both the rules and the
|
||||
dashboard, while `mise run rules` builds only the rules. The same commands work
|
||||
inside WSL; mise supplies the tool binaries on `PATH`.
|
||||
|
||||
The checked-in JSON remains the reference for the rendered dashboard, including
|
||||
The checked-in JSON remains the reference for the rendered dashboards, including
|
||||
queries, IDs, variables, annotations, transformations, and tab/row layout.
|
||||
`mise run check-dashboards` regenerates it and runs `git diff --exit-code`, displaying
|
||||
any drift. CI runs that check before the mixin tests. The dashboard uses its own
|
||||
`mise run check-dashboards` regenerates them, marks new dashboard files with
|
||||
`git add --intent-to-add` and runs `git diff --exit-code`, displaying any drift. CI runs that check before the mixin tests. The dashboard uses its own
|
||||
variables for selectors; from `_config` it reads only `collectors`,
|
||||
`enableFleetTab`, `enableOverviewTab` and the dashboard metadata below.
|
||||
`enableFleetTab`, `enableOverviewTab`, `enableTabDashboards` and the dashboard
|
||||
metadata below.
|
||||
|
||||
### Metadata
|
||||
|
||||
`dashboardUID`, `dashboardTitle`, `dashboardDescription` and `dashboardTags`
|
||||
set the dashboard's UID (`metadata.name`), title, description and tags. Give a
|
||||
customized dashboard its own UID so it can be imported next to the sample
|
||||
dashboard instead of replacing it:
|
||||
Every dashboard has a readable UID (`metadata.name`) built from
|
||||
`dashboardUIDPrefix`, and a title built from `dashboardTitlePrefix`:
|
||||
|
||||
| Dashboard | UID | Title |
|
||||
| --- | --- | --- |
|
||||
| Full, all tabs | `windows-exporter-full` | Windows Exporter / Full |
|
||||
| One tab | `windows-exporter-<tab>`, for example `windows-exporter-cpu` or `windows-exporter-scheduled-tasks` | Windows Exporter / `<tab>` |
|
||||
|
||||
`dashboardUID`, `dashboardTitle` and `dashboardDescription` override the UID,
|
||||
title and description of the full dashboard. `dashboardTags` sets the tags of
|
||||
all dashboards; each tab dashboard links to the others through a dropdown of
|
||||
dashboards with these tags, keeping the time range and variables. Set
|
||||
`enableTabDashboards: false` to render only the full dashboard. Give customized
|
||||
dashboards their own UIDs so they can be imported next to the sample dashboards
|
||||
instead of replacing them:
|
||||
|
||||
```jsonnet
|
||||
(import 'mixin.libsonnet') {
|
||||
_config+:: {
|
||||
dashboardUID: 'windows-compact',
|
||||
dashboardTitle: 'Windows (compact)',
|
||||
dashboardUIDPrefix: 'windows-compact',
|
||||
dashboardTitlePrefix: 'Windows (compact)',
|
||||
dashboardTags+: ['compact'],
|
||||
},
|
||||
}.grafanaDashboards['windows-exporter.json']
|
||||
}.grafanaDashboards
|
||||
```
|
||||
|
||||
In the Fleet dashboard, a hostname links to the Overview dashboard; in the full
|
||||
dashboard, it links to the Overview tab.
|
||||
|
||||
### Collectors
|
||||
|
||||
`_config.collectors` lists the windows_exporter collectors by their exporter
|
||||
@@ -309,7 +330,7 @@ dashboard with only the collector tabs and the Exporter tab, turn them off:
|
||||
}.grafanaDashboards['windows-exporter.json']
|
||||
```
|
||||
|
||||
The committed sample dashboard enables every collector. The Fleet hosts table
|
||||
The committed sample dashboards enable every collector. The Fleet hosts table
|
||||
always queries the default collectors; its columns stay empty for collectors
|
||||
that are turned off. Tab sources are
|
||||
functions of `on(collector)`; return `null` instead of a row, a `[id, width,
|
||||
@@ -325,8 +346,9 @@ Install Python 3, run `mise install`, then run:
|
||||
mise run test
|
||||
```
|
||||
|
||||
This checks Jsonnet formatting, checks that the committed dashboard is up to
|
||||
date, runs `dashboard-linter lint --strict`, validates generated rules with
|
||||
This checks Jsonnet formatting, runs `jsonnet-lint` on the Jsonnet sources to
|
||||
find unused variables and references to missing fields, checks that the
|
||||
committed dashboards are up to date, runs `dashboard-linter lint --strict`, validates generated rules with
|
||||
`promtool`, runs `pint --offline lint`, and evaluates the rules against synthetic
|
||||
time series.
|
||||
Tests cover all 25 recordings, alert timing and recovery, threshold
|
||||
@@ -347,8 +369,8 @@ committed dashboard JSON or its own workflow file, and can also be started
|
||||
manually. It caches pinned tool binaries and runs the same `mise run test` checks
|
||||
used locally.
|
||||
|
||||
Run `mise run lint-dashboards` to lint just the committed dashboard. `.dashboard-lint.yaml`
|
||||
documents exceptions for the sample dashboard's single-host selection, fleet
|
||||
Run `mise run lint-dashboards` to lint just the committed dashboards. `.dashboard-lint.yaml`
|
||||
documents exceptions for the sample dashboards' single-host selection, fleet
|
||||
queries, editable state, and the service table without numeric units. All panels
|
||||
include descriptions; missing titles or descriptions fail validation, including
|
||||
warnings.
|
||||
|
||||
@@ -11,11 +11,19 @@
|
||||
// Requires the time collector with its ntp subcollector enabled.
|
||||
enableTime: false,
|
||||
|
||||
// Grafana dashboard metadata. dashboardUID is the v2 metadata.name; change
|
||||
// it to import the dashboard next to the sample one.
|
||||
dashboardUID: 'Kdaassddw',
|
||||
dashboardTitle: 'Windows Exporter',
|
||||
// Grafana dashboard metadata. A UID is the v2 metadata.name; change the
|
||||
// UIDs to import the dashboards next to the sample ones.
|
||||
// Every tab also renders as a dashboard of its own with the UID
|
||||
// <dashboardUIDPrefix>-<tab> and the title "<dashboardTitlePrefix> / <tab>",
|
||||
// for example windows-exporter-cpu and "Windows Exporter / CPU".
|
||||
enableTabDashboards: true,
|
||||
dashboardUIDPrefix: 'windows-exporter',
|
||||
dashboardTitlePrefix: 'Windows Exporter',
|
||||
// The full dashboard has all tabs.
|
||||
dashboardUID: self.dashboardUIDPrefix + '-full',
|
||||
dashboardTitle: self.dashboardTitlePrefix + ' / Full',
|
||||
dashboardDescription: 'Fleet overview and per-host details for Windows hosts monitored by windows_exporter.',
|
||||
// The dashboards link to each other through their tags.
|
||||
dashboardTags: ['prometheus', 'windows', 'windows_exporter'],
|
||||
|
||||
// Dashboard tabs that don't belong to a single collector. Turn them off
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
// Renders dashboard/windows-exporter-dashboard.json. Run with jsonnet -J vendor -S.
|
||||
// The sample dashboard shows every collector; mixin users select theirs with
|
||||
// _config.collectors.
|
||||
// Renders the sample dashboards into ../../dashboard. Run with
|
||||
// jsonnet -J vendor -S -m <directory>. The sample dashboards show every
|
||||
// collector; mixin users select theirs with _config.collectors.
|
||||
local manifest = import 'lib/manifest.libsonnet';
|
||||
local mixin = (import 'mixin.libsonnet') + {
|
||||
_config+:: { collectors: { [collector]: true for collector in std.objectFields(super.collectors) } },
|
||||
};
|
||||
|
||||
(import 'lib/manifest.libsonnet')(mixin.grafanaDashboards['windows-exporter.json'])
|
||||
// The full dashboard keeps the file name it had before the tab dashboards.
|
||||
{
|
||||
[if name == 'windows-exporter.json' then 'windows-exporter-dashboard.json' else name]: manifest(mixin.grafanaDashboards[name])
|
||||
for name in std.objectFields(mixin.grafanaDashboards)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
grafanaDashboards+:: {
|
||||
'windows-exporter.json': (import 'windows-exporter.libsonnet')($._config),
|
||||
},
|
||||
local dashboards = (import 'windows-exporter.libsonnet')($._config),
|
||||
grafanaDashboards+:: { 'windows-exporter.json': dashboards.full } + dashboards.tabs,
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ local volumeFree = '100 * windows_logical_disk_free_bytes{job=~"$job", volume!~"
|
||||
local attention = [{ color: 'green', value: null }, { color: 'orange', value: 1 }];
|
||||
|
||||
// config supplies the alert thresholds, so a counter turns orange when the matching alert condition holds.
|
||||
function(on, config)
|
||||
// Hostnames link to the Overview tab of this dashboard, or to the dashboard with
|
||||
// the UID overview when it is given. Without the Overview tab, they link nowhere.
|
||||
function(on, config, overview=null)
|
||||
b.tab(
|
||||
'Fleet',
|
||||
b.rows([
|
||||
@@ -171,23 +173,27 @@ function(on, config)
|
||||
options: 'hostname',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'links',
|
||||
value: [
|
||||
{
|
||||
title: 'Show details for ${__data.fields.hostname}',
|
||||
url: '/d/${__dashboard.uid}?${__url_time_range}&${datasource:queryparam}&${job:queryparam}&var-hostname=$__all&var-instance=${__data.fields.instance}&dtab=Overview',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'custom.width',
|
||||
value: 180,
|
||||
},
|
||||
{
|
||||
id: 'custom.filterable',
|
||||
value: true,
|
||||
},
|
||||
property
|
||||
for property in [
|
||||
if config.enableOverviewTab then {
|
||||
id: 'links',
|
||||
value: [
|
||||
{
|
||||
title: 'Show details for ${__data.fields.hostname}',
|
||||
url: '/d/' + (if overview == null then '${__dashboard.uid}' else overview) + '?${__url_time_range}&${datasource:queryparam}&${job:queryparam}&var-hostname=$__all&var-instance=${__data.fields.instance}' + (if overview == null then '&dtab=Overview' else ''),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'custom.width',
|
||||
value: 180,
|
||||
},
|
||||
{
|
||||
id: 'custom.filterable',
|
||||
value: true,
|
||||
},
|
||||
]
|
||||
if property != null
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -70,9 +70,6 @@ function(on) [variable for variable in [
|
||||
new('instance', 'label_values(windows_os_hostname{job=~"$job", hostname=~"$hostname"}, instance)')
|
||||
+ query.spec.withLabel('Instance')
|
||||
,
|
||||
new('show_hostname', 'label_values(windows_os_hostname{job=~"$job", instance="$instance"}, hostname)')
|
||||
+ query.spec.withHide('hideVariable')
|
||||
,
|
||||
if on('logical_disk') then new('volume', 'label_values(windows_logical_disk_size_bytes{job=~"$job", instance="$instance"}, volume)')
|
||||
+ query.spec.withCurrent({
|
||||
text: [
|
||||
|
||||
@@ -1,10 +1,42 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
local dashboard = b.dashboard;
|
||||
|
||||
// config.collectors selects the collectors the dashboard renders,
|
||||
// Descriptions of the dashboards of single tabs, by tab title.
|
||||
local descriptions = {
|
||||
Fleet: 'Health, utilization and stopped services of all Windows hosts monitored by windows_exporter.',
|
||||
Overview: 'Operating system, uptime, CPU, memory, volumes, disk and network throughput of one Windows host.',
|
||||
CPU: 'CPU utilization by mode and core, scheduling, system calls and frequency of one Windows host.',
|
||||
Memory: 'Physical memory, commit charge, paging and kernel memory of one Windows host.',
|
||||
Disk: 'Volumes, physical disks, drives and Storage Spaces of one Windows host.',
|
||||
Network: 'Interface throughput, errors and discards, TCP and UDP of one Windows host.',
|
||||
SMB: 'SMB shares served and used by one Windows host.',
|
||||
Processes: 'CPU, memory, I/O, threads and handles per process of one Windows host.',
|
||||
Services: 'Service states and automatic services that are not running on one Windows host.',
|
||||
'Scheduled tasks': 'Scheduled task states, results and missed runs of one Windows host.',
|
||||
Updates: 'Pending Windows updates and the last update check of one Windows host.',
|
||||
GPU: 'GPU utilization, memory, sensors and top processes of one Windows host.',
|
||||
'Hyper-V': 'Hyper-V virtual machines, virtual disks, switches and network adapters of one Windows host.',
|
||||
Time: 'Clock synchronization, NTP sources and clock offset of one Windows host.',
|
||||
Exporter: 'Scrape status, collector health and resource usage of windows_exporter on one host.',
|
||||
};
|
||||
|
||||
local wordChar(c) = std.member('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_', c);
|
||||
// The leading identifier of text.
|
||||
local identifier(text) =
|
||||
local chars = std.stringChars(std.substr(text, 0, 32));
|
||||
local length = std.foldl(function(acc, c) if acc.done || !wordChar(c) then acc { done: true } else acc { n+: 1 }, chars, { n: 0, done: false }).n;
|
||||
std.substr(text, 0, length);
|
||||
// The variable names that value references as $name or ${name...}.
|
||||
local references(value) = std.set([
|
||||
identifier(if std.startsWith(part, '{') then std.substr(part, 1, 33) else part)
|
||||
for part in std.split(std.manifestJsonMinified(value), '$')[1:]
|
||||
]);
|
||||
|
||||
// config.collectors selects the collectors the dashboards render,
|
||||
// config.enableFleetTab and config.enableOverviewTab the tabs across
|
||||
// collectors, and config.dashboard* its UID, title, description and tags, see
|
||||
// config.libsonnet.
|
||||
// collectors, and config.dashboard* their UIDs, titles, description and tags,
|
||||
// see config.libsonnet. Returns the full dashboard with all tabs and, with
|
||||
// config.enableTabDashboards, a dashboard per tab by its file name.
|
||||
function(config)
|
||||
local known = std.objectFields((import '../config.libsonnet')._config.collectors);
|
||||
local unknown = std.setDiff(std.objectFields(config.collectors), known);
|
||||
@@ -15,10 +47,14 @@ function(config)
|
||||
std.get(config.collectors, collector, false);
|
||||
// All variables select hosts through windows_os_hostname.
|
||||
assert on('os') : 'The dashboard requires the os collector';
|
||||
local tabs = [
|
||||
local slug(title) = std.strReplace(std.asciiLower(title), ' ', '-');
|
||||
local uid(title) = config.dashboardUIDPrefix + '-' + slug(title);
|
||||
// overview is the UID of the Overview dashboard that Fleet links hosts to,
|
||||
// or null for the Overview tab of the same dashboard.
|
||||
local tabs(overview) = [
|
||||
tab
|
||||
for tab in [
|
||||
if config.enableFleetTab then (import 'fleet.libsonnet')(on, config),
|
||||
if config.enableFleetTab then (import 'fleet.libsonnet')(on, config, overview),
|
||||
if config.enableOverviewTab then (import 'overview.libsonnet')(on),
|
||||
(import 'cpu.libsonnet')(on),
|
||||
(import 'memory.libsonnet')(on),
|
||||
@@ -36,51 +72,113 @@ function(config)
|
||||
]
|
||||
if tab != null && !tab.empty
|
||||
];
|
||||
local fullTabs = tabs(null);
|
||||
|
||||
local panelNames = std.flattenArrays([std.objectFields(tab.elements) for tab in tabs]);
|
||||
local panelNames = std.flattenArrays([std.objectFields(tab.elements) for tab in fullTabs]);
|
||||
assert std.length(panelNames) == std.length(std.set(panelNames)) : 'Panel IDs must be unique across tabs';
|
||||
|
||||
dashboard.new(config.dashboardUID, config.dashboardTitle)
|
||||
+ dashboard.spec.withCursorSync('Crosshair')
|
||||
+ dashboard.spec.withDescription(config.dashboardDescription)
|
||||
+ dashboard.spec.withEditable(true)
|
||||
+ dashboard.spec.withLinks([
|
||||
{
|
||||
asDropdown: false,
|
||||
icon: 'doc',
|
||||
includeVars: false,
|
||||
keepTime: false,
|
||||
tags: [],
|
||||
targetBlank: true,
|
||||
title: 'windows_exporter',
|
||||
tooltip: '',
|
||||
type: 'link',
|
||||
url: 'https://github.com/prometheus-community/windows_exporter',
|
||||
},
|
||||
])
|
||||
+ dashboard.spec.withLiveNow(false)
|
||||
+ dashboard.spec.withPreload(false)
|
||||
+ dashboard.spec.withTags(config.dashboardTags)
|
||||
+ dashboard.spec.withTimeSettings({
|
||||
autoRefresh: '1m',
|
||||
autoRefreshIntervals: [
|
||||
'5s',
|
||||
'10s',
|
||||
'30s',
|
||||
'1m',
|
||||
'5m',
|
||||
'15m',
|
||||
'30m',
|
||||
'1h',
|
||||
'2h',
|
||||
'1d',
|
||||
],
|
||||
fiscalYearStartMonth: 0,
|
||||
from: 'now-3h',
|
||||
hideTimepicker: false,
|
||||
to: 'now',
|
||||
})
|
||||
+ dashboard.spec.withAnnotations((import 'annotations.libsonnet')(on))
|
||||
+ dashboard.spec.withVariables((import 'variables.libsonnet')(on))
|
||||
+ dashboard.spec.withElements(std.foldl(function(elements, tab) elements + tab.elements, tabs, {}))
|
||||
+ dashboard.spec.withLayout(b.tabs([tab.layout for tab in tabs]))
|
||||
local variables = (import 'variables.libsonnet')(on);
|
||||
local annotations = (import 'annotations.libsonnet')(on);
|
||||
|
||||
// Tab dashboards link to each other through a dropdown of the dashboards
|
||||
// with the same tags; the full dashboard has the tabs instead.
|
||||
local new(uid, title, description, elements, layout, dropdown=true) =
|
||||
// Grafana rejects longer dashboard UIDs.
|
||||
assert std.length(uid) <= 40 : 'Dashboard UID ' + uid + ' is longer than 40 characters; shorten dashboardUIDPrefix or dashboardUID';
|
||||
// Keeps the variables that the panels reference, directly or through
|
||||
// other variables, and the annotations whose variables are kept.
|
||||
// Variables only reference variables defined before them.
|
||||
local kept = std.foldl(
|
||||
function(used, variable)
|
||||
if std.setMember(variable.spec.name, used)
|
||||
then std.setUnion(used, references(variable))
|
||||
else used,
|
||||
std.reverse(variables),
|
||||
references(elements),
|
||||
);
|
||||
local dropped = std.set([variable.spec.name for variable in variables if !std.setMember(variable.spec.name, kept)]);
|
||||
dashboard.new(uid, title)
|
||||
+ dashboard.spec.withCursorSync('Crosshair')
|
||||
+ dashboard.spec.withDescription(description)
|
||||
+ dashboard.spec.withEditable(true)
|
||||
+ dashboard.spec.withLinks([
|
||||
link
|
||||
for link in [
|
||||
if dropdown then {
|
||||
asDropdown: true,
|
||||
icon: 'external link',
|
||||
includeVars: true,
|
||||
keepTime: true,
|
||||
tags: config.dashboardTags,
|
||||
targetBlank: false,
|
||||
title: config.dashboardTitlePrefix,
|
||||
tooltip: 'Dashboards for windows_exporter',
|
||||
type: 'dashboards',
|
||||
url: '',
|
||||
},
|
||||
{
|
||||
asDropdown: false,
|
||||
icon: 'doc',
|
||||
includeVars: false,
|
||||
keepTime: false,
|
||||
tags: [],
|
||||
targetBlank: true,
|
||||
title: 'windows_exporter',
|
||||
tooltip: '',
|
||||
type: 'link',
|
||||
url: 'https://github.com/prometheus-community/windows_exporter',
|
||||
},
|
||||
]
|
||||
if link != null
|
||||
])
|
||||
+ dashboard.spec.withLiveNow(false)
|
||||
+ dashboard.spec.withPreload(false)
|
||||
+ dashboard.spec.withTags(config.dashboardTags)
|
||||
+ dashboard.spec.withTimeSettings({
|
||||
autoRefresh: '1m',
|
||||
autoRefreshIntervals: [
|
||||
'5s',
|
||||
'10s',
|
||||
'30s',
|
||||
'1m',
|
||||
'5m',
|
||||
'15m',
|
||||
'30m',
|
||||
'1h',
|
||||
'2h',
|
||||
'1d',
|
||||
],
|
||||
fiscalYearStartMonth: 0,
|
||||
from: 'now-3h',
|
||||
hideTimepicker: false,
|
||||
to: 'now',
|
||||
})
|
||||
+ dashboard.spec.withAnnotations([
|
||||
annotation
|
||||
for annotation in annotations
|
||||
if std.length(std.setInter(references(annotation), dropped)) == 0
|
||||
])
|
||||
+ dashboard.spec.withVariables([variable for variable in variables if std.setMember(variable.spec.name, kept)])
|
||||
+ dashboard.spec.withElements(elements)
|
||||
+ dashboard.spec.withLayout(layout);
|
||||
|
||||
{
|
||||
full: new(
|
||||
config.dashboardUID,
|
||||
config.dashboardTitle,
|
||||
config.dashboardDescription,
|
||||
std.foldl(function(elements, tab) elements + tab.elements, fullTabs, {}),
|
||||
b.tabs([tab.layout for tab in fullTabs]),
|
||||
dropdown=false,
|
||||
),
|
||||
tabs: if config.enableTabDashboards then {
|
||||
['windows-exporter-' + slug(tab.layout.spec.title) + '.json']: new(
|
||||
uid(tab.layout.spec.title),
|
||||
config.dashboardTitlePrefix + ' / ' + tab.layout.spec.title,
|
||||
descriptions[tab.layout.spec.title],
|
||||
tab.elements,
|
||||
tab.layout.spec.layout,
|
||||
)
|
||||
for tab in tabs(if config.enableOverviewTab then uid('Overview'))
|
||||
} else {},
|
||||
}
|
||||
|
||||
+32
-12
@@ -8,7 +8,7 @@
|
||||
[vars]
|
||||
sources = "*.libsonnet *.jsonnet lib/*.libsonnet rules/*.libsonnet alerts/*.libsonnet dashboards/*.libsonnet tests/*.libsonnet tests/*.jsonnet"
|
||||
fmt_flags = "-n 2 --max-blank-lines 2 --string-style s --comment-style s"
|
||||
dashboard = "../../dashboard/windows-exporter-dashboard.json"
|
||||
dashboards = "../../dashboard"
|
||||
|
||||
[tasks.generate]
|
||||
description = "Generate Prometheus rules, alerts, and the dashboard"
|
||||
@@ -27,24 +27,35 @@ description = "Install dashboard Jsonnet libraries"
|
||||
run = "jb install"
|
||||
|
||||
[tasks.dashboard]
|
||||
description = "Regenerate the committed Grafana dashboard"
|
||||
description = "Regenerate the committed Grafana dashboards"
|
||||
alias = "dashboards"
|
||||
depends = ["vendor"]
|
||||
run = '''
|
||||
set -eu
|
||||
trap 'rm -f {{vars.dashboard}}.tmp' EXIT
|
||||
jsonnet -J vendor -S dashboard.jsonnet > {{vars.dashboard}}.tmp
|
||||
mv {{vars.dashboard}}.tmp {{vars.dashboard}}
|
||||
out=$(mktemp -d)
|
||||
trap 'rm -rf "$out"' EXIT
|
||||
jsonnet -J vendor -S -m "$out" dashboard.jsonnet > /dev/null
|
||||
rm -f {{vars.dashboards}}/windows-exporter-*.json
|
||||
cp "$out"/*.json {{vars.dashboards}}/
|
||||
'''
|
||||
|
||||
[tasks.check-dashboards]
|
||||
description = "Regenerate the dashboard and fail on uncommitted changes"
|
||||
description = "Regenerate the dashboards and fail on uncommitted changes"
|
||||
depends = ["dashboard"]
|
||||
run = "git diff --exit-code -- {{vars.dashboard}}"
|
||||
run = '''
|
||||
set -eu
|
||||
git add --intent-to-add -- {{vars.dashboards}}/windows-exporter-*.json
|
||||
git diff --exit-code -- {{vars.dashboards}}
|
||||
'''
|
||||
|
||||
[tasks.lint-dashboards]
|
||||
description = "Lint the committed dashboard in strict mode"
|
||||
run = "dashboard-linter lint --strict --config .dashboard-lint.yaml {{vars.dashboard}}"
|
||||
description = "Lint the committed dashboards in strict mode"
|
||||
run = '''
|
||||
set -eu
|
||||
for file in {{vars.dashboards}}/windows-exporter-*.json; do
|
||||
dashboard-linter lint --strict --config .dashboard-lint.yaml "$file"
|
||||
done
|
||||
'''
|
||||
|
||||
[tasks.fmt]
|
||||
description = "Format all mixin Jsonnet sources"
|
||||
@@ -59,15 +70,24 @@ run = [
|
||||
]
|
||||
|
||||
[tasks.lint]
|
||||
description = "Check Jsonnet formatting, dashboard drift, and Prometheus rules"
|
||||
description = "Check Jsonnet formatting and lint, dashboard drift, and Prometheus rules"
|
||||
depends = ["rules", "custom-rules", "vendor", "lint-dashboards"]
|
||||
run = '''
|
||||
set -eu
|
||||
for file in {{vars.sources}}; do
|
||||
jsonnetfmt {{vars.fmt_flags}} "$file" | diff -u "$file" -
|
||||
jsonnet-lint -J vendor "$file"
|
||||
done
|
||||
out=$(mktemp -d)
|
||||
trap 'rm -rf "$out"' EXIT
|
||||
jsonnet -J vendor -S -m "$out" dashboard.jsonnet > /dev/null
|
||||
(cd "$out" && ls windows-exporter-*.json) > "$out/generated"
|
||||
(cd {{vars.dashboards}} && ls windows-exporter-*.json) | diff -u "$out/generated" - \
|
||||
|| { echo "{{vars.dashboards}} has missing or stale dashboards; run mise run dashboard"; exit 1; }
|
||||
for file in $(cat "$out/generated"); do
|
||||
diff -u {{vars.dashboards}}/"$file" "$out/$file" \
|
||||
|| { echo "{{vars.dashboards}}/$file is out of date; run mise run dashboard"; exit 1; }
|
||||
done
|
||||
jsonnet -J vendor -S dashboard.jsonnet | diff -u {{vars.dashboard}} - \
|
||||
|| { echo "{{vars.dashboard}} is out of date; run mise run dashboard"; exit 1; }
|
||||
promtool check rules windows_rules.yaml windows_alerts.yaml tests/custom_rules.yaml tests/custom_alerts.yaml
|
||||
pint --offline lint windows_rules.yaml windows_alerts.yaml
|
||||
pint --offline lint tests/custom_rules.yaml tests/custom_alerts.yaml
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Renders the dashboard for several collector selections and checks that every
|
||||
// layout reference has an element, every element is placed and no tab or row is
|
||||
// empty. Run with jsonnet -J vendor tests/dashboard.test.jsonnet.
|
||||
// Renders the dashboards for several collector selections and checks that every
|
||||
// layout reference has an element, every element is placed, no tab or row is
|
||||
// empty and every tab dashboard matches its tab. Run with
|
||||
// jsonnet -J vendor tests/dashboard.test.jsonnet.
|
||||
local mixin = import '../mixin.libsonnet';
|
||||
|
||||
local render(collectors, config={}) =
|
||||
@@ -55,6 +56,59 @@ assert metadata.spec.title == 'Custom title' : 'metadata: got title ' + metadata
|
||||
assert metadata.spec.description == 'Custom description' : 'metadata: got description ' + metadata.spec.description;
|
||||
assert metadata.spec.tags == ['prometheus', 'windows', 'windows_exporter', 'custom'] : 'metadata: got tags ' + std.manifestJson(metadata.spec.tags);
|
||||
|
||||
// Every tab renders as a dashboard of its own with the panels and rows of that
|
||||
// tab and only the variables its panels use.
|
||||
local dashboards(collectors, config={}) = (mixin { _config+:: { collectors: collectors(super.collectors) } + config }).grafanaDashboards;
|
||||
local defaults = dashboards(function(collectors) collectors);
|
||||
local full = defaults['windows-exporter.json'];
|
||||
assert full.metadata.name == 'windows-exporter-full' : 'full: got UID ' + full.metadata.name;
|
||||
assert full.spec.title == 'Windows Exporter / Full' : 'full: got title ' + full.spec.title;
|
||||
assert std.objectFields(defaults) == [
|
||||
'windows-exporter-cpu.json',
|
||||
'windows-exporter-disk.json',
|
||||
'windows-exporter-exporter.json',
|
||||
'windows-exporter-fleet.json',
|
||||
'windows-exporter-memory.json',
|
||||
'windows-exporter-network.json',
|
||||
'windows-exporter-overview.json',
|
||||
'windows-exporter-services.json',
|
||||
'windows-exporter.json',
|
||||
] : 'tab dashboards: got ' + std.join(', ', std.objectFields(defaults));
|
||||
assert std.all([
|
||||
local tab = std.filter(function(tab) tab.spec.title == dashboard.spec.title[std.length('Windows Exporter / '):], full.spec.layout.spec.tabs)[0];
|
||||
dashboard.spec.layout == tab.spec.layout
|
||||
&& std.objectFields(dashboard.spec.elements) == std.set(placed(tab.spec.layout))
|
||||
&& std.startsWith(dashboard.metadata.name, 'windows-exporter-')
|
||||
for dashboard in [defaults[name] for name in std.objectFields(defaults) if name != 'windows-exporter.json']
|
||||
]) : 'A tab dashboard differs from its tab';
|
||||
local linkTypes(dashboard) = [link.type for link in dashboard.spec.links];
|
||||
assert linkTypes(full) == ['link'] : 'full: got links ' + std.join(', ', linkTypes(full));
|
||||
assert linkTypes(defaults['windows-exporter-cpu.json']) == ['dashboards', 'link'] : 'cpu: got links ' + std.join(', ', linkTypes(defaults['windows-exporter-cpu.json']));
|
||||
local variableNames(dashboard) = [variable.spec.name for variable in dashboard.spec.variables];
|
||||
local annotationNames(dashboard) = [annotation.spec.name for annotation in dashboard.spec.annotations];
|
||||
local fleet = defaults['windows-exporter-fleet.json'];
|
||||
assert variableNames(fleet) == ['datasource', 'job', 'hostname'] : 'fleet: got variables ' + std.join(', ', variableNames(fleet));
|
||||
assert annotationNames(fleet) == ['Annotations & Alerts'] : 'fleet: got annotations ' + std.join(', ', annotationNames(fleet));
|
||||
assert std.length(std.findSubstr('/d/windows-exporter-overview?', std.manifestJson(fleet.spec.elements))) == 1 : 'fleet: hosts do not link to the Overview dashboard';
|
||||
assert std.length(std.findSubstr('dtab=Overview', std.manifestJson(full.spec.elements))) == 1 : 'full: hosts do not link to the Overview tab';
|
||||
// Without the Overview tab, hostnames in Fleet link nowhere.
|
||||
local withoutOverview = dashboards(function(collectors) collectors, { enableOverviewTab: false });
|
||||
assert std.all([
|
||||
std.length(std.findSubstr('Show details for', std.manifestJson(dashboard.spec.elements))) == 0
|
||||
for dashboard in [withoutOverview['windows-exporter.json'], withoutOverview['windows-exporter-fleet.json']]
|
||||
]) : 'fleet without overview: hostnames link to a missing Overview';
|
||||
assert !std.objectHas(withoutOverview, 'windows-exporter-overview.json') : 'fleet without overview: got an Overview dashboard';
|
||||
local cpu = defaults['windows-exporter-cpu.json'];
|
||||
assert variableNames(cpu) == ['datasource', 'job', 'hostname', 'instance'] : 'cpu: got variables ' + std.join(', ', variableNames(cpu));
|
||||
assert annotationNames(cpu) == ['Annotations & Alerts', 'Reboots'] : 'cpu: got annotations ' + std.join(', ', annotationNames(cpu));
|
||||
local processes = dashboards(only({ process: true }))['windows-exporter-processes.json'];
|
||||
assert variableNames(processes) == ['datasource', 'job', 'hostname', 'instance', 'process', 'process_top'] : 'processes: got variables ' + std.join(', ', variableNames(processes));
|
||||
local custom = dashboards(function(collectors) collectors, { dashboardUIDPrefix: 'win', dashboardTitlePrefix: 'Windows' });
|
||||
assert custom['windows-exporter-cpu.json'].metadata.name == 'win-cpu' : 'custom prefix: got UID ' + custom['windows-exporter-cpu.json'].metadata.name;
|
||||
assert custom['windows-exporter-cpu.json'].spec.title == 'Windows / CPU' : 'custom prefix: got title ' + custom['windows-exporter-cpu.json'].spec.title;
|
||||
assert custom['windows-exporter.json'].metadata.name == 'win-full' : 'custom prefix: got full UID ' + custom['windows-exporter.json'].metadata.name;
|
||||
assert std.objectFields(dashboards(function(collectors) collectors, { enableTabDashboards: false })) == ['windows-exporter.json'] : 'enableTabDashboards: false renders tab dashboards';
|
||||
|
||||
assert std.length(std.objectFields(results.all)) == 15 : 'all: got ' + std.join(', ', std.objectFields(results.all));
|
||||
assert expect('defaults', {
|
||||
CPU: ['Summary', 'Utilization', 'Scheduling', 'System calls and frequency'],
|
||||
|
||||
Reference in New Issue
Block a user