mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-20 11:39:04 +02:00
dashboard: factor Grafonnet builders into tab modules (#2595)
Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
@@ -53,6 +53,9 @@ jobs:
|
|||||||
if: steps.tools-cache.outputs.cache-hit != 'true'
|
if: steps.tools-cache.outputs.cache-hit != 'true'
|
||||||
run: make tools
|
run: make tools
|
||||||
|
|
||||||
|
- name: Build dashboard and check generated changes
|
||||||
|
run: make check-dashboards
|
||||||
|
|
||||||
- name: Lint and test rules
|
- name: Lint and test rules
|
||||||
run: |
|
run: |
|
||||||
make test PROMTOOL="docker run --rm --entrypoint promtool -v \"$PWD:/mixin:ro\" -w /mixin prom/prometheus:v3.15.0"
|
make test PROMTOOL="docker run --rm --entrypoint promtool -v \"$PWD:/mixin:ro\" -w /mixin prom/prometheus:v3.15.0"
|
||||||
|
|||||||
+10
-3
@@ -17,9 +17,11 @@ DASHBOARD := ../../dashboard/windows-exporter-dashboard.json
|
|||||||
|
|
||||||
.DEFAULT_GOAL := generate
|
.DEFAULT_GOAL := generate
|
||||||
.DELETE_ON_ERROR:
|
.DELETE_ON_ERROR:
|
||||||
.PHONY: generate dashboard tools jsonnet-tools fmt lint test check-runbooks clean
|
.PHONY: generate rules dashboard dashboards check-dashboards tools jsonnet-tools fmt lint test check-runbooks clean
|
||||||
|
|
||||||
generate: windows_rules.yaml windows_alerts.yaml
|
generate: rules dashboard
|
||||||
|
|
||||||
|
rules: windows_rules.yaml windows_alerts.yaml
|
||||||
|
|
||||||
tools: jsonnet-tools
|
tools: jsonnet-tools
|
||||||
go install github.com/cloudflare/pint/cmd/pint@$(PINT_VERSION)
|
go install github.com/cloudflare/pint/cmd/pint@$(PINT_VERSION)
|
||||||
@@ -50,10 +52,15 @@ dashboard: vendor
|
|||||||
$(JSONNET) -J vendor -S dashboard.jsonnet > $(DASHBOARD).tmp
|
$(JSONNET) -J vendor -S dashboard.jsonnet > $(DASHBOARD).tmp
|
||||||
mv $(DASHBOARD).tmp $(DASHBOARD)
|
mv $(DASHBOARD).tmp $(DASHBOARD)
|
||||||
|
|
||||||
|
dashboards: dashboard
|
||||||
|
|
||||||
|
check-dashboards: dashboard
|
||||||
|
git diff --exit-code -- $(DASHBOARD)
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
$(JSONNETFMT) $(FMT_FLAGS) -i $(SOURCES)
|
$(JSONNETFMT) $(FMT_FLAGS) -i $(SOURCES)
|
||||||
|
|
||||||
lint: generate tests/custom_rules.yaml tests/custom_alerts.yaml vendor
|
lint: rules tests/custom_rules.yaml tests/custom_alerts.yaml vendor
|
||||||
@set -e; for file in $(SOURCES); do \
|
@set -e; for file in $(SOURCES); do \
|
||||||
$(JSONNETFMT) $(FMT_FLAGS) "$$file" | diff -u "$$file" -; \
|
$(JSONNETFMT) $(FMT_FLAGS) "$$file" | diff -u "$$file" -; \
|
||||||
done
|
done
|
||||||
|
|||||||
+34
-6
@@ -13,7 +13,7 @@ and [pint](https://github.com/cloudflare/pint) tools:
|
|||||||
cd contrib/mixin
|
cd contrib/mixin
|
||||||
make tools
|
make tools
|
||||||
export PATH="$(go env GOPATH)/bin:$PATH"
|
export PATH="$(go env GOPATH)/bin:$PATH"
|
||||||
make generate
|
make rules
|
||||||
```
|
```
|
||||||
|
|
||||||
Only the dashboard depends on Jsonnet libraries, so `jb install` is not required
|
Only the dashboard depends on Jsonnet libraries, so `jb install` is not required
|
||||||
@@ -201,15 +201,35 @@ Fork users should override `runbookURLPattern` to match their published URL.
|
|||||||
It is a Grafana v2 dashboard with tabs and needs Grafana 13 or later. See the
|
It is a Grafana v2 dashboard with tabs and needs Grafana 13 or later. See the
|
||||||
[dashboard documentation](../../dashboard/README.md) for its content.
|
[dashboard documentation](../../dashboard/README.md) for its content.
|
||||||
|
|
||||||
The dashboard is built with [grafonnet](https://github.com/grafana/grafonnet),
|
The dashboard uses additive builders with [grafonnet](https://github.com/grafana/grafonnet),
|
||||||
pinned in `jsonnetfile.json`:
|
pinned in `jsonnetfile.json`:
|
||||||
|
|
||||||
- `dashboards/windows-exporter.libsonnet` defines the tabs, panels and queries.
|
- `dashboards/windows-exporter.libsonnet` composes the dashboard, with one source
|
||||||
- `lib/dashboard.libsonnet` wraps grafonnet panel options and queries into v2
|
file per tab for its panels, queries, rows, and grid positions.
|
||||||
panels, and lays them out in tabs, rows and grids.
|
- `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.
|
||||||
|
- `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.
|
||||||
|
- `dashboards/variables.libsonnet` and `dashboards/annotations.libsonnet` use
|
||||||
|
native Grafonnet v2 builders for variables and annotations.
|
||||||
- `lib/manifest.libsonnet` renders JSON with the four-space indentation of the
|
- `lib/manifest.libsonnet` renders JSON with the four-space indentation of the
|
||||||
committed file.
|
committed file.
|
||||||
|
|
||||||
|
For example, a panel can be built with:
|
||||||
|
|
||||||
|
```jsonnet
|
||||||
|
local b = import 'dashboards/builders.libsonnet';
|
||||||
|
|
||||||
|
b.panel.new(200, 'CPU usage', 'timeseries')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('windows:cpu_usage:ratio{instance="$instance"}')
|
||||||
|
+ b.query.withLegendFormat('CPU'),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({ unit: 'percentunit', min: 0, max: 1 })
|
||||||
|
```
|
||||||
|
|
||||||
Regenerate the committed file after changing the dashboard source:
|
Regenerate the committed file after changing the dashboard source:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -217,7 +237,15 @@ make dashboard
|
|||||||
```
|
```
|
||||||
|
|
||||||
This runs `jb install` and writes `../../dashboard/windows-exporter-dashboard.json`.
|
This runs `jb install` and writes `../../dashboard/windows-exporter-dashboard.json`.
|
||||||
The dashboard uses its own variables and does not read `_config`.
|
`make dashboards` is an alias. `make generate` builds both the rules and the
|
||||||
|
dashboard, while `make rules` builds only the rules. The same commands work
|
||||||
|
inside WSL with the Go tool binaries on `PATH`.
|
||||||
|
|
||||||
|
The checked-in JSON remains the reference for the rendered dashboard, including
|
||||||
|
queries, IDs, variables, annotations, transformations, and tab/row layout.
|
||||||
|
`make 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`.
|
||||||
|
|
||||||
## Validate
|
## Validate
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
local b = import 'builders.libsonnet';
|
||||||
|
local annotation = b.dashboard.spec.annotations;
|
||||||
|
|
||||||
|
[
|
||||||
|
annotation.withKind()
|
||||||
|
+ annotation.spec.withBuiltIn(true)
|
||||||
|
+ annotation.spec.withEnable(true)
|
||||||
|
+ annotation.spec.withHide(true)
|
||||||
|
+ annotation.spec.withIconColor('rgba(0, 211, 255, 1)')
|
||||||
|
+ annotation.spec.withLegacyOptions({
|
||||||
|
type: 'dashboard',
|
||||||
|
})
|
||||||
|
+ 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}')),
|
||||||
|
]
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
local g = import 'github.com/grafana/grafonnet/gen/grafonnet-v13.0.0/main.libsonnet';
|
||||||
|
local v2 = g.apps.dashboard.v2;
|
||||||
|
local styles = import 'styles.libsonnet';
|
||||||
|
local grid = v2.spec.layout.GridLayoutKind;
|
||||||
|
local rows = v2.spec.layout.RowsLayoutKind;
|
||||||
|
local tabs = v2.spec.layout.TabsLayoutKind;
|
||||||
|
local item = grid.spec.items;
|
||||||
|
local row = rows.spec.rows;
|
||||||
|
local tab = tabs.spec.tabs;
|
||||||
|
local prometheus = g.query.prometheus;
|
||||||
|
|
||||||
|
// Unlike std.mergePatch, this keeps explicit nulls (notably threshold steps).
|
||||||
|
local merge(base, patch) = base + {
|
||||||
|
[key]: if std.objectHas(base, key) && std.isObject(base[key]) && std.isObject(patch[key])
|
||||||
|
then merge(base[key], patch[key]) else patch[key]
|
||||||
|
for key in std.objectFields(patch)
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
dashboard: v2,
|
||||||
|
|
||||||
|
// Grafonnet's v2 dashboard accepts elements but has no Panel/QueryGroup constructor.
|
||||||
|
// Keep that small schema boundary here; tab sources use the same additive builders.
|
||||||
|
panel: {
|
||||||
|
new(id, title, visualization): {
|
||||||
|
kind: 'Panel',
|
||||||
|
spec: {
|
||||||
|
id: id,
|
||||||
|
title: title,
|
||||||
|
description: '',
|
||||||
|
links: [],
|
||||||
|
data: {
|
||||||
|
kind: 'QueryGroup',
|
||||||
|
spec: { queries: [], queryOptions: {}, transformations: [] },
|
||||||
|
},
|
||||||
|
vizConfig: {
|
||||||
|
kind: 'VizConfig',
|
||||||
|
group: visualization,
|
||||||
|
version: '',
|
||||||
|
spec: {
|
||||||
|
fieldConfig: { defaults: styles[visualization].defaults, overrides: [] },
|
||||||
|
options: styles[visualization].options,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
withDescription(value): { spec+: { description: value } },
|
||||||
|
withLinks(value): { spec+: { links: value } },
|
||||||
|
withQueries(value): { spec+: { data+: { spec+: { queries: value } } } },
|
||||||
|
withQueryOptions(value): { spec+: { data+: { spec+: { queryOptions: value } } } },
|
||||||
|
withTransformations(value): { spec+: { data+: { spec+: { transformations: value } } } },
|
||||||
|
withDefaults(value): {
|
||||||
|
spec+: { vizConfig+: { spec+: { fieldConfig+: { defaults: merge(super.defaults, value) } } } },
|
||||||
|
},
|
||||||
|
withOptions(value): { spec+: { vizConfig+: { spec+: { options: merge(super.options, value) } } } },
|
||||||
|
withOverrides(value): { spec+: { vizConfig+: { spec+: { fieldConfig+: { overrides: value } } } } },
|
||||||
|
},
|
||||||
|
|
||||||
|
dataQuery(spec, group='prometheus', datasource='${datasource}'): {
|
||||||
|
kind: 'DataQuery',
|
||||||
|
group: group,
|
||||||
|
version: 'v0',
|
||||||
|
datasource: { name: datasource },
|
||||||
|
spec: spec,
|
||||||
|
},
|
||||||
|
|
||||||
|
query: {
|
||||||
|
new(expr, refId='A'): {
|
||||||
|
kind: 'PanelQuery',
|
||||||
|
spec: {
|
||||||
|
hidden: false,
|
||||||
|
refId: refId,
|
||||||
|
query: $.dataQuery(
|
||||||
|
prometheus.withExpr(expr)
|
||||||
|
+ prometheus.withEditorMode('code')
|
||||||
|
+ prometheus.withInstant(false)
|
||||||
|
+ prometheus.withRange(true)
|
||||||
|
+ prometheus.withLegendFormat('__auto')
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
withLegendFormat(value): {
|
||||||
|
spec+: { query+: { spec+: prometheus.withLegendFormat(value) } },
|
||||||
|
},
|
||||||
|
withInstant(value=true): {
|
||||||
|
spec+: { query+: { spec+: prometheus.withInstant(value) + prometheus.withRange(!value) } },
|
||||||
|
},
|
||||||
|
withRange(value): { spec+: { query+: { spec+: prometheus.withRange(value) } } },
|
||||||
|
withFormat(value): { spec+: { query+: { spec+: prometheus.withFormat(value) } } },
|
||||||
|
},
|
||||||
|
|
||||||
|
grid(items): (grid.withKind() + grid.spec.withItems(items)).spec.layout,
|
||||||
|
rows(rows_): (rows.withKind() + rows.spec.withRows(rows_)).spec.layout,
|
||||||
|
tabs(tabs_): (tabs.withKind() + tabs.spec.withTabs(tabs_)).spec.layout,
|
||||||
|
place(id, x, y, width, height):
|
||||||
|
item.withKind()
|
||||||
|
+ item.spec.withElement({ kind: 'ElementReference', name: 'panel-' + id })
|
||||||
|
+ item.spec.withX(x)
|
||||||
|
+ item.spec.withY(y)
|
||||||
|
+ item.spec.withWidth(width)
|
||||||
|
+ item.spec.withHeight(height),
|
||||||
|
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 }),
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
local b = import 'builders.libsonnet';
|
||||||
|
|
||||||
|
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.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.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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
|
||||||
|
b.panel.new(29, 'System calls and exceptions', 'timeseries')
|
||||||
|
+ 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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
)
|
||||||
@@ -0,0 +1,319 @@
|
|||||||
|
local b = import 'builders.libsonnet';
|
||||||
|
|
||||||
|
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.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,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
|
||||||
|
b.panel.new(38, 'Volume usage', 'timeseries')
|
||||||
|
+ 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, '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.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: 'custom.transform',
|
||||||
|
value: 'negative-Y',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
|
||||||
|
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: 'custom.transform',
|
||||||
|
value: 'negative-Y',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
|
||||||
|
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. The avg_*_requests_queued metrics grow like counters, so the panel takes their rate.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('rate(windows_logical_disk_avg_write_requests_queued{job=~"$job", instance="$instance", volume=~"$volume"}[$__rate_interval])', 'A')
|
||||||
|
+ b.query.withLegendFormat('{{volume}} write'),
|
||||||
|
b.query.new('rate(windows_logical_disk_avg_read_requests_queued{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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
)
|
||||||
@@ -0,0 +1,659 @@
|
|||||||
|
local b = import 'builders.libsonnet';
|
||||||
|
|
||||||
|
b.tab(
|
||||||
|
'Exporter',
|
||||||
|
b.rows([
|
||||||
|
b.row('Scrape', b.grid([
|
||||||
|
b.place(90, 0, 0, 4, 4),
|
||||||
|
b.place(91, 4, 0, 4, 4),
|
||||||
|
b.place(92, 8, 0, 4, 4),
|
||||||
|
b.place(93, 12, 0, 4, 4),
|
||||||
|
b.place(94, 16, 0, 4, 4),
|
||||||
|
b.place(95, 20, 0, 4, 4),
|
||||||
|
b.place(96, 0, 4, 12, 8),
|
||||||
|
b.place(97, 12, 4, 12, 8),
|
||||||
|
])),
|
||||||
|
b.row('Collectors', b.grid([
|
||||||
|
b.place(99, 0, 0, 8, 8),
|
||||||
|
b.place(100, 8, 0, 16, 8),
|
||||||
|
b.place(101, 0, 8, 12, 9),
|
||||||
|
b.place(102, 12, 8, 12, 9),
|
||||||
|
])),
|
||||||
|
b.row('Process and Go runtime', b.grid([
|
||||||
|
b.place(104, 0, 0, 8, 8),
|
||||||
|
b.place(105, 8, 0, 8, 8),
|
||||||
|
b.place(106, 16, 0, 8, 8),
|
||||||
|
b.place(107, 0, 8, 8, 8),
|
||||||
|
b.place(108, 8, 8, 8, 8),
|
||||||
|
b.place(109, 16, 8, 8, 8),
|
||||||
|
b.place(110, 0, 16, 12, 8),
|
||||||
|
b.place(111, 12, 16, 12, 8),
|
||||||
|
])),
|
||||||
|
]),
|
||||||
|
[
|
||||||
|
b.panel.new(90, 'Scrape status', 'stat')
|
||||||
|
+ b.panel.withDescription('Result of the last scrape by Prometheus.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('up{job=~"$job", instance="$instance"}', 'A')
|
||||||
|
+ b.query.withInstant(),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
mappings: [
|
||||||
|
{
|
||||||
|
options: {
|
||||||
|
'0': {
|
||||||
|
index: 0,
|
||||||
|
text: 'down',
|
||||||
|
},
|
||||||
|
'1': {
|
||||||
|
index: 1,
|
||||||
|
text: 'up',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'value',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
thresholds: {
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
color: 'red',
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: 'green',
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
unit: '',
|
||||||
|
}),
|
||||||
|
|
||||||
|
b.panel.new(91, 'Exporter version', 'stat')
|
||||||
|
+ b.panel.withDescription('Builds without a version show the first seven characters of the Git revision.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('windows_exporter_build_info{job=~"$job", instance="$instance", version!=""} or on (instance) label_replace(windows_exporter_build_info{job=~"$job", instance="$instance"}, "version", "$1", "revision", "(.{7}).*")', 'A')
|
||||||
|
+ b.query.withInstant()
|
||||||
|
+ b.query.withFormat('table'),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
unit: '',
|
||||||
|
})
|
||||||
|
+ b.panel.withOptions({
|
||||||
|
colorMode: 'none',
|
||||||
|
reduceOptions: {
|
||||||
|
fields: '/^version$/',
|
||||||
|
},
|
||||||
|
textMode: 'value',
|
||||||
|
}),
|
||||||
|
|
||||||
|
b.panel.new(92, 'Exporter uptime', 'stat')
|
||||||
|
+ b.panel.withDescription('Time since the exporter process started. A short uptime on a host with a long uptime means the exporter restarted.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('time() - process_start_time_seconds{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(93, 'Failed collectors', 'stat')
|
||||||
|
+ b.panel.withDescription('Collectors that failed at least once in the selected time range.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('count(min_over_time(windows_exporter_collector_success{job=~"$job", instance="$instance"}[$__range]) == 0) or vector(0)', 'A')
|
||||||
|
+ b.query.withInstant(),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
thresholds: {
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
color: 'green',
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: 'red',
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
b.panel.new(94, 'Timed-out collectors', 'stat')
|
||||||
|
+ b.panel.withDescription('Collectors that hit the collector timeout at least once in the selected time range.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('count(max_over_time(windows_exporter_collector_timeout{job=~"$job", instance="$instance"}[$__range]) == 1) or vector(0)', 'A')
|
||||||
|
+ b.query.withInstant(),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
thresholds: {
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
color: 'green',
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: 'red',
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
b.panel.new(95, 'Samples per scrape', 'stat')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('scrape_samples_scraped{job=~"$job", instance="$instance"}', 'A'),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
unit: 'short',
|
||||||
|
})
|
||||||
|
+ b.panel.withOptions({
|
||||||
|
graphMode: 'area',
|
||||||
|
}),
|
||||||
|
|
||||||
|
b.panel.new(96, 'Scrape duration', 'timeseries')
|
||||||
|
+ b.panel.withDescription('Scrape duration measured by Prometheus and by the exporter. It must stay below the scrape timeout of the job.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('scrape_duration_seconds{job=~"$job", instance="$instance"}', 'A')
|
||||||
|
+ b.query.withLegendFormat('Prometheus'),
|
||||||
|
b.query.new('windows_exporter_scrape_duration_seconds{job=~"$job", instance="$instance"}', 'B')
|
||||||
|
+ b.query.withLegendFormat('exporter'),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
min: 0,
|
||||||
|
unit: 's',
|
||||||
|
}),
|
||||||
|
|
||||||
|
b.panel.new(97, 'HTTP requests to /metrics', 'timeseries')
|
||||||
|
+ b.panel.withDescription('Scrape requests by HTTP status code, and errors while gathering or encoding metrics. More requests than one per scrape interval mean several jobs or Prometheus servers scrape this exporter.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('sum by (code) (rate(promhttp_metric_handler_requests_total{job=~"$job", instance="$instance"}[$__rate_interval])) > 0', 'A')
|
||||||
|
+ b.query.withLegendFormat('HTTP {{code}}'),
|
||||||
|
b.query.new('sum by (cause) (rate(promhttp_metric_handler_errors_total{job=~"$job", instance="$instance"}[$__rate_interval])) > 0', 'B')
|
||||||
|
+ b.query.withLegendFormat('{{cause}} errors'),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
min: 0,
|
||||||
|
unit: 'reqps',
|
||||||
|
})
|
||||||
|
+ b.panel.withOverrides([
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byRegexp',
|
||||||
|
options: '.* errors',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'color',
|
||||||
|
value: {
|
||||||
|
fixedColor: 'red',
|
||||||
|
mode: 'fixed',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
|
||||||
|
b.panel.new(99, 'Collectors', 'table')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('windows_exporter_collector_success{job=~"$job", instance="$instance"}', 'A')
|
||||||
|
+ b.query.withInstant()
|
||||||
|
+ b.query.withFormat('table'),
|
||||||
|
b.query.new('windows_exporter_collector_timeout{job=~"$job", instance="$instance"}', 'B')
|
||||||
|
+ b.query.withInstant()
|
||||||
|
+ b.query.withFormat('table'),
|
||||||
|
b.query.new('windows_exporter_collector_duration_seconds{job=~"$job", instance="$instance"}', 'C')
|
||||||
|
+ b.query.withInstant()
|
||||||
|
+ b.query.withFormat('table'),
|
||||||
|
])
|
||||||
|
+ b.panel.withTransformations([
|
||||||
|
{
|
||||||
|
group: 'joinByField',
|
||||||
|
kind: 'Transformation',
|
||||||
|
spec: {
|
||||||
|
options: {
|
||||||
|
byField: 'collector',
|
||||||
|
mode: 'outer',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: 'organize',
|
||||||
|
kind: 'Transformation',
|
||||||
|
spec: {
|
||||||
|
options: {
|
||||||
|
excludeByName: {
|
||||||
|
Time: true,
|
||||||
|
'Time 1': true,
|
||||||
|
'Time 2': true,
|
||||||
|
'Time 3': true,
|
||||||
|
__name__: true,
|
||||||
|
'__name__ 1': true,
|
||||||
|
'__name__ 2': true,
|
||||||
|
'__name__ 3': 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,
|
||||||
|
collector: 0,
|
||||||
|
},
|
||||||
|
renameByName: {
|
||||||
|
'Value #A': 'Status',
|
||||||
|
'Value #B': 'Timeout',
|
||||||
|
'Value #C': 'Duration',
|
||||||
|
collector: 'Collector',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
+ b.panel.withOptions({
|
||||||
|
sortBy: [
|
||||||
|
{
|
||||||
|
desc: true,
|
||||||
|
displayName: 'Duration',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
+ b.panel.withOverrides([
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'Value #A',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'mappings',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
options: {
|
||||||
|
'0': {
|
||||||
|
color: 'red',
|
||||||
|
index: 0,
|
||||||
|
text: 'failed',
|
||||||
|
},
|
||||||
|
'1': {
|
||||||
|
color: 'green',
|
||||||
|
index: 1,
|
||||||
|
text: 'ok',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'value',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'custom.cellOptions',
|
||||||
|
value: {
|
||||||
|
type: 'color-text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'Value #B',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'mappings',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
options: {
|
||||||
|
'0': {
|
||||||
|
color: 'green',
|
||||||
|
index: 0,
|
||||||
|
text: 'no',
|
||||||
|
},
|
||||||
|
'1': {
|
||||||
|
color: 'red',
|
||||||
|
index: 1,
|
||||||
|
text: 'yes',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'value',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'custom.cellOptions',
|
||||||
|
value: {
|
||||||
|
type: 'color-text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'Value #C',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'unit',
|
||||||
|
value: 's',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
|
||||||
|
b.panel.new(100, 'Collector duration', 'timeseries')
|
||||||
|
+ b.panel.withDescription('Time each collector spent per scrape. Compare against the scrape timeout.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('windows_exporter_collector_duration_seconds{job=~"$job", instance="$instance"}', 'A')
|
||||||
|
+ b.query.withLegendFormat('{{collector}}'),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
custom: {
|
||||||
|
fillOpacity: 40,
|
||||||
|
stacking: {
|
||||||
|
mode: 'normal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
min: 0,
|
||||||
|
unit: 's',
|
||||||
|
})
|
||||||
|
+ b.panel.withOptions({
|
||||||
|
legend: {
|
||||||
|
sortBy: 'Mean',
|
||||||
|
sortDesc: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
b.panel.new(101, 'Collector failures', 'state-timeline')
|
||||||
|
+ b.panel.withDescription('Collectors that failed at least once in the selected time range.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('windows_exporter_collector_success{job=~"$job", instance="$instance"} and on (collector) (min_over_time(windows_exporter_collector_success{job=~"$job", instance="$instance"}[$__range] @ end()) == 0)', 'A')
|
||||||
|
+ b.query.withLegendFormat('{{collector}}'),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
mappings: [
|
||||||
|
{
|
||||||
|
options: {
|
||||||
|
'0': {
|
||||||
|
color: 'red',
|
||||||
|
index: 0,
|
||||||
|
text: 'failed',
|
||||||
|
},
|
||||||
|
'1': {
|
||||||
|
color: 'green',
|
||||||
|
index: 1,
|
||||||
|
text: 'ok',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'value',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
|
||||||
|
b.panel.new(102, 'Collector timeouts', 'state-timeline')
|
||||||
|
+ b.panel.withDescription('Collectors that hit the collector timeout at least once in the selected time range.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('1 - windows_exporter_collector_timeout{job=~"$job", instance="$instance"} and on (collector) (max_over_time(windows_exporter_collector_timeout{job=~"$job", instance="$instance"}[$__range] @ end()) == 1)', 'A')
|
||||||
|
+ b.query.withLegendFormat('{{collector}}'),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
mappings: [
|
||||||
|
{
|
||||||
|
options: {
|
||||||
|
'0': {
|
||||||
|
color: 'red',
|
||||||
|
index: 0,
|
||||||
|
text: 'timed out',
|
||||||
|
},
|
||||||
|
'1': {
|
||||||
|
color: 'green',
|
||||||
|
index: 1,
|
||||||
|
text: 'in time',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'value',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
|
||||||
|
b.panel.new(104, 'Exporter CPU usage', 'timeseries')
|
||||||
|
+ b.panel.withDescription('CPU time used by the exporter process, as a share of one core. CPU usage grows with the number of scrapes, for example when two Prometheus jobs scrape the same exporter; CPU per scrape (right axis) does not.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('rate(process_cpu_seconds_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||||
|
+ b.query.withLegendFormat('CPU'),
|
||||||
|
b.query.new('rate(process_cpu_seconds_total{job=~"$job", instance="$instance"}[$__rate_interval]) / on (instance) sum by (instance) (rate(promhttp_metric_handler_requests_total{job=~"$job", instance="$instance"}[$__rate_interval]))', 'B')
|
||||||
|
+ b.query.withLegendFormat('CPU per scrape'),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
min: 0,
|
||||||
|
})
|
||||||
|
+ b.panel.withOptions({
|
||||||
|
legend: {
|
||||||
|
calcs: [],
|
||||||
|
displayMode: 'list',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
+ b.panel.withOverrides([
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'CPU per scrape',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'unit',
|
||||||
|
value: 's',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'custom.axisPlacement',
|
||||||
|
value: 'right',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
|
||||||
|
b.panel.new(105, 'Exporter memory', 'timeseries')
|
||||||
|
+ b.panel.withDescription('Working set and private bytes of the exporter process, and the Go heap in use. Steady growth points to a leak.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('process_resident_memory_bytes{job=~"$job", instance="$instance"}', 'A')
|
||||||
|
+ b.query.withLegendFormat('working set'),
|
||||||
|
b.query.new('process_virtual_memory_bytes{job=~"$job", instance="$instance"}', 'B')
|
||||||
|
+ b.query.withLegendFormat('private bytes'),
|
||||||
|
b.query.new('go_memstats_heap_inuse_bytes{job=~"$job", instance="$instance"}', 'C')
|
||||||
|
+ b.query.withLegendFormat('Go heap in use'),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
min: 0,
|
||||||
|
unit: 'bytes',
|
||||||
|
}),
|
||||||
|
|
||||||
|
b.panel.new(106, 'Exporter handles', 'timeseries')
|
||||||
|
+ b.panel.withDescription('Open handles of the exporter process (process_open_fds). Steady growth points to a handle leak.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('process_open_fds{job=~"$job", instance="$instance"}', 'A')
|
||||||
|
+ b.query.withLegendFormat('open handles'),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
min: 0,
|
||||||
|
unit: 'short',
|
||||||
|
})
|
||||||
|
+ b.panel.withOptions({
|
||||||
|
legend: {
|
||||||
|
calcs: [],
|
||||||
|
displayMode: 'list',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
b.panel.new(107, 'Goroutines by state', 'timeseries')
|
||||||
|
+ b.panel.withDescription('Steady growth of goroutines points to a goroutine leak. Goroutines that stay "not in Go" sit in a system call, for example a collector waiting on WMI or PDH. The states need an exporter build with the Go scheduler metrics; the total line works with every build.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('go_sched_goroutines_running_goroutines{job=~"$job", instance="$instance"}', 'A')
|
||||||
|
+ b.query.withLegendFormat('running'),
|
||||||
|
b.query.new('go_sched_goroutines_runnable_goroutines{job=~"$job", instance="$instance"}', 'B')
|
||||||
|
+ b.query.withLegendFormat('runnable'),
|
||||||
|
b.query.new('go_sched_goroutines_waiting_goroutines{job=~"$job", instance="$instance"}', 'C')
|
||||||
|
+ b.query.withLegendFormat('waiting'),
|
||||||
|
b.query.new('go_sched_goroutines_not_in_go_goroutines{job=~"$job", instance="$instance"}', 'D')
|
||||||
|
+ b.query.withLegendFormat('not in Go'),
|
||||||
|
b.query.new('go_goroutines{job=~"$job", instance="$instance"}', 'E')
|
||||||
|
+ b.query.withLegendFormat('total'),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
custom: {
|
||||||
|
fillOpacity: 40,
|
||||||
|
stacking: {
|
||||||
|
mode: 'normal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
min: 0,
|
||||||
|
unit: 'short',
|
||||||
|
})
|
||||||
|
+ b.panel.withOverrides([
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'total',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'color',
|
||||||
|
value: {
|
||||||
|
fixedColor: 'text',
|
||||||
|
mode: 'fixed',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'custom.stacking',
|
||||||
|
value: {
|
||||||
|
group: 'B',
|
||||||
|
mode: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'custom.fillOpacity',
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'custom.lineStyle',
|
||||||
|
value: {
|
||||||
|
dash: [
|
||||||
|
10,
|
||||||
|
10,
|
||||||
|
],
|
||||||
|
fill: 'dash',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
|
||||||
|
b.panel.new(108, 'Goroutines created and threads', 'timeseries')
|
||||||
|
+ b.panel.withDescription('Goroutines started per second, and OS threads owned by the Go runtime. Goroutines created needs an exporter build with the Go scheduler metrics.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('rate(go_sched_goroutines_created_goroutines_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||||
|
+ b.query.withLegendFormat('goroutines created/s'),
|
||||||
|
b.query.new('go_threads{job=~"$job", instance="$instance"}', 'B')
|
||||||
|
+ b.query.withLegendFormat('OS threads'),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
min: 0,
|
||||||
|
unit: 'short',
|
||||||
|
})
|
||||||
|
+ b.panel.withOverrides([
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'OS threads',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'custom.axisPlacement',
|
||||||
|
value: 'right',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
|
||||||
|
b.panel.new(109, 'Scheduler latency', 'timeseries')
|
||||||
|
+ b.panel.withDescription('Time goroutines wait in the runnable state before they run. High values mean the exporter does not get enough CPU. Needs an exporter build with the Go scheduler metrics.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('histogram_quantile(0.99, sum by (le) (rate(go_sched_latencies_seconds_bucket{job=~"$job", instance="$instance"}[$__rate_interval])))', 'A')
|
||||||
|
+ b.query.withLegendFormat('p99'),
|
||||||
|
b.query.new('histogram_quantile(0.5, sum by (le) (rate(go_sched_latencies_seconds_bucket{job=~"$job", instance="$instance"}[$__rate_interval])))', 'B')
|
||||||
|
+ b.query.withLegendFormat('p50'),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
min: 0,
|
||||||
|
unit: 's',
|
||||||
|
}),
|
||||||
|
|
||||||
|
b.panel.new(110, 'Garbage collection', 'timeseries')
|
||||||
|
+ b.panel.withDescription('Average GC pause and GC runs per second.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('rate(go_gc_duration_seconds_sum{job=~"$job", instance="$instance"}[$__rate_interval]) / rate(go_gc_duration_seconds_count{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||||
|
+ b.query.withLegendFormat('average pause'),
|
||||||
|
b.query.new('rate(go_gc_duration_seconds_count{job=~"$job", instance="$instance"}[$__rate_interval])', 'B')
|
||||||
|
+ b.query.withLegendFormat('GC runs'),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
min: 0,
|
||||||
|
unit: 's',
|
||||||
|
})
|
||||||
|
+ b.panel.withOverrides([
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'GC runs',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'unit',
|
||||||
|
value: 'ops',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'custom.axisPlacement',
|
||||||
|
value: 'right',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
|
||||||
|
b.panel.new(111, 'Allocation rate', 'timeseries')
|
||||||
|
+ b.panel.withDescription('Bytes allocated on the Go heap per second. Spikes line up with expensive collectors.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('rate(go_memstats_alloc_bytes_total{job=~"$job", instance="$instance"}[$__rate_interval])', 'A')
|
||||||
|
+ b.query.withLegendFormat('allocated'),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
min: 0,
|
||||||
|
unit: 'Bps',
|
||||||
|
})
|
||||||
|
+ b.panel.withOptions({
|
||||||
|
legend: {
|
||||||
|
calcs: [],
|
||||||
|
displayMode: 'list',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
)
|
||||||
@@ -0,0 +1,710 @@
|
|||||||
|
local b = import 'builders.libsonnet';
|
||||||
|
|
||||||
|
b.tab(
|
||||||
|
'Fleet',
|
||||||
|
b.grid([
|
||||||
|
b.place(2, 0, 0, 24, 10),
|
||||||
|
b.place(3, 0, 10, 8, 8),
|
||||||
|
b.place(4, 8, 10, 8, 8),
|
||||||
|
b.place(5, 16, 10, 8, 8),
|
||||||
|
b.place(6, 0, 18, 8, 8),
|
||||||
|
b.place(7, 8, 18, 8, 8),
|
||||||
|
b.place(8, 16, 18, 8, 8),
|
||||||
|
]),
|
||||||
|
[
|
||||||
|
b.panel.new(2, 'Hosts', 'table')
|
||||||
|
+ b.panel.withDescription('One row per scraped host. Click a hostname to open its details. Services up counts running services; Auto stopped counts services with start mode auto that are not running.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('max by (instance, hostname) (windows_os_hostname{job=~"$job", hostname=~"$hostname"})', 'A')
|
||||||
|
+ b.query.withInstant()
|
||||||
|
+ b.query.withFormat('table'),
|
||||||
|
b.query.new('(max by (instance, product, version) (windows_os_info{job=~"$job"})) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'B')
|
||||||
|
+ b.query.withInstant()
|
||||||
|
+ b.query.withFormat('table'),
|
||||||
|
b.query.new('(max by (instance) (time() - windows_system_boot_time_timestamp{job=~"$job"})) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'C')
|
||||||
|
+ b.query.withInstant()
|
||||||
|
+ b.query.withFormat('table'),
|
||||||
|
b.query.new('(max by (instance) (windows_cpu_logical_processor{job=~"$job"})) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'D')
|
||||||
|
+ b.query.withInstant()
|
||||||
|
+ b.query.withFormat('table'),
|
||||||
|
b.query.new('(100 * (1 - avg by (instance) (clamp_max(rate(windows_cpu_time_total{job=~"$job", mode="idle"}[$__rate_interval]), 1)))) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'E')
|
||||||
|
+ b.query.withInstant()
|
||||||
|
+ b.query.withFormat('table'),
|
||||||
|
b.query.new('(max by (instance) (windows_memory_physical_total_bytes{job=~"$job"})) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'F')
|
||||||
|
+ b.query.withInstant()
|
||||||
|
+ b.query.withFormat('table'),
|
||||||
|
b.query.new('(max by (instance) (100 * (1 - windows_memory_physical_free_bytes{job=~"$job"} / windows_memory_physical_total_bytes{job=~"$job"}))) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'G')
|
||||||
|
+ b.query.withInstant()
|
||||||
|
+ b.query.withFormat('table'),
|
||||||
|
b.query.new('(max by (instance) (100 * windows_memory_committed_bytes{job=~"$job"} / windows_memory_commit_limit{job=~"$job"})) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'H')
|
||||||
|
+ b.query.withInstant()
|
||||||
|
+ b.query.withFormat('table'),
|
||||||
|
b.query.new('(max by (instance) (100 * (1 - windows_logical_disk_free_bytes{job=~"$job", volume!~"HarddiskVolume.*"} / windows_logical_disk_size_bytes{job=~"$job", volume!~"HarddiskVolume.*"}))) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'I')
|
||||||
|
+ b.query.withInstant()
|
||||||
|
+ b.query.withFormat('table'),
|
||||||
|
b.query.new('(sum by (instance) (windows_service_state{job=~"$job", state="running"})) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'J')
|
||||||
|
+ b.query.withInstant()
|
||||||
|
+ b.query.withFormat('table'),
|
||||||
|
b.query.new('(count by (instance) (windows_service_state{job=~"$job", state="running"} == 0 and on (instance, name) windows_service_start_mode{job=~"$job", start_mode="auto"} == 1)) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'K')
|
||||||
|
+ b.query.withInstant()
|
||||||
|
+ b.query.withFormat('table'),
|
||||||
|
b.query.new('(max by (instance) (windows_system_processes{job=~"$job"})) and on (instance) windows_os_hostname{job=~"$job", hostname=~"$hostname"}', 'L')
|
||||||
|
+ b.query.withInstant()
|
||||||
|
+ b.query.withFormat('table'),
|
||||||
|
])
|
||||||
|
+ b.panel.withTransformations([
|
||||||
|
{
|
||||||
|
group: 'joinByField',
|
||||||
|
kind: 'Transformation',
|
||||||
|
spec: {
|
||||||
|
options: {
|
||||||
|
byField: 'instance',
|
||||||
|
mode: 'outer',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: 'organize',
|
||||||
|
kind: 'Transformation',
|
||||||
|
spec: {
|
||||||
|
options: {
|
||||||
|
excludeByName: {
|
||||||
|
Time: true,
|
||||||
|
'Time 1': true,
|
||||||
|
'Time 10': true,
|
||||||
|
'Time 11': true,
|
||||||
|
'Time 12': true,
|
||||||
|
'Time 2': true,
|
||||||
|
'Time 3': true,
|
||||||
|
'Time 4': true,
|
||||||
|
'Time 5': true,
|
||||||
|
'Time 6': true,
|
||||||
|
'Time 7': true,
|
||||||
|
'Time 8': true,
|
||||||
|
'Time 9': true,
|
||||||
|
'Value #A': true,
|
||||||
|
'Value #B': true,
|
||||||
|
},
|
||||||
|
includeByName: {},
|
||||||
|
indexByName: {
|
||||||
|
'Value #C': 4,
|
||||||
|
'Value #D': 5,
|
||||||
|
'Value #E': 6,
|
||||||
|
'Value #F': 7,
|
||||||
|
'Value #G': 8,
|
||||||
|
'Value #H': 9,
|
||||||
|
'Value #I': 10,
|
||||||
|
'Value #J': 11,
|
||||||
|
'Value #K': 12,
|
||||||
|
'Value #L': 13,
|
||||||
|
hostname: 0,
|
||||||
|
instance: 1,
|
||||||
|
product: 2,
|
||||||
|
version: 3,
|
||||||
|
},
|
||||||
|
renameByName: {
|
||||||
|
'Value #C': 'Uptime',
|
||||||
|
'Value #D': 'CPUs',
|
||||||
|
'Value #E': 'CPU',
|
||||||
|
'Value #F': 'Memory',
|
||||||
|
'Value #G': 'Memory used',
|
||||||
|
'Value #H': 'Commit used',
|
||||||
|
'Value #I': 'Fullest volume',
|
||||||
|
'Value #J': 'Services up',
|
||||||
|
'Value #K': 'Auto stopped',
|
||||||
|
'Value #L': 'Processes',
|
||||||
|
hostname: 'Hostname',
|
||||||
|
instance: 'Instance',
|
||||||
|
product: 'OS',
|
||||||
|
version: 'Version',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
+ b.panel.withOptions({
|
||||||
|
footer: {
|
||||||
|
fields: [
|
||||||
|
'CPUs',
|
||||||
|
'Memory',
|
||||||
|
'Processes',
|
||||||
|
],
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
sortBy: [
|
||||||
|
{
|
||||||
|
desc: false,
|
||||||
|
displayName: 'Hostname',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
+ b.panel.withOverrides([
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'hostname',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'links',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
title: 'Show details for ${__data.fields.hostname}',
|
||||||
|
url: '/d/${__dashboard.uid}?${__url_time_range}&${datasource:queryparam}&${job:queryparam}&var-hostname=$__all&var-instance=${__data.fields.instance}&dtab=Overview',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'custom.width',
|
||||||
|
value: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'custom.filterable',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'Value #C',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'unit',
|
||||||
|
value: 'dtdurations',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'custom.width',
|
||||||
|
value: 90,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'instance',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'custom.width',
|
||||||
|
value: 140,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'product',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'custom.width',
|
||||||
|
value: 160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'custom.filterable',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'version',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'custom.width',
|
||||||
|
value: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'custom.filterable',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'Value #D',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'unit',
|
||||||
|
value: 'none',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'custom.width',
|
||||||
|
value: 60,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'Value #E',
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'Value #F',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'unit',
|
||||||
|
value: 'bytes',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'custom.width',
|
||||||
|
value: 90,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'Value #G',
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'Value #H',
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'Value #I',
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'Value #J',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'unit',
|
||||||
|
value: 'none',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'Value #K',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'unit',
|
||||||
|
value: 'none',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'thresholds',
|
||||||
|
value: {
|
||||||
|
mode: 'absolute',
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
color: 'green',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: 'orange',
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'custom.cellOptions',
|
||||||
|
value: {
|
||||||
|
type: 'color-text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'noValue',
|
||||||
|
value: '0',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'Value #L',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'unit',
|
||||||
|
value: 'none',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'custom.width',
|
||||||
|
value: 90,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
|
||||||
|
b.panel.new(3, 'CPU utilization', 'timeseries')
|
||||||
|
+ b.panel.withDescription('Shows the 25 highest hosts at each point in time.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('topk(25, (100 * (1 - avg by (instance) (clamp_max(rate(windows_cpu_time_total{job=~"$job", mode="idle"}[$__rate_interval]), 1)))) * on (instance) group_left (hostname) max by (instance, hostname) (windows_os_hostname{job=~"$job", hostname=~"$hostname"}))', 'A')
|
||||||
|
+ b.query.withLegendFormat('{{hostname}}'),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
max: 100,
|
||||||
|
min: 0,
|
||||||
|
unit: 'percent',
|
||||||
|
})
|
||||||
|
+ b.panel.withOptions({
|
||||||
|
legend: {
|
||||||
|
calcs: [],
|
||||||
|
displayMode: 'list',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
b.panel.new(4, 'Memory utilization', 'timeseries')
|
||||||
|
+ b.panel.withDescription('Shows the 25 highest hosts at each point in time.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('topk(25, (max by (instance) (100 * (1 - windows_memory_physical_free_bytes{job=~"$job"} / windows_memory_physical_total_bytes{job=~"$job"}))) * on (instance) group_left (hostname) max by (instance, hostname) (windows_os_hostname{job=~"$job", hostname=~"$hostname"}))', 'A')
|
||||||
|
+ b.query.withLegendFormat('{{hostname}}'),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
max: 100,
|
||||||
|
min: 0,
|
||||||
|
unit: 'percent',
|
||||||
|
})
|
||||||
|
+ b.panel.withOptions({
|
||||||
|
legend: {
|
||||||
|
calcs: [],
|
||||||
|
displayMode: 'list',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
b.panel.new(5, 'Fullest volume', 'timeseries')
|
||||||
|
+ b.panel.withDescription('Usage of the fullest volume per host. Shows the 25 highest hosts at each point in time.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('topk(25, (max by (instance) (100 * (1 - windows_logical_disk_free_bytes{job=~"$job", volume!~"HarddiskVolume.*"} / windows_logical_disk_size_bytes{job=~"$job", volume!~"HarddiskVolume.*"}))) * on (instance) group_left (hostname) max by (instance, hostname) (windows_os_hostname{job=~"$job", hostname=~"$hostname"}))', 'A')
|
||||||
|
+ b.query.withLegendFormat('{{hostname}}'),
|
||||||
|
])
|
||||||
|
+ 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: [],
|
||||||
|
displayMode: 'list',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
b.panel.new(6, 'Network throughput', 'timeseries')
|
||||||
|
+ b.panel.withDescription('Sum over all interfaces. Received is drawn below the axis. Shows the 25 highest hosts at each point in time.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('topk(25, (sum by (instance) (rate(windows_net_bytes_sent_total{job=~"$job"}[$__rate_interval]) * 8)) * on (instance) group_left (hostname) max by (instance, hostname) (windows_os_hostname{job=~"$job", hostname=~"$hostname"}))', 'A')
|
||||||
|
+ b.query.withLegendFormat('{{hostname}} sent'),
|
||||||
|
b.query.new('topk(25, (sum by (instance) (rate(windows_net_bytes_received_total{job=~"$job"}[$__rate_interval]) * 8)) * on (instance) group_left (hostname) max by (instance, hostname) (windows_os_hostname{job=~"$job", hostname=~"$hostname"}))', 'B')
|
||||||
|
+ b.query.withLegendFormat('{{hostname}} received'),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
unit: 'bps',
|
||||||
|
})
|
||||||
|
+ b.panel.withOptions({
|
||||||
|
legend: {
|
||||||
|
calcs: [],
|
||||||
|
displayMode: 'list',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
+ b.panel.withOverrides([
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byRegexp',
|
||||||
|
options: '.* received$',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'custom.transform',
|
||||||
|
value: 'negative-Y',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
|
||||||
|
b.panel.new(7, 'Disk throughput', 'timeseries')
|
||||||
|
+ b.panel.withDescription('Sum over all volumes. Read is drawn below the axis. Shows the 25 highest hosts at each point in time.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('topk(25, (sum by (instance) (rate(windows_logical_disk_write_bytes_total{job=~"$job", volume!~"HarddiskVolume.*"}[$__rate_interval]))) * on (instance) group_left (hostname) max by (instance, hostname) (windows_os_hostname{job=~"$job", hostname=~"$hostname"}))', 'A')
|
||||||
|
+ b.query.withLegendFormat('{{hostname}} write'),
|
||||||
|
b.query.new('topk(25, (sum by (instance) (rate(windows_logical_disk_read_bytes_total{job=~"$job", volume!~"HarddiskVolume.*"}[$__rate_interval]))) * on (instance) group_left (hostname) max by (instance, hostname) (windows_os_hostname{job=~"$job", hostname=~"$hostname"}))', 'B')
|
||||||
|
+ b.query.withLegendFormat('{{hostname}} read'),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
unit: 'Bps',
|
||||||
|
})
|
||||||
|
+ b.panel.withOptions({
|
||||||
|
legend: {
|
||||||
|
calcs: [],
|
||||||
|
displayMode: 'list',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
+ b.panel.withOverrides([
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byRegexp',
|
||||||
|
options: '.* read$',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'custom.transform',
|
||||||
|
value: 'negative-Y',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
|
||||||
|
b.panel.new(8, 'Network errors and discards', 'timeseries')
|
||||||
|
+ b.panel.withDescription('Sum over all interfaces of received and outbound errors and discards. Shows the 25 highest hosts at each point in time.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('topk(25, (sum by (instance) (rate(windows_net_packets_received_errors_total{job=~"$job"}[$__rate_interval]) + rate(windows_net_packets_received_discarded_total{job=~"$job"}[$__rate_interval]) + rate(windows_net_packets_outbound_errors_total{job=~"$job"}[$__rate_interval]) + rate(windows_net_packets_outbound_discarded_total{job=~"$job"}[$__rate_interval]))) * on (instance) group_left (hostname) max by (instance, hostname) (windows_os_hostname{job=~"$job", hostname=~"$hostname"}))', 'A')
|
||||||
|
+ b.query.withLegendFormat('{{hostname}}'),
|
||||||
|
])
|
||||||
|
+ b.panel.withDefaults({
|
||||||
|
min: 0,
|
||||||
|
unit: 'pps',
|
||||||
|
})
|
||||||
|
+ b.panel.withOptions({
|
||||||
|
legend: {
|
||||||
|
calcs: [],
|
||||||
|
displayMode: 'list',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
)
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
local b = import 'builders.libsonnet';
|
||||||
|
|
||||||
|
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.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(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(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(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,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
)
|
||||||
@@ -0,0 +1,380 @@
|
|||||||
|
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(),
|
||||||
|
]),
|
||||||
|
|
||||||
|
b.panel.new(64, 'VMs in critical health', 'stat')
|
||||||
|
+ 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: [
|
||||||
|
{
|
||||||
|
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: [
|
||||||
|
{
|
||||||
|
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(66, 'Logical processors', 'stat')
|
||||||
|
+ 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. windows_hyperv_virtual_storage_device_latency_seconds holds the raw, growing counter in 100 ns ticks, so the panel divides its rate by the rate of I/O operations. Errors use the right axis.')
|
||||||
|
+ b.panel.withQueries([
|
||||||
|
b.query.new('rate(windows_hyperv_virtual_storage_device_latency_seconds{job=~"$job", instance="$instance"}[$__rate_interval]) / (rate(windows_hyperv_virtual_storage_device_operations_read_total{job=~"$job", instance="$instance"}[$__rate_interval]) + rate(windows_hyperv_virtual_storage_device_operations_written_total{job=~"$job", instance="$instance"}[$__rate_interval])) / 1e7', '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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
|
||||||
|
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$',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'custom.transform',
|
||||||
|
value: 'negative-Y',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
|
||||||
|
b.panel.new(81, 'Virtual switch dropped packets', 'timeseries')
|
||||||
|
+ 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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
)
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
local b = import 'builders.libsonnet';
|
||||||
|
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
|
||||||
|
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(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',
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
)
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
local b = import 'builders.libsonnet';
|
||||||
|
|
||||||
|
b.tab(
|
||||||
|
'Network',
|
||||||
|
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.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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
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(48, 'Network errors and discards', 'timeseries')
|
||||||
|
+ 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',
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
)
|
||||||
@@ -0,0 +1,490 @@
|
|||||||
|
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.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.withQueries([
|
||||||
|
b.query.new('windows_cpu_logical_processor{job=~"$job", instance="$instance"}', 'A')
|
||||||
|
+ b.query.withInstant(),
|
||||||
|
]),
|
||||||
|
|
||||||
|
b.panel.new(13, 'RAM', 'stat')
|
||||||
|
+ 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.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.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.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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: {
|
||||||
|
id: 'byName',
|
||||||
|
options: 'available',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'color',
|
||||||
|
value: {
|
||||||
|
fixedColor: 'green',
|
||||||
|
mode: 'fixed',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
|
||||||
|
b.panel.new(20, 'Volumes', 'table')
|
||||||
|
+ 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,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
id: 'custom.transform',
|
||||||
|
value: 'negative-Y',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
)
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
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.withQueries([
|
||||||
|
b.query.new('sum(windows_service_state{job=~"$job", instance="$instance", state="running"})', 'A')
|
||||||
|
+ b.query.withInstant(),
|
||||||
|
]),
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: 'orange',
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
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: [
|
||||||
|
{
|
||||||
|
color: 'green',
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: 'orange',
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
b.panel.new(53, 'Disabled services', 'stat')
|
||||||
|
+ 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,197 @@
|
|||||||
|
// Shared visualization defaults from the reference dashboard. Arrays are replaced whole.
|
||||||
|
{
|
||||||
|
timeseries: {
|
||||||
|
defaults: {
|
||||||
|
color: {
|
||||||
|
mode: 'palette-classic',
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
axisBorderShow: false,
|
||||||
|
axisCenteredZero: false,
|
||||||
|
axisColorMode: 'text',
|
||||||
|
axisLabel: '',
|
||||||
|
axisPlacement: 'auto',
|
||||||
|
barAlignment: 0,
|
||||||
|
drawStyle: 'line',
|
||||||
|
fillOpacity: 10,
|
||||||
|
gradientMode: 'opacity',
|
||||||
|
hideFrom: {
|
||||||
|
legend: false,
|
||||||
|
tooltip: false,
|
||||||
|
viz: false,
|
||||||
|
},
|
||||||
|
insertNulls: false,
|
||||||
|
lineInterpolation: 'linear',
|
||||||
|
lineWidth: 1,
|
||||||
|
pointSize: 4,
|
||||||
|
scaleDistribution: {
|
||||||
|
type: 'linear',
|
||||||
|
},
|
||||||
|
showPoints: 'never',
|
||||||
|
spanNulls: false,
|
||||||
|
stacking: {
|
||||||
|
group: 'A',
|
||||||
|
mode: 'none',
|
||||||
|
},
|
||||||
|
thresholdsStyle: {
|
||||||
|
mode: 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
thresholds: {
|
||||||
|
mode: 'absolute',
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
color: 'green',
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
unit: 'percentunit',
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
legend: {
|
||||||
|
calcs: [
|
||||||
|
'mean',
|
||||||
|
'max',
|
||||||
|
'lastNotNull',
|
||||||
|
],
|
||||||
|
displayMode: 'table',
|
||||||
|
placement: 'bottom',
|
||||||
|
showLegend: true,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
hideZeros: false,
|
||||||
|
mode: 'multi',
|
||||||
|
sort: 'desc',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
stat: {
|
||||||
|
defaults: {
|
||||||
|
color: {
|
||||||
|
mode: 'thresholds',
|
||||||
|
},
|
||||||
|
thresholds: {
|
||||||
|
mode: 'absolute',
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
color: 'text',
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
unit: 'none',
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
colorMode: 'value',
|
||||||
|
graphMode: 'none',
|
||||||
|
justifyMode: 'auto',
|
||||||
|
orientation: 'auto',
|
||||||
|
percentChangeColorMode: 'standard',
|
||||||
|
reduceOptions: {
|
||||||
|
calcs: [
|
||||||
|
'lastNotNull',
|
||||||
|
],
|
||||||
|
fields: '',
|
||||||
|
values: false,
|
||||||
|
},
|
||||||
|
showPercentChange: false,
|
||||||
|
textMode: 'auto',
|
||||||
|
wideLayout: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
defaults: {
|
||||||
|
color: {
|
||||||
|
mode: 'thresholds',
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
align: 'auto',
|
||||||
|
cellOptions: {
|
||||||
|
type: 'auto',
|
||||||
|
},
|
||||||
|
filterable: false,
|
||||||
|
inspect: false,
|
||||||
|
minWidth: 60,
|
||||||
|
},
|
||||||
|
thresholds: {
|
||||||
|
mode: 'absolute',
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
color: 'green',
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
cellHeight: 'sm',
|
||||||
|
enablePagination: false,
|
||||||
|
footer: {
|
||||||
|
countRows: false,
|
||||||
|
fields: '',
|
||||||
|
reducer: [
|
||||||
|
'sum',
|
||||||
|
],
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
showHeader: true,
|
||||||
|
sortBy: [
|
||||||
|
{
|
||||||
|
desc: true,
|
||||||
|
displayName: 'Used',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'state-timeline': {
|
||||||
|
defaults: {
|
||||||
|
color: {
|
||||||
|
mode: 'thresholds',
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
fillOpacity: 90,
|
||||||
|
hideFrom: {
|
||||||
|
legend: false,
|
||||||
|
tooltip: false,
|
||||||
|
viz: false,
|
||||||
|
},
|
||||||
|
insertNulls: false,
|
||||||
|
lineWidth: 0,
|
||||||
|
spanNulls: false,
|
||||||
|
},
|
||||||
|
max: 1,
|
||||||
|
min: 0,
|
||||||
|
thresholds: {
|
||||||
|
mode: 'absolute',
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
color: 'red',
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: 'green',
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
unit: 'none',
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
alignValue: 'left',
|
||||||
|
legend: {
|
||||||
|
displayMode: 'list',
|
||||||
|
placement: 'bottom',
|
||||||
|
showLegend: false,
|
||||||
|
},
|
||||||
|
mergeValues: false,
|
||||||
|
rowHeight: 1,
|
||||||
|
showValue: 'never',
|
||||||
|
tooltip: {
|
||||||
|
hideZeros: false,
|
||||||
|
mode: 'single',
|
||||||
|
sort: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
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',
|
||||||
|
}),
|
||||||
|
|
||||||
|
b.panel.new(85, 'Clock sync source', 'stat')
|
||||||
|
+ 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.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.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,102 @@
|
|||||||
|
local b = import 'builders.libsonnet';
|
||||||
|
local variables = b.dashboard.spec.variables;
|
||||||
|
local query = variables.QueryVariableKind;
|
||||||
|
local datasource = variables.DatasourceVariableKind;
|
||||||
|
local common = query.withKind()
|
||||||
|
+ query.spec.withAllowCustomValue(true)
|
||||||
|
+ query.spec.withCurrent({ text: '', value: '' })
|
||||||
|
+ query.spec.withHide('dontHide')
|
||||||
|
+ query.spec.withIncludeAll(false)
|
||||||
|
+ query.spec.withMulti(false)
|
||||||
|
+ query.spec.withOptions([])
|
||||||
|
+ query.spec.withRefresh('onTimeRangeChanged')
|
||||||
|
+ query.spec.withRegex('')
|
||||||
|
+ query.spec.withSkipUrlSync(false)
|
||||||
|
+ query.spec.withSort('alphabeticalAsc');
|
||||||
|
local new(name, definition) = common
|
||||||
|
+ query.spec.withName(name)
|
||||||
|
+ query.spec.withDefinition(definition)
|
||||||
|
+ query.spec.withQuery(b.dataQuery({
|
||||||
|
qryType: 1,
|
||||||
|
query: definition,
|
||||||
|
refId: 'PrometheusVariableQueryEditor-VariableQuery',
|
||||||
|
}));
|
||||||
|
|
||||||
|
[
|
||||||
|
datasource.withKind()
|
||||||
|
+ datasource.spec.withAllowCustomValue(true)
|
||||||
|
+ datasource.spec.withCurrent({
|
||||||
|
text: '',
|
||||||
|
value: '',
|
||||||
|
})
|
||||||
|
+ datasource.spec.withHide('dontHide')
|
||||||
|
+ datasource.spec.withIncludeAll(false)
|
||||||
|
+ datasource.spec.withLabel('Data source')
|
||||||
|
+ datasource.spec.withMulti(false)
|
||||||
|
+ datasource.spec.withName('datasource')
|
||||||
|
+ datasource.spec.withOptions([])
|
||||||
|
+ datasource.spec.withPluginId('prometheus')
|
||||||
|
+ datasource.spec.withRefresh('onDashboardLoad')
|
||||||
|
+ datasource.spec.withRegex('')
|
||||||
|
+ datasource.spec.withSkipUrlSync(false)
|
||||||
|
,
|
||||||
|
new('job', 'label_values(windows_os_hostname, job)')
|
||||||
|
+ query.spec.withCurrent({
|
||||||
|
text: [
|
||||||
|
'All',
|
||||||
|
],
|
||||||
|
value: [
|
||||||
|
'$__all',
|
||||||
|
],
|
||||||
|
})
|
||||||
|
+ query.spec.withIncludeAll(true)
|
||||||
|
+ query.spec.withLabel('Job')
|
||||||
|
+ query.spec.withMulti(true)
|
||||||
|
,
|
||||||
|
new('hostname', 'label_values(windows_os_hostname{job=~"$job"}, hostname)')
|
||||||
|
+ query.spec.withCurrent({
|
||||||
|
text: [
|
||||||
|
'All',
|
||||||
|
],
|
||||||
|
value: [
|
||||||
|
'$__all',
|
||||||
|
],
|
||||||
|
})
|
||||||
|
+ query.spec.withIncludeAll(true)
|
||||||
|
+ query.spec.withLabel('Hostname')
|
||||||
|
+ query.spec.withMulti(true)
|
||||||
|
,
|
||||||
|
new('instance', 'label_values(windows_os_hostname{job=~"$job", hostname=~"$hostname"}, instance)')
|
||||||
|
+ query.spec.withLabel('Instance')
|
||||||
|
,
|
||||||
|
new('show_hostname', 'label_values(windows_os_hostname{job=~"$job", instance="$instance"}, hostname)')
|
||||||
|
+ query.spec.withHide('hideVariable')
|
||||||
|
,
|
||||||
|
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).+$/'),
|
||||||
|
]
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,467 +0,0 @@
|
|||||||
// Helpers to build a Grafana v2 dashboard (dashboard.grafana.app/v2) with tabs.
|
|
||||||
//
|
|
||||||
// Grafonnet builds the resource, the panel options and field config, and the Prometheus queries.
|
|
||||||
// The panel options and field config have the same shape in v1 panels and in v2 VizConfig.
|
|
||||||
// The helpers wrap them into v2 elements and lay the elements out in tabs, rows and grids.
|
|
||||||
local g = import 'github.com/grafana/grafonnet/gen/grafonnet-v13.0.0/main.libsonnet';
|
|
||||||
|
|
||||||
local prometheus = g.query.prometheus;
|
|
||||||
local stat = g.panel.stat;
|
|
||||||
local stateTimeline = g.panel.stateTimeline;
|
|
||||||
local table = g.panel.table;
|
|
||||||
local timeSeries = g.panel.timeSeries;
|
|
||||||
|
|
||||||
local datasource = { name: '${datasource}' };
|
|
||||||
|
|
||||||
// steps builds absolute thresholds from [color, value] pairs. The first value is null (base).
|
|
||||||
local steps(pairs) = {
|
|
||||||
mode: 'absolute',
|
|
||||||
steps: [{ color: p[0], value: p[1] } for p in pairs],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Thresholds inside field overrides leave out the null value of the base step.
|
|
||||||
local overrideSteps(pairs) = {
|
|
||||||
mode: 'absolute',
|
|
||||||
steps: [{ color: p[0] } + (if p[1] == null then {} else { value: p[1] }) for p in pairs],
|
|
||||||
};
|
|
||||||
|
|
||||||
local optional(value, fn) = if value == null then {} else fn(value);
|
|
||||||
|
|
||||||
// vizConfig keeps the fields of a grafonnet panel that make up the v2 VizConfig spec.
|
|
||||||
local vizConfig(group, panel) = {
|
|
||||||
group: group,
|
|
||||||
kind: 'VizConfig',
|
|
||||||
spec: { fieldConfig: panel.fieldConfig, options: panel.options },
|
|
||||||
version: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
local query(q, refId) = {
|
|
||||||
kind: 'PanelQuery',
|
|
||||||
spec: {
|
|
||||||
hidden: false,
|
|
||||||
query: {
|
|
||||||
datasource: datasource,
|
|
||||||
group: 'prometheus',
|
|
||||||
kind: 'DataQuery',
|
|
||||||
spec:
|
|
||||||
prometheus.withEditorMode('code')
|
|
||||||
+ prometheus.withExpr(q.expr)
|
|
||||||
+ prometheus.withLegendFormat(if q.legend == '' then '__auto' else q.legend)
|
|
||||||
+ prometheus.withInstant(q.instant)
|
|
||||||
+ prometheus.withRange(!q.instant)
|
|
||||||
+ (if q.table then prometheus.withFormat('table') else {}),
|
|
||||||
version: 'v0',
|
|
||||||
},
|
|
||||||
refId: refId,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// element is a v2 panel. The dashboard function adds its ID and grid position.
|
|
||||||
local element(title, description, queries, viz, width, height, transformations=[]) = {
|
|
||||||
width:: width,
|
|
||||||
height:: height,
|
|
||||||
kind: 'Panel',
|
|
||||||
spec: {
|
|
||||||
data: {
|
|
||||||
kind: 'QueryGroup',
|
|
||||||
spec: {
|
|
||||||
queries: [query(queries[i], std.char(std.codepoint('A') + i)) for i in std.range(0, std.length(queries) - 1)],
|
|
||||||
queryOptions: {},
|
|
||||||
transformations: [
|
|
||||||
{ group: t.id, kind: 'Transformation', spec: { options: t.options } }
|
|
||||||
for t in transformations
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: description,
|
|
||||||
links: [],
|
|
||||||
title: title,
|
|
||||||
vizConfig: viz,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
local gridItems(bands) =
|
|
||||||
local heights = [std.foldl(std.max, [p.height for p in band], 0) for band in bands];
|
|
||||||
std.flattenArrays([
|
|
||||||
local y = std.foldl(function(sum, h) sum + h, heights[0:b], 0);
|
|
||||||
[
|
|
||||||
{
|
|
||||||
panel:: bands[b][i],
|
|
||||||
kind: 'GridLayoutItem',
|
|
||||||
spec: {
|
|
||||||
height: bands[b][i].height,
|
|
||||||
width: bands[b][i].width,
|
|
||||||
x: std.foldl(function(sum, p) sum + p.width, bands[b][0:i], 0),
|
|
||||||
y: y,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i in std.range(0, std.length(bands[b]) - 1)
|
|
||||||
]
|
|
||||||
for b in std.range(0, std.length(bands) - 1)
|
|
||||||
]);
|
|
||||||
|
|
||||||
{
|
|
||||||
// host selects the host chosen in the Instance variable.
|
|
||||||
host:: 'job=~"$job", instance="$instance"',
|
|
||||||
// fleet selects all hosts of the chosen jobs. Fleet queries add fleetJoin or fleetFilter for the Hostname variable.
|
|
||||||
fleet:: 'job=~"$job"',
|
|
||||||
|
|
||||||
// fleetJoin adds the hostname label and restricts to the selected hostnames.
|
|
||||||
fleetJoin(expr)::
|
|
||||||
'(%s) * on (instance) group_left (hostname) max by (instance, hostname) (windows_os_hostname{%s, hostname=~"$hostname"})' % [expr, self.fleet],
|
|
||||||
|
|
||||||
// fleetFilter restricts to the selected hostnames without adding labels (for tables).
|
|
||||||
fleetFilter(expr)::
|
|
||||||
'(%s) and on (instance) windows_os_hostname{%s, hostname=~"$hostname"}' % [expr, self.fleet],
|
|
||||||
|
|
||||||
// q is a Prometheus query. An empty legend uses Grafana's automatic legend.
|
|
||||||
q(expr, legend=''):: { expr: expr, legend: legend, instant: false, table: false },
|
|
||||||
|
|
||||||
steps:: steps,
|
|
||||||
pctSteps:: steps([['green', null], ['orange', 80], ['red', 90]]),
|
|
||||||
plainSteps:: steps([['green', null]]),
|
|
||||||
textSteps:: steps([['text', null]]),
|
|
||||||
|
|
||||||
prop(id, value):: { id: id, value: value },
|
|
||||||
fixedColor(color):: self.prop('color', { fixedColor: color, mode: 'fixed' }),
|
|
||||||
dashed:: [self.prop('custom.fillOpacity', 0), self.prop('custom.lineStyle', { dash: [10, 10], fill: 'dash' })],
|
|
||||||
|
|
||||||
byName(name, props):: timeSeries.fieldOverride.byName.new(name) + { properties: props },
|
|
||||||
byRegexp(regex, props):: timeSeries.fieldOverride.byRegexp.new(regex) + { properties: props },
|
|
||||||
|
|
||||||
// negativeY mirrors series whose display name matches the regular expression below the X axis.
|
|
||||||
negativeY(regex):: self.byRegexp(regex, [self.prop('custom.transform', 'negative-Y')]),
|
|
||||||
|
|
||||||
// valueMapping maps the values 0 and 1 to texts and colors.
|
|
||||||
valueMapping(zero, one):: [{ type: 'value', options: { '0': zero { index: 0 }, '1': one { index: 1 } } }],
|
|
||||||
|
|
||||||
// gaugeCell shows a table cell as a gauge from 0 to maxValue.
|
|
||||||
gaugeCell(unit, maxValue):: [
|
|
||||||
self.prop('unit', unit),
|
|
||||||
self.prop('min', 0),
|
|
||||||
self.prop('max', maxValue),
|
|
||||||
self.prop('thresholds', overrideSteps([['green', null], ['orange', 80], ['red', 90]])),
|
|
||||||
self.prop('color', { mode: 'continuous-GrYlRd' }),
|
|
||||||
self.prop('custom.cellOptions', { type: 'gauge', mode: 'basic', valueDisplayMode: 'text' }),
|
|
||||||
self.prop('decimals', 1),
|
|
||||||
],
|
|
||||||
overrideSteps:: overrideSteps,
|
|
||||||
|
|
||||||
unitCell(unit):: [self.prop('unit', unit)],
|
|
||||||
|
|
||||||
// timeseries draws a time series panel. legend is table (with calcs), list or hidden.
|
|
||||||
// sortBy sorts the table legend by that column, descending.
|
|
||||||
timeseries(
|
|
||||||
title,
|
|
||||||
targets,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
description='',
|
|
||||||
unit='',
|
|
||||||
stack=false,
|
|
||||||
min=null,
|
|
||||||
max=null,
|
|
||||||
decimals=null,
|
|
||||||
legend='table',
|
|
||||||
calcs=['mean', 'max', 'lastNotNull'],
|
|
||||||
overrides=[],
|
|
||||||
fill=10,
|
|
||||||
thresholds=self.plainSteps,
|
|
||||||
showThresholds=false,
|
|
||||||
sortBy='',
|
|
||||||
)::
|
|
||||||
local custom = timeSeries.fieldConfig.defaults.custom;
|
|
||||||
local panel =
|
|
||||||
custom.withAxisBorderShow(false)
|
|
||||||
+ custom.withAxisCenteredZero(false)
|
|
||||||
+ custom.withAxisColorMode('text')
|
|
||||||
+ custom.withAxisLabel('')
|
|
||||||
+ custom.withAxisPlacement('auto')
|
|
||||||
+ custom.withBarAlignment(0)
|
|
||||||
+ custom.withDrawStyle('line')
|
|
||||||
+ custom.withFillOpacity(fill)
|
|
||||||
+ custom.withGradientMode('opacity')
|
|
||||||
+ custom.hideFrom.withLegend(false)
|
|
||||||
+ custom.hideFrom.withTooltip(false)
|
|
||||||
+ custom.hideFrom.withViz(false)
|
|
||||||
+ custom.withInsertNulls(false)
|
|
||||||
+ custom.withLineInterpolation('linear')
|
|
||||||
+ custom.withLineWidth(1)
|
|
||||||
+ custom.withPointSize(4)
|
|
||||||
+ custom.scaleDistribution.withType('linear')
|
|
||||||
+ custom.withShowPoints('never')
|
|
||||||
+ custom.withSpanNulls(false)
|
|
||||||
+ custom.stacking.withGroup('A')
|
|
||||||
+ custom.stacking.withMode(if stack then 'normal' else 'none')
|
|
||||||
+ custom.thresholdsStyle.withMode(if showThresholds then 'dashed' else 'off')
|
|
||||||
+ timeSeries.standardOptions.color.withMode('palette-classic')
|
|
||||||
+ timeSeries.standardOptions.thresholds.withMode(thresholds.mode)
|
|
||||||
+ timeSeries.standardOptions.thresholds.withSteps(thresholds.steps)
|
|
||||||
+ timeSeries.standardOptions.withUnit(unit)
|
|
||||||
+ optional(min, timeSeries.standardOptions.withMin)
|
|
||||||
+ optional(max, timeSeries.standardOptions.withMax)
|
|
||||||
+ optional(decimals, timeSeries.standardOptions.withDecimals)
|
|
||||||
+ timeSeries.standardOptions.withOverrides(overrides)
|
|
||||||
+ timeSeries.options.legend.withPlacement('bottom')
|
|
||||||
+ (
|
|
||||||
if legend == 'table' then
|
|
||||||
timeSeries.options.legend.withCalcs(calcs)
|
|
||||||
+ timeSeries.options.legend.withDisplayMode('table')
|
|
||||||
+ timeSeries.options.legend.withShowLegend(true)
|
|
||||||
+ (if sortBy == '' then {} else timeSeries.options.legend.withSortBy(sortBy) + timeSeries.options.legend.withSortDesc(true))
|
|
||||||
else
|
|
||||||
timeSeries.options.legend.withCalcs([])
|
|
||||||
+ timeSeries.options.legend.withDisplayMode('list')
|
|
||||||
+ timeSeries.options.legend.withShowLegend(legend == 'list')
|
|
||||||
)
|
|
||||||
+ timeSeries.options.tooltip.withHideZeros(false)
|
|
||||||
+ timeSeries.options.tooltip.withMode('multi')
|
|
||||||
+ timeSeries.options.tooltip.withSort('desc');
|
|
||||||
element(title, description, targets, vizConfig('timeseries', panel), width=width, height=height),
|
|
||||||
|
|
||||||
// stat shows the last value of expr. With text, it shows that label of the result instead of the value.
|
|
||||||
stat(
|
|
||||||
title,
|
|
||||||
expr,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
description='',
|
|
||||||
unit='',
|
|
||||||
thresholds=self.textSteps,
|
|
||||||
graph=false,
|
|
||||||
colorMode='value',
|
|
||||||
decimals=null,
|
|
||||||
min=null,
|
|
||||||
max=null,
|
|
||||||
text='',
|
|
||||||
mappings=[],
|
|
||||||
)::
|
|
||||||
local panel =
|
|
||||||
stat.standardOptions.color.withMode('thresholds')
|
|
||||||
+ (if mappings == [] then {} else stat.standardOptions.withMappings(mappings))
|
|
||||||
+ stat.standardOptions.thresholds.withMode(thresholds.mode)
|
|
||||||
+ stat.standardOptions.thresholds.withSteps(thresholds.steps)
|
|
||||||
+ stat.standardOptions.withUnit(unit)
|
|
||||||
+ optional(decimals, stat.standardOptions.withDecimals)
|
|
||||||
+ optional(min, stat.standardOptions.withMin)
|
|
||||||
+ optional(max, stat.standardOptions.withMax)
|
|
||||||
+ stat.standardOptions.withOverrides([])
|
|
||||||
+ stat.options.withColorMode(colorMode)
|
|
||||||
+ stat.options.withGraphMode(if graph then 'area' else 'none')
|
|
||||||
+ stat.options.withJustifyMode('auto')
|
|
||||||
+ stat.options.withOrientation('auto')
|
|
||||||
+ stat.options.withPercentChangeColorMode('standard')
|
|
||||||
+ stat.options.reduceOptions.withCalcs(['lastNotNull'])
|
|
||||||
+ stat.options.reduceOptions.withFields(if text == '' then '' else '/^' + text + '$/')
|
|
||||||
+ stat.options.reduceOptions.withValues(false)
|
|
||||||
+ stat.options.withShowPercentChange(false)
|
|
||||||
+ stat.options.withTextMode(if text == '' then 'auto' else 'value')
|
|
||||||
+ stat.options.withWideLayout(true);
|
|
||||||
// A label shown instead of the value needs the query result as a table.
|
|
||||||
local target = self.q(expr) { instant: !graph, table: text != '' };
|
|
||||||
element(title, description, [target], vizConfig('stat', panel), width=width, height=height),
|
|
||||||
|
|
||||||
// table joins instant queries into one table. The value columns are named "Value #A", "Value #B" and
|
|
||||||
// so on by query; rename, order and the overrides refer to those names. footer lists columns summed
|
|
||||||
// in the table footer.
|
|
||||||
table(
|
|
||||||
title,
|
|
||||||
targets,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
description='',
|
|
||||||
joinBy='',
|
|
||||||
exclude=[],
|
|
||||||
rename={},
|
|
||||||
order={},
|
|
||||||
overrides=[],
|
|
||||||
sortBy=[],
|
|
||||||
transformations=[],
|
|
||||||
footer=null,
|
|
||||||
)::
|
|
||||||
local excluded = { Time: true } + { [e]: true for e in exclude } + {
|
|
||||||
['Time ' + (i + 1)]: true
|
|
||||||
for i in std.range(0, std.length(targets) - 1)
|
|
||||||
};
|
|
||||||
local panel =
|
|
||||||
table.standardOptions.color.withMode('thresholds')
|
|
||||||
+ table.fieldConfig.defaults.custom.withAlign('auto')
|
|
||||||
+ table.fieldConfig.defaults.custom.withCellOptions({ type: 'auto' })
|
|
||||||
+ table.fieldConfig.defaults.custom.withFilterable(false)
|
|
||||||
+ table.fieldConfig.defaults.custom.withInspect(false)
|
|
||||||
+ table.fieldConfig.defaults.custom.withMinWidth(60)
|
|
||||||
+ table.standardOptions.thresholds.withMode(self.plainSteps.mode)
|
|
||||||
+ table.standardOptions.thresholds.withSteps(self.plainSteps.steps)
|
|
||||||
+ table.standardOptions.withOverrides(overrides)
|
|
||||||
+ table.options.withCellHeight('sm')
|
|
||||||
+ table.options.withFooter({
|
|
||||||
countRows: false,
|
|
||||||
fields: if footer == null then '' else footer,
|
|
||||||
reducer: ['sum'],
|
|
||||||
show: footer != null,
|
|
||||||
})
|
|
||||||
+ table.options.withShowHeader(true)
|
|
||||||
+ table.options.withSortBy(sortBy)
|
|
||||||
+ { options+: { enablePagination: false } };
|
|
||||||
element(
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
[t { instant: true, table: true } for t in targets],
|
|
||||||
vizConfig('table', panel),
|
|
||||||
width=width,
|
|
||||||
height=height,
|
|
||||||
transformations=(if joinBy == '' then [] else [{ id: 'joinByField', options: { byField: joinBy, mode: 'outer' } }])
|
|
||||||
+ transformations
|
|
||||||
+ [{
|
|
||||||
id: 'organize',
|
|
||||||
options: { excludeByName: excluded, includeByName: {}, indexByName: order, renameByName: rename },
|
|
||||||
}],
|
|
||||||
),
|
|
||||||
|
|
||||||
local timeline(title, description, target, defaults, width, height) =
|
|
||||||
local custom = stateTimeline.fieldConfig.defaults.custom;
|
|
||||||
local panel =
|
|
||||||
{ fieldConfig+: { defaults+: defaults } }
|
|
||||||
+ custom.withFillOpacity(90)
|
|
||||||
+ custom.hideFrom.withLegend(false)
|
|
||||||
+ custom.hideFrom.withTooltip(false)
|
|
||||||
+ custom.hideFrom.withViz(false)
|
|
||||||
+ custom.withInsertNulls(false)
|
|
||||||
+ custom.withLineWidth(0)
|
|
||||||
+ custom.withSpanNulls(false)
|
|
||||||
+ stateTimeline.standardOptions.withMin(0)
|
|
||||||
+ stateTimeline.standardOptions.withOverrides([])
|
|
||||||
+ stateTimeline.options.withAlignValue('left')
|
|
||||||
+ stateTimeline.options.legend.withDisplayMode('list')
|
|
||||||
+ stateTimeline.options.legend.withPlacement('bottom')
|
|
||||||
+ stateTimeline.options.legend.withShowLegend(false)
|
|
||||||
+ stateTimeline.options.withMergeValues(false)
|
|
||||||
+ stateTimeline.options.withRowHeight(1)
|
|
||||||
+ stateTimeline.options.withShowValue('never')
|
|
||||||
+ stateTimeline.options.tooltip.withHideZeros(false)
|
|
||||||
+ stateTimeline.options.tooltip.withMode('single')
|
|
||||||
+ stateTimeline.options.tooltip.withSort('none');
|
|
||||||
element(title, description, [target], vizConfig('state-timeline', panel), width=width, height=height),
|
|
||||||
|
|
||||||
// stateTimeline draws one row per series, colored from green to red by value. It is used for per-core
|
|
||||||
// CPU load, where a line per core becomes unreadable.
|
|
||||||
stateTimeline(title, description, unit, maxValue, target, width, height)::
|
|
||||||
timeline(title, description, target, {
|
|
||||||
color: { mode: 'continuous-GrYlRd' },
|
|
||||||
max: maxValue,
|
|
||||||
thresholds: $.plainSteps,
|
|
||||||
unit: unit,
|
|
||||||
}, width, height),
|
|
||||||
|
|
||||||
// statusTimeline draws a 0/1 metric per series as a red/green timeline.
|
|
||||||
statusTimeline(title, description, target, okText, failText, width, height)::
|
|
||||||
timeline(title, description, target, {
|
|
||||||
color: { mode: 'thresholds' },
|
|
||||||
mappings: $.valueMapping({ color: 'red', text: failText }, { color: 'green', text: okText }),
|
|
||||||
max: 1,
|
|
||||||
thresholds: steps([['red', null], ['green', 1]]),
|
|
||||||
unit: 'none',
|
|
||||||
}, width, height),
|
|
||||||
|
|
||||||
// variable is a Prometheus query variable.
|
|
||||||
variable(name, label, query, multi=false, includeAll=false, hide=false, regex=''):: {
|
|
||||||
kind: 'QueryVariable',
|
|
||||||
spec: {
|
|
||||||
allowCustomValue: true,
|
|
||||||
current: if includeAll then { text: ['All'], value: ['$__all'] } else { text: '', value: '' },
|
|
||||||
definition: query,
|
|
||||||
hide: if hide then 'hideVariable' else 'dontHide',
|
|
||||||
includeAll: includeAll,
|
|
||||||
[if label != '' then 'label']: label,
|
|
||||||
multi: multi,
|
|
||||||
name: name,
|
|
||||||
options: [],
|
|
||||||
query: {
|
|
||||||
datasource: datasource,
|
|
||||||
group: 'prometheus',
|
|
||||||
kind: 'DataQuery',
|
|
||||||
spec: { qryType: 1, query: query, refId: 'PrometheusVariableQueryEditor-VariableQuery' },
|
|
||||||
version: 'v0',
|
|
||||||
},
|
|
||||||
refresh: 'onTimeRangeChanged',
|
|
||||||
regex: regex,
|
|
||||||
skipUrlSync: false,
|
|
||||||
sort: 'alphabeticalAsc',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// row is a row of a tab. bands is a list of panel lists; the panels of a band sit side by side
|
|
||||||
// and the bands are stacked.
|
|
||||||
row(title, bands):: { title: title, bands: bands },
|
|
||||||
|
|
||||||
// tab is a tab of the dashboard. A tab is either one grid of bands, or a list of rows.
|
|
||||||
tab(title, bands=[], rows=[]):: { title: title, rows: if rows == [] then [$.row('', bands)] else rows },
|
|
||||||
|
|
||||||
// dashboard builds the v2 dashboard from the tabs. Panels are numbered in order, and every row
|
|
||||||
// takes one number before its panels, as in the classic dashboard the layout was converted from.
|
|
||||||
// The element names (panel-<number>) are taken from these numbers.
|
|
||||||
dashboard(name, title, tabs, variables=[], annotations=[], spec={})::
|
|
||||||
local v2 = g.apps.dashboard.v2;
|
|
||||||
local rows = [
|
|
||||||
tabs[t].rows[r] { tab:: t }
|
|
||||||
for t in std.range(0, std.length(tabs) - 1)
|
|
||||||
for r in std.range(0, std.length(tabs[t].rows) - 1)
|
|
||||||
];
|
|
||||||
local panelCount(row) = std.length(std.flattenArrays(row.bands));
|
|
||||||
// The row itself takes the number before its first panel.
|
|
||||||
local firstID = [
|
|
||||||
std.foldl(function(sum, r) sum + 1 + panelCount(r), rows[0:i], 0) + 2
|
|
||||||
for i in std.range(0, std.length(rows) - 1)
|
|
||||||
];
|
|
||||||
local numbered = [
|
|
||||||
local items = gridItems(rows[i].bands);
|
|
||||||
rows[i] {
|
|
||||||
items:: [
|
|
||||||
items[j] {
|
|
||||||
id:: firstID[i] + j,
|
|
||||||
spec+: { element: { kind: 'ElementReference', name: 'panel-' + (firstID[i] + j) } },
|
|
||||||
}
|
|
||||||
for j in std.range(0, std.length(items) - 1)
|
|
||||||
],
|
|
||||||
}
|
|
||||||
for i in std.range(0, std.length(rows) - 1)
|
|
||||||
];
|
|
||||||
local grid(row) = { kind: 'GridLayout', spec: { items: row.items } };
|
|
||||||
local tabRows(t) = [r for r in numbered if r.tab == t];
|
|
||||||
v2.new(name, title)
|
|
||||||
+ v2.spec.withAnnotations(annotations)
|
|
||||||
+ v2.spec.withElements({
|
|
||||||
[item.spec.element.name]: item.panel { spec+: { id: item.id } }
|
|
||||||
for row in numbered
|
|
||||||
for item in row.items
|
|
||||||
})
|
|
||||||
+ v2.spec.withLayout({
|
|
||||||
kind: 'TabsLayout',
|
|
||||||
spec: {
|
|
||||||
tabs: [
|
|
||||||
local tab = tabs[t];
|
|
||||||
local r = tabRows(t);
|
|
||||||
{
|
|
||||||
kind: 'TabsLayoutTab',
|
|
||||||
spec: {
|
|
||||||
layout:
|
|
||||||
if std.length(r) == 1 && r[0].title == '' then grid(r[0])
|
|
||||||
else {
|
|
||||||
kind: 'RowsLayout',
|
|
||||||
spec: {
|
|
||||||
rows: [
|
|
||||||
{ kind: 'RowsLayoutRow', spec: { collapse: false, layout: grid(x), title: x.title } }
|
|
||||||
for x in r
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
title: tab.title,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for t in std.range(0, std.length(tabs) - 1)
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
+ v2.spec.withVariables(variables)
|
|
||||||
+ { spec+: spec },
|
|
||||||
}
|
|
||||||
+4
-2
@@ -48,8 +48,9 @@ Click a hostname in the Fleet tab to open that host in the Overview tab.
|
|||||||
|
|
||||||
Don't edit the JSON file by hand. It is generated from Jsonnet with [grafonnet](https://github.com/grafana/grafonnet) as part of the [windows_exporter mixin](../contrib/mixin):
|
Don't edit the JSON file by hand. It is generated from Jsonnet with [grafonnet](https://github.com/grafana/grafonnet) as part of the [windows_exporter mixin](../contrib/mixin):
|
||||||
|
|
||||||
- [dashboards/windows-exporter.libsonnet](../contrib/mixin/dashboards/windows-exporter.libsonnet) defines the tabs, panels and queries.
|
- [dashboards/windows-exporter.libsonnet](../contrib/mixin/dashboards/windows-exporter.libsonnet) composes the dashboard from one file per tab.
|
||||||
- [lib/dashboard.libsonnet](../contrib/mixin/lib/dashboard.libsonnet) holds the panel and layout helpers.
|
- [dashboards/builders.libsonnet](../contrib/mixin/dashboards/builders.libsonnet) holds the panel, query, and layout builders.
|
||||||
|
- [dashboards/styles.libsonnet](../contrib/mixin/dashboards/styles.libsonnet) shares the visualization defaults.
|
||||||
|
|
||||||
The generator writes the v2 schema directly, so it needs neither Grafana nor Prometheus.
|
The generator writes the v2 schema directly, so it needs neither Grafana nor Prometheus.
|
||||||
Install the pinned tools and regenerate the file in `contrib/mixin`:
|
Install the pinned tools and regenerate the file in `contrib/mixin`:
|
||||||
@@ -63,6 +64,7 @@ make dashboard
|
|||||||
|
|
||||||
`make dashboard` runs `jb install` to fetch grafonnet into `contrib/mixin/vendor` and renders the dashboard.
|
`make dashboard` runs `jb install` to fetch grafonnet into `contrib/mixin/vendor` and renders the dashboard.
|
||||||
`make lint` fails if the committed JSON file differs from the generated one.
|
`make lint` fails if the committed JSON file differs from the generated one.
|
||||||
|
`make check-dashboards` rebuilds it and runs `git diff --exit-code`, as CI does before the mixin tests.
|
||||||
Check new or changed queries in Grafana before you open a pull request, for example by importing the file into a local Grafana 13.
|
Check new or changed queries in Grafana before you open a pull request, for example by importing the file into a local Grafana 13.
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
Reference in New Issue
Block a user