// 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 exchange import ( "errors" "fmt" "log/slog" "maps" "os" "slices" "strings" "time" "github.com/alecthomas/kingpin/v2" "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/utils/recovery" "github.com/prometheus/client_golang/prometheus" ) const Name = "exchange" const ( subCollectorADAccessProcesses = "ADAccessProcesses" subCollectorTransportQueues = "TransportQueues" subCollectorHttpProxy = "HttpProxy" subCollectorActiveSync = "ActiveSync" subCollectorAvailabilityService = "AvailabilityService" subCollectorOutlookWebAccess = "OutlookWebAccess" subCollectorAutoDiscover = "Autodiscover" subCollectorWorkloadManagement = "WorkloadManagement" subCollectorRpcClientAccess = "RpcClientAccess" subCollectorMapiHTTPEmsmdb = "MapiHttpEmsmdb" ) type Config struct { CollectorsEnabled []string `yaml:"enabled"` } //nolint:gochecknoglobals var ConfigDefaults = Config{ CollectorsEnabled: []string{ subCollectorADAccessProcesses, subCollectorTransportQueues, subCollectorHttpProxy, subCollectorActiveSync, subCollectorAvailabilityService, subCollectorOutlookWebAccess, subCollectorAutoDiscover, subCollectorWorkloadManagement, subCollectorRpcClientAccess, subCollectorMapiHTTPEmsmdb, }, } type Collector struct { collectorADAccessProcesses collectorActiveSync collectorAutoDiscover collectorAvailabilityService collectorHTTPProxy collectorMapiHTTPEmsMDB collectorOWA collectorRpcClientAccess collectorTransportQueues collectorWorkloadManagementWorkloads config Config logger *slog.Logger collectorFns []func(ch chan<- prometheus.Metric) error closeFns []func() } func New(config *Config) *Collector { if config == nil { config = &ConfigDefaults } if config.CollectorsEnabled == nil { config.CollectorsEnabled = ConfigDefaults.CollectorsEnabled } c := &Collector{ config: *config, } return c } func NewWithFlags(app *kingpin.Application) *Collector { c := &Collector{ config: ConfigDefaults, } c.config.CollectorsEnabled = make([]string, 0) var listAllCollectors bool var collectorsEnabled string app.Flag( "collector.exchange.list", "List the collectors along with their perflib object name/ids", ).BoolVar(&listAllCollectors) app.Flag( "collector.exchange.enabled", "Comma-separated list of collectors to use. Defaults to all, if not specified.", ).Default(strings.Join(ConfigDefaults.CollectorsEnabled, ",")).StringVar(&collectorsEnabled) app.PreAction(func(*kingpin.ParseContext) error { if listAllCollectors { collectorDesc := map[string]string{ subCollectorADAccessProcesses: "[19108] MSExchange ADAccess Processes", subCollectorTransportQueues: "[20524] MSExchangeTransport Queues", subCollectorHttpProxy: "[36934] MSExchange HttpProxy", subCollectorActiveSync: "[25138] MSExchange ActiveSync", subCollectorAvailabilityService: "[24914] MSExchange Availability Service", subCollectorOutlookWebAccess: "[24618] MSExchange OWA", subCollectorAutoDiscover: "[29240] MSExchange Autodiscover", subCollectorWorkloadManagement: "[19430] MSExchange WorkloadManagement Workloads", subCollectorRpcClientAccess: "[29336] MSExchange RpcClientAccess", subCollectorMapiHTTPEmsmdb: "[26463] MSExchange MapiHttp Emsmdb", } sb := strings.Builder{} _, _ = fmt.Fprintf(&sb, "%-32s %-32s\n", "Collector Name", "[PerfID] Perflib Object") for _, cname := range ConfigDefaults.CollectorsEnabled { _, _ = fmt.Fprintf(&sb, "%-32s %-32s\n", cname, collectorDesc[cname]) } app.UsageTemplate(sb.String()).Usage(nil) os.Exit(0) } return nil }) app.Action(func(*kingpin.ParseContext) error { c.config.CollectorsEnabled = strings.Split(collectorsEnabled, ",") return nil }) return c } func (c *Collector) GetName() string { return Name } func (c *Collector) Close() error { for _, fn := range c.closeFns { fn() } c.closeFns = nil return nil } func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { c.logger = logger.With(slog.String("collector", Name)) subCollectors := map[string]struct { build func() error collect func(ch chan<- prometheus.Metric) error close func() }{ subCollectorADAccessProcesses: { build: c.buildADAccessProcesses, collect: c.collectADAccessProcesses, close: func() { c.perfDataCollectorADAccessProcesses.Close() }, }, subCollectorTransportQueues: { build: c.buildTransportQueues, collect: c.collectTransportQueues, close: func() { c.perfDataCollectorTransportQueues.Close() }, }, subCollectorHttpProxy: { build: c.buildHTTPProxy, collect: c.collectHTTPProxy, close: func() { c.perfDataCollectorHTTPProxy.Close() }, }, subCollectorActiveSync: { build: c.buildActiveSync, collect: c.collectActiveSync, close: func() { c.perfDataCollectorActiveSync.Close() }, }, subCollectorAvailabilityService: { build: c.buildAvailabilityService, collect: c.collectAvailabilityService, close: func() { c.perfDataCollectorAvailabilityService.Close() }, }, subCollectorOutlookWebAccess: { build: c.buildOWA, collect: c.collectOWA, close: func() { c.perfDataCollectorOWA.Close() }, }, subCollectorAutoDiscover: { build: c.buildAutoDiscover, collect: c.collectAutoDiscover, close: func() { c.perfDataCollectorAutoDiscover.Close() }, }, subCollectorWorkloadManagement: { build: c.buildWorkloadManagementWorkloads, collect: c.collectWorkloadManagementWorkloads, close: func() { c.perfDataCollectorWorkloadManagementWorkloads.Close() }, }, subCollectorRpcClientAccess: { build: c.buildRpcClientAccess, collect: c.collectRpcClientAccess, close: func() { c.perfDataCollectorRpcClientAccess.Close() }, }, subCollectorMapiHTTPEmsmdb: { build: c.buildMapiHTTPEmsMDB, collect: c.collectMapiHTTPEmsMDB, close: func() { c.perfDataCollectorMapiHTTPEmsMDB.Close() }, }, } collectorsEnabled := slices.Compact(slices.Sorted(slices.Values(c.config.CollectorsEnabled))) for _, name := range collectorsEnabled { if _, ok := subCollectors[name]; !ok { return fmt.Errorf("unknown sub collector: %s. Possible values: %s", name, strings.Join(slices.Sorted(maps.Keys(subCollectors)), ", "), ) } } c.collectorFns = make([]func(ch chan<- prometheus.Metric) error, 0, len(collectorsEnabled)) c.closeFns = make([]func(), 0, len(collectorsEnabled)) errs := make([]error, 0, len(collectorsEnabled)) for _, name := range collectorsEnabled { // Register close before build, so that Close also releases a partially built sub collector. c.closeFns = append(c.closeFns, subCollectors[name].close) if err := subCollectors[name].build(); err != nil { errs = append(errs, fmt.Errorf("failed to build %s collector: %w", name, err)) continue } c.collectorFns = append(c.collectorFns, subCollectors[name].collect) } return errors.Join(errs...) } // Collect collects exchange metrics and sends them to prometheus. func (c *Collector) Collect(ch chan<- prometheus.Metric, _ time.Duration) error { var g recovery.Group for _, fn := range c.collectorFns { g.Go(func() error { return fn(ch) }) } return g.Wait() } // toLabelName converts strings to lowercase and replaces all whitespaces and dots with underscores. func (c *Collector) toLabelName(name string) string { s := strings.ReplaceAll(strings.Join(strings.Fields(strings.ToLower(name)), "_"), ".", "_") s = strings.ReplaceAll(s, "__", "_") return s }