mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-17 10:09:05 +02:00
dashboard: add stat rows, sections, new tabs and collector selection (#2647)
Co-authored-by: Claude Opus 5.5 <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
a31fae00d1
commit
798570e9aa
@@ -168,6 +168,10 @@ Read [`contrib/mixin/README.md`](contrib/mixin/README.md) and
|
||||
Edit the Jsonnet/Grafonnet source under `contrib/mixin/dashboards`, including its
|
||||
tab modules and shared builders, and regenerate
|
||||
`dashboard/windows-exporter-dashboard.json`. Do not hand-edit generated JSON.
|
||||
Gate every tab, row, panel and variable on the collectors it queries with
|
||||
`on('<collector>')` (names from `_config.collectors`), and run
|
||||
`jsonnet -J vendor tests/dashboard.test.jsonnet` to check the layout for several
|
||||
collector selections.
|
||||
|
||||
From `contrib/mixin`, install the pinned tools with `make tools`, ensure they
|
||||
are on `PATH`, then run `make fmt`, `make generate`, `make check-dashboards` and
|
||||
|
||||
+35
-1
@@ -215,6 +215,12 @@ pinned in `jsonnetfile.json`:
|
||||
- `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.
|
||||
- Each tab is a list of rows. `b.flow` places `[id, width, height]` panels left
|
||||
to right and wraps at 24 columns, so rows need no hand-written coordinates.
|
||||
- `b.stat` builds the current values in the first row of each per-host tab
|
||||
(`b.summary`), with a sparkline of the time range behind each value. Only
|
||||
threshold steps (`b.levels(warn, crit)`) add color, so green, orange and red
|
||||
always mean health; informational values keep the text color.
|
||||
- `dashboards/styles.libsonnet` shares the visualization defaults; individual
|
||||
panels add their differences using `withDefaults`, `withOptions`, and
|
||||
`withOverrides`. Explicit nulls are preserved and arrays are replaced whole.
|
||||
@@ -251,7 +257,35 @@ The checked-in JSON remains the reference for the rendered dashboard, 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
|
||||
variables and does not read `_config`.
|
||||
variables for selectors; from `_config` it reads only `collectors`.
|
||||
|
||||
### Collectors
|
||||
|
||||
`_config.collectors` lists the windows_exporter collectors by their exporter
|
||||
names, such as `cpu`, `physical_disk`, `diskdrive`, `smb` or `update`. The
|
||||
dashboard renders only the tabs, rows, panels and variables of enabled
|
||||
collectors, so a tab for a collector you do not run does not appear. The
|
||||
defaults match the exporter's default collectors; `time` follows `enableTime`.
|
||||
The `os` collector is required, because the variables select hosts through
|
||||
`windows_os_hostname`. An unknown collector name fails the build. Enable
|
||||
optional collectors on top of the defaults with `collectors+:`; replacing the
|
||||
object with `collectors:` enables only the listed collectors:
|
||||
|
||||
```jsonnet
|
||||
(import 'mixin.libsonnet') {
|
||||
_config+:: {
|
||||
collectors+: { diskdrive: true, process: true, scheduled_task: true, update: true },
|
||||
},
|
||||
}.grafanaDashboards['windows-exporter.json']
|
||||
```
|
||||
|
||||
The committed sample dashboard enables 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,
|
||||
height]` panel or a stat ID to leave it out. Panels that no row places are not
|
||||
rendered, and tabs without rows are dropped. `tests/dashboard.test.jsonnet`
|
||||
renders several collector selections and checks the layout.
|
||||
|
||||
## Validate
|
||||
|
||||
|
||||
@@ -11,6 +11,31 @@
|
||||
// Requires the time collector with its ntp subcollector enabled.
|
||||
enableTime: false,
|
||||
|
||||
// Collectors enabled on the windows_exporter targets, by collector name.
|
||||
// The dashboard only renders the tabs, rows and panels of enabled
|
||||
// collectors. The defaults match the exporter's default collectors.
|
||||
collectors: {
|
||||
cpu: true,
|
||||
diskdrive: false,
|
||||
gpu: false,
|
||||
hyperv: false,
|
||||
logical_disk: true,
|
||||
memory: true,
|
||||
net: true,
|
||||
os: true,
|
||||
physical_disk: true,
|
||||
process: false,
|
||||
scheduled_task: false,
|
||||
service: true,
|
||||
smb: false,
|
||||
smbclient: false,
|
||||
system: true,
|
||||
tcp: false,
|
||||
time: $._config.enableTime,
|
||||
udp: false,
|
||||
update: false,
|
||||
},
|
||||
|
||||
runbookURLPattern: 'https://prometheus-community.github.io/windows_exporter/runbooks/%s/',
|
||||
|
||||
cpuHighUsageThreshold: 90,
|
||||
|
||||
@@ -1,2 +1,8 @@
|
||||
// Renders dashboard/windows-exporter-dashboard.json. Run with jsonnet -J vendor -S.
|
||||
(import 'lib/manifest.libsonnet')((import 'mixin.libsonnet').grafanaDashboards['windows-exporter.json'])
|
||||
// The sample dashboard shows every collector; mixin users select theirs with
|
||||
// _config.collectors.
|
||||
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'])
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
local annotation = b.dashboard.spec.annotations;
|
||||
|
||||
[
|
||||
// The Reboots annotation needs the system collector.
|
||||
function(on) [annotation for annotation in [
|
||||
annotation.withKind()
|
||||
+ annotation.spec.withBuiltIn(true)
|
||||
+ annotation.spec.withEnable(true)
|
||||
@@ -13,18 +14,20 @@ local annotation = b.dashboard.spec.annotations;
|
||||
+ annotation.spec.withName('Annotations & Alerts')
|
||||
+ annotation.spec.withQuery(b.dataQuery({}, 'grafana', '-- Grafana --'))
|
||||
,
|
||||
annotation.withKind()
|
||||
+ annotation.spec.withEnable(true)
|
||||
+ annotation.spec.withHide(false)
|
||||
+ annotation.spec.withIconColor('orange')
|
||||
+ annotation.spec.withLegacyOptions({
|
||||
expr: 'windows_system_boot_time_timestamp{job=~"$job", instance="$instance"} * 1000 > $__from < $__to',
|
||||
step: '1m',
|
||||
tagKeys: 'instance',
|
||||
textFormat: '',
|
||||
titleFormat: 'Reboot',
|
||||
useValueForTime: 'on',
|
||||
})
|
||||
+ annotation.spec.withName('Reboots')
|
||||
+ annotation.spec.withQuery(b.dataQuery({}, 'prometheus', '${datasource}')),
|
||||
]
|
||||
if on('system') then (
|
||||
annotation.withKind()
|
||||
+ annotation.spec.withEnable(true)
|
||||
+ annotation.spec.withHide(false)
|
||||
+ annotation.spec.withIconColor('orange')
|
||||
+ annotation.spec.withLegacyOptions({
|
||||
expr: 'windows_system_boot_time_timestamp{job=~"$job", instance="$instance"} * 1000 > $__from < $__to',
|
||||
step: '1m',
|
||||
tagKeys: 'instance',
|
||||
textFormat: '',
|
||||
titleFormat: 'Reboot',
|
||||
useValueForTime: 'on',
|
||||
})
|
||||
+ annotation.spec.withName('Reboots')
|
||||
+ annotation.spec.withQuery(b.dataQuery({}, 'prometheus', '${datasource}'))
|
||||
),
|
||||
] if annotation != null]
|
||||
|
||||
@@ -56,6 +56,71 @@ local merge(base, patch) = base + {
|
||||
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,
|
||||
@@ -90,7 +155,12 @@ local merge(base, patch) = base + {
|
||||
},
|
||||
|
||||
grid(items): (grid.withKind() + grid.spec.withItems(items)).spec.layout,
|
||||
rows(rows_): (rows.withKind() + rows.spec.withRows(rows_)).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()
|
||||
@@ -99,9 +169,45 @@ local merge(base, patch) = base + {
|
||||
+ 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 }),
|
||||
tab(title, layout, panels): {
|
||||
elements: { ['panel-' + panel.spec.id]: panel for panel in panels },
|
||||
layout: tab.withKind() + tab.withSpec({ title: title, 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,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,191 +1,203 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
local host = '{job=~"$job", instance="$instance"}';
|
||||
local cores = 'scalar(max(windows_cpu_logical_processor' + host + '))';
|
||||
local mode(m) = 'sum(rate(windows_cpu_time_total{job=~"$job", instance="$instance", mode="' + m + '"}[$__rate_interval])) / ' + cores;
|
||||
|
||||
b.tab(
|
||||
'CPU',
|
||||
b.grid([
|
||||
b.place(24, 0, 0, 12, 10),
|
||||
b.place(25, 12, 0, 12, 10),
|
||||
b.place(26, 0, 10, 8, 8),
|
||||
b.place(27, 8, 10, 8, 8),
|
||||
b.place(28, 16, 10, 8, 8),
|
||||
b.place(29, 0, 18, 12, 8),
|
||||
b.place(30, 12, 18, 12, 8),
|
||||
]),
|
||||
[
|
||||
b.panel.new(24, 'CPU utilization by mode', 'timeseries')
|
||||
+ b.panel.withDescription('Share of total CPU time across all logical processors.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum by (mode) (rate(windows_cpu_time_total{job=~"$job", instance="$instance", mode!="idle"}[$__rate_interval])) / scalar(count(windows_cpu_time_total{job=~"$job", instance="$instance", mode="idle"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{mode}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
fillOpacity: 40,
|
||||
stacking: {
|
||||
mode: 'normal',
|
||||
},
|
||||
},
|
||||
max: 1,
|
||||
min: 0,
|
||||
}),
|
||||
|
||||
b.panel.new(25, 'CPU utilization per core', 'state-timeline')
|
||||
+ b.panel.withDescription('Busy time of each logical processor, labelled processor group,core. Green is idle, red is fully busy.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('label_replace(clamp(1 - rate(windows_cpu_time_total{job=~"$job", instance="$instance", mode="idle"}[$__rate_interval]), 0, 1), "core", "$1,0$2", "core", "(\\\\d+),(\\\\d)")', 'A')
|
||||
+ b.query.withLegendFormat('{{core}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
color: {
|
||||
mode: 'continuous-GrYlRd',
|
||||
},
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'percentunit',
|
||||
}),
|
||||
|
||||
b.panel.new(26, 'Processor queue length', 'timeseries')
|
||||
+ b.panel.withDescription('Threads that are ready to run but waiting for a CPU. A sustained value above 2 per core indicates CPU contention.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_system_processor_queue_length{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('queue length'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'short',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(27, 'Context switches and interrupts', 'timeseries')
|
||||
+ b.panel.withDescription('Context switches, hardware interrupts and deferred procedure calls per second across the host. High rates can reflect scheduling or driver activity; correlate changes with CPU usage and workload rather than assuming they are faults.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_system_context_switches_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('context switches'),
|
||||
b.query.new('sum(rate(windows_cpu_interrupts_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'B')
|
||||
+ b.query.withLegendFormat('interrupts'),
|
||||
b.query.new('sum(rate(windows_cpu_dpcs_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'C')
|
||||
+ b.query.withLegendFormat('DPCs'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'ops',
|
||||
}),
|
||||
|
||||
b.panel.new(28, 'Processes and threads', 'timeseries')
|
||||
+ b.panel.withDescription('Current process and thread counts on the host. Sustained growth can help identify runaway process creation or thread leaks; thread counts use the right-hand axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_system_processes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('processes'),
|
||||
b.query.new('windows_system_threads{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('threads'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'short',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'threads',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.axisPlacement',
|
||||
value: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
function(on)
|
||||
b.tab(
|
||||
'CPU',
|
||||
b.rows([
|
||||
if on('cpu') then b.summary([160, 161, 162, if on('system') then 163, if on('system') then 164, 165]),
|
||||
if on('cpu') then b.row('Utilization', b.flow([[24, 12, 10], [25, 12, 10]])),
|
||||
if on('cpu') && on('system') then b.row('Scheduling', b.flow([[26, 8, 8], [27, 8, 8], [28, 8, 8]])),
|
||||
if on('cpu') then b.row('System calls and frequency', b.flow([if on('system') then [29, 12, 8], [30, 12, 8]])),
|
||||
]),
|
||||
[
|
||||
b.stat(160, 'CPU busy', 'Non-idle CPU time averaged across logical processors.', '1 - avg(clamp_max(rate(windows_cpu_time_total{job=~"$job", instance="$instance", mode="idle"}[$__rate_interval]), 1))', 'percentunit', steps=b.levels(0.8, 0.9))
|
||||
+ b.panel.withDefaults({ decimals: 1, max: 1, min: 0 }),
|
||||
b.stat(161, 'User', 'CPU time spent in user mode, as a share of all logical processors.', mode('user'), 'percentunit')
|
||||
+ b.panel.withDefaults({ decimals: 1, min: 0 }),
|
||||
b.stat(162, 'Privileged', 'CPU time spent in kernel mode, excluding interrupts and DPCs, as a share of all logical processors.', mode('privileged'), 'percentunit')
|
||||
+ b.panel.withDefaults({ decimals: 1, min: 0 }),
|
||||
b.stat(163, 'Queue per CPU', 'Threads waiting for a CPU per logical processor. A sustained value above 2 indicates CPU contention.', 'windows_system_processor_queue_length' + host + ' / ' + cores, 'short', steps=b.levels(1, 2))
|
||||
+ b.panel.withDefaults({ decimals: 2, min: 0 }),
|
||||
b.stat(164, 'Ctx switches', 'Context switches per second across the host.', 'rate(windows_system_context_switches_total' + host + '[$__rate_interval])', 'ops'),
|
||||
b.stat(165, 'Frequency', 'Average effective frequency across logical processors. Values above the nominal frequency mean turbo boost.', 'avg(1e4 * windows_cpu_core_frequency_mhz' + host + ' * rate(windows_cpu_processor_performance_total' + host + '[$__rate_interval]) / rate(windows_cpu_processor_mperf_total' + host + '[$__rate_interval]))', 'hertz'),
|
||||
|
||||
b.panel.new(29, 'System calls and exceptions', 'timeseries')
|
||||
+ b.panel.withDescription('System calls and exception dispatches per second. Exception dispatches include handled exceptions and do not directly count application crashes; compare with application logs. Exceptions use the right-hand axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_system_system_calls_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('system calls'),
|
||||
b.query.new('rate(windows_system_exception_dispatches_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('exception dispatches'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'ops',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'exception dispatches',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.axisPlacement',
|
||||
value: 'right',
|
||||
b.panel.new(24, 'CPU utilization by mode', 'timeseries')
|
||||
+ b.panel.withDescription('Share of total CPU time across all logical processors.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum by (mode) (rate(windows_cpu_time_total{job=~"$job", instance="$instance", mode!="idle"}[$__rate_interval])) / scalar(count(windows_cpu_time_total{job=~"$job", instance="$instance", mode="idle"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{mode}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
fillOpacity: 40,
|
||||
stacking: {
|
||||
mode: 'normal',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(30, 'CPU frequency', 'timeseries')
|
||||
+ b.panel.withDescription('Effective frequency from the APERF/MPERF counters, averaged and maximum across logical processors, against the nominal frequency. Values above nominal mean turbo boost.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('avg(1e4 * windows_cpu_core_frequency_mhz{job=~"$job", instance="$instance"} * rate(windows_cpu_processor_performance_total{job=~"$job", instance="$instance"}[$__rate_interval]) / rate(windows_cpu_processor_mperf_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'A')
|
||||
+ b.query.withLegendFormat('average'),
|
||||
b.query.new('max(1e4 * windows_cpu_core_frequency_mhz{job=~"$job", instance="$instance"} * rate(windows_cpu_processor_performance_total{job=~"$job", instance="$instance"}[$__rate_interval]) / rate(windows_cpu_processor_mperf_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'B')
|
||||
+ b.query.withLegendFormat('maximum'),
|
||||
b.query.new('avg(windows_cpu_core_frequency_mhz{job=~"$job", instance="$instance"}) * 1e6', 'C')
|
||||
+ b.query.withLegendFormat('nominal'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'hertz',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'nominal',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'text',
|
||||
mode: 'fixed',
|
||||
max: 1,
|
||||
min: 0,
|
||||
}),
|
||||
|
||||
b.panel.new(25, 'CPU utilization per core', 'state-timeline')
|
||||
+ b.panel.withDescription('Busy time of each logical processor, labelled processor group,core. Green is idle, red is fully busy.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('label_replace(clamp(1 - rate(windows_cpu_time_total{job=~"$job", instance="$instance", mode="idle"}[$__rate_interval]), 0, 1), "core", "$1,0$2", "core", "(\\\\d+),(\\\\d)")', 'A')
|
||||
+ b.query.withLegendFormat('{{core}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
color: {
|
||||
mode: 'continuous-GrYlRd',
|
||||
},
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'percentunit',
|
||||
}),
|
||||
|
||||
b.panel.new(26, 'Processor queue length', 'timeseries')
|
||||
+ b.panel.withDescription('Threads that are ready to run but waiting for a CPU. A sustained value above 2 per core indicates CPU contention.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_system_processor_queue_length{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('queue length'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'short',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(27, 'Context switches and interrupts', 'timeseries')
|
||||
+ b.panel.withDescription('Context switches, hardware interrupts and deferred procedure calls per second across the host. High rates can reflect scheduling or driver activity; correlate changes with CPU usage and workload rather than assuming they are faults.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_system_context_switches_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('context switches'),
|
||||
b.query.new('sum(rate(windows_cpu_interrupts_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'B')
|
||||
+ b.query.withLegendFormat('interrupts'),
|
||||
b.query.new('sum(rate(windows_cpu_dpcs_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'C')
|
||||
+ b.query.withLegendFormat('DPCs'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'ops',
|
||||
}),
|
||||
|
||||
b.panel.new(28, 'Processes and threads', 'timeseries')
|
||||
+ b.panel.withDescription('Current process and thread counts on the host. Sustained growth can help identify runaway process creation or thread leaks; thread counts use the right-hand axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_system_processes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('processes'),
|
||||
b.query.new('windows_system_threads{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('threads'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'short',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'threads',
|
||||
},
|
||||
{
|
||||
id: 'custom.fillOpacity',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'custom.lineStyle',
|
||||
value: {
|
||||
dash: [
|
||||
10,
|
||||
10,
|
||||
],
|
||||
fill: 'dash',
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.axisPlacement',
|
||||
value: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(29, 'System calls and exceptions', 'timeseries')
|
||||
+ b.panel.withDescription('System calls and exception dispatches per second. Exception dispatches include handled exceptions and do not directly count application crashes; compare with application logs. Exceptions use the right-hand axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_system_system_calls_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('system calls'),
|
||||
b.query.new('rate(windows_system_exception_dispatches_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('exception dispatches'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'ops',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'exception dispatches',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
]
|
||||
)
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.axisPlacement',
|
||||
value: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(30, 'CPU frequency', 'timeseries')
|
||||
+ b.panel.withDescription('Effective frequency from the APERF/MPERF counters, averaged and maximum across logical processors, against the nominal frequency. Values above nominal mean turbo boost.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('avg(1e4 * windows_cpu_core_frequency_mhz{job=~"$job", instance="$instance"} * rate(windows_cpu_processor_performance_total{job=~"$job", instance="$instance"}[$__rate_interval]) / rate(windows_cpu_processor_mperf_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'A')
|
||||
+ b.query.withLegendFormat('average'),
|
||||
b.query.new('max(1e4 * windows_cpu_core_frequency_mhz{job=~"$job", instance="$instance"} * rate(windows_cpu_processor_performance_total{job=~"$job", instance="$instance"}[$__rate_interval]) / rate(windows_cpu_processor_mperf_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'B')
|
||||
+ b.query.withLegendFormat('maximum'),
|
||||
b.query.new('avg(windows_cpu_core_frequency_mhz{job=~"$job", instance="$instance"}) * 1e6', 'C')
|
||||
+ b.query.withLegendFormat('nominal'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'hertz',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'nominal',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'text',
|
||||
mode: 'fixed',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'custom.fillOpacity',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'custom.lineStyle',
|
||||
value: {
|
||||
dash: [
|
||||
10,
|
||||
10,
|
||||
],
|
||||
fill: 'dash',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
grafanaDashboards+:: {
|
||||
'windows-exporter.json': import 'windows-exporter.libsonnet',
|
||||
'windows-exporter.json': (import 'windows-exporter.libsonnet')($._config),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,321 +1,400 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
local metric(name) = 'windows_logical_disk_' + name + '{job=~"$job", instance="$instance", volume=~"$volume"}';
|
||||
local sumRate(name) = 'sum(rate(' + metric(name) + '[$__rate_interval]))';
|
||||
local physical(name) = 'windows_physical_disk_' + name + '{job=~"$job", instance="$instance"}';
|
||||
local physicalRate(name) = 'rate(' + physical(name) + '[$__rate_interval])';
|
||||
// diskdrive names drives PHYSICALDRIVE<n>; physical_disk and logical_disk use <n> as disk.
|
||||
local drive(name, labels='') = 'label_replace(windows_diskdrive_' + name + '{job=~"$job", instance="$instance"' + labels + '}, "disk", "$1", "name", "PHYSICALDRIVE(\\\\d+)")';
|
||||
// Labels physical disk series with the drive model when diskdrive is enabled.
|
||||
local physicalByDisk(on, expr) =
|
||||
if on('diskdrive')
|
||||
then '(' + expr + ') * on (disk) group_left (model) max by (disk, model) (' + drive('info') + ') or on (disk) (' + expr + ')'
|
||||
else expr;
|
||||
local physicalGraph(on, id, title, description, queries, unit) =
|
||||
b.panel.new(id, title, 'timeseries')
|
||||
+ b.panel.withDescription(description + ' Requires the physical_disk collector; the diskdrive collector adds drive models.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new(physicalByDisk(on, query[1]), query[0])
|
||||
+ b.query.withLegendFormat((if on('diskdrive') then '{{disk}} {{model}}' else 'disk {{disk}}') + query[2])
|
||||
for query in queries
|
||||
])
|
||||
+ b.panel.withDefaults({ unit: unit } + if std.length(queries) > 1 then {} else { min: 0 })
|
||||
+ b.panel.withOverrides(if std.length(queries) > 1 then [{
|
||||
matcher: { id: 'byRegexp', options: '.* read$' },
|
||||
properties: [{ id: 'custom.transform', value: 'negative-Y' }],
|
||||
}] else []);
|
||||
|
||||
b.tab(
|
||||
'Disk',
|
||||
b.grid([
|
||||
b.place(37, 0, 0, 8, 8),
|
||||
b.place(38, 8, 0, 8, 8),
|
||||
b.place(39, 16, 0, 8, 8),
|
||||
b.place(40, 0, 8, 12, 8),
|
||||
b.place(41, 12, 8, 12, 8),
|
||||
b.place(42, 0, 16, 12, 8),
|
||||
b.place(43, 12, 16, 12, 8),
|
||||
]),
|
||||
[
|
||||
b.panel.new(37, 'Volumes', 'table')
|
||||
+ b.panel.withDescription('Size, available space and used percentage of each selected logical volume. Windows free-space counters can lag by 10 to 15 minutes; use trends rather than treating a single sample as immediate confirmation of reclaimed space.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_logical_disk_size_bytes{job=~"$job", instance="$instance", volume=~"$volume"}', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('windows_logical_disk_free_bytes{job=~"$job", instance="$instance", volume=~"$volume"}', 'B')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('100 * (1 - windows_logical_disk_free_bytes{job=~"$job", instance="$instance", volume=~"$volume"} / windows_logical_disk_size_bytes{job=~"$job", instance="$instance", volume=~"$volume"})', 'C')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withTransformations([
|
||||
{
|
||||
group: 'joinByField',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
byField: 'volume',
|
||||
mode: 'outer',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'organize',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
excludeByName: {
|
||||
Time: true,
|
||||
'Time 1': true,
|
||||
'Time 2': true,
|
||||
'Time 3': true,
|
||||
__name__: true,
|
||||
instance: true,
|
||||
'instance 1': true,
|
||||
'instance 2': true,
|
||||
'instance 3': true,
|
||||
job: true,
|
||||
'job 1': true,
|
||||
'job 2': true,
|
||||
'job 3': true,
|
||||
port: true,
|
||||
'port 1': true,
|
||||
'port 2': true,
|
||||
'port 3': true,
|
||||
},
|
||||
includeByName: {},
|
||||
indexByName: {
|
||||
'Value #A': 1,
|
||||
'Value #B': 2,
|
||||
'Value #C': 3,
|
||||
volume: 0,
|
||||
},
|
||||
renameByName: {
|
||||
'Value #A': 'Size',
|
||||
'Value #B': 'Free',
|
||||
'Value #C': 'Used',
|
||||
volume: 'Volume',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #A',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #B',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #C',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'percent',
|
||||
},
|
||||
{
|
||||
id: 'min',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'max',
|
||||
value: 100,
|
||||
},
|
||||
{
|
||||
id: 'thresholds',
|
||||
value: {
|
||||
mode: 'absolute',
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
mode: 'continuous-GrYlRd',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'custom.cellOptions',
|
||||
value: {
|
||||
mode: 'basic',
|
||||
type: 'gauge',
|
||||
valueDisplayMode: 'text',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'decimals',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
function(on)
|
||||
b.tab(
|
||||
'Disk',
|
||||
b.rows([
|
||||
b.summary([
|
||||
if on('logical_disk') then 172,
|
||||
if on('physical_disk') then 200 else if on('logical_disk') then 173,
|
||||
if on('logical_disk') then 174,
|
||||
if on('logical_disk') then 175,
|
||||
if on('logical_disk') then 176,
|
||||
if on('logical_disk') then 177,
|
||||
]),
|
||||
if on('logical_disk') then b.row('Volumes', b.flow([[37, 24, 7], [38, 12, 8], [39, 12, 8]])),
|
||||
if on('logical_disk') then b.row('Volume I/O', b.flow([[40, 12, 8], [41, 12, 8], [42, 12, 8], [43, 12, 8]])),
|
||||
if on('diskdrive') then b.row('Drives', b.flow([[201, 4, 6], [202, 20, 6]])),
|
||||
if on('physical_disk') then b.row('Physical disks', b.flow([[203, 12, 8], [204, 12, 8], [205, 12, 8], [206, 12, 8], [207, 12, 8], [208, 12, 8]])),
|
||||
]),
|
||||
[
|
||||
b.stat(172, 'Fullest volume', 'Used space of the fullest selected volume. Free-space counters can lag by 10 to 15 minutes.', 'max(1 - ' + metric('free_bytes') + ' / ' + metric('size_bytes') + ')', 'percentunit', steps=b.levels(0.8, 0.9))
|
||||
+ b.panel.withDefaults({ decimals: 1, max: 1, min: 0 }),
|
||||
b.stat(173, 'Busy time', 'Busy time of the busiest selected volume.', 'max(1 - clamp_max(rate(' + metric('idle_seconds_total') + '[$__rate_interval]), 1))', 'percentunit', steps=b.levels(0.8, 0.9))
|
||||
+ b.panel.withDefaults({ decimals: 1, max: 1, min: 0 }),
|
||||
b.stat(174, 'Read', 'Bytes read per second, summed over the selected volumes.', sumRate('read_bytes_total'), 'Bps'),
|
||||
b.stat(175, 'Write', 'Bytes written per second, summed over the selected volumes.', sumRate('write_bytes_total'), 'Bps'),
|
||||
b.stat(176, 'IOPS', 'Read and write operations per second, summed over the selected volumes.', sumRate('reads_total') + ' + ' + sumRate('writes_total'), 'iops'),
|
||||
b.stat(177, 'Latency', 'Average time per read or write operation over the selected volumes. 0 when there is no I/O.', '((' + sumRate('read_seconds_total') + ' + ' + sumRate('write_seconds_total') + ') / ((' + sumRate('reads_total') + ' + ' + sumRate('writes_total') + ') > 0)) or (0 * ' + sumRate('reads_total') + ')', 's', steps=b.levels(0.02, 0.05)),
|
||||
|
||||
b.panel.new(38, 'Volume usage', 'timeseries')
|
||||
+ b.panel.withDescription('Used space as a percentage of each selected logical volume. Sustained growth toward full capacity can prevent writes and disrupt applications; Windows free-space counters can lag by 10 to 15 minutes.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('100 * (1 - windows_logical_disk_free_bytes{job=~"$job", instance="$instance", volume=~"$volume"} / windows_logical_disk_size_bytes{job=~"$job", instance="$instance", volume=~"$volume"})', 'A')
|
||||
+ b.query.withLegendFormat('{{volume}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
thresholdsStyle: {
|
||||
mode: 'dashed',
|
||||
b.panel.new(37, 'Volumes', 'table')
|
||||
+ b.panel.withDescription('Label, physical disk number, file system, size, available space and used percentage of each selected logical volume. Windows free-space counters can lag by 10 to 15 minutes; use trends rather than treating a single sample as immediate confirmation of reclaimed space.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_logical_disk_size_bytes{job=~"$job", instance="$instance", volume=~"$volume"}', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('windows_logical_disk_free_bytes{job=~"$job", instance="$instance", volume=~"$volume"}', 'B')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('100 * (1 - windows_logical_disk_free_bytes{job=~"$job", instance="$instance", volume=~"$volume"} / windows_logical_disk_size_bytes{job=~"$job", instance="$instance", volume=~"$volume"})', 'C')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('max by (volume, volume_name, disk, filesystem) (windows_logical_disk_info{job=~"$job", instance="$instance", volume=~"$volume"})', 'D')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withTransformations([
|
||||
{
|
||||
group: 'joinByField',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
byField: 'volume',
|
||||
mode: 'outer',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
max: 100,
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
{
|
||||
group: 'organize',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
excludeByName: {
|
||||
Time: true,
|
||||
'Time 1': true,
|
||||
'Time 2': true,
|
||||
'Time 3': true,
|
||||
'Time 4': true,
|
||||
'Value #D': true,
|
||||
__name__: true,
|
||||
instance: true,
|
||||
'instance 1': true,
|
||||
'instance 2': true,
|
||||
'instance 3': true,
|
||||
job: true,
|
||||
'job 1': true,
|
||||
'job 2': true,
|
||||
'job 3': true,
|
||||
port: true,
|
||||
'port 1': true,
|
||||
'port 2': true,
|
||||
'port 3': true,
|
||||
},
|
||||
includeByName: {},
|
||||
indexByName: {
|
||||
'Value #A': 4,
|
||||
'Value #B': 5,
|
||||
'Value #C': 6,
|
||||
disk: 2,
|
||||
filesystem: 3,
|
||||
volume: 0,
|
||||
volume_name: 1,
|
||||
},
|
||||
renameByName: {
|
||||
'Value #A': 'Size',
|
||||
'Value #B': 'Free',
|
||||
'Value #C': 'Used',
|
||||
disk: 'Disk',
|
||||
filesystem: 'File system',
|
||||
volume: 'Volume',
|
||||
volume_name: 'Label',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'percent',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [
|
||||
'min',
|
||||
'max',
|
||||
'lastNotNull',
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(39, 'Volume busy time', 'timeseries')
|
||||
+ b.panel.withDescription('Share of time the volume was servicing requests (1 - idle time).')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('1 - clamp_max(rate(windows_logical_disk_idle_seconds_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval]), 1)', 'A')
|
||||
+ b.query.withLegendFormat('{{volume}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
max: 1,
|
||||
min: 0,
|
||||
}),
|
||||
|
||||
b.panel.new(40, 'Disk throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Reads are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_logical_disk_write_bytes_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{volume}} write'),
|
||||
b.query.new('rate(windows_logical_disk_read_bytes_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{volume}} read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'Bps',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* read$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
])
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #A',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(41, 'Disk IOPS', 'timeseries')
|
||||
+ b.panel.withDescription('Reads are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_logical_disk_writes_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{volume}} write'),
|
||||
b.query.new('rate(windows_logical_disk_reads_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{volume}} read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'iops',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* read$',
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #B',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(42, 'Disk latency', 'timeseries')
|
||||
+ b.panel.withDescription('Average time per read and write operation. Reads are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_logical_disk_write_seconds_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval]) / rate(windows_logical_disk_writes_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{volume}} write'),
|
||||
b.query.new('rate(windows_logical_disk_read_seconds_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval]) / rate(windows_logical_disk_reads_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{volume}} read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 's',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* read$',
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #C',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(43, 'Disk queue length', 'timeseries')
|
||||
+ b.panel.withDescription('Average number of read and write requests queued for the volume. Reads are drawn below the axis. A queue that stays high while latency rises indicates a disk bottleneck.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_logical_disk_write_seconds_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{volume}} write'),
|
||||
b.query.new('rate(windows_logical_disk_read_seconds_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{volume}} read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'short',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* read$',
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'percent',
|
||||
},
|
||||
{
|
||||
id: 'min',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'max',
|
||||
value: 100,
|
||||
},
|
||||
{
|
||||
id: 'thresholds',
|
||||
value: {
|
||||
mode: 'absolute',
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
mode: 'continuous-GrYlRd',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'custom.cellOptions',
|
||||
value: {
|
||||
mode: 'basic',
|
||||
type: 'gauge',
|
||||
valueDisplayMode: 'text',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'decimals',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
]),
|
||||
|
||||
b.panel.new(38, 'Volume usage', 'timeseries')
|
||||
+ b.panel.withDescription('Used space as a percentage of each selected logical volume. Sustained growth toward full capacity can prevent writes and disrupt applications; Windows free-space counters can lag by 10 to 15 minutes.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('100 * (1 - windows_logical_disk_free_bytes{job=~"$job", instance="$instance", volume=~"$volume"} / windows_logical_disk_size_bytes{job=~"$job", instance="$instance", volume=~"$volume"})', 'A')
|
||||
+ b.query.withLegendFormat('{{volume}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
thresholdsStyle: {
|
||||
mode: 'dashed',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
]
|
||||
)
|
||||
},
|
||||
max: 100,
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'percent',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [
|
||||
'min',
|
||||
'max',
|
||||
'lastNotNull',
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(39, 'Volume busy time', 'timeseries')
|
||||
+ b.panel.withDescription('Share of time the volume was servicing requests (1 - idle time).')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('1 - clamp_max(rate(windows_logical_disk_idle_seconds_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval]), 1)', 'A')
|
||||
+ b.query.withLegendFormat('{{volume}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
max: 1,
|
||||
min: 0,
|
||||
}),
|
||||
|
||||
b.panel.new(40, 'Volume throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Reads are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_logical_disk_write_bytes_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{volume}} write'),
|
||||
b.query.new('rate(windows_logical_disk_read_bytes_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{volume}} read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'Bps',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* read$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(41, 'Volume IOPS', 'timeseries')
|
||||
+ b.panel.withDescription('Reads are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_logical_disk_writes_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{volume}} write'),
|
||||
b.query.new('rate(windows_logical_disk_reads_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{volume}} read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'iops',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* read$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(42, 'Volume latency', 'timeseries')
|
||||
+ b.panel.withDescription('Average time per read and write operation. Reads are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_logical_disk_write_seconds_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval]) / rate(windows_logical_disk_writes_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{volume}} write'),
|
||||
b.query.new('rate(windows_logical_disk_read_seconds_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval]) / rate(windows_logical_disk_reads_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{volume}} read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 's',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* read$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(43, 'Volume queue length', 'timeseries')
|
||||
+ b.panel.withDescription('Average number of read and write requests queued for the volume. Reads are drawn below the axis. A queue that stays high while latency rises indicates a disk bottleneck.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_logical_disk_write_seconds_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{volume}} write'),
|
||||
b.query.new('rate(windows_logical_disk_read_seconds_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{volume}} read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'short',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* read$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.stat(200, 'Busiest disk', 'Busy time of the busiest physical disk. Requires the physical_disk collector.', 'max(1 - clamp_max(' + physicalRate('idle_seconds_total') + ', 1))', 'percentunit', steps=b.levels(0.8, 0.9))
|
||||
+ b.panel.withDefaults({ decimals: 1, max: 1, min: 0 }),
|
||||
b.stat(201, 'Drives not OK', 'Drives whose operational status is not OK. Requires the diskdrive collector.', 'count(' + drive('status', ', status!="OK"') + ' == 1) or (0 * count(' + drive('info') + '))', steps=[{ color: 'green', value: null }, { color: 'red', value: 1 }]),
|
||||
|
||||
b.joinedTable(202, 'Drives', 'Model, size, partitions and operational status of each drive. Requires the diskdrive collector.', 'disk', [
|
||||
'max by (disk, model) (' + drive('info') + ')',
|
||||
'max by (disk) (' + drive('size') + ')',
|
||||
'max by (disk) (' + drive('partitions') + ')',
|
||||
'max by (disk, status) (' + drive('status') + ' == 1)',
|
||||
], [['disk', 'Disk'], ['model', 'Model'], ['Value #B', 'Size'], ['Value #C', 'Partitions'], ['status', 'Status']], [
|
||||
{ matcher: { id: 'byName', options: 'Size' }, properties: [{ id: 'unit', value: 'bytes' }] },
|
||||
{
|
||||
matcher: { id: 'byName', options: 'Status' },
|
||||
properties: [
|
||||
{ id: 'color', value: { fixedColor: 'red', mode: 'fixed' } },
|
||||
{ id: 'mappings', value: [{ type: 'value', options: { OK: { color: 'green', index: 0 } } }] },
|
||||
{ id: 'custom.cellOptions', value: { type: 'color-text' } },
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
physicalGraph(on, 203, 'Disk busy time', 'Share of time each physical disk was servicing requests (1 - idle time).', [['A', '1 - clamp_max(' + physicalRate('idle_seconds_total') + ', 1)', '']], 'percentunit')
|
||||
+ b.panel.withDefaults({ max: 1 }),
|
||||
physicalGraph(on, 204, 'Disk throughput', 'Bytes read and written per second by each physical disk. Reads are drawn below the axis.', [['A', physicalRate('write_bytes_total'), ' write'], ['B', physicalRate('read_bytes_total'), ' read']], 'Bps'),
|
||||
physicalGraph(on, 205, 'Disk IOPS', 'Read and write operations per second by each physical disk. Reads are drawn below the axis.', [['A', physicalRate('writes_total'), ' write'], ['B', physicalRate('reads_total'), ' read']], 'iops'),
|
||||
physicalGraph(on, 206, 'Disk latency', 'Average time per read and write operation of each physical disk. Reads are drawn below the axis.', [['A', physicalRate('write_latency_seconds_total') + ' / (' + physicalRate('writes_total') + ' > 0)', ' write'], ['B', physicalRate('read_latency_seconds_total') + ' / (' + physicalRate('reads_total') + ' > 0)', ' read']], 's'),
|
||||
physicalGraph(on, 207, 'Disk queue length', 'Requests outstanding on each physical disk at the time of the scrape. A queue that stays high while latency rises indicates a disk bottleneck.', [['A', physical('requests_queued'), '']], 'short'),
|
||||
physicalGraph(on, 208, 'Split I/O', 'I/O requests per second that Windows split into multiple requests, because of a fragmented volume or a request too large for a single I/O.', [['A', physicalRate('split_ios_total'), '']], 'iops'),
|
||||
]
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,399 +1,401 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
local metric(name) = 'windows_gpu_' + name + '{job=~"$job", instance="$instance"}';
|
||||
local perGpu(name) = 'sum(max by (luid) (' + metric(name) + '))';
|
||||
|
||||
b.tab(
|
||||
'GPU',
|
||||
b.grid([
|
||||
b.place(57, 0, 0, 24, 5),
|
||||
b.place(58, 0, 5, 12, 8),
|
||||
b.place(59, 12, 5, 12, 8),
|
||||
b.place(60, 0, 13, 12, 8),
|
||||
b.place(61, 12, 13, 12, 8),
|
||||
b.place(120, 0, 21, 12, 8),
|
||||
b.place(121, 12, 21, 12, 8),
|
||||
b.place(122, 0, 29, 12, 8),
|
||||
b.place(123, 12, 29, 12, 8),
|
||||
b.place(124, 0, 37, 12, 8),
|
||||
b.place(125, 12, 37, 12, 8),
|
||||
b.place(126, 0, 45, 12, 8),
|
||||
b.place(127, 12, 45, 12, 8),
|
||||
b.place(128, 0, 53, 12, 8),
|
||||
b.place(129, 12, 53, 12, 8),
|
||||
b.place(130, 0, 61, 12, 8),
|
||||
b.place(131, 12, 61, 12, 8),
|
||||
]),
|
||||
[
|
||||
b.panel.new(57, 'GPUs', 'table')
|
||||
+ b.panel.withDescription('Needs the gpu collector, which is not enabled by default. Utilization is the busiest engine of the GPU.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (luid, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('clamp_max(max by (luid) (sum by (luid, eng, engtype) (rate(windows_gpu_engine_time_seconds{job=~"$job", instance="$instance"}[$__rate_interval]))), 1)', 'B')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('max by (luid) (windows_gpu_adapter_memory_dedicated_bytes{job=~"$job", instance="$instance"})', 'C')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('max by (luid) (windows_gpu_dedicated_video_memory_size_bytes{job=~"$job", instance="$instance"})', 'D')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('max by (luid) (windows_gpu_adapter_memory_shared_bytes{job=~"$job", instance="$instance"})', 'E')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('max by (luid) (windows_gpu_shared_system_memory_size_bytes{job=~"$job", instance="$instance"})', 'F')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withTransformations([
|
||||
{
|
||||
group: 'joinByField',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
byField: 'luid',
|
||||
mode: 'outer',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'organize',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
excludeByName: {
|
||||
Time: true,
|
||||
'Time 1': true,
|
||||
'Time 2': true,
|
||||
'Time 3': true,
|
||||
'Time 4': true,
|
||||
'Time 5': true,
|
||||
'Time 6': true,
|
||||
'Value #A': true,
|
||||
luid: true,
|
||||
},
|
||||
includeByName: {},
|
||||
indexByName: {
|
||||
'Value #B': 1,
|
||||
'Value #C': 2,
|
||||
'Value #D': 3,
|
||||
'Value #E': 4,
|
||||
'Value #F': 5,
|
||||
name: 0,
|
||||
},
|
||||
renameByName: {
|
||||
'Value #B': 'Utilization',
|
||||
'Value #C': 'Dedicated memory used',
|
||||
'Value #D': 'Dedicated memory',
|
||||
'Value #E': 'Shared memory used',
|
||||
'Value #F': 'Shared memory',
|
||||
name: 'GPU',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
+ b.panel.withOptions({
|
||||
sortBy: [],
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #B',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'percentunit',
|
||||
},
|
||||
{
|
||||
id: 'min',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'max',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
id: 'thresholds',
|
||||
value: {
|
||||
mode: 'absolute',
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
mode: 'continuous-GrYlRd',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'custom.cellOptions',
|
||||
value: {
|
||||
mode: 'basic',
|
||||
type: 'gauge',
|
||||
valueDisplayMode: 'text',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'decimals',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #C',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #D',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #E',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #F',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
function(on)
|
||||
b.tab(
|
||||
'GPU',
|
||||
b.rows([
|
||||
if on('gpu') then b.summary([184, 185, 186, 187, 188, 189]),
|
||||
if on('gpu') then b.row('Adapters', b.flow([[57, 24, 5], [58, 12, 8], [59, 12, 8], [125, 8, 8], [126, 8, 8], [127, 8, 8]])),
|
||||
if on('gpu') then b.row('Sensors and clocks', b.flow([[120, 12, 8], [121, 12, 8], [122, 8, 8], [123, 8, 8], [124, 8, 8]])),
|
||||
if on('gpu') then b.row('Processes', b.flow([[60, 12, 8], [61, 12, 8], [128, 12, 8], [129, 12, 8], [130, 12, 8], [131, 12, 8]])),
|
||||
]),
|
||||
[
|
||||
b.stat(184, 'Utilization', 'Utilization of the busiest engine on the busiest GPU.', 'max(clamp_max(max by (luid) (sum by (luid, eng, engtype) (rate(' + metric('engine_time_seconds') + '[$__rate_interval]))), 1))', 'percentunit', steps=b.levels(0.8, 0.9))
|
||||
+ b.panel.withDefaults({ decimals: 1, max: 1, min: 0 }),
|
||||
b.stat(185, 'VRAM used', 'Dedicated GPU memory in use, summed over all GPUs.', perGpu('adapter_memory_dedicated_bytes'), 'bytes')
|
||||
+ b.panel.withDefaults({ decimals: 1 }),
|
||||
b.stat(186, 'VRAM usage', 'Dedicated GPU memory in use as a share of the dedicated video memory of all GPUs.', perGpu('adapter_memory_dedicated_bytes') + ' / (' + perGpu('dedicated_video_memory_size_bytes') + ' > 0)', 'percentunit', steps=b.levels(0.8, 0.9))
|
||||
+ b.panel.withDefaults({ decimals: 1, max: 1, min: 0 }),
|
||||
b.stat(187, 'Shared memory', 'System memory used by the GPUs, summed over all GPUs.', perGpu('adapter_memory_shared_bytes'), 'bytes')
|
||||
+ b.panel.withDefaults({ decimals: 1 }),
|
||||
b.stat(188, 'Temperature', 'Temperature of the hottest GPU. Requires WDDM 2.4 or newer and driver support.', 'max(' + metric('temperature_celsius') + ')', 'celsius', steps=b.levels(80, 90)),
|
||||
b.stat(189, 'Power usage', 'Highest GPU power draw as a fraction of the driver-reported maximum power (TDP). Requires WDDM 2.4 or newer and driver support.', 'max(' + metric('power_usage_ratio') + ')', 'percentunit')
|
||||
+ b.panel.withDefaults({ decimals: 1, min: 0 }),
|
||||
|
||||
b.panel.new(58, 'GPU utilization by engine type', 'timeseries')
|
||||
+ b.panel.withDescription('Busiest engine of each engine type, as in Task Manager.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('clamp_max(max by (luid, engtype) (sum by (luid, eng, engtype) (rate(windows_gpu_engine_time_seconds{job=~"$job", instance="$instance"}[$__rate_interval]))), 1) * on (luid) group_left (name) max by (luid, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} {{engtype}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
max: 1,
|
||||
min: 0,
|
||||
}),
|
||||
|
||||
b.panel.new(59, 'GPU memory', 'timeseries')
|
||||
+ b.panel.withDescription('Dedicated (video) and shared (system) memory in use, against the size of each pool.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (luid) (windows_gpu_adapter_memory_dedicated_bytes{job=~"$job", instance="$instance"}) * on (luid) group_left (name) max by (luid, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} dedicated used'),
|
||||
b.query.new('max by (luid) (windows_gpu_dedicated_video_memory_size_bytes{job=~"$job", instance="$instance"}) * on (luid) group_left (name) max by (luid, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'B')
|
||||
+ b.query.withLegendFormat('{{name}} dedicated size'),
|
||||
b.query.new('max by (luid) (windows_gpu_adapter_memory_shared_bytes{job=~"$job", instance="$instance"}) * on (luid) group_left (name) max by (luid, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'C')
|
||||
+ b.query.withLegendFormat('{{name}} shared used'),
|
||||
b.query.new('max by (luid) (windows_gpu_shared_system_memory_size_bytes{job=~"$job", instance="$instance"}) * on (luid) group_left (name) max by (luid, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'D')
|
||||
+ b.query.withLegendFormat('{{name}} shared size'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* size',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.fillOpacity',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'custom.lineStyle',
|
||||
value: {
|
||||
dash: [
|
||||
10,
|
||||
10,
|
||||
],
|
||||
fill: 'dash',
|
||||
b.panel.new(57, 'GPUs', 'table')
|
||||
+ b.panel.withDescription('Needs the gpu collector, which is not enabled by default. Utilization is the busiest engine of the GPU.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (luid, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('clamp_max(max by (luid) (sum by (luid, eng, engtype) (rate(windows_gpu_engine_time_seconds{job=~"$job", instance="$instance"}[$__rate_interval]))), 1)', 'B')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('max by (luid) (windows_gpu_adapter_memory_dedicated_bytes{job=~"$job", instance="$instance"})', 'C')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('max by (luid) (windows_gpu_dedicated_video_memory_size_bytes{job=~"$job", instance="$instance"})', 'D')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('max by (luid) (windows_gpu_adapter_memory_shared_bytes{job=~"$job", instance="$instance"})', 'E')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('max by (luid) (windows_gpu_shared_system_memory_size_bytes{job=~"$job", instance="$instance"})', 'F')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withTransformations([
|
||||
{
|
||||
group: 'joinByField',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
byField: 'luid',
|
||||
mode: 'outer',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
},
|
||||
{
|
||||
group: 'organize',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
excludeByName: {
|
||||
Time: true,
|
||||
'Time 1': true,
|
||||
'Time 2': true,
|
||||
'Time 3': true,
|
||||
'Time 4': true,
|
||||
'Time 5': true,
|
||||
'Time 6': true,
|
||||
'Value #A': true,
|
||||
luid: true,
|
||||
},
|
||||
includeByName: {},
|
||||
indexByName: {
|
||||
'Value #B': 1,
|
||||
'Value #C': 2,
|
||||
'Value #D': 3,
|
||||
'Value #E': 4,
|
||||
'Value #F': 5,
|
||||
name: 0,
|
||||
},
|
||||
renameByName: {
|
||||
'Value #B': 'Utilization',
|
||||
'Value #C': 'Dedicated memory used',
|
||||
'Value #D': 'Dedicated memory',
|
||||
'Value #E': 'Shared memory used',
|
||||
'Value #F': 'Shared memory',
|
||||
name: 'GPU',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
+ b.panel.withOptions({
|
||||
sortBy: [],
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #B',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'percentunit',
|
||||
},
|
||||
{
|
||||
id: 'min',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'max',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
id: 'thresholds',
|
||||
value: {
|
||||
mode: 'absolute',
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
mode: 'continuous-GrYlRd',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'custom.cellOptions',
|
||||
value: {
|
||||
mode: 'basic',
|
||||
type: 'gauge',
|
||||
valueDisplayMode: 'text',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'decimals',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #C',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #D',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #E',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #F',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(60, 'Top 10 processes by GPU utilization', 'timeseries')
|
||||
+ b.panel.withDescription('Busiest GPU engine used by each process. Process names need the process collector; otherwise only the PID is shown.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('topk(10, (max by (process_id) (sum by (process_id, luid, eng) (rate(windows_gpu_engine_time_seconds{job=~"$job", instance="$instance"}[$__rate_interval]))) * on (process_id) group_left (process) max by (process_id, process) (windows_process_info{job=~"$job", instance="$instance"})) or on (process_id) max by (process_id) (sum by (process_id, luid, eng) (rate(windows_gpu_engine_time_seconds{job=~"$job", instance="$instance"}[$__rate_interval]))))', 'A')
|
||||
+ b.query.withLegendFormat('{{process}} ({{process_id}})'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
sortBy: 'Mean',
|
||||
sortDesc: true,
|
||||
},
|
||||
}),
|
||||
b.panel.new(58, 'GPU utilization by engine type', 'timeseries')
|
||||
+ b.panel.withDescription('Busiest engine of each engine type, as in Task Manager.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('clamp_max(max by (luid, engtype) (sum by (luid, eng, engtype) (rate(windows_gpu_engine_time_seconds{job=~"$job", instance="$instance"}[$__rate_interval]))), 1) * on (luid) group_left (name) max by (luid, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} {{engtype}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
max: 1,
|
||||
min: 0,
|
||||
}),
|
||||
|
||||
b.panel.new(61, 'Top 10 processes by dedicated GPU memory', 'timeseries')
|
||||
+ b.panel.withDescription('Process names need the process collector; otherwise only the PID is shown.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('topk(10, (sum by (process_id) (windows_gpu_process_memory_dedicated_bytes{job=~"$job", instance="$instance"}) * on (process_id) group_left (process) max by (process_id, process) (windows_process_info{job=~"$job", instance="$instance"})) or on (process_id) sum by (process_id) (windows_gpu_process_memory_dedicated_bytes{job=~"$job", instance="$instance"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{process}} ({{process_id}})'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
sortBy: 'Mean',
|
||||
sortDesc: true,
|
||||
},
|
||||
}),
|
||||
b.panel.new(59, 'GPU memory', 'timeseries')
|
||||
+ b.panel.withDescription('Dedicated (video) and shared (system) memory in use, against the size of each pool.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (luid) (windows_gpu_adapter_memory_dedicated_bytes{job=~"$job", instance="$instance"}) * on (luid) group_left (name) max by (luid, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} dedicated used'),
|
||||
b.query.new('max by (luid) (windows_gpu_dedicated_video_memory_size_bytes{job=~"$job", instance="$instance"}) * on (luid) group_left (name) max by (luid, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'B')
|
||||
+ b.query.withLegendFormat('{{name}} dedicated size'),
|
||||
b.query.new('max by (luid) (windows_gpu_adapter_memory_shared_bytes{job=~"$job", instance="$instance"}) * on (luid) group_left (name) max by (luid, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'C')
|
||||
+ b.query.withLegendFormat('{{name}} shared used'),
|
||||
b.query.new('max by (luid) (windows_gpu_shared_system_memory_size_bytes{job=~"$job", instance="$instance"}) * on (luid) group_left (name) max by (luid, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'D')
|
||||
+ b.query.withLegendFormat('{{name}} shared size'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* size',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.fillOpacity',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'custom.lineStyle',
|
||||
value: {
|
||||
dash: [
|
||||
10,
|
||||
10,
|
||||
],
|
||||
fill: 'dash',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(120, 'GPU temperature', 'timeseries')
|
||||
+ b.panel.withDescription('Current GPU temperature, driver-reported throttling threshold and maximum temperature in degrees Celsius. Sustained readings near the warning threshold can indicate insufficient cooling. Requires the gpu collector, WDDM 2.4 or newer and driver support. Unsupported sensor values are absent, rather than zero.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (luid, phys) (windows_gpu_temperature_celsius{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} temperature'),
|
||||
b.query.new('max by (luid, phys) (windows_gpu_temperature_warning_celsius{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'B')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} throttling threshold'),
|
||||
b.query.new('max by (luid, phys) (windows_gpu_temperature_max_celsius{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'C')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} maximum'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'celsius' }),
|
||||
b.panel.new(60, 'Top 10 processes by GPU utilization', 'timeseries')
|
||||
+ b.panel.withDescription('Busiest GPU engine used by each process. Process names need the process collector; otherwise only the PID is shown.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('topk(10, (max by (process_id) (sum by (process_id, luid, eng) (rate(windows_gpu_engine_time_seconds{job=~"$job", instance="$instance"}[$__rate_interval]))) * on (process_id) group_left (process) max by (process_id, process) (windows_process_info{job=~"$job", instance="$instance"})) or on (process_id) max by (process_id) (sum by (process_id, luid, eng) (rate(windows_gpu_engine_time_seconds{job=~"$job", instance="$instance"}[$__rate_interval]))))', 'A')
|
||||
+ b.query.withLegendFormat('{{process}} ({{process_id}})'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
sortBy: 'Mean',
|
||||
sortDesc: true,
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(121, 'GPU fan speed', 'timeseries')
|
||||
+ b.panel.withDescription('Current and maximum main-fan speed in revolutions per minute. A supported fan can report zero when stopped under light load. Requires the gpu collector, WDDM 2.4 or newer and driver support. Unsupported sensor values are absent, rather than zero.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (luid, phys) (windows_gpu_fan_speed_rpm{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} fan'),
|
||||
b.query.new('max by (luid, phys) (windows_gpu_fan_speed_max_rpm{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'B')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} maximum'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'rotrpm' }),
|
||||
b.panel.new(61, 'Top 10 processes by dedicated GPU memory', 'timeseries')
|
||||
+ b.panel.withDescription('Process names need the process collector; otherwise only the PID is shown.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('topk(10, (sum by (process_id) (windows_gpu_process_memory_dedicated_bytes{job=~"$job", instance="$instance"}) * on (process_id) group_left (process) max by (process_id, process) (windows_process_info{job=~"$job", instance="$instance"})) or on (process_id) sum by (process_id) (windows_gpu_process_memory_dedicated_bytes{job=~"$job", instance="$instance"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{process}} ({{process_id}})'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
sortBy: 'Mean',
|
||||
sortDesc: true,
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(122, 'GPU power usage', 'timeseries')
|
||||
+ b.panel.withDescription('Current GPU power draw as a fraction of the driver-reported maximum power (TDP). This is a ratio, not an absolute measurement in watts. Requires the gpu collector, WDDM 2.4 or newer and driver support. Unsupported sensor values are absent, rather than zero.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (luid, phys) (windows_gpu_power_usage_ratio{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} power'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'percentunit' }),
|
||||
b.panel.new(120, 'GPU temperature', 'timeseries')
|
||||
+ b.panel.withDescription('Current GPU temperature, driver-reported throttling threshold and maximum temperature in degrees Celsius. Sustained readings near the warning threshold can indicate insufficient cooling. Requires the gpu collector, WDDM 2.4 or newer and driver support. Unsupported sensor values are absent, rather than zero.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (luid, phys) (windows_gpu_temperature_celsius{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} temperature'),
|
||||
b.query.new('max by (luid, phys) (windows_gpu_temperature_warning_celsius{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'B')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} throttling threshold'),
|
||||
b.query.new('max by (luid, phys) (windows_gpu_temperature_max_celsius{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'C')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} maximum'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'celsius' }),
|
||||
|
||||
b.panel.new(123, 'GPU memory clock', 'timeseries')
|
||||
+ b.panel.withDescription('Current and maximum non-overclocked GPU memory clock in hertz. Clocks can fall at idle; correlate drops under load with temperature and power. Requires the gpu collector, WDDM 2.4 or newer and driver support. Unsupported sensor values are absent, rather than zero.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (luid, phys) (windows_gpu_memory_frequency_hertz{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} memory clock'),
|
||||
b.query.new('max by (luid, phys) (windows_gpu_memory_frequency_max_hertz{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'B')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} maximum'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'hertz' }),
|
||||
b.panel.new(121, 'GPU fan speed', 'timeseries')
|
||||
+ b.panel.withDescription('Current and maximum main-fan speed in revolutions per minute. A supported fan can report zero when stopped under light load. Requires the gpu collector, WDDM 2.4 or newer and driver support. Unsupported sensor values are absent, rather than zero.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (luid, phys) (windows_gpu_fan_speed_rpm{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} fan'),
|
||||
b.query.new('max by (luid, phys) (windows_gpu_fan_speed_max_rpm{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'B')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} maximum'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'rotrpm' }),
|
||||
|
||||
b.panel.new(124, 'GPU engine clock', 'timeseries')
|
||||
+ b.panel.withDescription('Current and maximum non-overclocked frequency per GPU engine in hertz. Only engines with a reported maximum clock are exposed, typically the 3D engine. Requires the gpu collector, WDDM 2.4 or newer and driver support. Unsupported sensor values are absent, rather than zero.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (luid, phys, eng) (windows_gpu_engine_frequency_hertz{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} engine {{eng}} clock'),
|
||||
b.query.new('max by (luid, phys, eng) (windows_gpu_engine_frequency_max_hertz{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'B')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} engine {{eng}} maximum'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'hertz' }),
|
||||
b.panel.new(122, 'GPU power usage', 'timeseries')
|
||||
+ b.panel.withDescription('Current GPU power draw as a fraction of the driver-reported maximum power (TDP). This is a ratio, not an absolute measurement in watts. Requires the gpu collector, WDDM 2.4 or newer and driver support. Unsupported sensor values are absent, rather than zero.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (luid, phys) (windows_gpu_power_usage_ratio{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} power'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'percentunit' }),
|
||||
|
||||
b.panel.new(125, 'GPU committed memory', 'timeseries')
|
||||
+ b.panel.withDescription('Total committed GPU memory per physical GPU in bytes. Commitment is a separate accounting measure from dedicated or shared memory residency; do not add these series together.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (luid, phys) (windows_gpu_adapter_memory_committed_bytes{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} committed'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'bytes' }),
|
||||
b.panel.new(123, 'GPU memory clock', 'timeseries')
|
||||
+ b.panel.withDescription('Current and maximum non-overclocked GPU memory clock in hertz. Clocks can fall at idle; correlate drops under load with temperature and power. Requires the gpu collector, WDDM 2.4 or newer and driver support. Unsupported sensor values are absent, rather than zero.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (luid, phys) (windows_gpu_memory_frequency_hertz{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} memory clock'),
|
||||
b.query.new('max by (luid, phys) (windows_gpu_memory_frequency_max_hertz{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'B')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} maximum'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'hertz' }),
|
||||
|
||||
b.panel.new(126, 'GPU local and non-local memory', 'timeseries')
|
||||
+ b.panel.withDescription('Local and non-local adapter memory usage in bytes, summed across memory partitions for each physical GPU. These describe memory locality and overlap the dedicated/shared accounting views.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum by (luid, phys) (windows_gpu_local_adapter_memory_bytes{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} local'),
|
||||
b.query.new('sum by (luid, phys) (windows_gpu_non_local_adapter_memory_bytes{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'B')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} non-local'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'bytes' }),
|
||||
b.panel.new(124, 'GPU engine clock', 'timeseries')
|
||||
+ b.panel.withDescription('Current and maximum non-overclocked frequency per GPU engine in hertz. Only engines with a reported maximum clock are exposed, typically the 3D engine. Requires the gpu collector, WDDM 2.4 or newer and driver support. Unsupported sensor values are absent, rather than zero.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (luid, phys, eng) (windows_gpu_engine_frequency_hertz{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} engine {{eng}} clock'),
|
||||
b.query.new('max by (luid, phys, eng) (windows_gpu_engine_frequency_max_hertz{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'B')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} engine {{eng}} maximum'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'hertz' }),
|
||||
|
||||
b.panel.new(127, 'GPU dedicated system memory capacity', 'timeseries')
|
||||
+ b.panel.withDescription('System memory dedicated to each GPU in bytes. This capacity is distinct from dedicated video memory and the shared system memory pool; it can be zero on discrete adapters.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (luid) (windows_gpu_dedicated_system_memory_size_bytes{job=~"$job", instance="$instance"}) * on (luid) group_left (name) max by (luid, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} dedicated system capacity'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'bytes' }),
|
||||
b.panel.new(125, 'GPU committed memory', 'timeseries')
|
||||
+ b.panel.withDescription('Total committed GPU memory per physical GPU in bytes. Commitment is a separate accounting measure from dedicated or shared memory residency; do not add these series together.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (luid, phys) (windows_gpu_adapter_memory_committed_bytes{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} committed'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'bytes' }),
|
||||
|
||||
b.panel.new(128, 'Top 10 processes by shared GPU memory', 'timeseries')
|
||||
+ b.panel.withDescription('Top ten processes at each point in time by shared GPU memory in bytes, summed across GPUs. Membership can change over time. Process names require the process collector; otherwise the PID is shown. Memory accounting views overlap and should not be added together.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('topk(10, (sum by (process_id) (windows_gpu_process_memory_shared_bytes{job=~"$job", instance="$instance"}) * on (process_id) group_left (process) max by (process_id, process) (windows_process_info{job=~"$job", instance="$instance"})) or on (process_id) sum by (process_id) (windows_gpu_process_memory_shared_bytes{job=~"$job", instance="$instance"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{process}} ({{process_id}})'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'bytes' }),
|
||||
b.panel.new(126, 'GPU local and non-local memory', 'timeseries')
|
||||
+ b.panel.withDescription('Local and non-local adapter memory usage in bytes, summed across memory partitions for each physical GPU. These describe memory locality and overlap the dedicated/shared accounting views.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum by (luid, phys) (windows_gpu_local_adapter_memory_bytes{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} local'),
|
||||
b.query.new('sum by (luid, phys) (windows_gpu_non_local_adapter_memory_bytes{job=~"$job", instance="$instance"}) * on (luid, phys) group_left (name) max by (luid, phys, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'B')
|
||||
+ b.query.withLegendFormat('{{name}} GPU {{phys}} non-local'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'bytes' }),
|
||||
|
||||
b.panel.new(129, 'Top 10 processes by committed GPU memory', 'timeseries')
|
||||
+ b.panel.withDescription('Top ten processes at each point in time by committed GPU memory in bytes, summed across GPUs. Membership can change over time. Process names require the process collector; otherwise the PID is shown. Memory accounting views overlap and should not be added together.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('topk(10, (sum by (process_id) (windows_gpu_process_memory_committed_bytes{job=~"$job", instance="$instance"}) * on (process_id) group_left (process) max by (process_id, process) (windows_process_info{job=~"$job", instance="$instance"})) or on (process_id) sum by (process_id) (windows_gpu_process_memory_committed_bytes{job=~"$job", instance="$instance"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{process}} ({{process_id}})'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'bytes' }),
|
||||
b.panel.new(127, 'GPU dedicated system memory capacity', 'timeseries')
|
||||
+ b.panel.withDescription('System memory dedicated to each GPU in bytes. This capacity is distinct from dedicated video memory and the shared system memory pool; it can be zero on discrete adapters.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (luid) (windows_gpu_dedicated_system_memory_size_bytes{job=~"$job", instance="$instance"}) * on (luid) group_left (name) max by (luid, name) (windows_gpu_info{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{name}} dedicated system capacity'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'bytes' }),
|
||||
|
||||
b.panel.new(130, 'Top 10 processes by local GPU memory', 'timeseries')
|
||||
+ b.panel.withDescription('Top ten processes at each point in time by local GPU memory in bytes, summed across GPUs. Membership can change over time. Process names require the process collector; otherwise the PID is shown. Memory accounting views overlap and should not be added together.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('topk(10, (sum by (process_id) (windows_gpu_process_memory_local_bytes{job=~"$job", instance="$instance"}) * on (process_id) group_left (process) max by (process_id, process) (windows_process_info{job=~"$job", instance="$instance"})) or on (process_id) sum by (process_id) (windows_gpu_process_memory_local_bytes{job=~"$job", instance="$instance"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{process}} ({{process_id}})'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'bytes' }),
|
||||
b.panel.new(128, 'Top 10 processes by shared GPU memory', 'timeseries')
|
||||
+ b.panel.withDescription('Top ten processes at each point in time by shared GPU memory in bytes, summed across GPUs. Membership can change over time. Process names require the process collector; otherwise the PID is shown. Memory accounting views overlap and should not be added together.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('topk(10, (sum by (process_id) (windows_gpu_process_memory_shared_bytes{job=~"$job", instance="$instance"}) * on (process_id) group_left (process) max by (process_id, process) (windows_process_info{job=~"$job", instance="$instance"})) or on (process_id) sum by (process_id) (windows_gpu_process_memory_shared_bytes{job=~"$job", instance="$instance"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{process}} ({{process_id}})'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'bytes' }),
|
||||
|
||||
b.panel.new(131, 'Top 10 processes by non-local GPU memory', 'timeseries')
|
||||
+ b.panel.withDescription('Top ten processes at each point in time by non-local GPU memory in bytes, summed across GPUs. Membership can change over time. Process names require the process collector; otherwise the PID is shown. Memory accounting views overlap and should not be added together.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('topk(10, (sum by (process_id) (windows_gpu_process_memory_non_local_bytes{job=~"$job", instance="$instance"}) * on (process_id) group_left (process) max by (process_id, process) (windows_process_info{job=~"$job", instance="$instance"})) or on (process_id) sum by (process_id) (windows_gpu_process_memory_non_local_bytes{job=~"$job", instance="$instance"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{process}} ({{process_id}})'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'bytes' }),
|
||||
]
|
||||
)
|
||||
b.panel.new(129, 'Top 10 processes by committed GPU memory', 'timeseries')
|
||||
+ b.panel.withDescription('Top ten processes at each point in time by committed GPU memory in bytes, summed across GPUs. Membership can change over time. Process names require the process collector; otherwise the PID is shown. Memory accounting views overlap and should not be added together.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('topk(10, (sum by (process_id) (windows_gpu_process_memory_committed_bytes{job=~"$job", instance="$instance"}) * on (process_id) group_left (process) max by (process_id, process) (windows_process_info{job=~"$job", instance="$instance"})) or on (process_id) sum by (process_id) (windows_gpu_process_memory_committed_bytes{job=~"$job", instance="$instance"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{process}} ({{process_id}})'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'bytes' }),
|
||||
|
||||
b.panel.new(130, 'Top 10 processes by local GPU memory', 'timeseries')
|
||||
+ b.panel.withDescription('Top ten processes at each point in time by local GPU memory in bytes, summed across GPUs. Membership can change over time. Process names require the process collector; otherwise the PID is shown. Memory accounting views overlap and should not be added together.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('topk(10, (sum by (process_id) (windows_gpu_process_memory_local_bytes{job=~"$job", instance="$instance"}) * on (process_id) group_left (process) max by (process_id, process) (windows_process_info{job=~"$job", instance="$instance"})) or on (process_id) sum by (process_id) (windows_gpu_process_memory_local_bytes{job=~"$job", instance="$instance"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{process}} ({{process_id}})'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'bytes' }),
|
||||
|
||||
b.panel.new(131, 'Top 10 processes by non-local GPU memory', 'timeseries')
|
||||
+ b.panel.withDescription('Top ten processes at each point in time by non-local GPU memory in bytes, summed across GPUs. Membership can change over time. Process names require the process collector; otherwise the PID is shown. Memory accounting views overlap and should not be added together.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('topk(10, (sum by (process_id) (windows_gpu_process_memory_non_local_bytes{job=~"$job", instance="$instance"}) * on (process_id) group_left (process) max by (process_id, process) (windows_process_info{job=~"$job", instance="$instance"})) or on (process_id) sum by (process_id) (windows_gpu_process_memory_non_local_bytes{job=~"$job", instance="$instance"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{process}} ({{process_id}})'),
|
||||
])
|
||||
+ b.panel.withDefaults({ min: 0, unit: 'bytes' }),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1,383 +1,323 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
|
||||
b.tab(
|
||||
'Hyper-V',
|
||||
b.rows([
|
||||
b.row('Host', b.grid([
|
||||
b.place(63, 0, 0, 4, 4),
|
||||
b.place(64, 4, 0, 4, 4),
|
||||
b.place(65, 8, 0, 4, 4),
|
||||
b.place(66, 12, 0, 4, 4),
|
||||
b.place(67, 16, 0, 4, 4),
|
||||
b.place(68, 20, 0, 4, 4),
|
||||
b.place(69, 0, 4, 12, 8),
|
||||
b.place(70, 12, 4, 12, 8),
|
||||
])),
|
||||
b.row('Memory', b.grid([
|
||||
b.place(72, 0, 0, 8, 8),
|
||||
b.place(73, 8, 0, 8, 8),
|
||||
b.place(74, 16, 0, 8, 8),
|
||||
])),
|
||||
b.row('Storage', b.grid([
|
||||
b.place(76, 0, 0, 8, 8),
|
||||
b.place(77, 8, 0, 8, 8),
|
||||
b.place(78, 16, 0, 8, 8),
|
||||
])),
|
||||
b.row('Network', b.grid([
|
||||
b.place(80, 0, 0, 8, 8),
|
||||
b.place(81, 8, 0, 8, 8),
|
||||
b.place(82, 16, 0, 8, 8),
|
||||
])),
|
||||
]),
|
||||
[
|
||||
b.panel.new(63, 'Virtual machines', 'stat')
|
||||
+ b.panel.withDescription('Needs the hyperv collector, which is not enabled by default.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(windows_hyperv_virtual_machine_health_total_count{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withInstant(),
|
||||
function(on)
|
||||
b.tab(
|
||||
'Hyper-V',
|
||||
b.rows([
|
||||
if on('hyperv') then b.summary([63, 64, 65, 66, 67, 68]),
|
||||
if on('hyperv') then b.row('CPU', b.flow([[69, 12, 8], [70, 12, 8]])),
|
||||
if on('hyperv') then b.row('Memory', b.flow([[72, 8, 8], [73, 8, 8], [74, 8, 8]])),
|
||||
if on('hyperv') then b.row('Storage', b.flow([[76, 8, 8], [77, 8, 8], [78, 8, 8]])),
|
||||
if on('hyperv') then b.row('Network', b.flow([[80, 8, 8], [81, 8, 8], [82, 8, 8]])),
|
||||
]),
|
||||
[
|
||||
b.stat(63, 'Virtual machines', 'Needs the hyperv collector, which is not enabled by default.', 'sum(windows_hyperv_virtual_machine_health_total_count{job=~"$job", instance="$instance"})', 'short'),
|
||||
|
||||
b.panel.new(64, 'VMs in critical health', 'stat')
|
||||
+ b.panel.withDescription('Number of Hyper-V virtual machines reporting critical health. Inspect the affected VM and Hyper-V event logs; this requires the optional hyperv collector.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(windows_hyperv_virtual_machine_health_total_count{job=~"$job", instance="$instance", state="critical"})', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
thresholds: {
|
||||
steps: [
|
||||
b.stat(64, 'VMs in critical health', 'Number of Hyper-V virtual machines reporting critical health. Inspect the affected VM and Hyper-V event logs; this requires the optional hyperv collector.', 'sum(windows_hyperv_virtual_machine_health_total_count{job=~"$job", instance="$instance", state="critical"})', steps=[{ color: 'green', value: null }, { color: 'red', value: 1 }]),
|
||||
|
||||
b.panel.new(65, 'Hyper-V WMI', 'stat')
|
||||
+ b.panel.withDescription('Whether the Hyper-V WMI namespace answers. When it stops answering, Hyper-V Manager and Failover Cluster Manager usually cannot manage the host.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_hyperv_wmi_health{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
mappings: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 1,
|
||||
options: {
|
||||
'0': {
|
||||
index: 0,
|
||||
text: 'not responding',
|
||||
},
|
||||
'1': {
|
||||
index: 1,
|
||||
text: 'ok',
|
||||
},
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'red',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'green',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: '',
|
||||
}),
|
||||
|
||||
b.panel.new(65, 'Hyper-V WMI', 'stat')
|
||||
+ b.panel.withDescription('Whether the Hyper-V WMI namespace answers. When it stops answering, Hyper-V Manager and Failover Cluster Manager usually cannot manage the host.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_hyperv_wmi_health{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
mappings: [
|
||||
b.stat(66, 'Logical processors', 'Number of host logical processors reported by Hyper-V. Use this as capacity context for host and virtual processor load; this requires the optional hyperv collector.', 'windows_hyperv_host_logical_processor_count{job=~"$job", instance="$instance"}', 'short'),
|
||||
|
||||
b.stat(67, 'Virtual processors', 'Virtual processors assigned to all VMs.', 'windows_hyperv_total_vm_processor_count{job=~"$job", instance="$instance"}', 'short'),
|
||||
|
||||
b.stat(68, 'vCPU per logical CPU', 'Virtual processors assigned to VMs per logical processor of the host.', 'windows_hyperv_total_vm_processor_count{job=~"$job", instance="$instance"} / windows_hyperv_host_logical_processor_count{job=~"$job", instance="$instance"}', 'short')
|
||||
+ b.panel.withDefaults({ decimals: 2 }),
|
||||
|
||||
b.panel.new(69, 'Host CPU time by state', 'timeseries')
|
||||
+ b.panel.withDescription('Share of all logical processors spent running guest code (VMs and the root partition) and in the hypervisor. With Hyper-V enabled, the CPU tab only sees the root partition.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum by (state) (rate(windows_hyperv_hypervisor_logical_processor_time_total{job=~"$job", instance="$instance", state!="idle"}[$__rate_interval])) / scalar(count(windows_hyperv_hypervisor_logical_processor_time_total{job=~"$job", instance="$instance", state="idle"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{state}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
fillOpacity: 40,
|
||||
stacking: {
|
||||
mode: 'normal',
|
||||
},
|
||||
},
|
||||
max: 1,
|
||||
min: 0,
|
||||
}),
|
||||
|
||||
b.panel.new(70, 'CPU usage per VM', 'timeseries')
|
||||
+ b.panel.withDescription('Virtual processor run time of each VM as a share of all logical processors of the host.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum by (vm) (rate(windows_hyperv_hypervisor_virtual_processor_run_time_total{job=~"$job", instance="$instance"}[$__rate_interval])) / scalar(max(windows_hyperv_host_logical_processor_count{job=~"$job", instance="$instance"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{vm}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
fillOpacity: 40,
|
||||
stacking: {
|
||||
mode: 'normal',
|
||||
},
|
||||
},
|
||||
min: 0,
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
sortBy: 'Mean',
|
||||
sortDesc: true,
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(72, 'Memory assigned to VMs', 'timeseries')
|
||||
+ b.panel.withDescription('Physical memory currently assigned to each VM with dynamic memory.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_hyperv_dynamic_memory_vm_physical_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('{{vm}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
fillOpacity: 40,
|
||||
stacking: {
|
||||
mode: 'normal',
|
||||
},
|
||||
},
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
sortBy: 'Mean',
|
||||
sortDesc: true,
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(73, 'VM memory pressure', 'timeseries')
|
||||
+ b.panel.withDescription('Memory a VM needs as a share of the memory it has. Above 100% the VM needs more memory than it is assigned.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_hyperv_dynamic_memory_vm_pressure_current_ratio{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('{{vm}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
thresholdsStyle: {
|
||||
mode: 'dashed',
|
||||
},
|
||||
},
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(74, 'Memory available for VMs', 'timeseries')
|
||||
+ b.panel.withDescription('Memory the dynamic memory balancer can still hand out to VMs.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_hyperv_dynamic_memory_balancer_available_memory_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('{{balancer}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(76, 'Virtual disk throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Reads are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_hyperv_virtual_storage_device_bytes_written{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{device}} write'),
|
||||
b.query.new('rate(windows_hyperv_virtual_storage_device_bytes_read{job=~"$job", instance="$instance"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{device}} read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'Bps',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
options: {
|
||||
'0': {
|
||||
index: 0,
|
||||
text: 'not responding',
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* read$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
'1': {
|
||||
index: 1,
|
||||
text: 'ok',
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(77, 'Virtual disk IOPS', 'timeseries')
|
||||
+ b.panel.withDescription('Reads are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_hyperv_virtual_storage_device_operations_written_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{device}} write'),
|
||||
b.query.new('rate(windows_hyperv_virtual_storage_device_operations_read_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{device}} read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'iops',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* read$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
},
|
||||
type: 'value',
|
||||
],
|
||||
},
|
||||
],
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'red',
|
||||
value: null,
|
||||
]),
|
||||
|
||||
b.panel.new(78, 'Virtual disk latency and errors', 'timeseries')
|
||||
+ b.panel.withDescription('Average I/O latency of each virtual disk: the total I/O latency divided by the number of completed I/O transfers. Errors use the right axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_hyperv_virtual_storage_device_io_latency_seconds_total{job=~"$job", instance="$instance"}[$__rate_interval]) / rate(windows_hyperv_virtual_storage_device_throughput_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{device}} latency'),
|
||||
b.query.new('rate(windows_hyperv_virtual_storage_device_error_count_total{job=~"$job", instance="$instance"}[$__rate_interval]) > 0', 'B')
|
||||
+ b.query.withLegendFormat('{{device}} errors'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 's',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* errors',
|
||||
},
|
||||
{
|
||||
color: 'green',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: '',
|
||||
}),
|
||||
|
||||
b.panel.new(66, 'Logical processors', 'stat')
|
||||
+ b.panel.withDescription('Number of host logical processors reported by Hyper-V. Use this as capacity context for host and virtual processor load; this requires the optional hyperv collector.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_hyperv_host_logical_processor_count{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
]),
|
||||
|
||||
b.panel.new(67, 'Virtual processors', 'stat')
|
||||
+ b.panel.withDescription('Virtual processors assigned to all VMs.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_hyperv_total_vm_processor_count{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
]),
|
||||
|
||||
b.panel.new(68, 'vCPU per logical CPU', 'stat')
|
||||
+ b.panel.withDescription('Virtual processors assigned to VMs per logical processor of the host.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_hyperv_total_vm_processor_count{job=~"$job", instance="$instance"} / windows_hyperv_host_logical_processor_count{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
decimals: 2,
|
||||
}),
|
||||
|
||||
b.panel.new(69, 'Host CPU time by state', 'timeseries')
|
||||
+ b.panel.withDescription('Share of all logical processors spent running guest code (VMs and the root partition) and in the hypervisor. With Hyper-V enabled, the CPU tab only sees the root partition.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum by (state) (rate(windows_hyperv_hypervisor_logical_processor_time_total{job=~"$job", instance="$instance", state!="idle"}[$__rate_interval])) / scalar(count(windows_hyperv_hypervisor_logical_processor_time_total{job=~"$job", instance="$instance", state="idle"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{state}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
fillOpacity: 40,
|
||||
stacking: {
|
||||
mode: 'normal',
|
||||
},
|
||||
},
|
||||
max: 1,
|
||||
min: 0,
|
||||
}),
|
||||
|
||||
b.panel.new(70, 'CPU usage per VM', 'timeseries')
|
||||
+ b.panel.withDescription('Virtual processor run time of each VM as a share of all logical processors of the host.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum by (vm) (rate(windows_hyperv_hypervisor_virtual_processor_run_time_total{job=~"$job", instance="$instance"}[$__rate_interval])) / scalar(max(windows_hyperv_host_logical_processor_count{job=~"$job", instance="$instance"}))', 'A')
|
||||
+ b.query.withLegendFormat('{{vm}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
fillOpacity: 40,
|
||||
stacking: {
|
||||
mode: 'normal',
|
||||
},
|
||||
},
|
||||
min: 0,
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
sortBy: 'Mean',
|
||||
sortDesc: true,
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(72, 'Memory assigned to VMs', 'timeseries')
|
||||
+ b.panel.withDescription('Physical memory currently assigned to each VM with dynamic memory.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_hyperv_dynamic_memory_vm_physical_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('{{vm}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
fillOpacity: 40,
|
||||
stacking: {
|
||||
mode: 'normal',
|
||||
},
|
||||
},
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
sortBy: 'Mean',
|
||||
sortDesc: true,
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(73, 'VM memory pressure', 'timeseries')
|
||||
+ b.panel.withDescription('Memory a VM needs as a share of the memory it has. Above 100% the VM needs more memory than it is assigned.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_hyperv_dynamic_memory_vm_pressure_current_ratio{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('{{vm}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
thresholdsStyle: {
|
||||
mode: 'dashed',
|
||||
},
|
||||
},
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(74, 'Memory available for VMs', 'timeseries')
|
||||
+ b.panel.withDescription('Memory the dynamic memory balancer can still hand out to VMs.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_hyperv_dynamic_memory_balancer_available_memory_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('{{balancer}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(76, 'Virtual disk throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Reads are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_hyperv_virtual_storage_device_bytes_written{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{device}} write'),
|
||||
b.query.new('rate(windows_hyperv_virtual_storage_device_bytes_read{job=~"$job", instance="$instance"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{device}} read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'Bps',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* read$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(77, 'Virtual disk IOPS', 'timeseries')
|
||||
+ b.panel.withDescription('Reads are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_hyperv_virtual_storage_device_operations_written_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{device}} write'),
|
||||
b.query.new('rate(windows_hyperv_virtual_storage_device_operations_read_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{device}} read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'iops',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* read$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(78, 'Virtual disk latency and errors', 'timeseries')
|
||||
+ b.panel.withDescription('Average I/O latency of each virtual disk: the total I/O latency divided by the number of completed I/O transfers. Errors use the right axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_hyperv_virtual_storage_device_io_latency_seconds_total{job=~"$job", instance="$instance"}[$__rate_interval]) / rate(windows_hyperv_virtual_storage_device_throughput_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{device}} latency'),
|
||||
b.query.new('rate(windows_hyperv_virtual_storage_device_error_count_total{job=~"$job", instance="$instance"}[$__rate_interval]) > 0', 'B')
|
||||
+ b.query.withLegendFormat('{{device}} errors'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 's',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* errors',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'ops',
|
||||
},
|
||||
{
|
||||
id: 'custom.axisPlacement',
|
||||
value: 'right',
|
||||
},
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'red',
|
||||
mode: 'fixed',
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'ops',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(80, 'Virtual switch throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Received traffic is drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_hyperv_vswitch_bytes_sent_total{job=~"$job", instance="$instance"}[$__rate_interval]) * 8', 'A')
|
||||
+ b.query.withLegendFormat('{{vswitch}} sent'),
|
||||
b.query.new('rate(windows_hyperv_vswitch_bytes_received_total{job=~"$job", instance="$instance"}[$__rate_interval]) * 8', 'B')
|
||||
+ b.query.withLegendFormat('{{vswitch}} received'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'bps',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* received$',
|
||||
{
|
||||
id: 'custom.axisPlacement',
|
||||
value: 'right',
|
||||
},
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'red',
|
||||
mode: 'fixed',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
]),
|
||||
|
||||
b.panel.new(80, 'Virtual switch throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Received traffic is drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_hyperv_vswitch_bytes_sent_total{job=~"$job", instance="$instance"}[$__rate_interval]) * 8', 'A')
|
||||
+ b.query.withLegendFormat('{{vswitch}} sent'),
|
||||
b.query.new('rate(windows_hyperv_vswitch_bytes_received_total{job=~"$job", instance="$instance"}[$__rate_interval]) * 8', 'B')
|
||||
+ b.query.withLegendFormat('{{vswitch}} received'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'bps',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* received$',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(81, 'Virtual switch dropped packets', 'timeseries')
|
||||
+ b.panel.withDescription('Incoming and outgoing packets dropped per second by each Hyper-V virtual switch. Correlate sustained drops with network load, resource pressure and switch policies; this requires the optional hyperv collector.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_hyperv_vswitch_dropped_packets_incoming_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{vswitch}} incoming'),
|
||||
b.query.new('rate(windows_hyperv_vswitch_dropped_packets_outcoming_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{vswitch}} outgoing'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'pps',
|
||||
}),
|
||||
|
||||
b.panel.new(82, 'VM network adapter throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Received traffic is drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_hyperv_virtual_network_adapter_sent_bytes_total{job=~"$job", instance="$instance"}[$__rate_interval]) * 8', 'A')
|
||||
+ b.query.withLegendFormat('{{adapter}} sent'),
|
||||
b.query.new('rate(windows_hyperv_virtual_network_adapter_received_bytes_total{job=~"$job", instance="$instance"}[$__rate_interval]) * 8', 'B')
|
||||
+ b.query.withLegendFormat('{{adapter}} received'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'bps',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* received$',
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
]),
|
||||
|
||||
b.panel.new(81, 'Virtual switch dropped packets', 'timeseries')
|
||||
+ b.panel.withDescription('Incoming and outgoing packets dropped per second by each Hyper-V virtual switch. Correlate sustained drops with network load, resource pressure and switch policies; this requires the optional hyperv collector.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_hyperv_vswitch_dropped_packets_incoming_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{vswitch}} incoming'),
|
||||
b.query.new('rate(windows_hyperv_vswitch_dropped_packets_outcoming_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{vswitch}} outgoing'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'pps',
|
||||
}),
|
||||
|
||||
b.panel.new(82, 'VM network adapter throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Received traffic is drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_hyperv_virtual_network_adapter_sent_bytes_total{job=~"$job", instance="$instance"}[$__rate_interval]) * 8', 'A')
|
||||
+ b.query.withLegendFormat('{{adapter}} sent'),
|
||||
b.query.new('rate(windows_hyperv_virtual_network_adapter_received_bytes_total{job=~"$job", instance="$instance"}[$__rate_interval]) * 8', 'B')
|
||||
+ b.query.withLegendFormat('{{adapter}} received'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'bps',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* received$',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
]
|
||||
)
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1,135 +1,148 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
local metric(name) = 'windows_memory_' + name + '{job=~"$job", instance="$instance"}';
|
||||
|
||||
b.tab(
|
||||
'Memory',
|
||||
b.grid([
|
||||
b.place(32, 0, 0, 12, 8),
|
||||
b.place(33, 12, 0, 12, 8),
|
||||
b.place(34, 0, 8, 12, 8),
|
||||
b.place(35, 12, 8, 12, 8),
|
||||
]),
|
||||
[
|
||||
b.panel.new(32, 'Physical memory', 'timeseries')
|
||||
+ b.panel.withDescription('Available memory is the standby (cache), free and zero page lists.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_memory_physical_total_bytes{job=~"$job", instance="$instance"} - windows_memory_physical_free_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('used'),
|
||||
b.query.new('windows_memory_physical_free_bytes{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('available'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
fillOpacity: 40,
|
||||
stacking: {
|
||||
mode: 'normal',
|
||||
},
|
||||
},
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'used',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'orange',
|
||||
mode: 'fixed',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'available',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'green',
|
||||
mode: 'fixed',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
function(on)
|
||||
b.tab(
|
||||
'Memory',
|
||||
b.rows([
|
||||
if on('memory') then b.summary([166, 167, 168, 169, 170, 171]),
|
||||
if on('memory') then b.row('Physical memory and commit', b.flow([[32, 12, 8], [33, 12, 8]])),
|
||||
if on('memory') then b.row('Paging and kernel memory', b.flow([[34, 12, 8], [35, 12, 8]])),
|
||||
]),
|
||||
[
|
||||
b.stat(166, 'Memory used', 'Share of physical memory in use: total minus available memory.', '1 - ' + metric('physical_free_bytes') + ' / ' + metric('physical_total_bytes'), 'percentunit', steps=b.levels(0.8, 0.9))
|
||||
+ b.panel.withDefaults({ decimals: 1, max: 1, min: 0 }),
|
||||
b.stat(167, 'Available', 'Physical memory available to processes: the standby, free and zero page lists.', metric('physical_free_bytes'), 'bytes')
|
||||
+ b.panel.withDefaults({ decimals: 1 }),
|
||||
b.stat(168, 'Commit used', 'Committed virtual memory as a share of the commit limit. Allocations fail at 100%.', metric('committed_bytes') + ' / ' + metric('commit_limit'), 'percentunit', steps=b.levels(0.8, 0.9))
|
||||
+ b.panel.withDefaults({ decimals: 1, max: 1, min: 0 }),
|
||||
b.stat(169, 'Page reads', 'Hard page faults read from disk per second. Sustained page reads indicate memory pressure.', 'rate(' + metric('swap_page_reads_total') + '[$__rate_interval])', 'ops'),
|
||||
b.stat(170, 'Nonpaged pool', 'Kernel memory that cannot be paged out. Steady growth can indicate a driver leak.', metric('pool_nonpaged_bytes'), 'bytes')
|
||||
+ b.panel.withDefaults({ decimals: 1 }),
|
||||
b.stat(171, 'System cache', 'Physical memory used by the file system cache.', metric('cache_bytes'), 'bytes')
|
||||
+ b.panel.withDefaults({ decimals: 1 }),
|
||||
|
||||
b.panel.new(33, 'Commit charge', 'timeseries')
|
||||
+ b.panel.withDescription('Committed virtual memory against the commit limit (physical memory + page files).')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_memory_committed_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('committed'),
|
||||
b.query.new('windows_memory_commit_limit{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('limit'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'limit',
|
||||
b.panel.new(32, 'Physical memory', 'timeseries')
|
||||
+ b.panel.withDescription('Available memory is the standby (cache), free and zero page lists.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_memory_physical_total_bytes{job=~"$job", instance="$instance"} - windows_memory_physical_free_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('used'),
|
||||
b.query.new('windows_memory_physical_free_bytes{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('available'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
fillOpacity: 40,
|
||||
stacking: {
|
||||
mode: 'normal',
|
||||
},
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'red',
|
||||
mode: 'fixed',
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'used',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'orange',
|
||||
mode: 'fixed',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'available',
|
||||
},
|
||||
{
|
||||
id: 'custom.fillOpacity',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'custom.lineStyle',
|
||||
value: {
|
||||
dash: [
|
||||
10,
|
||||
10,
|
||||
],
|
||||
fill: 'dash',
|
||||
properties: [
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'green',
|
||||
mode: 'fixed',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(33, 'Commit charge', 'timeseries')
|
||||
+ b.panel.withDescription('Committed virtual memory against the commit limit (physical memory + page files).')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_memory_committed_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('committed'),
|
||||
b.query.new('windows_memory_commit_limit{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('limit'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'limit',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
properties: [
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'red',
|
||||
mode: 'fixed',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'custom.fillOpacity',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'custom.lineStyle',
|
||||
value: {
|
||||
dash: [
|
||||
10,
|
||||
10,
|
||||
],
|
||||
fill: 'dash',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(34, 'Paging', 'timeseries')
|
||||
+ b.panel.withDescription('Hard page faults read from or written to disk. Sustained page reads indicate memory pressure.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_memory_swap_page_reads_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('page reads'),
|
||||
b.query.new('rate(windows_memory_swap_page_writes_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('page writes'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'ops',
|
||||
}),
|
||||
b.panel.new(34, 'Paging', 'timeseries')
|
||||
+ b.panel.withDescription('Hard page faults read from or written to disk. Sustained page reads indicate memory pressure.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_memory_swap_page_reads_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('page reads'),
|
||||
b.query.new('rate(windows_memory_swap_page_writes_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('page writes'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'ops',
|
||||
}),
|
||||
|
||||
b.panel.new(35, 'Kernel pools and cache', 'timeseries')
|
||||
+ b.panel.withDescription('A steadily growing nonpaged pool usually means a driver leaks memory.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_memory_pool_nonpaged_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('nonpaged pool'),
|
||||
b.query.new('windows_memory_pool_paged_bytes{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('paged pool'),
|
||||
b.query.new('windows_memory_cache_bytes{job=~"$job", instance="$instance"}', 'C')
|
||||
+ b.query.withLegendFormat('system cache'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
}),
|
||||
]
|
||||
)
|
||||
b.panel.new(35, 'Kernel pools and cache', 'timeseries')
|
||||
+ b.panel.withDescription('A steadily growing nonpaged pool usually means a driver leaks memory.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_memory_pool_nonpaged_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('nonpaged pool'),
|
||||
b.query.new('windows_memory_pool_paged_bytes{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('paged pool'),
|
||||
b.query.new('windows_memory_cache_bytes{job=~"$job", instance="$instance"}', 'C')
|
||||
+ b.query.withLegendFormat('system cache'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
}),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1,218 +1,233 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
local nic(name) = 'windows_net_' + name + '{job=~"$job", instance="$instance", nic=~"$nic"}';
|
||||
local sumRate(metric) = 'sum(rate(' + metric + '[$__rate_interval]))';
|
||||
local tcp(name) = 'windows_tcp_' + name + '{job=~"$job", instance="$instance"}';
|
||||
|
||||
b.tab(
|
||||
'Network',
|
||||
b.rows([
|
||||
b.row('Interfaces', b.grid([b.place(45, 0, 0, 12, 8), b.place(46, 12, 0, 12, 8), b.place(47, 0, 8, 12, 8), b.place(48, 12, 8, 12, 8)])),
|
||||
b.row('TCP', b.grid([b.place(140, 0, 0, 12, 8), b.place(141, 12, 0, 12, 8), b.place(142, 0, 8, 12, 8), b.place(143, 12, 8, 12, 8)])),
|
||||
b.row('UDP', b.grid([b.place(144, 0, 0, 12, 8), b.place(145, 12, 0, 12, 8)])),
|
||||
]),
|
||||
[
|
||||
b.panel.new(45, 'Network throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Received traffic is drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_net_bytes_sent_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval]) * 8', 'A')
|
||||
+ b.query.withLegendFormat('{{nic}} sent'),
|
||||
b.query.new('rate(windows_net_bytes_received_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval]) * 8', 'B')
|
||||
+ b.query.withLegendFormat('{{nic}} received'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'bps',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* received$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
function(on)
|
||||
b.tab(
|
||||
'Network',
|
||||
b.rows([
|
||||
b.summary([if on('net') then 178, if on('net') then 179, if on('net') then 180, if on('net') then 181, if on('tcp') then 182, if on('tcp') then 183]),
|
||||
if on('net') then b.row('Interfaces', b.flow([[45, 12, 8], [46, 12, 8], [47, 12, 8], [48, 12, 8]])),
|
||||
if on('tcp') then b.row('TCP', b.flow([[140, 12, 8], [141, 12, 8], [142, 12, 8], [143, 12, 8]])),
|
||||
if on('udp') then b.row('UDP', b.flow([[144, 12, 8], [145, 12, 8]])),
|
||||
]),
|
||||
[
|
||||
b.stat(178, 'Sent', 'Bits sent per second, summed over the selected interfaces.', sumRate(nic('bytes_sent_total')) + ' * 8', 'bps'),
|
||||
b.stat(179, 'Received', 'Bits received per second, summed over the selected interfaces.', sumRate(nic('bytes_received_total')) + ' * 8', 'bps'),
|
||||
b.stat(180, 'Utilization', 'Utilization of the busiest selected interface: bytes sent and received per second against its current bandwidth.', 'max(rate(' + nic('bytes_total') + '[$__rate_interval]) / (' + nic('current_bandwidth_bytes') + ' > 0))', 'percentunit', steps=b.levels(0.8, 0.9))
|
||||
+ b.panel.withDefaults({ decimals: 1, min: 0 }),
|
||||
b.stat(181, 'Errors', 'Packets per second that were discarded or had errors, inbound and outbound, summed over the selected interfaces.', std.join(' + ', [sumRate(nic(name)) for name in ['packets_received_errors_total', 'packets_received_discarded_total', 'packets_outbound_errors_total', 'packets_outbound_discarded_total']]), 'pps', steps=b.levels(1, 10))
|
||||
+ b.panel.withDefaults({ min: 0 }),
|
||||
b.stat(182, 'TCP sessions', 'TCP connections in the established or close-wait state, IPv4 and IPv6. Requires the tcp collector.', 'sum(' + tcp('connections_established') + ')', 'short'),
|
||||
b.stat(183, 'Retransmits', 'Retransmitted segments as a share of sent segments. A rate that stays above a few percent points to packet loss. Requires the tcp collector.', '(' + sumRate(tcp('segments_retransmitted_total')) + ' / (' + sumRate(tcp('segments_sent_total')) + ' > 0)) or (0 * ' + sumRate(tcp('segments_sent_total')) + ')', 'percentunit', steps=b.levels(0.01, 0.05))
|
||||
+ b.panel.withDefaults({ decimals: 2, min: 0 }),
|
||||
|
||||
b.panel.new(46, 'Network utilization', 'timeseries')
|
||||
+ b.panel.withDescription('Sent plus received traffic relative to the link speed reported by the adapter. Interfaces without a link speed are not shown.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_net_bytes_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval]) / (windows_net_current_bandwidth_bytes{job=~"$job", instance="$instance", nic=~"$nic"} > 0)', 'A')
|
||||
+ b.query.withLegendFormat('{{nic}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
b.panel.new(45, 'Network throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Received traffic is drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_net_bytes_sent_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval]) * 8', 'A')
|
||||
+ b.query.withLegendFormat('{{nic}} sent'),
|
||||
b.query.new('rate(windows_net_bytes_received_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval]) * 8', 'B')
|
||||
+ b.query.withLegendFormat('{{nic}} received'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'bps',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* received$',
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(47, 'Packets', 'timeseries')
|
||||
+ b.panel.withDescription('Received packets are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_net_packets_sent_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{nic}} sent'),
|
||||
b.query.new('rate(windows_net_packets_received_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{nic}} received'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'pps',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* received$',
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
]),
|
||||
|
||||
b.panel.new(48, 'Network errors and discards', 'timeseries')
|
||||
+ b.panel.withDescription('Receive and transmit packet errors, discarded packets and received packets with unknown protocols per second for each selected interface. Errors can indicate link or driver problems; discards can reflect buffer pressure or filtering.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_net_packets_received_errors_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{nic}} received errors'),
|
||||
b.query.new('rate(windows_net_packets_received_discarded_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{nic}} received discarded'),
|
||||
b.query.new('rate(windows_net_packets_outbound_errors_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])', 'C')
|
||||
+ b.query.withLegendFormat('{{nic}} outbound errors'),
|
||||
b.query.new('rate(windows_net_packets_outbound_discarded_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])', 'D')
|
||||
+ b.query.withLegendFormat('{{nic}} outbound discarded'),
|
||||
b.query.new('rate(windows_net_packets_received_unknown_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])', 'E')
|
||||
+ b.query.withLegendFormat('{{nic}} received unknown protocol'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'pps',
|
||||
}),
|
||||
|
||||
b.panel.new(140, 'TCP connections by state', 'timeseries')
|
||||
+ b.panel.withDescription('Needs the tcp collector, which is not enabled by default. A growing CLOSE_WAIT count means an application does not close its sockets. Many TIME_WAIT connections can use up the dynamic port range for outgoing connections.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum by (state) (windows_tcp_connections_state_count{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{state}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'short',
|
||||
})
|
||||
+ b.panel.withOverrides([]),
|
||||
|
||||
b.panel.new(141, 'TCP connections opened, failed and reset', 'timeseries')
|
||||
+ b.panel.withDescription('Outgoing (active) and incoming (passive) connections opened per second, connection attempts that failed, and established connections that were reset.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(rate(windows_tcp_connections_active_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'A')
|
||||
+ b.query.withLegendFormat('opened outgoing'),
|
||||
b.query.new('sum(rate(windows_tcp_connections_passive_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'B')
|
||||
+ b.query.withLegendFormat('accepted incoming'),
|
||||
b.query.new('sum(rate(windows_tcp_connection_failures_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'C')
|
||||
+ b.query.withLegendFormat('failed'),
|
||||
b.query.new('sum(rate(windows_tcp_connections_reset_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'D')
|
||||
+ b.query.withLegendFormat('reset'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'ops',
|
||||
})
|
||||
+ b.panel.withOverrides([{ matcher: { id: 'byName', options: 'failed' }, properties: [{ id: 'color', value: { fixedColor: 'red', mode: 'fixed' } }] }, { matcher: { id: 'byName', options: 'reset' }, properties: [{ id: 'color', value: { fixedColor: 'orange', mode: 'fixed' } }] }]),
|
||||
|
||||
b.panel.new(142, 'TCP segments', 'timeseries')
|
||||
+ b.panel.withDescription('Received segments are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(rate(windows_tcp_segments_sent_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'A')
|
||||
+ b.query.withLegendFormat('sent'),
|
||||
b.query.new('sum(rate(windows_tcp_segments_received_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'B')
|
||||
+ b.query.withLegendFormat('received'),
|
||||
b.query.new('sum(rate(windows_tcp_segments_retransmitted_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'C')
|
||||
+ b.query.withLegendFormat('retransmitted'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'pps',
|
||||
})
|
||||
+ b.panel.withOverrides([{ matcher: { id: 'byRegexp', options: 'received' }, properties: [{ id: 'custom.transform', value: 'negative-Y' }] }, { matcher: { id: 'byName', options: 'retransmitted' }, properties: [{ id: 'color', value: { fixedColor: 'red', mode: 'fixed' } }] }]),
|
||||
|
||||
b.panel.new(143, 'TCP retransmission rate', 'timeseries')
|
||||
+ b.panel.withDescription('Retransmitted segments as a share of sent segments. A rate that stays above a few percent points to packet loss on the network.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(rate(windows_tcp_segments_retransmitted_total{job=~"$job", instance="$instance"}[$__rate_interval])) / sum(rate(windows_tcp_segments_sent_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'A')
|
||||
+ b.query.withLegendFormat('retransmitted'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
thresholdsStyle: {
|
||||
mode: 'dashed',
|
||||
b.panel.new(46, 'Network utilization', 'timeseries')
|
||||
+ b.panel.withDescription('Sent plus received traffic relative to the link speed reported by the adapter. Interfaces without a link speed are not shown.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_net_bytes_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval]) / (windows_net_current_bandwidth_bytes{job=~"$job", instance="$instance", nic=~"$nic"} > 0)', 'A')
|
||||
+ b.query.withLegendFormat('{{nic}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 0.01,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 0.05,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
})
|
||||
+ b.panel.withOverrides([]),
|
||||
}),
|
||||
|
||||
b.panel.new(144, 'UDP datagrams', 'timeseries')
|
||||
+ b.panel.withDescription('Needs the udp collector, which is not enabled by default. Received datagrams are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(rate(windows_udp_datagram_sent_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'A')
|
||||
+ b.query.withLegendFormat('sent'),
|
||||
b.query.new('sum(rate(windows_udp_datagram_received_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'B')
|
||||
+ b.query.withLegendFormat('received'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'pps',
|
||||
})
|
||||
+ b.panel.withOverrides([{ matcher: { id: 'byRegexp', options: 'received' }, properties: [{ id: 'custom.transform', value: 'negative-Y' }] }]),
|
||||
b.panel.new(47, 'Packets', 'timeseries')
|
||||
+ b.panel.withDescription('Received packets are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_net_packets_sent_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{nic}} sent'),
|
||||
b.query.new('rate(windows_net_packets_received_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{nic}} received'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'pps',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: '.* received$',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(145, 'UDP errors', 'timeseries')
|
||||
+ b.panel.withDescription('Received datagrams that could not be delivered, and datagrams for a port that no application listens on.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(rate(windows_udp_datagram_received_errors_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'A')
|
||||
+ b.query.withLegendFormat('received errors'),
|
||||
b.query.new('sum(rate(windows_udp_datagram_no_port_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'B')
|
||||
+ b.query.withLegendFormat('no listening port'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'pps',
|
||||
})
|
||||
+ b.panel.withOverrides([{ matcher: { id: 'byName', options: 'received errors' }, properties: [{ id: 'color', value: { fixedColor: 'red', mode: 'fixed' } }] }]),
|
||||
]
|
||||
)
|
||||
b.panel.new(48, 'Network errors and discards', 'timeseries')
|
||||
+ b.panel.withDescription('Receive and transmit packet errors, discarded packets and received packets with unknown protocols per second for each selected interface. Errors can indicate link or driver problems; discards can reflect buffer pressure or filtering.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('rate(windows_net_packets_received_errors_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])', 'A')
|
||||
+ b.query.withLegendFormat('{{nic}} received errors'),
|
||||
b.query.new('rate(windows_net_packets_received_discarded_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])', 'B')
|
||||
+ b.query.withLegendFormat('{{nic}} received discarded'),
|
||||
b.query.new('rate(windows_net_packets_outbound_errors_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])', 'C')
|
||||
+ b.query.withLegendFormat('{{nic}} outbound errors'),
|
||||
b.query.new('rate(windows_net_packets_outbound_discarded_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])', 'D')
|
||||
+ b.query.withLegendFormat('{{nic}} outbound discarded'),
|
||||
b.query.new('rate(windows_net_packets_received_unknown_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])', 'E')
|
||||
+ b.query.withLegendFormat('{{nic}} received unknown protocol'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'pps',
|
||||
}),
|
||||
|
||||
b.panel.new(140, 'TCP connections by state', 'timeseries')
|
||||
+ b.panel.withDescription('Needs the tcp collector, which is not enabled by default. A growing CLOSE_WAIT count means an application does not close its sockets. Many TIME_WAIT connections can use up the dynamic port range for outgoing connections.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum by (state) (windows_tcp_connections_state_count{job=~"$job", instance="$instance"})', 'A')
|
||||
+ b.query.withLegendFormat('{{state}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'short',
|
||||
})
|
||||
+ b.panel.withOverrides([]),
|
||||
|
||||
b.panel.new(141, 'TCP connections opened, failed and reset', 'timeseries')
|
||||
+ b.panel.withDescription('Outgoing (active) and incoming (passive) connections opened per second, connection attempts that failed, and established connections that were reset.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(rate(windows_tcp_connections_active_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'A')
|
||||
+ b.query.withLegendFormat('opened outgoing'),
|
||||
b.query.new('sum(rate(windows_tcp_connections_passive_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'B')
|
||||
+ b.query.withLegendFormat('accepted incoming'),
|
||||
b.query.new('sum(rate(windows_tcp_connection_failures_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'C')
|
||||
+ b.query.withLegendFormat('failed'),
|
||||
b.query.new('sum(rate(windows_tcp_connections_reset_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'D')
|
||||
+ b.query.withLegendFormat('reset'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'ops',
|
||||
})
|
||||
+ b.panel.withOverrides([{ matcher: { id: 'byName', options: 'failed' }, properties: [{ id: 'color', value: { fixedColor: 'red', mode: 'fixed' } }] }, { matcher: { id: 'byName', options: 'reset' }, properties: [{ id: 'color', value: { fixedColor: 'orange', mode: 'fixed' } }] }]),
|
||||
|
||||
b.panel.new(142, 'TCP segments', 'timeseries')
|
||||
+ b.panel.withDescription('Received segments are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(rate(windows_tcp_segments_sent_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'A')
|
||||
+ b.query.withLegendFormat('sent'),
|
||||
b.query.new('sum(rate(windows_tcp_segments_received_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'B')
|
||||
+ b.query.withLegendFormat('received'),
|
||||
b.query.new('sum(rate(windows_tcp_segments_retransmitted_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'C')
|
||||
+ b.query.withLegendFormat('retransmitted'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'pps',
|
||||
})
|
||||
+ b.panel.withOverrides([{ matcher: { id: 'byRegexp', options: 'received' }, properties: [{ id: 'custom.transform', value: 'negative-Y' }] }, { matcher: { id: 'byName', options: 'retransmitted' }, properties: [{ id: 'color', value: { fixedColor: 'red', mode: 'fixed' } }] }]),
|
||||
|
||||
b.panel.new(143, 'TCP retransmission rate', 'timeseries')
|
||||
+ b.panel.withDescription('Retransmitted segments as a share of sent segments. A rate that stays above a few percent points to packet loss on the network.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(rate(windows_tcp_segments_retransmitted_total{job=~"$job", instance="$instance"}[$__rate_interval])) / sum(rate(windows_tcp_segments_sent_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'A')
|
||||
+ b.query.withLegendFormat('retransmitted'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
thresholdsStyle: {
|
||||
mode: 'dashed',
|
||||
},
|
||||
},
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 0.01,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 0.05,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
})
|
||||
+ b.panel.withOverrides([]),
|
||||
|
||||
b.panel.new(144, 'UDP datagrams', 'timeseries')
|
||||
+ b.panel.withDescription('Needs the udp collector, which is not enabled by default. Received datagrams are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(rate(windows_udp_datagram_sent_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'A')
|
||||
+ b.query.withLegendFormat('sent'),
|
||||
b.query.new('sum(rate(windows_udp_datagram_received_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'B')
|
||||
+ b.query.withLegendFormat('received'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'pps',
|
||||
})
|
||||
+ b.panel.withOverrides([{ matcher: { id: 'byRegexp', options: 'received' }, properties: [{ id: 'custom.transform', value: 'negative-Y' }] }]),
|
||||
|
||||
b.panel.new(145, 'UDP errors', 'timeseries')
|
||||
+ b.panel.withDescription('Received datagrams that could not be delivered, and datagrams for a port that no application listens on.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(rate(windows_udp_datagram_received_errors_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'A')
|
||||
+ b.query.withLegendFormat('received errors'),
|
||||
b.query.new('sum(rate(windows_udp_datagram_no_port_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'B')
|
||||
+ b.query.withLegendFormat('no listening port'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
min: 0,
|
||||
unit: 'pps',
|
||||
})
|
||||
+ b.panel.withOverrides([{ matcher: { id: 'byName', options: 'received errors' }, properties: [{ id: 'color', value: { fixedColor: 'red', mode: 'fixed' } }] }]),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1,497 +1,488 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
|
||||
b.tab(
|
||||
'Overview',
|
||||
b.grid([
|
||||
b.place(10, 0, 0, 4, 4),
|
||||
b.place(11, 4, 0, 3, 4),
|
||||
b.place(12, 7, 0, 2, 4),
|
||||
b.place(13, 9, 0, 2, 4),
|
||||
b.place(14, 11, 0, 3, 4),
|
||||
b.place(15, 14, 0, 3, 4),
|
||||
b.place(16, 17, 0, 3, 4),
|
||||
b.place(17, 20, 0, 4, 4),
|
||||
b.place(18, 0, 4, 12, 8),
|
||||
b.place(19, 12, 4, 12, 8),
|
||||
b.place(20, 0, 12, 8, 8),
|
||||
b.place(21, 8, 12, 8, 8),
|
||||
b.place(22, 16, 12, 8, 8),
|
||||
]),
|
||||
[
|
||||
b.panel.new(10, 'Operating system', 'stat')
|
||||
+ b.panel.withDescription('Windows product name and version reported by the os collector. Use this to distinguish hosts when investigating version-specific behavior.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_os_info{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: '',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
colorMode: 'none',
|
||||
reduceOptions: {
|
||||
fields: '/^product$/',
|
||||
},
|
||||
textMode: 'value',
|
||||
}),
|
||||
|
||||
b.panel.new(11, 'Uptime', 'stat')
|
||||
+ b.panel.withDescription('Time since the last boot.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('time() - windows_system_boot_time_timestamp{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'orange',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'text',
|
||||
value: 3600,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'dtdurations',
|
||||
}),
|
||||
|
||||
b.panel.new(12, 'CPUs', 'stat')
|
||||
+ b.panel.withDescription('Number of logical processors available to Windows, including SMT threads. This is the logical CPU count rather than the number of physical sockets.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_cpu_logical_processor{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
function(on)
|
||||
b.tab(
|
||||
'Overview',
|
||||
b.rows([
|
||||
b.row('Summary', b.flow([if on('os') then [10, 4, 4], if on('system') then [11, 3, 4], if on('cpu') then [12, 2, 4], if on('memory') then [13, 2, 4], if on('cpu') then [14, 3, 4], if on('memory') then [15, 3, 4], if on('memory') then [16, 3, 4], if on('service') then [17, 4, 4]])),
|
||||
b.row('CPU and memory', b.flow([if on('cpu') then [18, 12, 8], if on('memory') then [19, 12, 8]])),
|
||||
b.row('Storage and network', b.flow([if on('logical_disk') then [20, 8, 8], if on('logical_disk') then [21, 8, 8], if on('net') then [22, 8, 8]])),
|
||||
]),
|
||||
|
||||
b.panel.new(13, 'RAM', 'stat')
|
||||
+ b.panel.withDescription('Total physical memory visible to Windows in bytes. Compare with used memory and commit charge when investigating memory pressure.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_memory_physical_total_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
decimals: 1,
|
||||
unit: 'bytes',
|
||||
}),
|
||||
|
||||
b.panel.new(14, 'CPU busy', 'stat')
|
||||
+ b.panel.withDescription('Percentage of non-idle CPU time averaged across logical processors over the rate interval. Sustained high values together with a growing processor queue can indicate CPU contention.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('100 * (1 - avg by (instance) (clamp_max(rate(windows_cpu_time_total{job=~"$job", instance="$instance", mode="idle"}[$__rate_interval]), 1)))', 'A'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
decimals: 1,
|
||||
max: 100,
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'percent',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
graphMode: 'area',
|
||||
}),
|
||||
|
||||
b.panel.new(15, 'Memory used', 'stat')
|
||||
+ b.panel.withDescription('Percentage of physical memory in use, calculated as total minus available memory. This differs from commit charge, which measures promised virtual memory against the commit limit.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('100 * (1 - windows_memory_physical_free_bytes{job=~"$job", instance="$instance"} / windows_memory_physical_total_bytes{job=~"$job", instance="$instance"})', 'A'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
decimals: 1,
|
||||
max: 100,
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'percent',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
graphMode: 'area',
|
||||
}),
|
||||
|
||||
b.panel.new(16, 'Commit used', 'stat')
|
||||
+ b.panel.withDescription('Committed virtual memory as a share of the commit limit (physical memory + page files). Allocations fail at 100%.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('100 * windows_memory_committed_bytes{job=~"$job", instance="$instance"} / windows_memory_commit_limit{job=~"$job", instance="$instance"}', 'A'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
decimals: 1,
|
||||
max: 100,
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'percent',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
graphMode: 'area',
|
||||
}),
|
||||
|
||||
b.panel.new(17, 'Stopped auto services', 'stat')
|
||||
+ b.panel.withDescription('Services with start mode "auto" that are not running. See the Services tab for names.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('count(windows_service_state{job=~"$job", instance="$instance", state="running"} == 0 and on (name) windows_service_start_mode{job=~"$job", instance="$instance", start_mode="auto"} == 1) or vector(0)', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(18, 'CPU utilization', 'timeseries')
|
||||
+ b.panel.withDescription('Percentage of non-idle CPU time averaged across logical processors. This measures CPU time rather than the frequency-adjusted processor utility shown by Windows Task Manager.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('100 * (1 - avg by (instance) (clamp_max(rate(windows_cpu_time_total{job=~"$job", instance="$instance", mode="idle"}[$__rate_interval]), 1)))', 'A')
|
||||
+ b.query.withLegendFormat('CPU busy'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
max: 100,
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'percent',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(19, 'Physical memory', 'timeseries')
|
||||
+ b.panel.withDescription('Available memory is the standby (cache), free and zero page lists.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_memory_physical_total_bytes{job=~"$job", instance="$instance"} - windows_memory_physical_free_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('used'),
|
||||
b.query.new('windows_memory_physical_free_bytes{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('available'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
fillOpacity: 40,
|
||||
stacking: {
|
||||
mode: 'normal',
|
||||
[
|
||||
b.panel.new(10, 'Operating system', 'stat')
|
||||
+ b.panel.withDescription('Windows product name and version reported by the os collector. Use this to distinguish hosts when investigating version-specific behavior.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_os_info{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: '',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
colorMode: 'none',
|
||||
reduceOptions: {
|
||||
fields: '/^product$/',
|
||||
},
|
||||
},
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'used',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'orange',
|
||||
mode: 'fixed',
|
||||
textMode: 'value',
|
||||
}),
|
||||
|
||||
b.panel.new(11, 'Uptime', 'stat')
|
||||
+ b.panel.withDescription('Time since the last boot.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('time() - windows_system_boot_time_timestamp{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'orange',
|
||||
value: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'available',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'green',
|
||||
mode: 'fixed',
|
||||
{
|
||||
color: 'text',
|
||||
value: 3600,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
],
|
||||
},
|
||||
unit: 'dtdurations',
|
||||
}),
|
||||
|
||||
b.panel.new(20, 'Volumes', 'table')
|
||||
+ b.panel.withDescription('Size, available space and used percentage of each selected logical volume. Windows free-space counters can lag by 10 to 15 minutes; use trends rather than treating a single sample as immediate confirmation of reclaimed space.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_logical_disk_size_bytes{job=~"$job", instance="$instance", volume=~"$volume"}', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('windows_logical_disk_free_bytes{job=~"$job", instance="$instance", volume=~"$volume"}', 'B')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('100 * (1 - windows_logical_disk_free_bytes{job=~"$job", instance="$instance", volume=~"$volume"} / windows_logical_disk_size_bytes{job=~"$job", instance="$instance", volume=~"$volume"})', 'C')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withTransformations([
|
||||
{
|
||||
group: 'joinByField',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
byField: 'volume',
|
||||
mode: 'outer',
|
||||
b.panel.new(12, 'CPUs', 'stat')
|
||||
+ b.panel.withDescription('Number of logical processors available to Windows, including SMT threads. This is the logical CPU count rather than the number of physical sockets.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_cpu_logical_processor{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
]),
|
||||
|
||||
b.panel.new(13, 'RAM', 'stat')
|
||||
+ b.panel.withDescription('Total physical memory visible to Windows in bytes. Compare with used memory and commit charge when investigating memory pressure.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_memory_physical_total_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
decimals: 1,
|
||||
unit: 'bytes',
|
||||
}),
|
||||
|
||||
b.panel.new(14, 'CPU busy', 'stat')
|
||||
+ b.panel.withDescription('Percentage of non-idle CPU time averaged across logical processors over the rate interval. Sustained high values together with a growing processor queue can indicate CPU contention.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('100 * (1 - avg by (instance) (clamp_max(rate(windows_cpu_time_total{job=~"$job", instance="$instance", mode="idle"}[$__rate_interval]), 1)))', 'A'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
decimals: 1,
|
||||
max: 100,
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'percent',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
graphMode: 'area',
|
||||
}),
|
||||
|
||||
b.panel.new(15, 'Memory used', 'stat')
|
||||
+ b.panel.withDescription('Percentage of physical memory in use, calculated as total minus available memory. This differs from commit charge, which measures promised virtual memory against the commit limit.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('100 * (1 - windows_memory_physical_free_bytes{job=~"$job", instance="$instance"} / windows_memory_physical_total_bytes{job=~"$job", instance="$instance"})', 'A'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
decimals: 1,
|
||||
max: 100,
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'percent',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
graphMode: 'area',
|
||||
}),
|
||||
|
||||
b.panel.new(16, 'Commit used', 'stat')
|
||||
+ b.panel.withDescription('Committed virtual memory as a share of the commit limit (physical memory + page files). Allocations fail at 100%.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('100 * windows_memory_committed_bytes{job=~"$job", instance="$instance"} / windows_memory_commit_limit{job=~"$job", instance="$instance"}', 'A'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
decimals: 1,
|
||||
max: 100,
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'percent',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
graphMode: 'area',
|
||||
}),
|
||||
|
||||
b.panel.new(17, 'Stopped auto services', 'stat')
|
||||
+ b.panel.withDescription('Services with start mode "auto" that are not running. See the Services tab for names.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('count(windows_service_state{job=~"$job", instance="$instance", state="running"} == 0 and on (name) windows_service_start_mode{job=~"$job", instance="$instance", start_mode="auto"} == 1) or vector(0)', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(18, 'CPU utilization', 'timeseries')
|
||||
+ b.panel.withDescription('Percentage of non-idle CPU time averaged across logical processors. This measures CPU time rather than the frequency-adjusted processor utility shown by Windows Task Manager.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('100 * (1 - avg by (instance) (clamp_max(rate(windows_cpu_time_total{job=~"$job", instance="$instance", mode="idle"}[$__rate_interval]), 1)))', 'A')
|
||||
+ b.query.withLegendFormat('CPU busy'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
max: 100,
|
||||
min: 0,
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 'percent',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(19, 'Physical memory', 'timeseries')
|
||||
+ b.panel.withDescription('Available memory is the standby (cache), free and zero page lists.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_memory_physical_total_bytes{job=~"$job", instance="$instance"} - windows_memory_physical_free_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('used'),
|
||||
b.query.new('windows_memory_physical_free_bytes{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('available'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
custom: {
|
||||
fillOpacity: 40,
|
||||
stacking: {
|
||||
mode: 'normal',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'organize',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
excludeByName: {
|
||||
Time: true,
|
||||
'Time 1': true,
|
||||
'Time 2': true,
|
||||
'Time 3': true,
|
||||
__name__: true,
|
||||
instance: true,
|
||||
'instance 1': true,
|
||||
'instance 2': true,
|
||||
'instance 3': true,
|
||||
job: true,
|
||||
'job 1': true,
|
||||
'job 2': true,
|
||||
'job 3': true,
|
||||
port: true,
|
||||
'port 1': true,
|
||||
'port 2': true,
|
||||
'port 3': true,
|
||||
min: 0,
|
||||
unit: 'bytes',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'used',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'orange',
|
||||
mode: 'fixed',
|
||||
},
|
||||
},
|
||||
includeByName: {},
|
||||
indexByName: {
|
||||
'Value #A': 1,
|
||||
'Value #B': 2,
|
||||
'Value #C': 3,
|
||||
volume: 0,
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'available',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
fixedColor: 'green',
|
||||
mode: 'fixed',
|
||||
},
|
||||
},
|
||||
renameByName: {
|
||||
'Value #A': 'Size',
|
||||
'Value #B': 'Free',
|
||||
'Value #C': 'Used',
|
||||
volume: 'Volume',
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(20, 'Volumes', 'table')
|
||||
+ b.panel.withDescription('Size, available space and used percentage of each selected logical volume. Windows free-space counters can lag by 10 to 15 minutes; use trends rather than treating a single sample as immediate confirmation of reclaimed space.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_logical_disk_size_bytes{job=~"$job", instance="$instance", volume=~"$volume"}', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('windows_logical_disk_free_bytes{job=~"$job", instance="$instance", volume=~"$volume"}', 'B')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
b.query.new('100 * (1 - windows_logical_disk_free_bytes{job=~"$job", instance="$instance", volume=~"$volume"} / windows_logical_disk_size_bytes{job=~"$job", instance="$instance", volume=~"$volume"})', 'C')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withTransformations([
|
||||
{
|
||||
group: 'joinByField',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
byField: 'volume',
|
||||
mode: 'outer',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #A',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #B',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #C',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'percent',
|
||||
},
|
||||
{
|
||||
id: 'min',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'max',
|
||||
value: 100,
|
||||
},
|
||||
{
|
||||
id: 'thresholds',
|
||||
value: {
|
||||
mode: 'absolute',
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
{
|
||||
group: 'organize',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
excludeByName: {
|
||||
Time: true,
|
||||
'Time 1': true,
|
||||
'Time 2': true,
|
||||
'Time 3': true,
|
||||
__name__: true,
|
||||
instance: true,
|
||||
'instance 1': true,
|
||||
'instance 2': true,
|
||||
'instance 3': true,
|
||||
job: true,
|
||||
'job 1': true,
|
||||
'job 2': true,
|
||||
'job 3': true,
|
||||
port: true,
|
||||
'port 1': true,
|
||||
'port 2': true,
|
||||
'port 3': true,
|
||||
},
|
||||
includeByName: {},
|
||||
indexByName: {
|
||||
'Value #A': 1,
|
||||
'Value #B': 2,
|
||||
'Value #C': 3,
|
||||
volume: 0,
|
||||
},
|
||||
renameByName: {
|
||||
'Value #A': 'Size',
|
||||
'Value #B': 'Free',
|
||||
'Value #C': 'Used',
|
||||
volume: 'Volume',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
mode: 'continuous-GrYlRd',
|
||||
},
|
||||
])
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #A',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #B',
|
||||
},
|
||||
{
|
||||
id: 'custom.cellOptions',
|
||||
value: {
|
||||
mode: 'basic',
|
||||
type: 'gauge',
|
||||
valueDisplayMode: 'text',
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: {
|
||||
id: 'byName',
|
||||
options: 'Value #C',
|
||||
},
|
||||
{
|
||||
id: 'decimals',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
properties: [
|
||||
{
|
||||
id: 'unit',
|
||||
value: 'percent',
|
||||
},
|
||||
{
|
||||
id: 'min',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: 'max',
|
||||
value: 100,
|
||||
},
|
||||
{
|
||||
id: 'thresholds',
|
||||
value: {
|
||||
mode: 'absolute',
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 90,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'color',
|
||||
value: {
|
||||
mode: 'continuous-GrYlRd',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'custom.cellOptions',
|
||||
value: {
|
||||
mode: 'basic',
|
||||
type: 'gauge',
|
||||
valueDisplayMode: 'text',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'decimals',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(21, 'Disk throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Sum over the selected volumes. Reads are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(rate(windows_logical_disk_write_bytes_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval]))', 'A')
|
||||
+ b.query.withLegendFormat('write'),
|
||||
b.query.new('sum(rate(windows_logical_disk_read_bytes_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval]))', 'B')
|
||||
+ b.query.withLegendFormat('read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'Bps',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: 'read',
|
||||
b.panel.new(21, 'Disk throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Sum over the selected volumes. Reads are drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(rate(windows_logical_disk_write_bytes_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval]))', 'A')
|
||||
+ b.query.withLegendFormat('write'),
|
||||
b.query.new('sum(rate(windows_logical_disk_read_bytes_total{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval]))', 'B')
|
||||
+ b.query.withLegendFormat('read'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'Bps',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: 'read',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
||||
b.panel.new(22, 'Network throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Sum over the selected interfaces. Received traffic is drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(rate(windows_net_bytes_sent_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])) * 8', 'A')
|
||||
+ b.query.withLegendFormat('sent'),
|
||||
b.query.new('sum(rate(windows_net_bytes_received_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])) * 8', 'B')
|
||||
+ b.query.withLegendFormat('received'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'bps',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: 'received',
|
||||
b.panel.new(22, 'Network throughput', 'timeseries')
|
||||
+ b.panel.withDescription('Sum over the selected interfaces. Received traffic is drawn below the axis.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(rate(windows_net_bytes_sent_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])) * 8', 'A')
|
||||
+ b.query.withLegendFormat('sent'),
|
||||
b.query.new('sum(rate(windows_net_bytes_received_total{job=~"$job", instance="$instance", nic=~"$nic"}[$__rate_interval])) * 8', 'B')
|
||||
+ b.query.withLegendFormat('received'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 'bps',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
legend: {
|
||||
calcs: [],
|
||||
displayMode: 'list',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
})
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: {
|
||||
id: 'byRegexp',
|
||||
options: 'received',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
]
|
||||
)
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.transform',
|
||||
value: 'negative-Y',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -4,11 +4,9 @@ local selector = '{job=~"$job", instance="$instance", process=~"$process", proce
|
||||
local gauge(metric, at='') = 'windows_process_' + metric + selector + at;
|
||||
local rate(metric, at='') = 'rate(' + gauge(metric) + '[$__rate_interval]' + at + ')';
|
||||
local sumRates(metric, at='') = 'sum by (process, process_id) (' + rate(metric, at) + ')';
|
||||
local stat(id, title, description, expr, unit) =
|
||||
b.panel.new(id, title, 'stat')
|
||||
+ b.panel.withDescription(description + ' Includes all selected processes, regardless of Top N. Requires the process collector.')
|
||||
+ b.panel.withQueries([b.query.new(expr) + b.query.withInstant()])
|
||||
+ b.panel.withDefaults({ min: 0, unit: unit });
|
||||
local stat(id, title, description, expr, unit, steps=null) =
|
||||
b.stat(id, title, description + ' Includes all selected processes, regardless of Top N. Requires the process collector.', expr, unit, steps)
|
||||
+ b.panel.withDefaults({ min: 0 });
|
||||
local graph(id, title, description, expr, ranking, unit) =
|
||||
b.panel.new(id, title, 'timeseries')
|
||||
+ b.panel.withDescription(description + ' Shows the Top N selected processes ranked at the end of the time range; that set stays fixed throughout the graph. Requires the process collector.')
|
||||
@@ -19,39 +17,30 @@ local graph(id, title, description, expr, ranking, unit) =
|
||||
+ b.panel.withQueryOptions({ maxDataPoints: 300 })
|
||||
+ b.panel.withDefaults({ min: 0, unit: unit });
|
||||
|
||||
b.tab(
|
||||
'Processes',
|
||||
b.grid([
|
||||
b.place(150, 0, 0, 4, 4),
|
||||
b.place(151, 4, 0, 4, 4),
|
||||
b.place(152, 8, 0, 4, 4),
|
||||
b.place(153, 12, 0, 4, 4),
|
||||
b.place(154, 16, 0, 4, 4),
|
||||
b.place(155, 20, 0, 4, 4),
|
||||
b.place(112, 0, 4, 12, 8),
|
||||
b.place(113, 12, 4, 12, 8),
|
||||
b.place(114, 0, 12, 12, 8),
|
||||
b.place(115, 12, 12, 12, 8),
|
||||
b.place(116, 0, 20, 12, 8),
|
||||
b.place(117, 12, 20, 12, 8),
|
||||
b.place(118, 0, 28, 12, 8),
|
||||
b.place(119, 12, 28, 12, 8),
|
||||
]),
|
||||
[
|
||||
stat(150, 'Processes', 'Number of distinct process IDs currently collected, excluding Idle.', 'count(count by (process_id) (' + gauge('info') + '))', 'short'),
|
||||
stat(151, 'Threads', 'Total active threads in the selected processes.', 'sum(' + gauge('threads') + ')', 'short'),
|
||||
stat(152, 'Handles', 'Total open handles in the selected processes.', 'sum(' + gauge('handles') + ')', 'short'),
|
||||
stat(153, 'Process CPU busy', 'CPU time used by the selected processes as a share of all logical processors of the host, excluding Idle.', 'sum(' + rate('cpu_time_total') + ') / scalar(max(windows_cpu_logical_processor{job=~"$job", instance="$instance"}))', 'percentunit'),
|
||||
stat(154, 'Private working set', 'Physical memory private to the selected processes; shared pages are excluded.', 'sum(' + gauge('working_set_private_bytes') + ')', 'bytes'),
|
||||
stat(155, 'Private bytes', 'Total memory allocated by the selected processes that cannot be shared; it may include nonresident memory.', 'sum(' + gauge('private_bytes') + ')', 'bytes'),
|
||||
function(on)
|
||||
b.tab(
|
||||
'Processes',
|
||||
b.rows([
|
||||
if on('process') then b.summary([150, 151, 152, if on('cpu') then 153, 154, 155]),
|
||||
if on('process') then b.row('CPU and memory', b.flow([[112, 12, 8], [113, 12, 8], [114, 12, 8], [119, 12, 8]])),
|
||||
if on('process') then b.row('I/O', b.flow([[115, 12, 8], [118, 12, 8]])),
|
||||
if on('process') then b.row('Threads and handles', b.flow([[116, 12, 8], [117, 12, 8]])),
|
||||
]),
|
||||
[
|
||||
stat(150, 'Processes', 'Number of distinct process IDs currently collected, excluding Idle.', 'count(count by (process_id) (' + gauge('info') + '))', 'short'),
|
||||
stat(151, 'Threads', 'Total active threads in the selected processes.', 'sum(' + gauge('threads') + ')', 'short'),
|
||||
stat(152, 'Handles', 'Total open handles in the selected processes.', 'sum(' + gauge('handles') + ')', 'short'),
|
||||
stat(153, 'CPU busy', 'CPU time used by the selected processes as a share of all logical processors of the host, excluding Idle.', 'sum(' + rate('cpu_time_total') + ') / scalar(max(windows_cpu_logical_processor{job=~"$job", instance="$instance"}))', 'percentunit', steps=b.levels(0.8, 0.9)),
|
||||
stat(154, 'Private working set', 'Physical memory private to the selected processes; shared pages are excluded.', 'sum(' + gauge('working_set_private_bytes') + ')', 'bytes'),
|
||||
stat(155, 'Private bytes', 'Total memory allocated by the selected processes that cannot be shared; it may include nonresident memory.', 'sum(' + gauge('private_bytes') + ')', 'bytes'),
|
||||
|
||||
graph(112, 'Process CPU usage', 'User and privileged CPU time per process as a share of one logical processor. Values above 100% mean the process uses more than one core.', sumRates('cpu_time_total'), sumRates('cpu_time_total', ' @ end()'), 'percentunit'),
|
||||
graph(113, 'Process working set', 'Physical memory in the working set of each process, including shared pages.', gauge('working_set_bytes'), gauge('working_set_bytes', ' @ end()'), 'bytes'),
|
||||
graph(114, 'Process private bytes', 'Memory allocated by each process that cannot be shared with other processes.', gauge('private_bytes'), gauge('private_bytes', ' @ end()'), 'bytes'),
|
||||
graph(115, 'Process I/O throughput', 'Total bytes issued for read, write and other I/O per process, including file, network and device operations.', sumRates('io_bytes_total'), sumRates('io_bytes_total', ' @ end()'), 'Bps'),
|
||||
graph(116, 'Process threads', 'Active threads in each process.', gauge('threads'), gauge('threads', ' @ end()'), 'short'),
|
||||
graph(117, 'Process handles', 'Open handles in each process. Steady growth can indicate a handle leak.', gauge('handles'), gauge('handles', ' @ end()'), 'short'),
|
||||
graph(118, 'Process I/O operations', 'Total read, write and other I/O operations per second per process. Includes file, network and device I/O.', sumRates('io_operations_total'), sumRates('io_operations_total', ' @ end()'), 'iops'),
|
||||
graph(119, 'Process page faults', 'Page faults per second by each process, including soft faults that do not require reading from disk.', rate('page_faults_total'), rate('page_faults_total', ' @ end()'), 'ops'),
|
||||
]
|
||||
)
|
||||
graph(112, 'Process CPU usage', 'User and privileged CPU time per process as a share of one logical processor. Values above 100% mean the process uses more than one core.', sumRates('cpu_time_total'), sumRates('cpu_time_total', ' @ end()'), 'percentunit'),
|
||||
graph(113, 'Process working set', 'Physical memory in the working set of each process, including shared pages.', gauge('working_set_bytes'), gauge('working_set_bytes', ' @ end()'), 'bytes'),
|
||||
graph(114, 'Process private bytes', 'Memory allocated by each process that cannot be shared with other processes.', gauge('private_bytes'), gauge('private_bytes', ' @ end()'), 'bytes'),
|
||||
graph(115, 'Process I/O throughput', 'Total bytes issued for read, write and other I/O per process, including file, network and device operations.', sumRates('io_bytes_total'), sumRates('io_bytes_total', ' @ end()'), 'Bps'),
|
||||
graph(116, 'Process threads', 'Active threads in each process.', gauge('threads'), gauge('threads', ' @ end()'), 'short'),
|
||||
graph(117, 'Process handles', 'Open handles in each process. Steady growth can indicate a handle leak.', gauge('handles'), gauge('handles', ' @ end()'), 'short'),
|
||||
graph(118, 'Process I/O operations', 'Total read, write and other I/O operations per second per process. Includes file, network and device I/O.', sumRates('io_operations_total'), sumRates('io_operations_total', ' @ end()'), 'iops'),
|
||||
graph(119, 'Process page faults', 'Page faults per second by each process, including soft faults that do not require reading from disk.', rate('page_faults_total'), rate('page_faults_total', ' @ end()'), 'ops'),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
local metric(name, labels='') = 'windows_scheduled_task_' + name + '{job=~"$job", instance="$instance"' + (if labels == '' then '' else ', ' + labels) + '}';
|
||||
// Disabled tasks do not run, so their results and missed runs need no attention.
|
||||
local enabled(expr) = expr + ' and on (task) ' + metric('state', 'state!="disabled"') + ' == 1';
|
||||
local requirement = ' Requires the scheduled_task collector, which is not enabled by default.';
|
||||
// Counts fall back to 0 only while the collector reports tasks, so a missing collector shows no data.
|
||||
local orZero = ' or (0 * count(' + metric('state') + '))';
|
||||
local table(id, title, description, queries, columns, unit='short') =
|
||||
b.panel.new(id, title, 'table')
|
||||
+ b.panel.withDescription(description + requirement)
|
||||
+ b.panel.withQueries([
|
||||
b.query.new(query.expr, query.refId)
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table')
|
||||
for query in queries
|
||||
])
|
||||
+ b.panel.withTransformations(
|
||||
(if std.length(queries) > 1 then [{
|
||||
group: 'joinByField',
|
||||
kind: 'Transformation',
|
||||
spec: { options: { byField: 'task', mode: 'outer' } },
|
||||
}] else [])
|
||||
+ [{
|
||||
group: 'organize',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
excludeByName: { [name]: true for name in ['Time', 'Time 1', 'Time 2', 'Value', 'Value #A'] if !std.member([column[0] for column in columns], 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 },
|
||||
},
|
||||
},
|
||||
}]
|
||||
)
|
||||
+ b.panel.withDefaults({ unit: unit })
|
||||
+ b.panel.withOptions({ sortBy: [{ desc: false, displayName: 'Task' }] });
|
||||
// Only results that can need attention get a color.
|
||||
local resultColors = {
|
||||
id: 'mappings',
|
||||
value: [{
|
||||
type: 'value',
|
||||
options: {
|
||||
success: { color: 'green', index: 0 },
|
||||
terminated: { color: 'orange', index: 1 },
|
||||
unknown: { color: 'orange', index: 2 },
|
||||
},
|
||||
}],
|
||||
};
|
||||
|
||||
function(on)
|
||||
b.tab(
|
||||
'Scheduled tasks',
|
||||
b.rows([
|
||||
if on('scheduled_task') then b.summary([190, 191, 192, 193, 194, 195]),
|
||||
if on('scheduled_task') then b.row('Attention', b.flow([[196, 12, 10], [197, 12, 10]])),
|
||||
if on('scheduled_task') then b.row('Activity', b.flow([[198, 24, 20]])),
|
||||
if on('scheduled_task') then b.row('All tasks', b.flow([[199, 24, 14]])),
|
||||
]),
|
||||
[
|
||||
b.stat(190, 'Tasks', 'Number of registered tasks that match the collector include and exclude filters.' + requirement, 'count(count by (task) (' + metric('state') + '))'),
|
||||
b.stat(191, 'Running', 'Tasks that are running now.' + requirement, 'sum(' + metric('state', 'state="running"') + ')' + orZero),
|
||||
b.stat(192, 'Disabled', 'Tasks that are disabled and do not run.' + requirement, 'sum(' + metric('state', 'state="disabled"') + ')' + orZero),
|
||||
b.stat(193, 'Unknown code', 'Enabled tasks whose last result code has no named status. This is usually a non-zero exit code of the task or a Task Scheduler error, so check the task history.' + requirement, 'count(' + enabled(metric('last_result_status', 'status="unknown"') + ' == 1') + ')' + orZero, steps=[{ color: 'green', value: null }, { color: 'orange', value: 1 }]),
|
||||
b.stat(194, 'Missed runs', 'Enabled tasks that missed at least one scheduled run, for example because the host was off or asleep.' + requirement, 'count(' + enabled(metric('missed_runs') + ' > 0') + ')' + orZero),
|
||||
b.stat(195, 'Never run', 'Tasks that have not run since they were registered.' + requirement, 'sum(' + metric('last_result_status', 'status="has_not_run"') + ')' + orZero),
|
||||
|
||||
table(196, 'Tasks with an unknown result code', 'Enabled tasks whose last result code has no named status, usually a non-zero exit code of the task or a Task Scheduler error.', [
|
||||
{ refId: 'A', expr: 'max by (task) (' + enabled(metric('last_result_status', 'status="unknown"') + ' == 1') + ')' },
|
||||
], [['task', 'Task']], unit='none'),
|
||||
|
||||
table(197, 'Tasks with missed runs', 'Enabled tasks that missed at least one scheduled run, with the number of missed runs reported by Task Scheduler.', [
|
||||
{ refId: 'A', expr: 'max by (task) (' + enabled(metric('missed_runs') + ' > 0') + ')' },
|
||||
], [['task', 'Task'], ['Value', 'Missed runs']])
|
||||
+ b.panel.withOptions({ sortBy: [{ desc: true, displayName: 'Missed runs' }] }),
|
||||
|
||||
b.panel.new(198, 'Task runs', 'state-timeline')
|
||||
+ b.panel.withDescription('Tasks that were running at a scrape in the selected time range. Runs shorter than the scrape interval can be missed.' + requirement)
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (task) (' + metric('state', 'state="running"') + ') and on (task) (max_over_time(' + metric('state', 'state="running"') + '[$__range] @ end()) == 1)', 'A')
|
||||
+ b.query.withLegendFormat('{{task}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
mappings: [
|
||||
{
|
||||
options: {
|
||||
'0': {
|
||||
color: 'transparent',
|
||||
index: 0,
|
||||
text: 'not running',
|
||||
},
|
||||
'1': {
|
||||
color: 'green',
|
||||
index: 1,
|
||||
text: 'running',
|
||||
},
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
table(199, 'Tasks', 'All tasks with their state, last result and missed runs. Tasks that never ran report no missed runs.', [
|
||||
{ refId: 'A', expr: 'max by (task, state, status) ((' + metric('state') + ' == 1) * on (task) group_left (status) (' + metric('last_result_status') + ' == 1))' },
|
||||
{ refId: 'B', expr: 'max by (task) (' + metric('missed_runs') + ')' },
|
||||
], [['task', 'Task'], ['state', 'State'], ['status', 'Last result'], ['Value #B', 'Missed runs']])
|
||||
+ b.panel.withDefaults({ custom: { filterable: true } })
|
||||
+ b.panel.withOverrides([
|
||||
{
|
||||
matcher: { id: 'byName', options: 'Last result' },
|
||||
properties: [
|
||||
{ id: 'color', value: { fixedColor: 'text', mode: 'fixed' } },
|
||||
resultColors,
|
||||
{ id: 'custom.cellOptions', value: { type: 'color-text' } },
|
||||
],
|
||||
},
|
||||
]),
|
||||
]
|
||||
)
|
||||
@@ -1,136 +1,85 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
|
||||
b.tab(
|
||||
'Services',
|
||||
b.grid([
|
||||
b.place(50, 0, 0, 6, 4),
|
||||
b.place(51, 6, 0, 6, 4),
|
||||
b.place(52, 12, 0, 6, 4),
|
||||
b.place(53, 18, 0, 6, 4),
|
||||
b.place(54, 0, 4, 12, 10),
|
||||
b.place(55, 12, 4, 12, 10),
|
||||
]),
|
||||
[
|
||||
b.panel.new(50, 'Running services', 'stat')
|
||||
+ b.panel.withDescription('Number of services currently in the running state, reported by the service collector. Compare unexpected drops with the stopped automatic services table and service change history.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(windows_service_state{job=~"$job", instance="$instance", state="running"})', 'A')
|
||||
+ b.query.withInstant(),
|
||||
function(on)
|
||||
b.tab(
|
||||
'Services',
|
||||
b.rows([
|
||||
if on('service') then b.summary([50, 51, 52, 53]),
|
||||
if on('service') then b.row('State', b.flow([[54, 12, 10], [55, 12, 10]])),
|
||||
]),
|
||||
[
|
||||
b.stat(50, 'Running services', 'Number of services currently in the running state, reported by the service collector. Compare unexpected drops with the stopped automatic services table and service change history.', 'sum(windows_service_state{job=~"$job", instance="$instance", state="running"})', 'short'),
|
||||
|
||||
b.panel.new(51, 'Stopped auto services', 'stat')
|
||||
+ b.panel.withDescription('Services with start mode "auto" that are not running.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('count(windows_service_state{job=~"$job", instance="$instance", state="running"} == 0 and on (name) windows_service_start_mode{job=~"$job", instance="$instance", start_mode="auto"} == 1) or vector(0)', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
b.stat(51, 'Stopped auto services', 'Services with start mode "auto" that are not running.', 'count(windows_service_state{job=~"$job", instance="$instance", state="running"} == 0 and on (name) windows_service_start_mode{job=~"$job", instance="$instance", start_mode="auto"} == 1) or vector(0)', steps=[{ color: 'green', value: null }, { color: 'orange', value: 1 }]),
|
||||
|
||||
b.stat(52, 'Pending services', 'Services in a start, stop, pause or continue pending state. A service that stays pending is hung.', 'sum(windows_service_state{job=~"$job", instance="$instance", state=~".* pending"}) or vector(0)', steps=[{ color: 'green', value: null }, { color: 'orange', value: 1 }]),
|
||||
|
||||
b.stat(53, 'Disabled services', 'Number of services configured with disabled startup. A disabled service cannot start until its startup mode changes; disabled services are not necessarily unhealthy.', 'sum(windows_service_start_mode{job=~"$job", instance="$instance", start_mode="disabled"})', 'short'),
|
||||
|
||||
b.panel.new(54, 'Automatic services not running', 'table')
|
||||
+ b.panel.withDescription('Services configured to start automatically that are not in the running state. Delayed-start and trigger-start services may show up here until they start.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (name, state) (windows_service_state{job=~"$job", instance="$instance"} == 1) and on (name) (windows_service_state{job=~"$job", instance="$instance", state="running"} == 0) and on (name) (windows_service_start_mode{job=~"$job", instance="$instance", start_mode="auto"} == 1)', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withTransformations([
|
||||
{
|
||||
group: 'organize',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
excludeByName: {
|
||||
Time: true,
|
||||
'Time 1': true,
|
||||
Value: true,
|
||||
},
|
||||
includeByName: {},
|
||||
indexByName: {
|
||||
name: 0,
|
||||
state: 1,
|
||||
},
|
||||
renameByName: {
|
||||
name: 'Service',
|
||||
state: 'State',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
+ b.panel.withOptions({
|
||||
sortBy: [
|
||||
{
|
||||
color: 'orange',
|
||||
value: 1,
|
||||
desc: false,
|
||||
displayName: 'Service',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
}),
|
||||
|
||||
b.panel.new(52, 'Pending services', 'stat')
|
||||
+ b.panel.withDescription('Services in a start, stop, pause or continue pending state. A service that stays pending is hung.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(windows_service_state{job=~"$job", instance="$instance", state=~".* pending"}) or vector(0)', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
thresholds: {
|
||||
steps: [
|
||||
b.panel.new(55, 'Service state changes', 'state-timeline')
|
||||
+ b.panel.withDescription('Services that started or stopped in the selected time range.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_service_state{job=~"$job", instance="$instance", state="running"} and on (name) (changes(windows_service_state{job=~"$job", instance="$instance", state="running"}[$__range] @ end()) > 0)', 'A')
|
||||
+ b.query.withLegendFormat('{{name}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
mappings: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 1,
|
||||
options: {
|
||||
'0': {
|
||||
color: 'red',
|
||||
index: 0,
|
||||
text: 'not running',
|
||||
},
|
||||
'1': {
|
||||
color: 'green',
|
||||
index: 1,
|
||||
text: 'running',
|
||||
},
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
b.panel.new(53, 'Disabled services', 'stat')
|
||||
+ b.panel.withDescription('Number of services configured with disabled startup. A disabled service cannot start until its startup mode changes; disabled services are not necessarily unhealthy.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('sum(windows_service_start_mode{job=~"$job", instance="$instance", start_mode="disabled"})', 'A')
|
||||
+ b.query.withInstant(),
|
||||
]),
|
||||
|
||||
b.panel.new(54, 'Automatic services not running', 'table')
|
||||
+ b.panel.withDescription('Services configured to start automatically that are not in the running state. Delayed-start and trigger-start services may show up here until they start.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (name, state) (windows_service_state{job=~"$job", instance="$instance"} == 1) and on (name) (windows_service_state{job=~"$job", instance="$instance", state="running"} == 0) and on (name) (windows_service_start_mode{job=~"$job", instance="$instance", start_mode="auto"} == 1)', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withTransformations([
|
||||
{
|
||||
group: 'organize',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
excludeByName: {
|
||||
Time: true,
|
||||
'Time 1': true,
|
||||
Value: true,
|
||||
},
|
||||
includeByName: {},
|
||||
indexByName: {
|
||||
name: 0,
|
||||
state: 1,
|
||||
},
|
||||
renameByName: {
|
||||
name: 'Service',
|
||||
state: 'State',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
+ b.panel.withOptions({
|
||||
sortBy: [
|
||||
{
|
||||
desc: false,
|
||||
displayName: 'Service',
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
b.panel.new(55, 'Service state changes', 'state-timeline')
|
||||
+ b.panel.withDescription('Services that started or stopped in the selected time range.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_service_state{job=~"$job", instance="$instance", state="running"} and on (name) (changes(windows_service_state{job=~"$job", instance="$instance", state="running"}[$__range] @ end()) > 0)', 'A')
|
||||
+ b.query.withLegendFormat('{{name}}'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
mappings: [
|
||||
{
|
||||
options: {
|
||||
'0': {
|
||||
color: 'red',
|
||||
index: 0,
|
||||
text: 'not running',
|
||||
},
|
||||
'1': {
|
||||
color: 'green',
|
||||
index: 1,
|
||||
text: 'running',
|
||||
},
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
}),
|
||||
]
|
||||
)
|
||||
}),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
local server(name) = 'windows_smb_server_shares_' + name + '{job=~"$job", instance="$instance"}';
|
||||
local serverRate(name) = 'rate(' + server(name) + '[$__rate_interval])';
|
||||
local client(name) = 'windows_smbclient_' + name + '{job=~"$job", instance="$instance"}';
|
||||
local clientRate(name) = 'rate(' + client(name) + '[$__rate_interval])';
|
||||
local serverRequirement = ' Requires the smb collector, which is not enabled by default.';
|
||||
local clientRequirement = ' Requires the smbclient collector, which is not enabled by default.';
|
||||
// Shares this host serves, and \\server\share paths this host connects to.
|
||||
local share = '{{share}}';
|
||||
local path = '\\\\{{server}}\\{{share}}';
|
||||
|
||||
function(on)
|
||||
b.tab(
|
||||
'SMB',
|
||||
b.rows([
|
||||
b.summary([
|
||||
if on('smb') then 220,
|
||||
if on('smb') then 221,
|
||||
if on('smb') then 222,
|
||||
if on('smbclient') then 223,
|
||||
if on('smbclient') then 224,
|
||||
if on('smbclient') then 225,
|
||||
]),
|
||||
if on('smb') then b.row('Server shares', b.flow([[226, 12, 8], [227, 12, 8], [228, 8, 8], [229, 8, 8], [230, 8, 8]])),
|
||||
if on('smbclient') then b.row('Client shares', b.flow([[231, 12, 8], [232, 12, 8], [233, 12, 8], [234, 12, 8], [235, 12, 8], [236, 12, 8]])),
|
||||
]),
|
||||
[
|
||||
b.stat(220, 'Open files', 'Files open on the shares of this SMB server.' + serverRequirement, 'sum(' + server('current_open_file_count') + ')'),
|
||||
b.stat(221, 'Connections', 'Client connections (tree connects) to the shares of this SMB server.' + serverRequirement, 'sum(' + server('tree_connect_count') + ')'),
|
||||
b.stat(222, 'Server traffic', 'Bytes sent and received per second by the shares of this SMB server.' + serverRequirement, 'sum(' + serverRate('received_bytes_total') + ') + sum(' + serverRate('sent_bytes_total') + ')', 'Bps'),
|
||||
b.stat(223, 'Client traffic', 'Bytes read and written per second by this host on remote SMB shares.' + clientRequirement, 'sum(' + clientRate('data_bytes_total') + ')', 'Bps'),
|
||||
b.stat(224, 'Client latency', 'Average time per request of this host on remote SMB shares. 0 when there are no requests.' + clientRequirement, '(sum(' + clientRate('request_seconds_total') + ') / (sum(' + clientRate('requests_total') + ') > 0)) or (0 * sum(' + clientRate('requests_total') + '))', 's', steps=b.levels(0.02, 0.05)),
|
||||
b.stat(225, 'Credit stalls', 'Requests per second delayed because the server granted too few SMB credits. Sustained stalls limit throughput.' + clientRequirement, 'sum(' + clientRate('stalls_total') + ')', 'ops', steps=[{ color: 'green', value: null }, { color: 'orange', value: 1 }]),
|
||||
|
||||
b.graph(226, 'Share traffic', 'Bytes sent and received per second per share. Received bytes are drawn below the axis.' + serverRequirement, 'Bps', [
|
||||
[serverRate('sent_bytes_total'), share + ' sent'],
|
||||
[serverRate('received_bytes_total'), share + ' received'],
|
||||
], below='.* received$'),
|
||||
b.graph(227, 'Share requests', 'Read, write and metadata requests per second per share.' + serverRequirement, 'ops', [
|
||||
[serverRate('read_requests_count_total'), share + ' read'],
|
||||
[serverRate('write_requests_count_total'), share + ' write'],
|
||||
[serverRate('metadata_requests_count_total'), share + ' metadata'],
|
||||
]),
|
||||
b.graph(228, 'Open files', 'Files open per share.' + serverRequirement, 'short', [[server('current_open_file_count'), share]]),
|
||||
b.graph(229, 'Connections', 'Client connections (tree connects) per share.' + serverRequirement, 'short', [[server('tree_connect_count'), share]]),
|
||||
b.graph(230, 'Files opened', 'Files opened per second per share.' + serverRequirement, 'ops', [[serverRate('files_opened_count_total'), share]]),
|
||||
|
||||
b.graph(231, 'Client traffic', 'Bytes read and written per second per remote share. Reads are drawn below the axis.' + clientRequirement, 'Bps', [
|
||||
[clientRate('write_bytes_total'), path + ' write'],
|
||||
[clientRate('read_bytes_total'), path + ' read'],
|
||||
], below='.* read$'),
|
||||
b.graph(232, 'Client requests', 'Read and write requests per second per remote share. Reads are drawn below the axis.' + clientRequirement, 'ops', [
|
||||
[clientRate('write_requests_total'), path + ' write'],
|
||||
[clientRate('read_requests_total'), path + ' read'],
|
||||
], below='.* read$'),
|
||||
b.graph(233, 'Client latency', 'Average time per read and write request per remote share. Reads are drawn below the axis.' + clientRequirement, 's', [
|
||||
[clientRate('write_seconds_total') + ' / (' + clientRate('write_requests_total') + ' > 0)', path + ' write'],
|
||||
[clientRate('read_seconds_total') + ' / (' + clientRate('read_requests_total') + ' > 0)', path + ' read'],
|
||||
], below='.* read$'),
|
||||
b.graph(234, 'Client queue length', 'Requests outstanding per remote share at the time of the scrape.' + clientRequirement, 'short', [[client('requests_queued'), path]]),
|
||||
b.graph(235, 'Credit stalls', 'Requests per second per remote share delayed because the server granted too few SMB credits.' + clientRequirement, 'ops', [[clientRate('stalls_total'), path]]),
|
||||
b.graph(236, 'Metadata requests', 'Metadata requests per second per remote share, such as opening files and listing directories.' + clientRequirement, 'ops', [[clientRate('metadata_requests_total'), path]]),
|
||||
]
|
||||
)
|
||||
@@ -1,110 +1,89 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
|
||||
b.tab(
|
||||
'Time',
|
||||
b.grid([
|
||||
b.place(84, 0, 0, 6, 4),
|
||||
b.place(85, 6, 0, 6, 4),
|
||||
b.place(86, 12, 0, 6, 4),
|
||||
b.place(87, 18, 0, 6, 4),
|
||||
b.place(88, 0, 4, 24, 8),
|
||||
]),
|
||||
[
|
||||
b.panel.new(84, 'Time zone', 'stat')
|
||||
+ b.panel.withDescription('Needs the time collector, which is not enabled by default.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_time_timezone{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: '',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
colorMode: 'none',
|
||||
reduceOptions: {
|
||||
fields: '/^timezone$/',
|
||||
},
|
||||
textMode: 'value',
|
||||
}),
|
||||
function(on)
|
||||
b.tab(
|
||||
'Time',
|
||||
b.rows([
|
||||
if on('time') then b.summary([84, 85, 86, 87]),
|
||||
if on('time') then b.row('Clock', b.flow([[88, 24, 8]])),
|
||||
]),
|
||||
[
|
||||
b.panel.new(84, 'Time zone', 'stat')
|
||||
+ b.panel.withDescription('Needs the time collector, which is not enabled by default.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_time_timezone{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: '',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
colorMode: 'none',
|
||||
reduceOptions: {
|
||||
fields: '/^timezone$/',
|
||||
},
|
||||
textMode: 'value',
|
||||
}),
|
||||
|
||||
b.panel.new(85, 'Clock sync source', 'stat')
|
||||
+ b.panel.withDescription('Active Windows Time synchronization provider. A non-NTP provider can be expected on virtual machines; this requires the optional time collector.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_time_clock_sync_source{job=~"$job", instance="$instance"} == 1', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: '',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
colorMode: 'none',
|
||||
reduceOptions: {
|
||||
fields: '/^type$/',
|
||||
},
|
||||
textMode: 'value',
|
||||
}),
|
||||
b.panel.new(85, 'Clock sync source', 'stat')
|
||||
+ b.panel.withDescription('Active Windows Time synchronization provider. A non-NTP provider can be expected on virtual machines; this requires the optional time collector.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_time_clock_sync_source{job=~"$job", instance="$instance"} == 1', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: '',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
colorMode: 'none',
|
||||
reduceOptions: {
|
||||
fields: '/^type$/',
|
||||
},
|
||||
textMode: 'value',
|
||||
}),
|
||||
|
||||
b.panel.new(86, 'NTP time sources', 'stat')
|
||||
+ b.panel.withDescription('Number of time sources the NTP client uses. 0 means the clock is not synchronized.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_time_ntp_client_time_sources{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withInstant(),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'red',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'green',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
b.stat(86, 'NTP time sources', 'Number of time sources the NTP client uses. 0 means the clock is not synchronized.', 'windows_time_ntp_client_time_sources{job=~"$job", instance="$instance"}', steps=[{ color: 'red', value: null }, { color: 'green', value: 1 }]),
|
||||
|
||||
b.panel.new(87, 'Clock offset', 'stat')
|
||||
+ b.panel.withDescription('Absolute offset between the system clock and the selected time source.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('abs(windows_time_computed_time_offset_seconds{job=~"$job", instance="$instance"})', 'A'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 0.5,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 's',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
graphMode: 'area',
|
||||
}),
|
||||
b.panel.new(87, 'Clock offset', 'stat')
|
||||
+ b.panel.withDescription('Absolute offset between the system clock and the selected time source.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('abs(windows_time_computed_time_offset_seconds{job=~"$job", instance="$instance"})', 'A'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
thresholds: {
|
||||
steps: [
|
||||
{
|
||||
color: 'green',
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
value: 0.5,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
unit: 's',
|
||||
})
|
||||
+ b.panel.withOptions({
|
||||
graphMode: 'area',
|
||||
}),
|
||||
|
||||
b.panel.new(88, 'Clock offset and NTP round-trip delay', 'timeseries')
|
||||
+ b.panel.withDescription('Signed offset from the selected time source and the NTP request round-trip delay, in seconds. Persistent offset or increased delay can affect time-sensitive applications; this compares the host to its chosen source and does not independently verify that source.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_time_computed_time_offset_seconds{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('clock offset'),
|
||||
b.query.new('windows_time_ntp_round_trip_delay_seconds{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('NTP round-trip delay'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 's',
|
||||
}),
|
||||
]
|
||||
)
|
||||
b.panel.new(88, 'Clock offset and NTP round-trip delay', 'timeseries')
|
||||
+ b.panel.withDescription('Signed offset from the selected time source and the NTP request round-trip delay, in seconds. Persistent offset or increased delay can affect time-sensitive applications; this compares the host to its chosen source and does not independently verify that source.')
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('windows_time_computed_time_offset_seconds{job=~"$job", instance="$instance"}', 'A')
|
||||
+ b.query.withLegendFormat('clock offset'),
|
||||
b.query.new('windows_time_ntp_round_trip_delay_seconds{job=~"$job", instance="$instance"}', 'B')
|
||||
+ b.query.withLegendFormat('NTP round-trip delay'),
|
||||
])
|
||||
+ b.panel.withDefaults({
|
||||
unit: 's',
|
||||
}),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
local metric(name, labels='') = 'windows_update_' + name + '{job=~"$job", instance="$instance"' + (if labels == '' then '' else ', ' + labels) + '}';
|
||||
local requirement = ' Requires the update collector, which is not enabled by default. The collector queries Windows Update in the background, every 6 hours by default.';
|
||||
// Counts fall back to 0 only while the collector reports, so a missing collector shows no data.
|
||||
local pending(labels) = 'count(' + metric('pending_info', labels) + ') or (0 * ' + metric('scrape_timestamp_seconds') + ')';
|
||||
local days(n) = n * 86400;
|
||||
|
||||
function(on)
|
||||
b.tab(
|
||||
'Updates',
|
||||
b.rows([
|
||||
if on('update') then b.summary([210, 211, 212, 214, 215]),
|
||||
if on('update') then b.row('Pending updates', b.flow([[216, 24, 10]])),
|
||||
if on('update') then b.row('History', b.flow([[217, 12, 8], [218, 12, 8]])),
|
||||
]),
|
||||
[
|
||||
b.stat(210, 'Pending', 'Updates that are available but not installed.' + requirement, pending('')),
|
||||
b.stat(211, 'Critical', 'Pending updates with the Microsoft security severity Critical.' + requirement, pending('severity="Critical"'), steps=[{ color: 'green', value: null }, { color: 'red', value: 1 }]),
|
||||
b.stat(212, 'Important', 'Pending updates with the Microsoft security severity Important.' + requirement, pending('severity="Important"'), steps=[{ color: 'green', value: null }, { color: 'orange', value: 1 }]),
|
||||
b.stat(214, 'Oldest pending', 'Time since the oldest pending update was published. Orange after 30 days, red after 60 days.' + requirement, 'time() - min(' + metric('pending_published_timestamp') + ')', 'dtdurations', steps=[{ color: 'green', value: null }, { color: 'orange', value: days(30) }, { color: 'red', value: days(60) }])
|
||||
+ b.panel.withDefaults({ noValue: 'none' })
|
||||
+ b.panel.withOptions({ reduceOptions: { calcs: ['last'] } }),
|
||||
b.stat(215, 'Last check', 'Time since the collector last queried Windows Update. Orange after 1 day, red after 7 days.' + requirement, 'time() - ' + metric('scrape_timestamp_seconds'), 'dtdurations', steps=[{ color: 'green', value: null }, { color: 'orange', value: days(1) }, { color: 'red', value: days(7) }])
|
||||
+ b.panel.withOptions({ reduceOptions: { calcs: ['last'] } }),
|
||||
|
||||
b.panel.new(216, 'Pending updates', 'table')
|
||||
+ b.panel.withDescription('Pending updates with their category, security severity and publish date. Category names follow the Windows display language.' + requirement)
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (title, category, severity) (' + '(' + metric('pending_published_timestamp') + ' * on (id, revision) group_right ' + metric('pending_info') + ') or on (id, revision) (0 * ' + metric('pending_info') + ')) * 1000', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
+ b.panel.withTransformations([{
|
||||
group: 'organize',
|
||||
kind: 'Transformation',
|
||||
spec: {
|
||||
options: {
|
||||
excludeByName: { Time: true },
|
||||
includeByName: {},
|
||||
indexByName: { title: 0, category: 1, severity: 2, Value: 3 },
|
||||
renameByName: { title: 'Update', category: 'Category', severity: 'Severity', Value: 'Published' },
|
||||
},
|
||||
},
|
||||
}])
|
||||
+ b.panel.withDefaults({ unit: 'dateTimeFromNow', mappings: [{ type: 'value', options: { '0': { index: 0, text: 'unknown' } } }] })
|
||||
+ b.panel.withOptions({ sortBy: [{ desc: false, displayName: 'Published' }] })
|
||||
+ b.panel.withOverrides([{
|
||||
matcher: { id: 'byName', options: 'Severity' },
|
||||
properties: [
|
||||
{ id: 'color', value: { fixedColor: 'text', mode: 'fixed' } },
|
||||
{ id: 'mappings', value: [{ type: 'value', options: { Critical: { color: 'red', index: 0 }, Important: { color: 'orange', index: 1 } } }] },
|
||||
{ id: 'custom.cellOptions', value: { type: 'color-text' } },
|
||||
],
|
||||
}]),
|
||||
|
||||
b.graph(217, 'Pending updates by category', 'Number of pending updates per update category.' + requirement, 'short', [
|
||||
['count by (category) (' + metric('pending_info') + ')', '{{category}}'],
|
||||
])
|
||||
+ b.panel.withDefaults({ custom: { drawStyle: 'bars', fillOpacity: 80, stacking: { mode: 'normal' } } }),
|
||||
b.graph(218, 'Windows Update query duration', 'Duration of the last background query to the Windows Update API. Slow queries can point to a slow or unreachable update server.' + requirement, 's', [
|
||||
[metric('scrape_query_duration_seconds'), 'query duration'],
|
||||
])
|
||||
+ b.panel.withOptions({ legend: { calcs: [], displayMode: 'list' } }),
|
||||
]
|
||||
)
|
||||
@@ -22,7 +22,8 @@ local new(name, definition) = common
|
||||
refId: 'PrometheusVariableQueryEditor-VariableQuery',
|
||||
}));
|
||||
|
||||
[
|
||||
// Variables of disabled collectors are left out.
|
||||
function(on) [variable for variable in [
|
||||
datasource.withKind()
|
||||
+ datasource.spec.withAllowCustomValue(true)
|
||||
+ datasource.spec.withCurrent({
|
||||
@@ -72,57 +73,57 @@ local new(name, definition) = common
|
||||
new('show_hostname', 'label_values(windows_os_hostname{job=~"$job", instance="$instance"}, hostname)')
|
||||
+ query.spec.withHide('hideVariable')
|
||||
,
|
||||
new('volume', 'label_values(windows_logical_disk_size_bytes{job=~"$job", instance="$instance"}, volume)')
|
||||
+ query.spec.withCurrent({
|
||||
text: [
|
||||
'All',
|
||||
],
|
||||
value: [
|
||||
'$__all',
|
||||
],
|
||||
})
|
||||
+ query.spec.withIncludeAll(true)
|
||||
+ query.spec.withLabel('Volume')
|
||||
+ query.spec.withMulti(true)
|
||||
+ query.spec.withRegex('/^(?!HarddiskVolume).+$/')
|
||||
if on('logical_disk') then new('volume', 'label_values(windows_logical_disk_size_bytes{job=~"$job", instance="$instance"}, volume)')
|
||||
+ query.spec.withCurrent({
|
||||
text: [
|
||||
'All',
|
||||
],
|
||||
value: [
|
||||
'$__all',
|
||||
],
|
||||
})
|
||||
+ query.spec.withIncludeAll(true)
|
||||
+ query.spec.withLabel('Volume')
|
||||
+ query.spec.withMulti(true)
|
||||
+ query.spec.withRegex('/^(?!HarddiskVolume).+$/')
|
||||
,
|
||||
new('nic', 'label_values(windows_net_bytes_total{job=~"$job", instance="$instance"}, nic)')
|
||||
+ query.spec.withCurrent({
|
||||
text: [
|
||||
'All',
|
||||
],
|
||||
value: [
|
||||
'$__all',
|
||||
],
|
||||
})
|
||||
+ query.spec.withIncludeAll(true)
|
||||
+ query.spec.withLabel('Network interface')
|
||||
+ query.spec.withMulti(true)
|
||||
+ query.spec.withRegex('/^(?!isatap|Teredo|6to4).+$/'),
|
||||
new('process', 'label_values(windows_process_info{job=~"$job", instance="$instance", process_id!="0"}, process)')
|
||||
+ query.spec.withCurrent({
|
||||
text: [
|
||||
'All',
|
||||
],
|
||||
value: [
|
||||
'$__all',
|
||||
],
|
||||
})
|
||||
+ query.spec.withIncludeAll(true)
|
||||
+ query.spec.withLabel('Process')
|
||||
+ query.spec.withMulti(true),
|
||||
variables.CustomVariableKind.withKind()
|
||||
+ variables.CustomVariableKind.withSpec({
|
||||
name: 'process_top',
|
||||
label: 'Top N processes',
|
||||
description: 'Maximum number of processes per graph. Summary counters include all selected processes.',
|
||||
query: '5,10,20',
|
||||
current: { text: '10', value: '10' },
|
||||
options: [{ text: value, value: value, selected: value == '10' } for value in ['5', '10', '20']],
|
||||
multi: false,
|
||||
includeAll: false,
|
||||
allowCustomValue: false,
|
||||
hide: 'dontHide',
|
||||
skipUrlSync: false,
|
||||
}),
|
||||
]
|
||||
if on('net') then new('nic', 'label_values(windows_net_bytes_total{job=~"$job", instance="$instance"}, nic)')
|
||||
+ query.spec.withCurrent({
|
||||
text: [
|
||||
'All',
|
||||
],
|
||||
value: [
|
||||
'$__all',
|
||||
],
|
||||
})
|
||||
+ query.spec.withIncludeAll(true)
|
||||
+ query.spec.withLabel('Network interface')
|
||||
+ query.spec.withMulti(true)
|
||||
+ query.spec.withRegex('/^(?!isatap|Teredo|6to4).+$/'),
|
||||
if on('process') then new('process', 'label_values(windows_process_info{job=~"$job", instance="$instance", process_id!="0"}, process)')
|
||||
+ query.spec.withCurrent({
|
||||
text: [
|
||||
'All',
|
||||
],
|
||||
value: [
|
||||
'$__all',
|
||||
],
|
||||
})
|
||||
+ query.spec.withIncludeAll(true)
|
||||
+ query.spec.withLabel('Process')
|
||||
+ query.spec.withMulti(true),
|
||||
if on('process') then variables.CustomVariableKind.withKind()
|
||||
+ variables.CustomVariableKind.withSpec({
|
||||
name: 'process_top',
|
||||
label: 'Top N processes',
|
||||
description: 'Maximum number of processes per graph. Summary counters include all selected processes.',
|
||||
query: '5,10,20',
|
||||
current: { text: '10', value: '10' },
|
||||
options: [{ text: value, value: value, selected: value == '10' } for value in ['5', '10', '20']],
|
||||
multi: false,
|
||||
includeAll: false,
|
||||
allowCustomValue: false,
|
||||
hide: 'dontHide',
|
||||
skipUrlSync: false,
|
||||
}),
|
||||
] if variable != null]
|
||||
|
||||
@@ -1,68 +1,87 @@
|
||||
local b = import 'builders.libsonnet';
|
||||
local dashboard = b.dashboard;
|
||||
local tabs = [
|
||||
import 'fleet.libsonnet',
|
||||
import 'overview.libsonnet',
|
||||
import 'cpu.libsonnet',
|
||||
import 'memory.libsonnet',
|
||||
import 'disk.libsonnet',
|
||||
import 'network.libsonnet',
|
||||
import 'processes.libsonnet',
|
||||
import 'services.libsonnet',
|
||||
import 'gpu.libsonnet',
|
||||
import 'hyper_v.libsonnet',
|
||||
import 'time.libsonnet',
|
||||
import 'exporter.libsonnet',
|
||||
];
|
||||
|
||||
local panelNames = std.flattenArrays([std.objectFields(tab.elements) for tab in tabs]);
|
||||
assert std.length(panelNames) == std.length(std.set(panelNames)) : 'Panel IDs must be unique across tabs';
|
||||
// config.collectors selects the collectors the dashboard renders, see config.libsonnet.
|
||||
function(config)
|
||||
local known = std.objectFields((import '../config.libsonnet')._config.collectors);
|
||||
local unknown = std.setDiff(std.objectFields(config.collectors), known);
|
||||
assert std.length(unknown) == 0 : 'Unknown collectors in _config.collectors: ' + std.join(', ', unknown);
|
||||
// A collector missing from a replaced collectors object counts as disabled.
|
||||
local on(collector) =
|
||||
assert std.member(known, collector) : 'Unknown collector ' + collector;
|
||||
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 = [
|
||||
tab
|
||||
for tab in [
|
||||
(import 'fleet.libsonnet')(on),
|
||||
(import 'overview.libsonnet')(on),
|
||||
(import 'cpu.libsonnet')(on),
|
||||
(import 'memory.libsonnet')(on),
|
||||
(import 'disk.libsonnet')(on),
|
||||
(import 'network.libsonnet')(on),
|
||||
(import 'smb.libsonnet')(on),
|
||||
(import 'processes.libsonnet')(on),
|
||||
(import 'services.libsonnet')(on),
|
||||
(import 'scheduled_tasks.libsonnet')(on),
|
||||
(import 'updates.libsonnet')(on),
|
||||
(import 'gpu.libsonnet')(on),
|
||||
(import 'hyper_v.libsonnet')(on),
|
||||
(import 'time.libsonnet')(on),
|
||||
(import 'exporter.libsonnet')(on),
|
||||
]
|
||||
if !tab.empty
|
||||
];
|
||||
|
||||
dashboard.new('Kdaassddw', 'Windows Exporter')
|
||||
+ dashboard.spec.withCursorSync('Crosshair')
|
||||
+ dashboard.spec.withDescription('Fleet overview and per-host details for Windows hosts monitored by windows_exporter.')
|
||||
+ 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([
|
||||
'prometheus',
|
||||
'windows',
|
||||
'windows_exporter',
|
||||
])
|
||||
+ 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')
|
||||
+ dashboard.spec.withVariables(import 'variables.libsonnet')
|
||||
+ dashboard.spec.withElements(std.foldl(function(elements, tab) elements + tab.elements, tabs, {}))
|
||||
+ dashboard.spec.withLayout(b.tabs([tab.layout for tab in tabs]))
|
||||
local panelNames = std.flattenArrays([std.objectFields(tab.elements) for tab in tabs]);
|
||||
assert std.length(panelNames) == std.length(std.set(panelNames)) : 'Panel IDs must be unique across tabs';
|
||||
|
||||
dashboard.new('Kdaassddw', 'Windows Exporter')
|
||||
+ dashboard.spec.withCursorSync('Crosshair')
|
||||
+ dashboard.spec.withDescription('Fleet overview and per-host details for Windows hosts monitored by windows_exporter.')
|
||||
+ 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([
|
||||
'prometheus',
|
||||
'windows',
|
||||
'windows_exporter',
|
||||
])
|
||||
+ 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]))
|
||||
|
||||
@@ -79,9 +79,10 @@ run = '''jsonnet -e '(import "mixin.libsonnet") { _config+:: { enableActiveDirec
|
||||
|
||||
[tasks.test]
|
||||
description = "Run all mixin lint, runbook, and rule tests"
|
||||
depends = ["lint", "check-runbooks"]
|
||||
depends = ["lint", "check-runbooks", "vendor"]
|
||||
run = [
|
||||
"jsonnet tests/runbooks.test.jsonnet",
|
||||
"jsonnet -J vendor tests/dashboard.test.jsonnet > /dev/null",
|
||||
"promtool test rules tests/*.test.yaml",
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// 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.
|
||||
local mixin = import '../mixin.libsonnet';
|
||||
|
||||
local render(collectors) =
|
||||
(mixin { _config+:: { collectors: collectors(super.collectors) } }).grafanaDashboards['windows-exporter.json'];
|
||||
local all(value) = function(collectors) { [name]: value for name in std.objectFields(collectors) };
|
||||
|
||||
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 summary(dashboard) =
|
||||
local tabs = dashboard.spec.layout.spec.tabs;
|
||||
local names = std.flattenArrays([placed(tab.spec.layout) for tab in tabs]);
|
||||
assert std.set(names) == std.objectFields(dashboard.spec.elements) : 'Placed panels and elements differ';
|
||||
assert std.length(names) == std.length(std.set(names)) : 'A panel is placed twice';
|
||||
assert std.all([std.length(row.spec.layout.spec.items) > 0 for tab in tabs for row in tab.spec.layout.spec.rows]) : 'Empty row';
|
||||
{
|
||||
[tab.spec.title]: [row.spec.title for row in tab.spec.layout.spec.rows]
|
||||
for tab in tabs
|
||||
};
|
||||
|
||||
local only(enabled) = function(collectors) all(false)(collectors) + { os: true } + enabled;
|
||||
local exporter = ['Summary', 'Scrape', 'Collectors', 'Process and Go runtime'];
|
||||
|
||||
local results = {
|
||||
all: summary(render(all(true))),
|
||||
defaults: summary(render(function(collectors) collectors)),
|
||||
osOnly: summary(render(only({}))),
|
||||
smbClientOnly: summary(render(only({ smbclient: true }))),
|
||||
diskWithoutDiskdrive: summary(render(only({ logical_disk: true, physical_disk: true }))).Disk,
|
||||
diskWithDiskdrive: summary(render(only({ logical_disk: true, physical_disk: true, diskdrive: true }))).Disk,
|
||||
// A collectors object given without +: enables only the listed collectors.
|
||||
replaced: summary(render(function(_) { os: true, cpu: true })),
|
||||
};
|
||||
|
||||
local expect(name, want) =
|
||||
assert results[name] == want : name + ': got ' + std.manifestJson(results[name]);
|
||||
true;
|
||||
|
||||
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'],
|
||||
Disk: ['Summary', 'Volumes', 'Volume I/O', 'Physical disks'],
|
||||
Exporter: exporter,
|
||||
Fleet: ['Hosts', 'Utilization', 'Throughput and errors'],
|
||||
Memory: ['Summary', 'Physical memory and commit', 'Paging and kernel memory'],
|
||||
Network: ['Summary', 'Interfaces'],
|
||||
Overview: ['Summary', 'CPU and memory', 'Storage and network'],
|
||||
Services: ['Summary', 'State'],
|
||||
});
|
||||
assert expect('osOnly', { Exporter: exporter, Fleet: ['Hosts'], Overview: ['Summary'] });
|
||||
assert expect('smbClientOnly', { Exporter: exporter, Fleet: ['Hosts'], Overview: ['Summary'], SMB: ['Summary', 'Client shares'] });
|
||||
assert expect('diskWithoutDiskdrive', ['Summary', 'Volumes', 'Volume I/O', 'Physical disks']);
|
||||
assert expect('diskWithDiskdrive', ['Summary', 'Volumes', 'Volume I/O', 'Drives', 'Physical disks']);
|
||||
assert expect('replaced', {
|
||||
CPU: ['Summary', 'Utilization', 'System calls and frequency'],
|
||||
Exporter: exporter,
|
||||
Fleet: ['Hosts', 'Utilization'],
|
||||
Overview: ['Summary', 'CPU and memory'],
|
||||
});
|
||||
|
||||
results
|
||||
+11
-2
@@ -8,7 +8,8 @@ and takes several ideas from Grafana's [windows-observ-lib](https://github.com/g
|
||||
|
||||
- Grafana 13 or later with a Prometheus data source. The file uses the v2 dashboard schema, which Grafana needs for tabs.
|
||||
- windows_exporter with the default collectors (`cpu`, `logical_disk`, `memory`, `net`, `os`, `physical_disk`, `service`, `system`).
|
||||
The Processes, GPU, Hyper-V and Time tabs and the TCP and UDP rows of the Network tab need the `process`, `gpu`, `hyperv`, `time`, `tcp` and `udp` collectors, which are not enabled by default.
|
||||
The SMB, Processes, Scheduled tasks, Updates, GPU, Hyper-V and Time tabs, the TCP and UDP rows of the Network tab and the Drives row of the Disk tab need the `smb` or `smbclient`, `process`, `scheduled_task`, `update`, `gpu`, `hyperv`, `time`, `tcp`, `udp` and `diskdrive` collectors, which are not enabled by default.
|
||||
The sample shows every tab; build the dashboard from the [mixin](../contrib/mixin#collectors) with your collectors to hide the others.
|
||||
The process names in the GPU tab need the `process` collector.
|
||||
GPU temperature, fan, power and clock panels require WDDM 2.4 or newer and driver support; unsupported sensors are absent.
|
||||
|
||||
@@ -37,10 +38,13 @@ The **Reboots** annotation marks each reboot of the selected host on all graphs.
|
||||
| Overview | Summary of the selected host: operating system, uptime, CPUs, memory, commit charge, volumes, disk and network throughput. |
|
||||
| CPU | Utilization by mode and per core, processor queue length, context switches, interrupts, processes, threads, system calls and the effective CPU frequency. |
|
||||
| Memory | Used and available physical memory, commit charge, hard page faults, kernel pools and the system cache. |
|
||||
| Disk | Volume size and free space, usage, busy time, throughput, IOPS, latency and queue length. |
|
||||
| Disk | Volume label, disk, file system, size and free space, usage, busy time, throughput, IOPS, latency and queue length; drive model, size, partitions and status; busy time, throughput, IOPS, latency, queue length and split I/O per physical disk, labelled with the drive model when `diskdrive` is enabled. |
|
||||
| Network | Throughput, utilization, packets, errors and discards per interface; TCP connection states and rates, segments and retransmissions; UDP datagrams and errors. |
|
||||
| SMB | Open files, connections, traffic, requests and opened files per share this host serves; traffic, requests, latency, queue length, credit stalls and metadata requests per remote share this host uses. |
|
||||
| Services | Running, pending and disabled services, automatic services that are not running, and services that started or stopped in the time range. |
|
||||
| Processes | CPU usage, working set, private bytes, I/O throughput and operations, threads, handles and page faults per process, labelled with name and PID. |
|
||||
| Scheduled tasks | Task count, running, disabled and never-run tasks, enabled tasks with an unknown result code or missed runs, tasks that ran in the time range, and all tasks with state, last result and missed runs. |
|
||||
| Updates | Pending updates by severity and category, the oldest pending update, the time since the last Windows Update check, a list of pending updates and the query duration. |
|
||||
| GPU | Utilization by engine type, memory capacity, commitment and locality, temperature, fans, power, clocks, and top processes by GPU utilization and memory. |
|
||||
| Hyper-V | VM count and health, Hyper-V WMI health, host CPU time, CPU and memory per VM, virtual disks, virtual switches and VM network adapters. |
|
||||
| Time | Time zone, clock sync source, NTP time sources, clock offset and NTP round-trip delay. |
|
||||
@@ -48,6 +52,11 @@ The **Reboots** annotation marks each reboot of the selected host on all graphs.
|
||||
|
||||
Click a hostname in the Fleet tab to open that host in the Overview tab.
|
||||
|
||||
Each per-host tab starts with a row of current values, each with a sparkline
|
||||
of the time range behind it. Green, orange and red show health against fixed
|
||||
thresholds; values without a meaningful threshold, like throughput or counts,
|
||||
stay in the text color.
|
||||
|
||||
The Processes tab starts with totals for all selected processes: process count,
|
||||
threads, handles, CPU usage across the host, private working set and private bytes.
|
||||
The synthetic Idle process (PID 0) is excluded. Each graph shows at most **Top N**
|
||||
|
||||
+10334
-977
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user