2024-11-24 13:11:14 +00:00
|
|
|
// Copyright 2024 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.
|
|
|
|
|
2021-12-18 18:01:29 +00:00
|
|
|
//go:build windows
|
2018-11-30 00:51:12 +00:00
|
|
|
|
2023-11-04 19:51:35 +00:00
|
|
|
package iis
|
2016-08-27 14:33:23 +00:00
|
|
|
|
|
|
|
import (
|
2024-11-17 20:51:12 +00:00
|
|
|
"errors"
|
2016-08-27 14:33:23 +00:00
|
|
|
"fmt"
|
2024-09-10 22:34:10 +00:00
|
|
|
"log/slog"
|
2024-11-17 20:51:12 +00:00
|
|
|
"maps"
|
2021-12-14 13:33:04 +00:00
|
|
|
"regexp"
|
2024-11-17 20:51:12 +00:00
|
|
|
"slices"
|
2023-02-04 22:57:54 +00:00
|
|
|
"strings"
|
2021-12-14 13:33:04 +00:00
|
|
|
|
2023-03-12 23:32:17 +00:00
|
|
|
"github.com/alecthomas/kingpin/v2"
|
2024-11-03 16:23:26 +00:00
|
|
|
"github.com/prometheus-community/windows_exporter/internal/mi"
|
2024-11-17 20:51:12 +00:00
|
|
|
"github.com/prometheus-community/windows_exporter/internal/perfdata"
|
2024-10-03 18:34:45 +00:00
|
|
|
"github.com/prometheus-community/windows_exporter/internal/types"
|
2016-08-27 14:33:23 +00:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
2021-09-25 15:00:39 +00:00
|
|
|
"golang.org/x/sys/windows/registry"
|
2016-08-27 14:33:23 +00:00
|
|
|
)
|
|
|
|
|
2024-08-05 13:50:41 +00:00
|
|
|
const Name = "iis"
|
2023-04-16 09:45:07 +00:00
|
|
|
|
2023-11-04 19:51:35 +00:00
|
|
|
type Config struct {
|
2024-08-11 11:28:39 +00:00
|
|
|
SiteInclude *regexp.Regexp `yaml:"site_include"`
|
|
|
|
SiteExclude *regexp.Regexp `yaml:"site_exclude"`
|
|
|
|
AppInclude *regexp.Regexp `yaml:"app_include"`
|
|
|
|
AppExclude *regexp.Regexp `yaml:"app_exclude"`
|
2023-11-04 19:51:35 +00:00
|
|
|
}
|
2023-04-01 07:48:23 +00:00
|
|
|
|
2023-11-04 19:51:35 +00:00
|
|
|
var ConfigDefaults = Config{
|
2024-10-03 18:34:45 +00:00
|
|
|
SiteInclude: types.RegExpAny,
|
|
|
|
SiteExclude: types.RegExpEmpty,
|
|
|
|
AppInclude: types.RegExpAny,
|
|
|
|
AppExclude: types.RegExpEmpty,
|
2016-09-01 14:04:43 +00:00
|
|
|
}
|
|
|
|
|
2024-08-05 13:50:41 +00:00
|
|
|
type Collector struct {
|
2024-11-17 20:51:12 +00:00
|
|
|
config Config
|
|
|
|
iisVersion simpleVersion
|
2023-04-22 10:17:51 +00:00
|
|
|
|
2024-08-10 18:02:07 +00:00
|
|
|
info *prometheus.Desc
|
2024-11-17 20:51:12 +00:00
|
|
|
collectorWebService
|
|
|
|
collectorAppPoolWAS
|
|
|
|
collectorW3SVCW3WP
|
|
|
|
collectorWebServiceCache
|
2016-08-27 14:33:23 +00:00
|
|
|
}
|
|
|
|
|
2024-08-24 17:14:38 +00:00
|
|
|
func New(config *Config) *Collector {
|
2023-11-04 19:51:35 +00:00
|
|
|
if config == nil {
|
|
|
|
config = &ConfigDefaults
|
|
|
|
}
|
|
|
|
|
2024-08-11 11:28:39 +00:00
|
|
|
if config.AppExclude == nil {
|
|
|
|
config.AppExclude = ConfigDefaults.AppExclude
|
|
|
|
}
|
|
|
|
|
|
|
|
if config.AppInclude == nil {
|
|
|
|
config.AppInclude = ConfigDefaults.AppInclude
|
|
|
|
}
|
|
|
|
|
|
|
|
if config.SiteExclude == nil {
|
|
|
|
config.SiteExclude = ConfigDefaults.SiteExclude
|
|
|
|
}
|
|
|
|
|
|
|
|
if config.SiteInclude == nil {
|
|
|
|
config.SiteInclude = ConfigDefaults.SiteInclude
|
|
|
|
}
|
|
|
|
|
2024-08-05 13:50:41 +00:00
|
|
|
c := &Collector{
|
2024-08-11 11:28:39 +00:00
|
|
|
config: *config,
|
2023-11-04 19:51:35 +00:00
|
|
|
}
|
2024-08-10 18:02:07 +00:00
|
|
|
|
2023-11-04 19:51:35 +00:00
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
2024-08-05 13:50:41 +00:00
|
|
|
func NewWithFlags(app *kingpin.Application) *Collector {
|
|
|
|
c := &Collector{
|
2024-08-11 11:28:39 +00:00
|
|
|
config: ConfigDefaults,
|
2023-11-04 19:51:35 +00:00
|
|
|
}
|
2023-04-01 07:48:23 +00:00
|
|
|
|
2024-08-11 11:28:39 +00:00
|
|
|
var appExclude, appInclude, siteExclude, siteInclude string
|
|
|
|
|
|
|
|
app.Flag(
|
|
|
|
"collector.iis.app-exclude",
|
|
|
|
"Regexp of apps to exclude. App name must both match include and not match exclude to be included.",
|
2024-10-10 19:44:04 +00:00
|
|
|
).Default("").StringVar(&appExclude)
|
2024-08-11 11:28:39 +00:00
|
|
|
|
|
|
|
app.Flag(
|
|
|
|
"collector.iis.app-include",
|
|
|
|
"Regexp of apps to include. App name must both match include and not match exclude to be included.",
|
2024-10-10 19:44:04 +00:00
|
|
|
).Default(".+").StringVar(&appInclude)
|
2024-08-11 11:28:39 +00:00
|
|
|
|
|
|
|
app.Flag(
|
|
|
|
"collector.iis.site-exclude",
|
|
|
|
"Regexp of sites to exclude. Site name must both match include and not match exclude to be included.",
|
2024-10-10 19:44:04 +00:00
|
|
|
).Default("").StringVar(&siteExclude)
|
2024-08-11 11:28:39 +00:00
|
|
|
|
|
|
|
app.Flag(
|
|
|
|
"collector.iis.site-include",
|
|
|
|
"Regexp of sites to include. Site name must both match include and not match exclude to be included.",
|
2024-10-10 19:44:04 +00:00
|
|
|
).Default(".+").StringVar(&siteInclude)
|
2024-08-11 11:28:39 +00:00
|
|
|
|
|
|
|
app.Action(func(*kingpin.ParseContext) error {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
c.config.AppExclude, err = regexp.Compile(fmt.Sprintf("^(?:%s)$", appExclude))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("collector.iis.app-exclude: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.config.AppInclude, err = regexp.Compile(fmt.Sprintf("^(?:%s)$", appInclude))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("collector.iis.app-include: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.config.SiteExclude, err = regexp.Compile(fmt.Sprintf("^(?:%s)$", siteExclude))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("collector.iis.site-exclude: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.config.SiteInclude, err = regexp.Compile(fmt.Sprintf("^(?:%s)$", siteInclude))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("collector.iis.site-include: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
2023-11-04 19:51:35 +00:00
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
2024-08-05 13:50:41 +00:00
|
|
|
func (c *Collector) GetName() string {
|
2023-11-04 19:51:35 +00:00
|
|
|
return Name
|
2023-04-16 09:45:07 +00:00
|
|
|
}
|
|
|
|
|
2024-11-17 20:51:12 +00:00
|
|
|
func (c *Collector) Close() error {
|
|
|
|
c.perfDataCollectorWebService.Close()
|
|
|
|
c.perfDataCollectorAppPoolWAS.Close()
|
|
|
|
c.perfDataCollectorW3SVCW3WP.Close()
|
|
|
|
c.perfDataCollectorWebServiceCache.Close()
|
2023-04-22 10:17:51 +00:00
|
|
|
|
2024-08-05 13:50:41 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-11-03 16:23:26 +00:00
|
|
|
func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
2024-09-10 22:34:10 +00:00
|
|
|
logger = logger.With(slog.String("collector", Name))
|
2024-08-24 17:14:38 +00:00
|
|
|
|
2024-11-17 20:51:12 +00:00
|
|
|
c.iisVersion = c.getIISVersion(logger)
|
2023-11-04 19:51:35 +00:00
|
|
|
|
2024-08-10 18:02:07 +00:00
|
|
|
c.info = prometheus.NewDesc(
|
2024-10-03 18:34:45 +00:00
|
|
|
prometheus.BuildFQName(types.Namespace, Name, "info"),
|
2024-05-03 05:51:29 +00:00
|
|
|
"ISS information",
|
|
|
|
[]string{},
|
2024-11-17 20:51:12 +00:00
|
|
|
prometheus.Labels{"version": fmt.Sprintf("%d.%d", c.iisVersion.major, c.iisVersion.minor)},
|
2024-05-03 05:51:29 +00:00
|
|
|
)
|
|
|
|
|
2024-11-17 20:51:12 +00:00
|
|
|
if err := c.buildWebService(); err != nil {
|
|
|
|
return fmt.Errorf("failed to build Web Service collector: %w", err)
|
|
|
|
}
|
2023-11-04 19:51:35 +00:00
|
|
|
|
2024-11-17 20:51:12 +00:00
|
|
|
if err := c.buildAppPoolWAS(); err != nil {
|
|
|
|
return fmt.Errorf("failed to build APP_POOL_WAS collector: %w", err)
|
|
|
|
}
|
2023-11-04 19:51:35 +00:00
|
|
|
|
2024-11-17 20:51:12 +00:00
|
|
|
if err := c.buildW3SVCW3WP(); err != nil {
|
|
|
|
return fmt.Errorf("failed to build W3SVC_W3WP collector: %w", err)
|
|
|
|
}
|
2023-11-04 19:51:35 +00:00
|
|
|
|
2024-11-17 20:51:12 +00:00
|
|
|
if err := c.buildWebServiceCache(); err != nil {
|
|
|
|
return fmt.Errorf("failed to build Web Service Cache collector: %w", err)
|
|
|
|
}
|
2023-11-04 19:51:35 +00:00
|
|
|
|
|
|
|
return nil
|
2016-08-27 14:33:23 +00:00
|
|
|
}
|
|
|
|
|
2024-08-11 11:28:39 +00:00
|
|
|
type simpleVersion struct {
|
|
|
|
major uint64
|
|
|
|
minor uint64
|
|
|
|
}
|
|
|
|
|
2024-11-17 20:51:12 +00:00
|
|
|
func (c *Collector) getIISVersion(logger *slog.Logger) simpleVersion {
|
2024-08-11 11:28:39 +00:00
|
|
|
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\InetStp\`, registry.QUERY_VALUE)
|
|
|
|
if err != nil {
|
2024-09-10 22:34:10 +00:00
|
|
|
logger.Warn("Couldn't open registry to determine IIS version",
|
|
|
|
slog.Any("err", err),
|
|
|
|
)
|
|
|
|
|
2024-08-11 11:28:39 +00:00
|
|
|
return simpleVersion{}
|
|
|
|
}
|
2024-09-10 22:34:10 +00:00
|
|
|
|
2024-08-11 11:28:39 +00:00
|
|
|
defer func() {
|
|
|
|
err = k.Close()
|
|
|
|
if err != nil {
|
2024-09-10 22:34:10 +00:00
|
|
|
logger.Warn("Failed to close registry key",
|
|
|
|
slog.Any("err", err),
|
|
|
|
)
|
2024-08-11 11:28:39 +00:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
major, _, err := k.GetIntegerValue("MajorVersion")
|
|
|
|
if err != nil {
|
2024-09-10 22:34:10 +00:00
|
|
|
logger.Warn("Couldn't open registry to determine IIS version",
|
|
|
|
slog.Any("err", err),
|
|
|
|
)
|
|
|
|
|
2024-08-11 11:28:39 +00:00
|
|
|
return simpleVersion{}
|
|
|
|
}
|
2024-09-10 22:34:10 +00:00
|
|
|
|
2024-08-11 11:28:39 +00:00
|
|
|
minor, _, err := k.GetIntegerValue("MinorVersion")
|
|
|
|
if err != nil {
|
2024-09-10 22:34:10 +00:00
|
|
|
logger.Warn("Couldn't open registry to determine IIS version",
|
|
|
|
slog.Any("err", err),
|
|
|
|
)
|
|
|
|
|
2024-08-11 11:28:39 +00:00
|
|
|
return simpleVersion{}
|
|
|
|
}
|
|
|
|
|
2024-09-10 22:34:10 +00:00
|
|
|
logger.Debug(fmt.Sprintf("Detected IIS %d.%d\n", major, minor))
|
2024-08-11 11:28:39 +00:00
|
|
|
|
|
|
|
return simpleVersion{
|
|
|
|
major: major,
|
|
|
|
minor: minor,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-27 14:33:23 +00:00
|
|
|
// Collect sends the metric values for each metric
|
|
|
|
// to the provided prometheus Metric channel.
|
2024-11-17 20:51:12 +00:00
|
|
|
func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
|
|
|
|
ch <- prometheus.MustNewConstMetric(
|
|
|
|
c.info,
|
|
|
|
prometheus.GaugeValue,
|
|
|
|
1,
|
|
|
|
)
|
2016-08-27 14:33:23 +00:00
|
|
|
|
2024-11-17 20:51:12 +00:00
|
|
|
errs := make([]error, 0, 4)
|
2024-09-10 22:34:10 +00:00
|
|
|
|
2024-11-17 20:51:12 +00:00
|
|
|
if err := c.collectWebService(ch); err != nil {
|
|
|
|
errs = append(errs, fmt.Errorf("failed to collect Web Service metrics: %w", err))
|
2021-09-25 15:00:39 +00:00
|
|
|
}
|
2016-08-27 14:33:23 +00:00
|
|
|
|
2024-11-17 20:51:12 +00:00
|
|
|
if err := c.collectAppPoolWAS(ch); err != nil {
|
|
|
|
errs = append(errs, fmt.Errorf("failed to collect APP_POOL_WAS metrics: %w", err))
|
2021-09-25 15:00:39 +00:00
|
|
|
}
|
2016-08-27 14:33:23 +00:00
|
|
|
|
2024-11-17 20:51:12 +00:00
|
|
|
if err := c.collectW3SVCW3WP(ch); err != nil {
|
|
|
|
errs = append(errs, fmt.Errorf("failed to collect W3SVC_W3WP metrics: %w", err))
|
2021-09-25 15:00:39 +00:00
|
|
|
}
|
2017-04-26 13:19:33 +00:00
|
|
|
|
2024-11-17 20:51:12 +00:00
|
|
|
if err := c.collectWebServiceCache(ch); err != nil {
|
|
|
|
errs = append(errs, fmt.Errorf("failed to collect Web Service Cache metrics: %w", err))
|
|
|
|
}
|
2023-02-04 22:57:54 +00:00
|
|
|
|
2024-11-17 20:51:12 +00:00
|
|
|
return errors.Join(errs...)
|
2023-02-04 22:57:54 +00:00
|
|
|
}
|
|
|
|
|
2024-11-17 20:51:12 +00:00
|
|
|
// deduplicateIISNames deduplicate IIS site names from various IIS perflib objects.
|
2023-02-04 22:57:54 +00:00
|
|
|
//
|
|
|
|
// E.G. Given the following list of site names, "Site_B" would be
|
|
|
|
// discarded, and "Site_B#2" would be kept and presented as "Site_B" in the
|
2024-08-05 13:50:41 +00:00
|
|
|
// Collector metrics.
|
2024-08-10 20:05:33 +00:00
|
|
|
// [ "Site_A", "Site_B", "Site_C", "Site_B#2" ].
|
2024-11-17 20:51:12 +00:00
|
|
|
func deduplicateIISNames(counterValues map[string]map[string]perfdata.CounterValues) {
|
|
|
|
services := slices.Collect(maps.Keys(counterValues))
|
2023-02-04 22:57:54 +00:00
|
|
|
|
2024-11-17 20:51:12 +00:00
|
|
|
// Ensure IIS entry with the highest suffix occurs last
|
|
|
|
slices.Sort(services)
|
2023-02-04 22:57:54 +00:00
|
|
|
|
|
|
|
// Use map to deduplicate IIS entries
|
|
|
|
for _, entry := range services {
|
2024-11-17 20:51:12 +00:00
|
|
|
name := strings.Split(entry, "#")[0]
|
|
|
|
if name == entry {
|
2022-02-01 20:52:11 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-11-17 20:51:12 +00:00
|
|
|
counterValues[name] = counterValues[entry]
|
|
|
|
delete(counterValues, entry)
|
2021-09-25 15:00:39 +00:00
|
|
|
}
|
2016-08-27 14:33:23 +00:00
|
|
|
}
|