mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-17 01:59:04 +02:00
update: include optional updates such as the monthly preview update (#2688)
Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
be5036dfa8
commit
e88c8a712c
@@ -9,24 +9,25 @@ function(on)
|
||||
b.tab(
|
||||
'Updates',
|
||||
b.rows([
|
||||
if on('update') then b.summary([210, 211, 212, 214, 215]),
|
||||
if on('update') then b.summary([210, 211, 212, 213, 214, 215]),
|
||||
if on('update') then b.row('Pending updates', b.flow([[216, 24, 10]])),
|
||||
if on('update') then b.row('History', b.flow([[217, 12, 8], [218, 12, 8]])),
|
||||
]),
|
||||
[
|
||||
b.stat(210, 'Pending', 'Updates that are available but not installed.' + requirement, pending('')),
|
||||
b.stat(210, 'Pending', 'Updates that are available but not installed, including optional updates.' + requirement, pending('')),
|
||||
b.stat(211, 'Critical', 'Pending updates with the Microsoft security severity Critical.' + requirement, pending('severity="Critical"'), steps=[{ color: 'green', value: null }, { color: 'red', value: 1 }]),
|
||||
b.stat(212, 'Important', 'Pending updates with the Microsoft security severity Important.' + requirement, pending('severity="Important"'), steps=[{ color: 'green', value: null }, { color: 'orange', value: 1 }]),
|
||||
b.stat(214, 'Oldest pending', 'Time since the oldest pending update was published. Orange after 30 days, red after 60 days.' + requirement, 'time() - min(' + metric('pending_published_timestamp') + ')', 'dtdurations', steps=[{ color: 'green', value: null }, { color: 'orange', value: days(30) }, { color: 'red', value: days(60) }])
|
||||
b.stat(213, 'Optional', 'Pending optional updates, such as the monthly non-security preview update. Windows does not install them automatically.' + requirement, pending('deployment_action="optional_installation"')),
|
||||
b.stat(214, 'Oldest pending', 'Time since the oldest pending regular update was published. Optional updates are left out. Orange after 30 days, red after 60 days.' + requirement, 'time() - min(' + metric('pending_published_timestamp') + ' and on (id, revision) ' + metric('pending_info', 'deployment_action="installation"') + ')', 'dtdurations', steps=[{ color: 'green', value: null }, { color: 'orange', value: days(30) }, { color: 'red', value: days(60) }])
|
||||
+ b.panel.withDefaults({ noValue: 'none' })
|
||||
+ b.panel.withOptions({ reduceOptions: { calcs: ['last'] } }),
|
||||
b.stat(215, 'Last check', 'Time since the collector last queried Windows Update. Orange after 1 day, red after 7 days.' + requirement, 'time() - ' + metric('scrape_timestamp_seconds'), 'dtdurations', steps=[{ color: 'green', value: null }, { color: 'orange', value: days(1) }, { color: 'red', value: days(7) }])
|
||||
+ b.panel.withOptions({ reduceOptions: { calcs: ['last'] } }),
|
||||
|
||||
b.panel.new(216, 'Pending updates', 'table')
|
||||
+ b.panel.withDescription('Pending updates with their category, security severity and publish date. Category names follow the Windows display language.' + requirement)
|
||||
+ b.panel.withDescription('Pending updates with their category, security severity, type and publish date. Optional updates, such as the monthly non-security preview update, are not installed automatically. Category names follow the Windows display language.' + requirement)
|
||||
+ b.panel.withQueries([
|
||||
b.query.new('max by (title, category, severity) (' + '(' + metric('pending_published_timestamp') + ' * on (id, revision) group_right ' + metric('pending_info') + ') or on (id, revision) (0 * ' + metric('pending_info') + ')) * 1000', 'A')
|
||||
b.query.new('max by (title, category, severity, deployment_action) (' + '(' + metric('pending_published_timestamp') + ' * on (id, revision) group_right ' + metric('pending_info') + ') or on (id, revision) (0 * ' + metric('pending_info') + ')) * 1000', 'A')
|
||||
+ b.query.withInstant()
|
||||
+ b.query.withFormat('table'),
|
||||
])
|
||||
@@ -37,8 +38,8 @@ function(on)
|
||||
options: {
|
||||
excludeByName: { Time: true },
|
||||
includeByName: {},
|
||||
indexByName: { title: 0, category: 1, severity: 2, Value: 3 },
|
||||
renameByName: { title: 'Update', category: 'Category', severity: 'Severity', Value: 'Published' },
|
||||
indexByName: { title: 0, category: 1, severity: 2, deployment_action: 3, Value: 4 },
|
||||
renameByName: { title: 'Update', category: 'Category', severity: 'Severity', deployment_action: 'Type', Value: 'Published' },
|
||||
},
|
||||
},
|
||||
}])
|
||||
@@ -51,6 +52,11 @@ function(on)
|
||||
{ id: 'mappings', value: [{ type: 'value', options: { Critical: { color: 'red', index: 0 }, Important: { color: 'orange', index: 1 } } }] },
|
||||
{ id: 'custom.cellOptions', value: { type: 'color-text' } },
|
||||
],
|
||||
}, {
|
||||
matcher: { id: 'byName', options: 'Type' },
|
||||
properties: [
|
||||
{ id: 'mappings', value: [{ type: 'value', options: { installation: { index: 0, text: 'Regular' }, optional_installation: { index: 1, text: 'Optional' } } }] },
|
||||
],
|
||||
}]),
|
||||
|
||||
b.graph(217, 'Pending updates by category', 'Number of pending updates per update category.' + requirement, 'short', [
|
||||
|
||||
@@ -12728,7 +12728,7 @@
|
||||
"transformations": []
|
||||
}
|
||||
},
|
||||
"description": "Updates that are available but not installed. Requires the update collector, which is not enabled by default. The collector queries Windows Update in the background, every 6 hours by default.",
|
||||
"description": "Updates that are available but not installed, including optional updates. Requires the update collector, which is not enabled by default. The collector queries Windows Update in the background, every 6 hours by default.",
|
||||
"id": 210,
|
||||
"links": [],
|
||||
"title": "Pending",
|
||||
@@ -12953,6 +12953,91 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"panel-213": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"data": {
|
||||
"kind": "QueryGroup",
|
||||
"spec": {
|
||||
"queries": [
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"hidden": false,
|
||||
"query": {
|
||||
"datasource": {
|
||||
"name": "${datasource}"
|
||||
},
|
||||
"group": "prometheus",
|
||||
"kind": "DataQuery",
|
||||
"spec": {
|
||||
"editorMode": "code",
|
||||
"expr": "count(windows_update_pending_info{job=~\"$job\", instance=\"$instance\", deployment_action=\"optional_installation\"}) or (0 * windows_update_scrape_timestamp_seconds{job=~\"$job\", instance=\"$instance\"})",
|
||||
"instant": false,
|
||||
"legendFormat": "__auto",
|
||||
"range": true
|
||||
},
|
||||
"version": "v0"
|
||||
},
|
||||
"refId": "A"
|
||||
}
|
||||
}
|
||||
],
|
||||
"queryOptions": {
|
||||
"maxDataPoints": 100
|
||||
},
|
||||
"transformations": []
|
||||
}
|
||||
},
|
||||
"description": "Pending optional updates, such as the monthly non-security preview update. Windows does not install them automatically. Requires the update collector, which is not enabled by default. The collector queries Windows Update in the background, every 6 hours by default.",
|
||||
"id": 213,
|
||||
"links": [],
|
||||
"title": "Optional",
|
||||
"vizConfig": {
|
||||
"group": "stat",
|
||||
"kind": "VizConfig",
|
||||
"spec": {
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"fixedColor": "text",
|
||||
"mode": "fixed"
|
||||
},
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "text",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "short"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
}
|
||||
},
|
||||
"version": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"panel-214": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
@@ -12972,7 +13057,7 @@
|
||||
"kind": "DataQuery",
|
||||
"spec": {
|
||||
"editorMode": "code",
|
||||
"expr": "time() - min(windows_update_pending_published_timestamp{job=~\"$job\", instance=\"$instance\"})",
|
||||
"expr": "time() - min(windows_update_pending_published_timestamp{job=~\"$job\", instance=\"$instance\"} and on (id, revision) windows_update_pending_info{job=~\"$job\", instance=\"$instance\", deployment_action=\"installation\"})",
|
||||
"instant": false,
|
||||
"legendFormat": "__auto",
|
||||
"range": true
|
||||
@@ -12989,7 +13074,7 @@
|
||||
"transformations": []
|
||||
}
|
||||
},
|
||||
"description": "Time since the oldest pending update was published. Orange after 30 days, red after 60 days. Requires the update collector, which is not enabled by default. The collector queries Windows Update in the background, every 6 hours by default.",
|
||||
"description": "Time since the oldest pending regular update was published. Optional updates are left out. Orange after 30 days, red after 60 days. Requires the update collector, which is not enabled by default. The collector queries Windows Update in the background, every 6 hours by default.",
|
||||
"id": 214,
|
||||
"links": [],
|
||||
"title": "Oldest pending",
|
||||
@@ -13157,7 +13242,7 @@
|
||||
"kind": "DataQuery",
|
||||
"spec": {
|
||||
"editorMode": "code",
|
||||
"expr": "max by (title, category, severity) ((windows_update_pending_published_timestamp{job=~\"$job\", instance=\"$instance\"} * on (id, revision) group_right windows_update_pending_info{job=~\"$job\", instance=\"$instance\"}) or on (id, revision) (0 * windows_update_pending_info{job=~\"$job\", instance=\"$instance\"})) * 1000",
|
||||
"expr": "max by (title, category, severity, deployment_action) ((windows_update_pending_published_timestamp{job=~\"$job\", instance=\"$instance\"} * on (id, revision) group_right windows_update_pending_info{job=~\"$job\", instance=\"$instance\"}) or on (id, revision) (0 * windows_update_pending_info{job=~\"$job\", instance=\"$instance\"})) * 1000",
|
||||
"format": "table",
|
||||
"instant": true,
|
||||
"legendFormat": "__auto",
|
||||
@@ -13181,14 +13266,16 @@
|
||||
},
|
||||
"includeByName": {},
|
||||
"indexByName": {
|
||||
"Value": 3,
|
||||
"Value": 4,
|
||||
"category": 1,
|
||||
"deployment_action": 3,
|
||||
"severity": 2,
|
||||
"title": 0
|
||||
},
|
||||
"renameByName": {
|
||||
"Value": "Published",
|
||||
"category": "Category",
|
||||
"deployment_action": "Type",
|
||||
"severity": "Severity",
|
||||
"title": "Update"
|
||||
}
|
||||
@@ -13198,7 +13285,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"description": "Pending updates with their category, security severity and publish date. Category names follow the Windows display language. Requires the update collector, which is not enabled by default. The collector queries Windows Update in the background, every 6 hours by default.",
|
||||
"description": "Pending updates with their category, security severity, type and publish date. Optional updates, such as the monthly non-security preview update, are not installed automatically. Category names follow the Windows display language. Requires the update collector, which is not enabled by default. The collector queries Windows Update in the background, every 6 hours by default.",
|
||||
"id": 216,
|
||||
"links": [],
|
||||
"title": "Pending updates",
|
||||
@@ -13281,6 +13368,32 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Type"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "mappings",
|
||||
"value": [
|
||||
{
|
||||
"options": {
|
||||
"installation": {
|
||||
"index": 0,
|
||||
"text": "Regular"
|
||||
},
|
||||
"optional_installation": {
|
||||
"index": 1,
|
||||
"text": "Optional"
|
||||
}
|
||||
},
|
||||
"type": "value"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -29025,7 +29138,7 @@
|
||||
"name": "panel-210"
|
||||
},
|
||||
"height": 4,
|
||||
"width": 5,
|
||||
"width": 4,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
@@ -29038,8 +29151,8 @@
|
||||
"name": "panel-211"
|
||||
},
|
||||
"height": 4,
|
||||
"width": 5,
|
||||
"x": 5,
|
||||
"width": 4,
|
||||
"x": 4,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
@@ -29051,8 +29164,21 @@
|
||||
"name": "panel-212"
|
||||
},
|
||||
"height": 4,
|
||||
"width": 5,
|
||||
"x": 10,
|
||||
"width": 4,
|
||||
"x": 8,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "GridLayoutItem",
|
||||
"spec": {
|
||||
"element": {
|
||||
"kind": "ElementReference",
|
||||
"name": "panel-213"
|
||||
},
|
||||
"height": 4,
|
||||
"width": 4,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
@@ -29064,8 +29190,8 @@
|
||||
"name": "panel-214"
|
||||
},
|
||||
"height": 4,
|
||||
"width": 5,
|
||||
"x": 15,
|
||||
"width": 4,
|
||||
"x": 16,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
"transformations": []
|
||||
}
|
||||
},
|
||||
"description": "Updates that are available but not installed. Requires the update collector, which is not enabled by default. The collector queries Windows Update in the background, every 6 hours by default.",
|
||||
"description": "Updates that are available but not installed, including optional updates. Requires the update collector, which is not enabled by default. The collector queries Windows Update in the background, every 6 hours by default.",
|
||||
"id": 210,
|
||||
"links": [],
|
||||
"title": "Pending",
|
||||
@@ -320,6 +320,91 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"panel-213": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"data": {
|
||||
"kind": "QueryGroup",
|
||||
"spec": {
|
||||
"queries": [
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"hidden": false,
|
||||
"query": {
|
||||
"datasource": {
|
||||
"name": "${datasource}"
|
||||
},
|
||||
"group": "prometheus",
|
||||
"kind": "DataQuery",
|
||||
"spec": {
|
||||
"editorMode": "code",
|
||||
"expr": "count(windows_update_pending_info{job=~\"$job\", instance=\"$instance\", deployment_action=\"optional_installation\"}) or (0 * windows_update_scrape_timestamp_seconds{job=~\"$job\", instance=\"$instance\"})",
|
||||
"instant": false,
|
||||
"legendFormat": "__auto",
|
||||
"range": true
|
||||
},
|
||||
"version": "v0"
|
||||
},
|
||||
"refId": "A"
|
||||
}
|
||||
}
|
||||
],
|
||||
"queryOptions": {
|
||||
"maxDataPoints": 100
|
||||
},
|
||||
"transformations": []
|
||||
}
|
||||
},
|
||||
"description": "Pending optional updates, such as the monthly non-security preview update. Windows does not install them automatically. Requires the update collector, which is not enabled by default. The collector queries Windows Update in the background, every 6 hours by default.",
|
||||
"id": 213,
|
||||
"links": [],
|
||||
"title": "Optional",
|
||||
"vizConfig": {
|
||||
"group": "stat",
|
||||
"kind": "VizConfig",
|
||||
"spec": {
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"fixedColor": "text",
|
||||
"mode": "fixed"
|
||||
},
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "text",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "short"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
}
|
||||
},
|
||||
"version": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"panel-214": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
@@ -339,7 +424,7 @@
|
||||
"kind": "DataQuery",
|
||||
"spec": {
|
||||
"editorMode": "code",
|
||||
"expr": "time() - min(windows_update_pending_published_timestamp{job=~\"$job\", instance=\"$instance\"})",
|
||||
"expr": "time() - min(windows_update_pending_published_timestamp{job=~\"$job\", instance=\"$instance\"} and on (id, revision) windows_update_pending_info{job=~\"$job\", instance=\"$instance\", deployment_action=\"installation\"})",
|
||||
"instant": false,
|
||||
"legendFormat": "__auto",
|
||||
"range": true
|
||||
@@ -356,7 +441,7 @@
|
||||
"transformations": []
|
||||
}
|
||||
},
|
||||
"description": "Time since the oldest pending update was published. Orange after 30 days, red after 60 days. Requires the update collector, which is not enabled by default. The collector queries Windows Update in the background, every 6 hours by default.",
|
||||
"description": "Time since the oldest pending regular update was published. Optional updates are left out. Orange after 30 days, red after 60 days. Requires the update collector, which is not enabled by default. The collector queries Windows Update in the background, every 6 hours by default.",
|
||||
"id": 214,
|
||||
"links": [],
|
||||
"title": "Oldest pending",
|
||||
@@ -524,7 +609,7 @@
|
||||
"kind": "DataQuery",
|
||||
"spec": {
|
||||
"editorMode": "code",
|
||||
"expr": "max by (title, category, severity) ((windows_update_pending_published_timestamp{job=~\"$job\", instance=\"$instance\"} * on (id, revision) group_right windows_update_pending_info{job=~\"$job\", instance=\"$instance\"}) or on (id, revision) (0 * windows_update_pending_info{job=~\"$job\", instance=\"$instance\"})) * 1000",
|
||||
"expr": "max by (title, category, severity, deployment_action) ((windows_update_pending_published_timestamp{job=~\"$job\", instance=\"$instance\"} * on (id, revision) group_right windows_update_pending_info{job=~\"$job\", instance=\"$instance\"}) or on (id, revision) (0 * windows_update_pending_info{job=~\"$job\", instance=\"$instance\"})) * 1000",
|
||||
"format": "table",
|
||||
"instant": true,
|
||||
"legendFormat": "__auto",
|
||||
@@ -548,14 +633,16 @@
|
||||
},
|
||||
"includeByName": {},
|
||||
"indexByName": {
|
||||
"Value": 3,
|
||||
"Value": 4,
|
||||
"category": 1,
|
||||
"deployment_action": 3,
|
||||
"severity": 2,
|
||||
"title": 0
|
||||
},
|
||||
"renameByName": {
|
||||
"Value": "Published",
|
||||
"category": "Category",
|
||||
"deployment_action": "Type",
|
||||
"severity": "Severity",
|
||||
"title": "Update"
|
||||
}
|
||||
@@ -565,7 +652,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"description": "Pending updates with their category, security severity and publish date. Category names follow the Windows display language. Requires the update collector, which is not enabled by default. The collector queries Windows Update in the background, every 6 hours by default.",
|
||||
"description": "Pending updates with their category, security severity, type and publish date. Optional updates, such as the monthly non-security preview update, are not installed automatically. Category names follow the Windows display language. Requires the update collector, which is not enabled by default. The collector queries Windows Update in the background, every 6 hours by default.",
|
||||
"id": 216,
|
||||
"links": [],
|
||||
"title": "Pending updates",
|
||||
@@ -648,6 +735,32 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Type"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "mappings",
|
||||
"value": [
|
||||
{
|
||||
"options": {
|
||||
"installation": {
|
||||
"index": 0,
|
||||
"text": "Regular"
|
||||
},
|
||||
"optional_installation": {
|
||||
"index": 1,
|
||||
"text": "Optional"
|
||||
}
|
||||
},
|
||||
"type": "value"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -922,7 +1035,7 @@
|
||||
"name": "panel-210"
|
||||
},
|
||||
"height": 4,
|
||||
"width": 5,
|
||||
"width": 4,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
@@ -935,8 +1048,8 @@
|
||||
"name": "panel-211"
|
||||
},
|
||||
"height": 4,
|
||||
"width": 5,
|
||||
"x": 5,
|
||||
"width": 4,
|
||||
"x": 4,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
@@ -948,8 +1061,21 @@
|
||||
"name": "panel-212"
|
||||
},
|
||||
"height": 4,
|
||||
"width": 5,
|
||||
"x": 10,
|
||||
"width": 4,
|
||||
"x": 8,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "GridLayoutItem",
|
||||
"spec": {
|
||||
"element": {
|
||||
"kind": "ElementReference",
|
||||
"name": "panel-213"
|
||||
},
|
||||
"height": 4,
|
||||
"width": 4,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
@@ -961,8 +1087,8 @@
|
||||
"name": "panel-214"
|
||||
},
|
||||
"height": 4,
|
||||
"width": 5,
|
||||
"x": 15,
|
||||
"width": 4,
|
||||
"x": 16,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
|
||||
@@ -27,23 +27,27 @@ Default: `6h0m0s`
|
||||
|
||||
## Metrics
|
||||
|
||||
| Name | Description | Type | Labels |
|
||||
|------------------------------------------------|------------------------------------------------------------------|-------|-----------------------------------------------|
|
||||
| `windows_update_pending_info` | Expose information for a single pending update item | gauge | `id`,`revision`,`category`,`severity`,`title` |
|
||||
| `windows_update_pending_published_timestamp` | Expose last published timestamp for a single pending update item | gauge | `id`,`revision` |
|
||||
| `windows_update_scrape_query_duration_seconds` | Duration of the last scrape query to the Windows Update API | gauge | |
|
||||
| `windows_update_scrape_timestamp_seconds` | Timestamp of the last scrape | gauge | |
|
||||
Pending updates include optional updates, such as the monthly non-security preview update. The `deployment_action` label is `installation` for regular updates and `optional_installation` for optional ones. Filter with `deployment_action="installation"` to ignore optional updates.
|
||||
|
||||
| Name | Description | Type | Labels |
|
||||
|------------------------------------------------|------------------------------------------------------------------|-------|-------------------------------------------------------------------|
|
||||
| `windows_update_pending_info` | Expose information for a single pending update item | gauge | `id`,`revision`,`category`,`severity`,`title`,`deployment_action` |
|
||||
| `windows_update_pending_published_timestamp` | Expose last published timestamp for a single pending update item | gauge | `id`,`revision` |
|
||||
| `windows_update_scrape_query_duration_seconds` | Duration of the last scrape query to the Windows Update API | gauge | |
|
||||
| `windows_update_scrape_timestamp_seconds` | Timestamp of the last scrape | gauge | |
|
||||
|
||||
### Example metrics
|
||||
```
|
||||
# HELP windows_update_pending_info Expose information for a single pending update item
|
||||
# TYPE windows_update_pending_info gauge
|
||||
windows_update_pending_info{category="Definition Updates",id="a32ca1d0-ddd4-486b-b708-d941db4f1051",revision="204",severity="",title="Update for Windows Security platform - KB5007651 (Version 10.0.27840.1000)"} 1
|
||||
windows_update_pending_info{category="Definition Updates",id="b50a64de-a0bb-465b-9842-9963b6eee21e",revision="200",severity="",title="Security Intelligence Update for Microsoft Defender Antivirus - KB2267602 (Version 1.429.146.0) - Current Channel (Broad)"} 1
|
||||
windows_update_pending_info{category="Definition Updates",deployment_action="installation",id="a32ca1d0-ddd4-486b-b708-d941db4f1051",revision="204",severity="",title="Update for Windows Security platform - KB5007651 (Version 10.0.27840.1000)"} 1
|
||||
windows_update_pending_info{category="Definition Updates",deployment_action="installation",id="b50a64de-a0bb-465b-9842-9963b6eee21e",revision="200",severity="",title="Security Intelligence Update for Microsoft Defender Antivirus - KB2267602 (Version 1.429.146.0) - Current Channel (Broad)"} 1
|
||||
windows_update_pending_info{category="Updates",deployment_action="optional_installation",id="4c5a2079-dd6c-4b78-a20b-07b2d5ea2217",revision="1",severity="",title="2026-09 Preview Update (KB5124010) (26300.9550)"} 1
|
||||
# HELP windows_update_pending_published_timestamp Expose last published timestamp for a single pending update item
|
||||
# TYPE windows_update_pending_published_timestamp gauge
|
||||
windows_update_pending_published_timestamp{id="a32ca1d0-ddd4-486b-b708-d941db4f1051",revision="204"} 1.747872e+09
|
||||
windows_update_pending_published_timestamp{id="b50a64de-a0bb-465b-9842-9963b6eee21e",revision="200"} 1.7479584e+09
|
||||
windows_update_pending_published_timestamp{id="4c5a2079-dd6c-4b78-a20b-07b2d5ea2217",revision="1"} 1.7900352e+09
|
||||
# HELP windows_update_scrape_query_duration_seconds Duration of the last scrape query to the Windows Update API
|
||||
# TYPE windows_update_scrape_query_duration_seconds gauge
|
||||
windows_update_scrape_query_duration_seconds 2.8161838
|
||||
@@ -60,5 +64,11 @@ Add extended information like cmdline or owner to other process metrics.
|
||||
windows_update_pending_published_timestamp * on(id, revision) group_left(severity, title) windows_update_pending_info
|
||||
```
|
||||
|
||||
Count pending updates without optional updates, such as the monthly non-security preview update.
|
||||
|
||||
```
|
||||
count(windows_update_pending_info{deployment_action="installation"})
|
||||
```
|
||||
|
||||
## Alerting examples
|
||||
_This collector does not yet have alerting examples, we would appreciate your help adding them!_
|
||||
|
||||
@@ -50,6 +50,13 @@ var ConfigDefaults = Config{
|
||||
ScrapeInterval: 6 * time.Hour,
|
||||
}
|
||||
|
||||
// searchCriteria finds pending updates of both deployment actions. Criteria
|
||||
// without a DeploymentAction clause imply DeploymentAction='Installation' and
|
||||
// miss optional updates, such as the monthly non-security preview update.
|
||||
// OR is only allowed at the top level of a WUA criteria string.
|
||||
const searchCriteria = "IsInstalled=0 and IsHidden=0 and DeploymentAction='Installation'" +
|
||||
" or IsInstalled=0 and IsHidden=0 and DeploymentAction='OptionalInstallation'"
|
||||
|
||||
var (
|
||||
ErrNoUpdates = errors.New("pending gather update metrics")
|
||||
ErrUpdateServiceDisabled = errors.New("windows updates service is disabled")
|
||||
@@ -118,7 +125,7 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
||||
c.pendingUpdate = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "pending_info"),
|
||||
"Expose information for a single pending update item",
|
||||
[]string{"id", "revision", "category", "severity", "title"},
|
||||
[]string{"id", "revision", "category", "severity", "title", "deployment_action"},
|
||||
nil,
|
||||
)
|
||||
|
||||
@@ -289,7 +296,7 @@ func (c *Collector) fetchUpdates(logger *slog.Logger, searcher *wuapi.UpdateSear
|
||||
|
||||
timeStart := time.Now()
|
||||
|
||||
result, err := searcher.Search("IsInstalled=0 and IsHidden=0")
|
||||
result, err := searcher.Search(searchCriteria)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("search for updates: %w", err)
|
||||
}
|
||||
@@ -310,6 +317,10 @@ func (c *Collector) fetchUpdates(logger *slog.Logger, searcher *wuapi.UpdateSear
|
||||
}
|
||||
defer updates.Release()
|
||||
|
||||
// Both criteria clauses are evaluated separately, so an update could be
|
||||
// returned twice. Its publication metric must be emitted only once.
|
||||
seen := make(map[string]struct{})
|
||||
|
||||
for item, err := range updates.All() {
|
||||
if err != nil {
|
||||
if _, ok := errors.AsType[*ole.CollectionItemError](err); !ok {
|
||||
@@ -330,6 +341,13 @@ func (c *Collector) fetchUpdates(logger *slog.Logger, searcher *wuapi.UpdateSear
|
||||
continue
|
||||
}
|
||||
|
||||
key := update.identity + "/" + update.revision
|
||||
if _, ok := seen[key]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
seen[key] = struct{}{}
|
||||
|
||||
metricsBuf = append(metricsBuf, prometheus.MustNewConstMetric(
|
||||
c.pendingUpdate,
|
||||
prometheus.GaugeValue,
|
||||
@@ -339,6 +357,7 @@ func (c *Collector) fetchUpdates(logger *slog.Logger, searcher *wuapi.UpdateSear
|
||||
update.category,
|
||||
update.severity,
|
||||
update.title,
|
||||
update.deploymentAction,
|
||||
))
|
||||
|
||||
if update.lastPublished != (time.Time{}) {
|
||||
@@ -362,12 +381,13 @@ func (c *Collector) fetchUpdates(logger *slog.Logger, searcher *wuapi.UpdateSear
|
||||
}
|
||||
|
||||
type windowsUpdate struct {
|
||||
identity string
|
||||
revision string
|
||||
category string
|
||||
severity string
|
||||
title string
|
||||
lastPublished time.Time
|
||||
identity string
|
||||
revision string
|
||||
category string
|
||||
severity string
|
||||
title string
|
||||
deploymentAction string
|
||||
lastPublished time.Time
|
||||
}
|
||||
|
||||
// getUpdateStatus retrieves the update status of the given item.
|
||||
@@ -389,6 +409,11 @@ func (c *Collector) getUpdateStatus(item *wuapi.Update) (windowsUpdate, error) {
|
||||
return windowsUpdate{}, fmt.Errorf("get Title: %w", err)
|
||||
}
|
||||
|
||||
deploymentAction, err := item.DeploymentAction()
|
||||
if err != nil {
|
||||
return windowsUpdate{}, fmt.Errorf("get DeploymentAction: %w", err)
|
||||
}
|
||||
|
||||
identity, err := item.Identity()
|
||||
if err != nil {
|
||||
return windowsUpdate{}, fmt.Errorf("get Identity: %w", err)
|
||||
@@ -421,15 +446,27 @@ func (c *Collector) getUpdateStatus(item *wuapi.Update) (windowsUpdate, error) {
|
||||
}
|
||||
|
||||
return windowsUpdate{
|
||||
identity: updateID,
|
||||
revision: strconv.FormatInt(int64(revision), 10),
|
||||
category: categoryName,
|
||||
severity: severity,
|
||||
title: title,
|
||||
lastPublished: lastPublishedDate,
|
||||
identity: updateID,
|
||||
revision: strconv.FormatInt(int64(revision), 10),
|
||||
category: categoryName,
|
||||
severity: severity,
|
||||
title: title,
|
||||
deploymentAction: deploymentActionLabel(deploymentAction),
|
||||
lastPublished: lastPublishedDate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func deploymentActionLabel(action wuapi.DeploymentAction) string {
|
||||
switch action {
|
||||
case wuapi.DeploymentActionInstallation:
|
||||
return "installation"
|
||||
case wuapi.DeploymentActionOptionalInstallation:
|
||||
return "optional_installation"
|
||||
default:
|
||||
return strconv.FormatInt(int64(action), 10)
|
||||
}
|
||||
}
|
||||
|
||||
func getUpdateCategory(update *wuapi.Update) (string, error) {
|
||||
categories, err := update.Categories()
|
||||
if err != nil {
|
||||
|
||||
@@ -38,7 +38,7 @@ func TestFetchUpdatesPartialCollection(t *testing.T) {
|
||||
name string
|
||||
countError bool
|
||||
}{
|
||||
{name: "skip unreadable item"},
|
||||
{name: "skip unreadable and duplicate items"},
|
||||
{name: "count failure", countError: true},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@@ -47,13 +47,13 @@ func TestFetchUpdatesPartialCollection(t *testing.T) {
|
||||
logger := slog.New(slog.NewTextHandler(&logs, nil))
|
||||
c := &Collector{
|
||||
logger: logger,
|
||||
pendingUpdate: prometheus.NewDesc("test_pending", "", []string{"id", "revision", "category", "severity", "title"}, nil),
|
||||
pendingUpdate: prometheus.NewDesc("test_pending", "", []string{"id", "revision", "category", "severity", "title", "deployment_action"}, nil),
|
||||
pendingUpdateLastPublished: prometheus.NewDesc("test_published", "", []string{"id", "revision"}, nil),
|
||||
queryDurationSeconds: prometheus.NewDesc("test_duration", "", nil, nil),
|
||||
lastScrapeMetric: prometheus.NewDesc("test_timestamp", "", nil, nil),
|
||||
}
|
||||
|
||||
searcher, attempted := fakeUpdateSearch(tc.countError)
|
||||
searcher, attempted, criteria := fakeUpdateSearch(tc.countError)
|
||||
|
||||
metrics, err := c.fetchUpdates(logger, searcher)
|
||||
if tc.countError {
|
||||
@@ -65,10 +65,12 @@ func TestFetchUpdatesPartialCollection(t *testing.T) {
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []int32{0, 1, 2}, *attempted)
|
||||
require.Len(t, metrics, 6, "two readable updates retain info and publication metrics plus scrape metrics")
|
||||
require.Equal(t, searchCriteria, *criteria)
|
||||
require.Equal(t, []int32{0, 1, 2, 3}, *attempted)
|
||||
require.Len(t, metrics, 6, "two distinct readable updates retain info and publication metrics plus scrape metrics")
|
||||
|
||||
ids := []string{}
|
||||
actions := []string{}
|
||||
|
||||
for _, metric := range metrics {
|
||||
if metric.Desc() != c.pendingUpdate {
|
||||
@@ -79,13 +81,17 @@ func TestFetchUpdatesPartialCollection(t *testing.T) {
|
||||
require.NoError(t, metric.Write(&sample))
|
||||
|
||||
for _, label := range sample.GetLabel() {
|
||||
if label.GetName() == "id" {
|
||||
switch label.GetName() {
|
||||
case "id":
|
||||
ids = append(ids, label.GetValue())
|
||||
case "deployment_action":
|
||||
actions = append(actions, label.GetValue())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
require.Equal(t, []string{"update-0", "update-2"}, ids)
|
||||
require.Equal(t, []string{"installation", "optional_installation"}, actions)
|
||||
require.Contains(t, logs.String(), "failed to fetch Windows Update history item")
|
||||
require.Contains(t, logs.String(), "get collection item 1")
|
||||
})
|
||||
@@ -93,11 +99,15 @@ func TestFetchUpdatesPartialCollection(t *testing.T) {
|
||||
}
|
||||
|
||||
// fakeUpdateSearch implements only the native slots exercised by a scrape.
|
||||
// Item 1 cannot be read; both surrounding items remain available.
|
||||
func fakeUpdateSearch(countError bool) (*wuapi.UpdateSearcher, *[]int32) {
|
||||
// Item 1 cannot be read; both surrounding items remain available. Item 0 is a
|
||||
// regular update and item 2 an optional one. Item 3 repeats the identity of
|
||||
// item 0. The search criteria are recorded.
|
||||
func fakeUpdateSearch(countError bool) (*wuapi.UpdateSearcher, *[]int32, *string) {
|
||||
release := windows.NewCallback(func(uintptr) uintptr { return 0 })
|
||||
attempted := []int32{}
|
||||
|
||||
var criteria string
|
||||
|
||||
var selected int32
|
||||
|
||||
var categoryMethods [10]uintptr
|
||||
@@ -119,11 +129,11 @@ func fakeUpdateSearch(countError bool) (*wuapi.UpdateSearcher, *[]int32) {
|
||||
return 0
|
||||
})
|
||||
identityMethods[8] = windows.NewCallback(func(_ uintptr, out **uint16) uintptr {
|
||||
return fakeBSTR(fmt.Sprintf("update-%d", selected), out)
|
||||
return fakeBSTR(fmt.Sprintf("update-%d", selected%3), out)
|
||||
})
|
||||
identity := &wuapi.UpdateIdentity{VTable: &identityMethods[0]}
|
||||
|
||||
var updateMethods [35]uintptr
|
||||
var updateMethods [49]uintptr
|
||||
|
||||
updateMethods[2] = release
|
||||
updateMethods[7] = windows.NewCallback(func(_ uintptr, out **uint16) uintptr {
|
||||
@@ -147,6 +157,14 @@ func fakeUpdateSearch(countError bool) (*wuapi.UpdateSearcher, *[]int32) {
|
||||
updateMethods[34] = windows.NewCallback(func(_ uintptr, out **uint16) uintptr {
|
||||
return fakeBSTR("Critical", out)
|
||||
})
|
||||
updateMethods[48] = windows.NewCallback(func(_ uintptr, out *wuapi.DeploymentAction) uintptr {
|
||||
*out = wuapi.DeploymentActionInstallation
|
||||
if selected == 2 {
|
||||
*out = wuapi.DeploymentActionOptionalInstallation
|
||||
}
|
||||
|
||||
return 0
|
||||
})
|
||||
update := &wuapi.Update{VTable: &updateMethods[0]}
|
||||
|
||||
var collectionMethods [11]uintptr
|
||||
@@ -169,7 +187,7 @@ func fakeUpdateSearch(countError bool) (*wuapi.UpdateSearcher, *[]int32) {
|
||||
return 0x80070005
|
||||
}
|
||||
|
||||
*out = 3
|
||||
*out = 4
|
||||
|
||||
return 0
|
||||
})
|
||||
@@ -188,13 +206,14 @@ func fakeUpdateSearch(countError bool) (*wuapi.UpdateSearcher, *[]int32) {
|
||||
var searcherMethods [20]uintptr
|
||||
|
||||
searcherMethods[2] = release
|
||||
searcherMethods[19] = windows.NewCallback(func(_ uintptr, _ *uint16, out **wuapi.SearchResult) uintptr {
|
||||
searcherMethods[19] = windows.NewCallback(func(_ uintptr, input *uint16, out **wuapi.SearchResult) uintptr {
|
||||
criteria = windows.UTF16PtrToString(input)
|
||||
*out = result
|
||||
|
||||
return 0
|
||||
})
|
||||
|
||||
return &wuapi.UpdateSearcher{VTable: &searcherMethods[0]}, &attempted
|
||||
return &wuapi.UpdateSearcher{VTable: &searcherMethods[0]}, &attempted, &criteria
|
||||
}
|
||||
|
||||
func fakeBSTR(value string, out **uint16) uintptr {
|
||||
|
||||
@@ -42,6 +42,7 @@ const (
|
||||
updateIdentity = 19
|
||||
updateDeploymentTime = 30
|
||||
updateSeverity = 34
|
||||
updateDeploymentAction = 48
|
||||
identityRevision = 7
|
||||
identityUpdateID = 8
|
||||
categoryCollectionItem = 7
|
||||
@@ -113,6 +114,23 @@ func (u *Update) Identity() (*UpdateIdentity, error) {
|
||||
func (u *Update) LastDeploymentChangeTime() (ole.DATE, error) {
|
||||
return u.Get[ole.DATE](updateDeploymentTime)
|
||||
}
|
||||
|
||||
// DeploymentAction is the WUA DeploymentAction enumeration.
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/wuapi/ne-wuapi-deploymentaction
|
||||
type DeploymentAction int32
|
||||
|
||||
const (
|
||||
DeploymentActionNone DeploymentAction = 0
|
||||
DeploymentActionInstallation DeploymentAction = 1
|
||||
DeploymentActionUninstallation DeploymentAction = 2
|
||||
DeploymentActionDetection DeploymentAction = 3
|
||||
DeploymentActionOptionalInstallation DeploymentAction = 4
|
||||
)
|
||||
|
||||
func (u *Update) DeploymentAction() (DeploymentAction, error) {
|
||||
return u.Get[DeploymentAction](updateDeploymentAction)
|
||||
}
|
||||
|
||||
func (i *UpdateIdentity) UpdateID() (string, error) { return i.String(identityUpdateID) }
|
||||
func (i *UpdateIdentity) RevisionNumber() (int32, error) { return i.Get[int32](identityRevision) }
|
||||
func (c *Category) Name() (string, error) { return c.String(categoryName) }
|
||||
|
||||
Reference in New Issue
Block a user