From 3e8693f1e32ea527098a1db605e3695c2cf74fac Mon Sep 17 00:00:00 2001 From: Sanjeevi Subramani Date: Sun, 15 Jun 2025 11:10:21 +0530 Subject: [PATCH] iis: Add HTTP Service Request Queues (#1948) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jan-Otto Kröpke --- docs/collector.iis.md | 4 + internal/collector/iis/iis.go | 28 ++-- .../iis/iis_http_service_request_queues.go | 134 ++++++++++++++++++ 3 files changed, 158 insertions(+), 8 deletions(-) create mode 100644 internal/collector/iis/iis_http_service_request_queues.go diff --git a/docs/collector.iis.md b/docs/collector.iis.md index d35cbc7e..c4fc7c25 100644 --- a/docs/collector.iis.md +++ b/docs/collector.iis.md @@ -130,6 +130,10 @@ If given, an application needs to *not* match the exclude regexp in order for th | `windows_iis_server_output_cache_hits_total` | Total number of successful lookups in output cache (since service startup) | counter | None | | `windows_iis_server_output_cache_items_flushed_total` | Total number of items flushed from output cache (since service startup) | counter | None | | `windows_iis_server_output_cache_flushes_total` | Total number of flushes of output cache (since service startup) | counter | None | +| `http_requests_current_queue_size` | Http Request Current queue size | counter | None | +| `http_request_total_rejected_request` | Http Request total rejected request | counter | None | +| `http_requests_max_queue_item_age` | Http Request Max queue Item age | counter | None | +| `http_requests_arrival_rate` | Http requests Arrival Rate | counter | None | ### Example metric _This collector does not yet have explained examples, we would appreciate your help adding them!_ diff --git a/internal/collector/iis/iis.go b/internal/collector/iis/iis.go index eed5cad3..3d49bba9 100644 --- a/internal/collector/iis/iis.go +++ b/internal/collector/iis/iis.go @@ -53,8 +53,11 @@ type Collector struct { config Config iisVersion simpleVersion + logger *slog.Logger + info *prometheus.Desc collectorWebService + collectorHttpServiceRequestQueues collectorAppPoolWAS collectorW3SVCW3WP collectorWebServiceCache @@ -150,6 +153,7 @@ func (c *Collector) GetName() string { func (c *Collector) Close() error { c.perfDataCollectorWebService.Close() + c.perfDataCollectorHttpServiceRequestQueues.Close() c.perfDataCollectorAppPoolWAS.Close() c.w3SVCW3WPPerfDataCollector.Close() c.serviceCachePerfDataCollector.Close() @@ -158,9 +162,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)) - c.iisVersion = c.getIISVersion(logger) + c.iisVersion = c.getIISVersion() c.info = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "info"), @@ -175,6 +179,10 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { errs = append(errs, fmt.Errorf("failed to build Web Service collector: %w", err)) } + if err := c.buildHttpServiceRequestQueues(); err != nil { + errs = append(errs, fmt.Errorf("failed to build Http Service collector: %w", err)) + } + if err := c.buildAppPoolWAS(); err != nil { errs = append(errs, fmt.Errorf("failed to build APP_POOL_WAS collector: %w", err)) } @@ -195,10 +203,10 @@ type simpleVersion struct { minor uint64 } -func (c *Collector) getIISVersion(logger *slog.Logger) simpleVersion { +func (c *Collector) getIISVersion() simpleVersion { k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\InetStp\`, registry.QUERY_VALUE) if err != nil { - logger.Warn("couldn't open registry to determine IIS version", + c.logger.Warn("couldn't open registry to determine IIS version", slog.Any("err", err), ) @@ -208,7 +216,7 @@ func (c *Collector) getIISVersion(logger *slog.Logger) simpleVersion { defer func() { err = k.Close() if err != nil { - logger.Warn("Failed to close registry key", + c.logger.Warn("Failed to close registry key", slog.Any("err", err), ) } @@ -216,7 +224,7 @@ func (c *Collector) getIISVersion(logger *slog.Logger) simpleVersion { major, _, err := k.GetIntegerValue("MajorVersion") if err != nil { - logger.Warn("Couldn't open registry to determine IIS version", + c.logger.Warn("Couldn't open registry to determine IIS version", slog.Any("err", err), ) @@ -225,14 +233,14 @@ func (c *Collector) getIISVersion(logger *slog.Logger) simpleVersion { minor, _, err := k.GetIntegerValue("MinorVersion") if err != nil { - logger.Warn("Couldn't open registry to determine IIS version", + c.logger.Warn("Couldn't open registry to determine IIS version", slog.Any("err", err), ) return simpleVersion{} } - logger.Debug(fmt.Sprintf("Detected IIS %d.%d\n", major, minor)) + c.logger.Debug(fmt.Sprintf("Detected IIS %d.%d\n", major, minor)) return simpleVersion{ major: major, @@ -255,6 +263,10 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error { errs = append(errs, fmt.Errorf("failed to collect Web Service metrics: %w", err)) } + if err := c.collectHttpServiceRequestQueues(ch); err != nil { + errs = append(errs, fmt.Errorf("failed to collect Http Service Request Queues metrics: %w", err)) + } + if err := c.collectAppPoolWAS(ch); err != nil { errs = append(errs, fmt.Errorf("failed to collect APP_POOL_WAS metrics: %w", err)) } diff --git a/internal/collector/iis/iis_http_service_request_queues.go b/internal/collector/iis/iis_http_service_request_queues.go new file mode 100644 index 00000000..0e047746 --- /dev/null +++ b/internal/collector/iis/iis_http_service_request_queues.go @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build windows + +package iis + +import ( + "fmt" + "strings" + + "github.com/prometheus-community/windows_exporter/internal/pdh" + "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus/client_golang/prometheus" +) + +type collectorHttpServiceRequestQueues struct { + perfDataCollectorHttpServiceRequestQueues *pdh.Collector + perfDataObjectHttpServiceRequestQueues []perfDataCounterValuesHttpServiceRequestQueues + + httpRequestQueuesCurrentQueueSize *prometheus.Desc + httpRequestQueuesTotalRejectedRequest *prometheus.Desc + httpRequestQueuesMaxQueueItemAge *prometheus.Desc + httpRequestQueuesArrivalRate *prometheus.Desc +} + +type perfDataCounterValuesHttpServiceRequestQueues struct { + Name string + + HttpRequestQueuesCurrentQueueSize float64 `perfdata:"CurrentQueueSize"` + HttpRequestQueuesTotalRejectedRequests float64 `perfdata:"RejectedRequests"` + HttpRequestQueuesMaxQueueItemAge float64 `perfdata:"MaxQueueItemAge"` + HttpRequestQueuesArrivalRate float64 `perfdata:"ArrivalRate"` +} + +func (p perfDataCounterValuesHttpServiceRequestQueues) GetName() string { + return p.Name +} + +func (c *Collector) buildHttpServiceRequestQueues() error { + var err error + + c.logger.Info("IIS/HttpServiceRequestQueues collector is in an experimental state! The configuration and metrics may change in future. Please report any issues.") + + c.perfDataCollectorHttpServiceRequestQueues, err = pdh.NewCollector[perfDataCounterValuesHttpServiceRequestQueues](pdh.CounterTypeRaw, "HTTP Service Request Queues", pdh.InstancesAll) + if err != nil { + return fmt.Errorf("failed to create Http Service collector: %w", err) + } + + c.httpRequestQueuesCurrentQueueSize = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "http_requests_current_queue_size"), + "Http Request Current Queue Size", + []string{"site"}, + nil, + ) + c.httpRequestQueuesTotalRejectedRequest = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "http_request_total_rejected_request"), + "Http Request Total Rejected Request", + []string{"site"}, + nil, + ) + c.httpRequestQueuesMaxQueueItemAge = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "http_requests_max_queue_item_age"), + "Http Request Max Queue Item Age. The values might be bogus if the queue is empty.", + []string{"site"}, + nil, + ) + c.httpRequestQueuesArrivalRate = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "http_requests_arrival_rate"), + "Http Request Arrival Rate", + []string{"site"}, + nil, + ) + + return nil +} + +func (c *Collector) collectHttpServiceRequestQueues(ch chan<- prometheus.Metric) error { + err := c.perfDataCollectorHttpServiceRequestQueues.Collect(&c.perfDataObjectHttpServiceRequestQueues) + if err != nil { + return fmt.Errorf("failed to collect Http Service Request Queues metrics: %w", err) + } + + deduplicateIISNames(c.perfDataObjectHttpServiceRequestQueues) + + for _, data := range c.perfDataObjectHttpServiceRequestQueues { + if strings.HasPrefix(data.Name, "---") { + continue + } + + if c.config.SiteExclude.MatchString(data.Name) || !c.config.SiteInclude.MatchString(data.Name) { + continue + } + + ch <- prometheus.MustNewConstMetric( + c.httpRequestQueuesCurrentQueueSize, + prometheus.GaugeValue, + data.HttpRequestQueuesCurrentQueueSize, + data.Name, + ) + ch <- prometheus.MustNewConstMetric( + c.httpRequestQueuesTotalRejectedRequest, + prometheus.GaugeValue, + data.HttpRequestQueuesTotalRejectedRequests, + data.Name, + ) + ch <- prometheus.MustNewConstMetric( + c.httpRequestQueuesMaxQueueItemAge, + prometheus.GaugeValue, + data.HttpRequestQueuesMaxQueueItemAge, + data.Name, + ) + ch <- prometheus.MustNewConstMetric( + c.httpRequestQueuesArrivalRate, + prometheus.GaugeValue, + data.HttpRequestQueuesArrivalRate, + data.Name, + ) + } + + return nil +}