Files
windows_exporter/contrib/mixin/dashboards/exporter.libsonnet
T
2026-10-09 12:00:27 +02:00

668 lines
20 KiB
Jsonnet

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('Number of collectors that failed on the latest scrape, including timeouts. The graph shows the count over the selected time range.')
+ b.panel.withQueries([
b.query.new('count(windows_exporter_collector_success{job=~"$job", instance="$instance"} == 0) or vector(0)', 'A'),
])
+ b.panel.withDefaults({
decimals: 0,
min: 0,
thresholds: {
steps: [
{
color: 'green',
value: null,
},
{
color: 'red',
value: 1,
},
],
},
})
+ b.panel.withOptions({
graphMode: 'area',
}),
b.panel.new(94, 'Timed-out collectors', 'stat')
+ b.panel.withDescription('Number of collectors that timed out on the latest scrape. The graph shows the count over the selected time range.')
+ b.panel.withQueries([
b.query.new('count(windows_exporter_collector_timeout{job=~"$job", instance="$instance"} == 1) or vector(0)', 'A'),
])
+ b.panel.withDefaults({
decimals: 0,
min: 0,
thresholds: {
steps: [
{
color: 'green',
value: null,
},
{
color: 'red',
value: 1,
},
],
},
})
+ b.panel.withOptions({
graphMode: 'area',
}),
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',
},
}),
]
)