update: expose publish date of updates (#2050)

This commit is contained in:
Jan-Otto Kröpke
2025-05-23 21:18:29 +02:00
committed by GitHub
parent 92f213ca7c
commit 6dd21a8e00
2 changed files with 104 additions and 23 deletions

View File

@@ -26,17 +26,23 @@ Define the interval of scraping Windows Update information
## Metrics
| Name | Description | Type | Labels |
|--------------------------------|-------------------------------------------------------------|-------|-------------------------------|
| `windows_update_pending_info` | Expose information for a single pending update item | gauge | `category`,`severity`,`title` |
| `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 | |
| Name | Description | Type | Labels |
|------------------------------------------------|------------------------------------------------------------------|-------|-------------------------------|
| `windows_update_pending_info` | Expose information for a single pending update item | gauge | `category`,`severity`,`title` |
| `windows_update_pending_published_timestamp` | Expose last published timestamp for a single pending update item | gauge | `title` |
| `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 Pending Windows Updates
# TYPE windows_update_pending gauge
windows_update_pending{category="Drivers",severity="",title="Intel Corporation - Bluetooth - 23.60.5.10"} 1
# 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
# 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
# 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
@@ -46,7 +52,12 @@ windows_update_scrape_timestamp_seconds 1.727539734e+09
```
## Useful queries
_This collector does not yet have any useful queries added, we would appreciate your help adding them!_
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
```
## Alerting examples
_This collector does not yet have alerting examples, we would appreciate your help adding them!_

View File

@@ -60,11 +60,14 @@ type Collector struct {
mu sync.RWMutex
ctxCancelFn context.CancelFunc
logger *slog.Logger
metricsBuf []prometheus.Metric
pendingUpdate *prometheus.Desc
queryDurationSeconds *prometheus.Desc
lastScrapeMetric *prometheus.Desc
pendingUpdate *prometheus.Desc
pendingUpdateLastPublished *prometheus.Desc
queryDurationSeconds *prometheus.Desc
lastScrapeMetric *prometheus.Desc
}
func New(config *Config) *Collector {
@@ -146,9 +149,9 @@ func (c *Collector) Close() error {
}
func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
logger = logger.With(slog.String("collector", Name))
c.logger = logger.With(slog.String("collector", Name))
logger.Info("update collector is in an experimental state! The configuration and metrics may change in future. Please report any issues.")
c.logger.Info("update collector is in an experimental state! The configuration and metrics may change in future. Please report any issues.")
ctx, cancel := context.WithCancel(context.Background())
@@ -164,7 +167,14 @@ 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{"category", "severity", "title"},
[]string{"id", "revision", "category", "severity", "title"},
nil,
)
c.pendingUpdateLastPublished = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "pending_published_timestamp"),
"Expose last published timestamp for a single pending update item",
[]string{"id", "revision"},
nil,
)
@@ -241,9 +251,16 @@ func (c *Collector) scheduleUpdateStatus(ctx context.Context, logger *slog.Logge
defer musQueryInterface.Release()
_, err = oleutil.PutProperty(musQueryInterface, "UserLocale", 1033)
if err != nil {
initErrCh <- fmt.Errorf("failed to set ClientApplicationID: %w", err)
return
}
_, err = oleutil.PutProperty(musQueryInterface, "ClientApplicationID", "windows_exporter")
if err != nil {
initErrCh <- fmt.Errorf("put ClientApplicationID: %w", err)
initErrCh <- fmt.Errorf("failed to set ClientApplicationID: %w", err)
return
}
@@ -320,7 +337,7 @@ func (c *Collector) scheduleUpdateStatus(ctx context.Context, logger *slog.Logge
}
func (c *Collector) fetchUpdates(logger *slog.Logger, usd *ole.IDispatch) ([]prometheus.Metric, error) {
metricsBuf := make([]prometheus.Metric, 0, len(c.metricsBuf))
metricsBuf := make([]prometheus.Metric, 0, len(c.metricsBuf)*2+1)
timeStart := time.Now()
@@ -367,10 +384,22 @@ func (c *Collector) fetchUpdates(logger *slog.Logger, usd *ole.IDispatch) ([]pro
c.pendingUpdate,
prometheus.GaugeValue,
1,
update.identity,
update.revision,
update.category,
update.severity,
update.title,
))
if update.lastPublished != (time.Time{}) {
metricsBuf = append(metricsBuf, prometheus.MustNewConstMetric(
c.pendingUpdateLastPublished,
prometheus.GaugeValue,
float64(update.lastPublished.Unix()),
update.identity,
update.revision,
))
}
}
metricsBuf = append(metricsBuf, prometheus.MustNewConstMetric(
@@ -383,9 +412,12 @@ func (c *Collector) fetchUpdates(logger *slog.Logger, usd *ole.IDispatch) ([]pro
}
type windowsUpdate struct {
category string
severity string
title string
identity string
revision string
category string
severity string
title string
lastPublished time.Time
}
// getUpdateStatus retrieves the update status of the given item.
@@ -423,10 +455,48 @@ func (c *Collector) getUpdateStatus(updd *ole.IDispatch, item int) (windowsUpdat
return windowsUpdate{}, fmt.Errorf("get Title: %w", err)
}
// Get the Identity object
identityVariant, err := oleutil.GetProperty(updateItem, "Identity")
if err != nil {
return windowsUpdate{}, fmt.Errorf("get Identity: %w", err)
}
identity := identityVariant.ToIDispatch()
defer identity.Release()
// Read the UpdateID
updateIDVariant, err := oleutil.GetProperty(identity, "UpdateID")
if err != nil {
return windowsUpdate{}, fmt.Errorf("get UpdateID: %w", err)
}
revisionVariant, err := oleutil.GetProperty(identity, "RevisionNumber")
if err != nil {
return windowsUpdate{}, fmt.Errorf("get RevisionNumber: %w", err)
}
lastPublished, err := oleutil.GetProperty(updateItem, "LastDeploymentChangeTime")
if err != nil {
return windowsUpdate{}, fmt.Errorf("get LastDeploymentChangeTime: %w", err)
}
lastPublishedDate, err := ole.GetVariantDate(uint64(lastPublished.Val))
if err != nil {
c.logger.Debug("failed to convert LastDeploymentChangeTime",
slog.String("title", title.ToString()),
slog.Any("err", err),
)
lastPublishedDate = time.Time{}
}
return windowsUpdate{
category: categoryName,
severity: severity.ToString(),
title: title.ToString(),
identity: updateIDVariant.ToString(),
revision: strconv.FormatInt(revisionVariant.Val, 10),
category: categoryName,
severity: severity.ToString(),
title: title.ToString(),
lastPublished: lastPublishedDate,
}, nil
}