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 ## Metrics
| Name | Description | Type | Labels | | Name | Description | Type | Labels |
|--------------------------------|-------------------------------------------------------------|-------|-------------------------------| |------------------------------------------------|------------------------------------------------------------------|-------|-------------------------------|
| `windows_update_pending_info` | Expose information for a single pending update item | gauge | `category`,`severity`,`title` | | `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_pending_published_timestamp` | Expose last published timestamp for a single pending update item | gauge | `title` |
| `windows_update_scrape_timestamp_seconds` | Timestamp of the last scrape | gauge | | | `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 ### Example metrics
``` ```
# HELP windows_update_pending Pending Windows Updates # HELP windows_update_pending_info Expose information for a single pending update item
# TYPE windows_update_pending gauge # TYPE windows_update_pending_info gauge
windows_update_pending{category="Drivers",severity="",title="Intel Corporation - Bluetooth - 23.60.5.10"} 1 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 # 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 # TYPE windows_update_scrape_query_duration_seconds gauge
windows_update_scrape_query_duration_seconds 2.8161838 windows_update_scrape_query_duration_seconds 2.8161838
@@ -46,7 +52,12 @@ windows_update_scrape_timestamp_seconds 1.727539734e+09
``` ```
## Useful queries ## 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 ## Alerting examples
_This collector does not yet have alerting examples, we would appreciate your help adding them!_ _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 mu sync.RWMutex
ctxCancelFn context.CancelFunc ctxCancelFn context.CancelFunc
logger *slog.Logger
metricsBuf []prometheus.Metric metricsBuf []prometheus.Metric
pendingUpdate *prometheus.Desc pendingUpdate *prometheus.Desc
queryDurationSeconds *prometheus.Desc pendingUpdateLastPublished *prometheus.Desc
lastScrapeMetric *prometheus.Desc queryDurationSeconds *prometheus.Desc
lastScrapeMetric *prometheus.Desc
} }
func New(config *Config) *Collector { func New(config *Config) *Collector {
@@ -146,9 +149,9 @@ func (c *Collector) Close() error {
} }
func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) 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()) ctx, cancel := context.WithCancel(context.Background())
@@ -164,7 +167,14 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
c.pendingUpdate = prometheus.NewDesc( c.pendingUpdate = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "pending_info"), prometheus.BuildFQName(types.Namespace, Name, "pending_info"),
"Expose information for a single pending update item", "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, nil,
) )
@@ -241,9 +251,16 @@ func (c *Collector) scheduleUpdateStatus(ctx context.Context, logger *slog.Logge
defer musQueryInterface.Release() 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") _, err = oleutil.PutProperty(musQueryInterface, "ClientApplicationID", "windows_exporter")
if err != nil { if err != nil {
initErrCh <- fmt.Errorf("put ClientApplicationID: %w", err) initErrCh <- fmt.Errorf("failed to set ClientApplicationID: %w", err)
return 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) { 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() timeStart := time.Now()
@@ -367,10 +384,22 @@ func (c *Collector) fetchUpdates(logger *slog.Logger, usd *ole.IDispatch) ([]pro
c.pendingUpdate, c.pendingUpdate,
prometheus.GaugeValue, prometheus.GaugeValue,
1, 1,
update.identity,
update.revision,
update.category, update.category,
update.severity, update.severity,
update.title, 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( metricsBuf = append(metricsBuf, prometheus.MustNewConstMetric(
@@ -383,9 +412,12 @@ func (c *Collector) fetchUpdates(logger *slog.Logger, usd *ole.IDispatch) ([]pro
} }
type windowsUpdate struct { type windowsUpdate struct {
category string identity string
severity string revision string
title string category string
severity string
title string
lastPublished time.Time
} }
// getUpdateStatus retrieves the update status of the given item. // 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) 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{ return windowsUpdate{
category: categoryName, identity: updateIDVariant.ToString(),
severity: severity.ToString(), revision: strconv.FormatInt(revisionVariant.Val, 10),
title: title.ToString(), category: categoryName,
severity: severity.ToString(),
title: title.ToString(),
lastPublished: lastPublishedDate,
}, nil }, nil
} }