Skip to content

iis: Add HTTP Service Request Queues #1948

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/collector.iis.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!_
Expand Down
28 changes: 20 additions & 8 deletions internal/collector/iis/iis.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,11 @@ type Collector struct {
config Config
iisVersion simpleVersion

logger *slog.Logger

info *prometheus.Desc
collectorWebService
collectorHttpServiceRequestQueues
collectorAppPoolWAS
collectorW3SVCW3WP
collectorWebServiceCache
Expand Down Expand Up @@ -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()
Expand All @@ -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"),
Expand All @@ -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))
}
Expand All @@ -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),
)

Expand All @@ -208,15 +216,15 @@ 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),
)
}
}()

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),
)

Expand All @@ -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,
Expand All @@ -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))
}
Expand Down
134 changes: 134 additions & 0 deletions internal/collector/iis/iis_http_service_request_queues.go
Original file line number Diff line number Diff line change
@@ -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
}