Compare commits

...

6 Commits

Author SHA1 Message Date
Jan-Otto Kröpke
a2db81494e mssql: set windows_exporter_collector_success to 0, if errors occurs (#1777)
Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
2024-11-27 00:14:53 +01:00
Jan-Otto Kröpke
64bf0a6551 collector: don't fail if perf counters are empty. (#1776)
Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
2024-11-26 23:56:36 +01:00
Jan-Otto Kröpke
40b6f53479 textfile: set windows_exporter_collector_success to 0, if an errors occurs (#1775)
Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
2024-11-26 23:47:23 +01:00
Jan-Otto Kröpke
ca04ad8fd2 fix: use log.file=eventlog as default value, if running as windows service (#1771)
Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
2024-11-26 21:46:54 +01:00
Jan-Otto Kröpke
fd55ac4894 collector: refactor metrics handler (#1773)
Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
2024-11-26 21:13:47 +01:00
Jan-Otto Kröpke
c8eeb595c0 feat: Support OpenMetrics (#1772)
Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
2024-11-26 19:43:52 +01:00
18 changed files with 304 additions and 259 deletions

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="all" type="GoApplicationRunConfiguration" factoryName="Go Application" folderName="run"> <configuration default="false" name="all" type="GoApplicationRunConfiguration" factoryName="Go Application" folderName="run">
<module name="windows_exporter" /> <module name="windows_exporter" />
<working_directory value="$PROJECT_DIR$" /> <working_directory value="$PROJECT_DIR$" />
<parameters value="--web.listen-address=127.0.0.1:9182 --log.level=debug --collectors.enabled=ad,adcs,adfs,cache,container,cpu,cpu_info,cs,dfsr,dhcp,diskdrive,dns,exchange,filetime,fsrmquota,hyperv,iis,license,logical_disk,logon,memory,mscluster,msmq,mssql,net,netframework,nps,os,pagefile,perfdata,physical_disk,printer,process,remote_fx,scheduled_task,service,smb,smbclient,smtp,system,tcp,terminal_services,textfile,thermalzone,time,udp,update,vmware" /> <parameters value="--web.listen-address=127.0.0.1:9182 --log.level=info --collectors.enabled=ad,adcs,adfs,cache,container,cpu,cpu_info,cs,dfsr,dhcp,diskdrive,dns,exchange,filetime,fsrmquota,hyperv,iis,license,logical_disk,logon,memory,mscluster,msmq,mssql,net,netframework,nps,os,pagefile,perfdata,physical_disk,printer,process,remote_fx,scheduled_task,service,smb,smbclient,smtp,system,tcp,terminal_services,thermalzone,time,udp,update,vmware --debug.enabled" />
<sudo value="true" /> <sudo value="true" />
<kind value="PACKAGE" /> <kind value="PACKAGE" />
<package value="github.com/prometheus-community/windows_exporter/cmd/windows_exporter" /> <package value="github.com/prometheus-community/windows_exporter/cmd/windows_exporter" />

View File

@@ -199,6 +199,14 @@ Windows Server 2012 and 2012R2 are supported as best-effort only, but not guaran
The prometheus metrics will be exposed on [localhost:9182](http://localhost:9182) The prometheus metrics will be exposed on [localhost:9182](http://localhost:9182)
### HTTP Endpoints
windows_exporter provides the following HTTP endpoints:
* `/metrics`: Exposes metrics in the [Prometheus text format](https://prometheus.io/docs/instrumenting/exposition_formats/).
* `/health`: Returns 200 OK when the exporter is running.
* `/debug/pprof/`: Exposes the [pprof](https://golang.org/pkg/net/http/pprof/) endpoints. Only, if `--debug.enabled` is set.
## Examples ## Examples
### Enable only service collector and specify a custom query ### Enable only service collector and specify a custom query

View File

@@ -87,10 +87,6 @@ func run() int {
"web.disable-exporter-metrics", "web.disable-exporter-metrics",
"Exclude metrics about the exporter itself (promhttp_*, process_*, go_*).", "Exclude metrics about the exporter itself (promhttp_*, process_*, go_*).",
).Bool() ).Bool()
maxRequests = app.Flag(
"telemetry.max-requests",
"Maximum number of concurrent requests. 0 to disable.",
).Default("5").Int()
enabledCollectors = app.Flag( enabledCollectors = app.Flag(
"collectors.enabled", "collectors.enabled",
"Comma-separated list of collectors to use. Use '[defaults]' as a placeholder for all the collectors enabled by default."). "Comma-separated list of collectors to use. Use '[defaults]' as a placeholder for all the collectors enabled by default.").
@@ -109,7 +105,14 @@ func run() int {
).Default("normal").String() ).Default("normal").String()
) )
logConfig := &log.Config{} logFile := &log.AllowedFile{}
_ = logFile.Set("stdout")
if windowsservice.IsService {
_ = logFile.Set("eventlog")
}
logConfig := &log.Config{File: logFile}
flag.AddFlags(app, logConfig) flag.AddFlags(app, logConfig)
app.Version(version.Print("windows_exporter")) app.Version(version.Print("windows_exporter"))
@@ -220,7 +223,6 @@ func run() int {
mux.Handle("GET "+*metricsPath, httphandler.New(logger, collectors, &httphandler.Options{ mux.Handle("GET "+*metricsPath, httphandler.New(logger, collectors, &httphandler.Options{
DisableExporterMetrics: *disableExporterMetrics, DisableExporterMetrics: *disableExporterMetrics,
TimeoutMargin: *timeoutMargin, TimeoutMargin: *timeoutMargin,
MaxRequests: *maxRequests,
})) }))
if *debugEnabled { if *debugEnabled {

View File

@@ -13,8 +13,8 @@
~ limitations under the License. ~ limitations under the License.
--> -->
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" <Wix xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util"
xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util"> xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Fragment> <Fragment>
<DirectoryRef Id="APPLICATIONFOLDER"> <DirectoryRef Id="APPLICATIONFOLDER">
<Component Transitive="yes"> <Component Transitive="yes">
@@ -28,7 +28,7 @@
Start="auto" Start="auto"
Type="ownProcess" Type="ownProcess"
Vital="yes" Vital="yes"
Arguments="--log.file eventlog [ConfigFileFlag] [CollectorsFlag] [ListenFlag] [MetricsPathFlag] [TextfileDirsFlag] [ExtraFlags]"> Arguments="[ConfigFileFlag] [CollectorsFlag] [ListenFlag] [MetricsPathFlag] [TextfileDirsFlag] [ExtraFlags]">
<util:ServiceConfig <util:ServiceConfig
ResetPeriodInDays="1" ResetPeriodInDays="1"
FirstFailureActionType="restart" FirstFailureActionType="restart"

View File

@@ -24,9 +24,9 @@
~ limitations under the License. ~ limitations under the License.
--> -->
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" <Wix xmlns:fw="http://wixtoolset.org/schemas/v4/wxs/firewall"
xmlns:fw="http://wixtoolset.org/schemas/v4/wxs/firewall" xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui"
xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui"> xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Package UpgradeCode="66a6eb5b-1fc2-4b14-a362-5ceec6413308" Name="$(var.ProductName)" Version="$(var.Version)" <Package UpgradeCode="66a6eb5b-1fc2-4b14-a362-5ceec6413308" Name="$(var.ProductName)" Version="$(var.Version)"
Manufacturer="prometheus-community" Language="1033" Scope="perMachine"> Manufacturer="prometheus-community" Language="1033" Scope="perMachine">
<SummaryInformation Manufacturer="prometheus-community" Description="$(var.ProductName) $(var.Version) installer" /> <SummaryInformation Manufacturer="prometheus-community" Description="$(var.ProductName) $(var.Version) installer" />

View File

@@ -257,13 +257,15 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
// Result must order, to prevent test failures. // Result must order, to prevent test failures.
sort.Strings(c.config.CollectorsEnabled) sort.Strings(c.config.CollectorsEnabled)
errs := make([]error, 0, len(c.config.CollectorsEnabled))
for _, name := range c.config.CollectorsEnabled { for _, name := range c.config.CollectorsEnabled {
if _, ok := subCollectors[name]; !ok { if _, ok := subCollectors[name]; !ok {
return fmt.Errorf("unknown collector: %s", name) return fmt.Errorf("unknown collector: %s", name)
} }
if err := subCollectors[name].build(); err != nil { if err := subCollectors[name].build(); err != nil {
return fmt.Errorf("failed to build %s collector: %w", name, err) errs = append(errs, fmt.Errorf("failed to build %s collector: %w", name, err))
} }
c.collectorFns = append(c.collectorFns, subCollectors[name].collect) c.collectorFns = append(c.collectorFns, subCollectors[name].collect)
@@ -291,7 +293,7 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
nil, nil,
) )
return nil return errors.Join(errs...)
} }
// Collect sends the metric values for each metric // Collect sends the metric values for each metric
@@ -408,7 +410,7 @@ func (c *Collector) collect(
errs = append(errs, err) errs = append(errs, err)
success = 0.0 success = 0.0
c.logger.Error(fmt.Sprintf("mssql class collector %s for instance %s failed after %s", collector, sqlInstance, duration), c.logger.Debug(fmt.Sprintf("mssql class collector %s for instance %s failed after %s", collector, sqlInstance, duration),
slog.Any("err", err), slog.Any("err", err),
) )
} else { } else {

View File

@@ -304,8 +304,7 @@ func (c *Collector) collect(ch chan<- prometheus.Metric) error {
} }
for nicName, nicData := range data { for nicName, nicData := range data {
if c.config.NicExclude.MatchString(nicName) || if c.config.NicExclude.MatchString(nicName) || !c.config.NicInclude.MatchString(nicName) {
!c.config.NicInclude.MatchString(nicName) {
continue continue
} }

View File

@@ -54,7 +54,7 @@ type Collector struct {
// Only set for testing to get predictable output. // Only set for testing to get predictable output.
mTime *float64 mTime *float64
mTimeDesc *prometheus.Desc modTimeDesc *prometheus.Desc
} }
func New(config *Config) *Collector { func New(config *Config) *Collector {
@@ -105,9 +105,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 {
c.logger = logger.With(slog.String("collector", Name)) c.logger = logger.With(slog.String("collector", Name))
c.logger.Info("textfile Collector directories: " + strings.Join(c.config.TextFileDirectories, ",")) c.logger.Info("textfile directories: " + strings.Join(c.config.TextFileDirectories, ","))
c.mTimeDesc = prometheus.NewDesc( c.modTimeDesc = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, "textfile", "mtime_seconds"), prometheus.BuildFQName(types.Namespace, "textfile", "mtime_seconds"),
"Unixtime mtime of textfiles successfully read.", "Unixtime mtime of textfiles successfully read.",
[]string{"file"}, []string{"file"},
@@ -165,7 +165,7 @@ func (c *Collector) convertMetricFamily(logger *slog.Logger, metricFamily *dto.M
for _, metric := range metricFamily.GetMetric() { for _, metric := range metricFamily.GetMetric() {
if metric.TimestampMs != nil { if metric.TimestampMs != nil {
logger.Warn(fmt.Sprintf("Ignoring unsupported custom timestamp on textfile Collector metric %v", metric)) logger.Warn(fmt.Sprintf("Ignoring unsupported custom timestamp on textfile metric %v", metric))
} }
labels := metric.GetLabel() labels := metric.GetLabel()
@@ -259,23 +259,24 @@ func (c *Collector) convertMetricFamily(logger *slog.Logger, metricFamily *dto.M
} }
} }
func (c *Collector) exportMTimes(mTimes map[string]time.Time, ch chan<- prometheus.Metric) { func (c *Collector) exportMTimes(modTimes map[string]time.Time, ch chan<- prometheus.Metric) {
// Export the mtimes of the successful files. // Export the mtimes of the successful files.
if len(mTimes) > 0 { if len(modTimes) > 0 {
// Sorting is needed for predictable output comparison in tests. // Sorting is needed for predictable output comparison in tests.
filenames := make([]string, 0, len(mTimes)) filenames := make([]string, 0, len(modTimes))
for filename := range mTimes { for filename := range modTimes {
filenames = append(filenames, filename) filenames = append(filenames, filename)
} }
sort.Strings(filenames) sort.Strings(filenames)
for _, filename := range filenames { for _, filename := range filenames {
mtime := float64(mTimes[filename].UnixNano() / 1e9) modTime := float64(modTimes[filename].UnixNano() / 1e9)
if c.mTime != nil { if c.mTime != nil {
mtime = *c.mTime modTime = *c.mTime
} }
ch <- prometheus.MustNewConstMetric(c.mTimeDesc, prometheus.GaugeValue, mtime, filename)
ch <- prometheus.MustNewConstMetric(c.modTimeDesc, prometheus.GaugeValue, modTime, filename)
} }
} }
} }
@@ -314,6 +315,8 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
// This will ensure that duplicate metrics are correctly detected between multiple .prom files. // This will ensure that duplicate metrics are correctly detected between multiple .prom files.
var metricFamilies []*dto.MetricFamily var metricFamilies []*dto.MetricFamily
errs := make([]error, 0)
// Iterate over files and accumulate their metrics. // Iterate over files and accumulate their metrics.
for _, directory := range c.config.TextFileDirectories { for _, directory := range c.config.TextFileDirectories {
err := filepath.WalkDir(directory, func(path string, dirEntry os.DirEntry, err error) error { err := filepath.WalkDir(directory, func(path string, dirEntry os.DirEntry, err error) error {
@@ -326,24 +329,20 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
families_array, err := scrapeFile(path, c.logger) families_array, err := scrapeFile(path, c.logger)
if err != nil { if err != nil {
c.logger.Error(fmt.Sprintf("Error scraping file: %q. Skip File.", path), errs = append(errs, fmt.Errorf("error scraping file %q: %w", path, err))
slog.Any("err", err),
)
return nil return nil
} }
fileInfo, err := os.Stat(path) fileInfo, err := os.Stat(path)
if err != nil { if err != nil {
c.logger.Error(fmt.Sprintf("Error reading file info: %q. Skip File.", path), errs = append(errs, fmt.Errorf("error reading file info %q: %w", path, err))
slog.Any("err", err),
)
return nil return nil
} }
if _, hasName := mTimes[fileInfo.Name()]; hasName { if _, hasName := mTimes[fileInfo.Name()]; hasName {
c.logger.Error(fmt.Sprintf("Duplicate filename detected: %q. Skip File.", path)) errs = append(errs, fmt.Errorf("duplicate filename detected: %q", path))
return nil return nil
} }
@@ -355,25 +354,24 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
return nil return nil
}) })
if err != nil && directory != "" { if err != nil && directory != "" {
c.logger.Error("Error reading textfile Collector directory: "+directory, errs = append(errs, fmt.Errorf("error reading textfile directory %q: %w", directory, err))
slog.Any("err", err),
)
} }
} }
c.exportMTimes(mTimes, ch)
// If duplicates are detected across *multiple* files, return error. // If duplicates are detected across *multiple* files, return error.
if duplicateMetricEntry(metricFamilies) { if duplicateMetricEntry(metricFamilies) {
c.logger.Error("Duplicate metrics detected across multiple files") c.logger.Warn("duplicate metrics detected across multiple files")
} else { } else {
for _, mf := range metricFamilies { for _, mf := range metricFamilies {
c.convertMetricFamily(c.logger, mf, ch) c.convertMetricFamily(c.logger, mf, ch)
} }
} }
c.exportMTimes(mTimes, ch) return errors.Join(errs...)
return nil
} }
func scrapeFile(path string, logger *slog.Logger) ([]*dto.MetricFamily, error) { func scrapeFile(path string, logger *slog.Logger) ([]*dto.MetricFamily, error) {

View File

@@ -26,6 +26,7 @@ import (
"github.com/prometheus-community/windows_exporter/pkg/collector" "github.com/prometheus-community/windows_exporter/pkg/collector"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -48,30 +49,26 @@ func TestMultipleDirectories(t *testing.T) {
metrics := make(chan prometheus.Metric) metrics := make(chan prometheus.Metric)
got := "" got := ""
errCh := make(chan error, 1)
go func() { go func() {
for { errCh <- textFileCollector.Collect(metrics)
var metric dto.Metric
val := <-metrics close(metrics)
err := val.Write(&metric)
if err != nil {
t.Errorf("Unexpected error %s", err)
}
got += metric.String()
}
}() }()
err := textFileCollector.Collect(metrics) for val := range metrics {
if err != nil { var metric dto.Metric
t.Errorf("Unexpected error %s", err)
err := val.Write(&metric)
require.NoError(t, err)
got += metric.String()
} }
require.NoError(t, <-errCh)
for _, f := range []string{"dir1", "dir2", "dir3", "dir3sub"} { for _, f := range []string{"dir1", "dir2", "dir3", "dir3sub"} {
if !strings.Contains(got, f) { assert.Contains(t, got, f)
t.Errorf("Unexpected output %s: %q", f, got)
}
} }
} }
@@ -89,31 +86,24 @@ func TestDuplicateFileName(t *testing.T) {
metrics := make(chan prometheus.Metric) metrics := make(chan prometheus.Metric)
got := "" got := ""
errCh := make(chan error, 1)
go func() { go func() {
for { errCh <- textFileCollector.Collect(metrics)
var metric dto.Metric
val := <-metrics close(metrics)
err := val.Write(&metric)
if err != nil {
t.Errorf("Unexpected error %s", err)
}
got += metric.String()
}
}() }()
err := textFileCollector.Collect(metrics) for val := range metrics {
if err != nil { var metric dto.Metric
t.Errorf("Unexpected error %s", err)
err := val.Write(&metric)
require.NoError(t, err)
got += metric.String()
} }
if !strings.Contains(got, "file") { require.ErrorContains(t, <-errCh, "duplicate filename detected")
t.Errorf("Unexpected output %q", got)
}
if strings.Contains(got, "sub_file") { assert.Contains(t, got, "file")
t.Errorf("Unexpected output %q", got) assert.NotContains(t, got, "sub_file")
}
} }

View File

@@ -36,7 +36,7 @@ var _ http.Handler = (*MetricsHTTPHandler)(nil)
const defaultScrapeTimeout = 10.0 const defaultScrapeTimeout = 10.0
type MetricsHTTPHandler struct { type MetricsHTTPHandler struct {
metricCollectors *collector.MetricCollectors metricCollectors *collector.Collection
// exporterMetricsRegistry is a separate registry for the metrics about // exporterMetricsRegistry is a separate registry for the metrics about
// the exporter itself. // the exporter itself.
exporterMetricsRegistry *prometheus.Registry exporterMetricsRegistry *prometheus.Registry
@@ -49,15 +49,13 @@ type MetricsHTTPHandler struct {
type Options struct { type Options struct {
DisableExporterMetrics bool DisableExporterMetrics bool
TimeoutMargin float64 TimeoutMargin float64
MaxRequests int
} }
func New(logger *slog.Logger, metricCollectors *collector.MetricCollectors, options *Options) *MetricsHTTPHandler { func New(logger *slog.Logger, metricCollectors *collector.Collection, options *Options) *MetricsHTTPHandler {
if options == nil { if options == nil {
options = &Options{ options = &Options{
DisableExporterMetrics: false, DisableExporterMetrics: false,
TimeoutMargin: 0.5, TimeoutMargin: 0.5,
MaxRequests: 5,
} }
} }
@@ -65,7 +63,9 @@ func New(logger *slog.Logger, metricCollectors *collector.MetricCollectors, opti
metricCollectors: metricCollectors, metricCollectors: metricCollectors,
logger: logger, logger: logger,
options: *options, options: *options,
concurrencyCh: make(chan struct{}, options.MaxRequests),
// We are expose metrics directly from the memory region of the Win32 API. We should not allow more than one request at a time.
concurrencyCh: make(chan struct{}, 1),
} }
if !options.DisableExporterMetrics { if !options.DisableExporterMetrics {
@@ -126,82 +126,48 @@ func (c *MetricsHTTPHandler) getScrapeTimeout(logger *slog.Logger, r *http.Reque
func (c *MetricsHTTPHandler) handlerFactory(logger *slog.Logger, scrapeTimeout time.Duration, requestedCollectors []string) (http.Handler, error) { func (c *MetricsHTTPHandler) handlerFactory(logger *slog.Logger, scrapeTimeout time.Duration, requestedCollectors []string) (http.Handler, error) {
reg := prometheus.NewRegistry() reg := prometheus.NewRegistry()
var metricCollectors *collector.MetricCollectors
if len(requestedCollectors) == 0 {
metricCollectors = c.metricCollectors
} else {
filteredCollectors := make(collector.Map)
for _, name := range requestedCollectors {
metricCollector, ok := c.metricCollectors.Collectors[name]
if !ok {
return nil, fmt.Errorf("couldn't find collector %s", name)
}
filteredCollectors[name] = metricCollector
}
metricCollectors = &collector.MetricCollectors{
Collectors: filteredCollectors,
MISession: c.metricCollectors.MISession,
PerfCounterQuery: c.metricCollectors.PerfCounterQuery,
}
}
reg.MustRegister(version.NewCollector("windows_exporter")) reg.MustRegister(version.NewCollector("windows_exporter"))
if err := reg.Register(metricCollectors.NewPrometheusCollector(scrapeTimeout, c.logger)); err != nil { collectionHandler, err := c.metricCollectors.NewHandler(scrapeTimeout, c.logger, requestedCollectors)
if err != nil {
return nil, fmt.Errorf("couldn't create collector handler: %w", err)
}
if err := reg.Register(collectionHandler); err != nil {
return nil, fmt.Errorf("couldn't register Prometheus collector: %w", err) return nil, fmt.Errorf("couldn't register Prometheus collector: %w", err)
} }
var handler http.Handler var regHandler http.Handler
if c.exporterMetricsRegistry != nil { if c.exporterMetricsRegistry != nil {
handler = promhttp.HandlerFor( regHandler = promhttp.HandlerFor(
prometheus.Gatherers{c.exporterMetricsRegistry, reg}, prometheus.Gatherers{c.exporterMetricsRegistry, reg},
promhttp.HandlerOpts{ promhttp.HandlerOpts{
ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError), ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError),
ErrorHandling: promhttp.ContinueOnError, ErrorHandling: promhttp.ContinueOnError,
MaxRequestsInFlight: c.options.MaxRequests, MaxRequestsInFlight: 1,
Registry: c.exporterMetricsRegistry, Registry: c.exporterMetricsRegistry,
EnableOpenMetrics: true,
ProcessStartTime: c.metricCollectors.GetStartTime(),
}, },
) )
// Note that we have to use h.exporterMetricsRegistry here to // Note that we have to use h.exporterMetricsRegistry here to
// use the same promhttp metrics for all expositions. // use the same promhttp metrics for all expositions.
handler = promhttp.InstrumentMetricHandler( regHandler = promhttp.InstrumentMetricHandler(
c.exporterMetricsRegistry, handler, c.exporterMetricsRegistry, regHandler,
) )
} else { } else {
handler = promhttp.HandlerFor( regHandler = promhttp.HandlerFor(
reg, reg,
promhttp.HandlerOpts{ promhttp.HandlerOpts{
ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError), ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError),
ErrorHandling: promhttp.ContinueOnError, ErrorHandling: promhttp.ContinueOnError,
MaxRequestsInFlight: c.options.MaxRequests, MaxRequestsInFlight: 1,
EnableOpenMetrics: true,
ProcessStartTime: c.metricCollectors.GetStartTime(),
}, },
) )
} }
return c.withConcurrencyLimit(handler.ServeHTTP), nil return regHandler, nil
}
func (c *MetricsHTTPHandler) withConcurrencyLimit(next http.HandlerFunc) http.HandlerFunc {
if c.options.MaxRequests <= 0 {
return next
}
return func(w http.ResponseWriter, r *http.Request) {
select {
case c.concurrencyCh <- struct{}{}:
defer func() { <-c.concurrencyCh }()
default:
w.WriteHeader(http.StatusServiceUnavailable)
_, _ = w.Write([]byte("Too many concurrent requests"))
return
}
next(w, r)
}
} }

View File

@@ -34,6 +34,9 @@ func AddFlags(a *kingpin.Application, config *log.Config) {
config.Config = new(promslog.Config) config.Config = new(promslog.Config)
flag.AddFlags(a, config.Config) flag.AddFlags(a, config.Config)
config.File = &log.AllowedFile{} if config.File == nil {
a.Flag(FileFlagName, FileFlagHelp).Default("stderr").SetValue(config.File) config.File = &log.AllowedFile{}
}
a.Flag(FileFlagName, FileFlagHelp).Default(config.File.String()).SetValue(config.File)
} }

View File

@@ -34,6 +34,10 @@ type AllowedFile struct {
} }
func (f *AllowedFile) String() string { func (f *AllowedFile) String() string {
if f == nil {
return ""
}
return f.s return f.s
} }

View File

@@ -193,6 +193,13 @@ func (c *Collector) Collect() (CounterValues, error) {
} }
func (c *Collector) collectRoutine() { func (c *Collector) collectRoutine() {
var (
itemCount uint32
bytesNeeded uint32
)
buf := make([]byte, 1)
for range c.collectCh { for range c.collectCh {
if ret := PdhCollectQueryData(c.handle); ret != ErrorSuccess { if ret := PdhCollectQueryData(c.handle); ret != ErrorSuccess {
c.counterValuesCh <- nil c.counterValuesCh <- nil
@@ -207,25 +214,24 @@ func (c *Collector) collectRoutine() {
for _, counter := range c.counters { for _, counter := range c.counters {
for _, instance := range counter.Instances { for _, instance := range counter.Instances {
// Get the info with the current buffer size // Get the info with the current buffer size
var itemCount uint32 bytesNeeded = uint32(cap(buf))
// Get the info with the current buffer size for {
bufLen := uint32(0) ret := PdhGetRawCounterArray(instance, &bytesNeeded, &itemCount, &buf[0])
ret := PdhGetRawCounterArray(instance, &bufLen, &itemCount, nil) if ret == ErrorSuccess {
if ret != PdhMoreData { break
return nil, fmt.Errorf("PdhGetRawCounterArray: %w", NewPdhError(ret)) }
}
buf := make([]byte, bufLen) if err := NewPdhError(ret); ret != PdhMoreData && !isKnownCounterDataError(err) {
ret = PdhGetRawCounterArray(instance, &bufLen, &itemCount, &buf[0])
if ret != ErrorSuccess {
if err := NewPdhError(ret); !isKnownCounterDataError(err) {
return nil, fmt.Errorf("PdhGetRawCounterArray: %w", err) return nil, fmt.Errorf("PdhGetRawCounterArray: %w", err)
} }
continue if bytesNeeded <= uint32(cap(buf)) {
return nil, fmt.Errorf("PdhGetRawCounterArray reports buffer too small (%d), but buffer is large enough (%d): %w", uint32(cap(buf)), bytesNeeded, NewPdhError(ret))
}
buf = make([]byte, bytesNeeded)
} }
items := unsafe.Slice((*PdhRawCounterItem)(unsafe.Pointer(&buf[0])), itemCount) items := unsafe.Slice((*PdhRawCounterItem)(unsafe.Pointer(&buf[0])), itemCount)

View File

@@ -61,4 +61,6 @@ func BenchmarkTestCollector(b *testing.B) {
} }
performanceData.Close() performanceData.Close()
b.ReportAllocs()
} }

View File

@@ -11,8 +11,6 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build windows
package collector package collector
import ( import (
@@ -27,26 +25,10 @@ import (
"github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/mi"
"github.com/prometheus-community/windows_exporter/internal/perfdata" "github.com/prometheus-community/windows_exporter/internal/perfdata"
types "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus-community/windows_exporter/internal/types"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
) )
// Interface guard.
var _ prometheus.Collector = (*Prometheus)(nil)
// Prometheus implements prometheus.Collector for a set of Windows MetricCollectors.
type Prometheus struct {
maxScrapeDuration time.Duration
logger *slog.Logger
metricCollectors *MetricCollectors
// Base metrics returned by Prometheus
scrapeDurationDesc *prometheus.Desc
collectorScrapeDurationDesc *prometheus.Desc
collectorScrapeSuccessDesc *prometheus.Desc
collectorScrapeTimeoutDesc *prometheus.Desc
}
type collectorStatus struct { type collectorStatus struct {
name string name string
statusCode collectorStatusCode statusCode collectorStatusCode
@@ -60,64 +42,26 @@ const (
failed failed
) )
// NewPrometheusCollector returns a new Prometheus where the set of MetricCollectors must func (c *Collection) collectAll(ch chan<- prometheus.Metric, logger *slog.Logger, maxScrapeDuration time.Duration) {
// return metrics within the given timeout.
func (c *MetricCollectors) NewPrometheusCollector(timeout time.Duration, logger *slog.Logger) *Prometheus {
return &Prometheus{
maxScrapeDuration: timeout,
metricCollectors: c,
logger: logger,
scrapeDurationDesc: prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, "exporter", "scrape_duration_seconds"),
"windows_exporter: Total scrape duration.",
nil,
nil,
),
collectorScrapeDurationDesc: prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, "exporter", "collector_duration_seconds"),
"windows_exporter: Duration of a collection.",
[]string{"collector"},
nil,
),
collectorScrapeSuccessDesc: prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, "exporter", "collector_success"),
"windows_exporter: Whether the collector was successful.",
[]string{"collector"},
nil,
),
collectorScrapeTimeoutDesc: prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, "exporter", "collector_timeout"),
"windows_exporter: Whether the collector timed out.",
[]string{"collector"},
nil,
),
}
}
func (p *Prometheus) Describe(_ chan<- *prometheus.Desc) {}
// Collect sends the collected metrics from each of the MetricCollectors to
// prometheus.
func (p *Prometheus) Collect(ch chan<- prometheus.Metric) {
collectorStartTime := time.Now() collectorStartTime := time.Now()
// WaitGroup to wait for all collectors to finish // WaitGroup to wait for all collectors to finish
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(len(p.metricCollectors.Collectors)) wg.Add(len(c.collectors))
// Using a channel to collect the status of each collector // Using a channel to collect the status of each collector
// A channel is safe to use concurrently while a map is not // A channel is safe to use concurrently while a map is not
collectorStatusCh := make(chan collectorStatus, len(p.metricCollectors.Collectors)) collectorStatusCh := make(chan collectorStatus, len(c.collectors))
// Execute all collectors concurrently // Execute all collectors concurrently
// timeout handling is done in the execute function // timeout handling is done in the execute function
for name, metricsCollector := range p.metricCollectors.Collectors { for name, metricsCollector := range c.collectors {
go func(name string, metricsCollector Collector) { go func(name string, metricsCollector Collector) {
defer wg.Done() defer wg.Done()
collectorStatusCh <- collectorStatus{ collectorStatusCh <- collectorStatus{
name: name, name: name,
statusCode: p.execute(name, metricsCollector, ch), statusCode: c.collectCollector(ch, logger, name, metricsCollector, maxScrapeDuration),
} }
}(name, metricsCollector) }(name, metricsCollector)
} }
@@ -139,14 +83,14 @@ func (p *Prometheus) Collect(ch chan<- prometheus.Metric) {
} }
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
p.collectorScrapeSuccessDesc, c.collectorScrapeSuccessDesc,
prometheus.GaugeValue, prometheus.GaugeValue,
successValue, successValue,
status.name, status.name,
) )
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
p.collectorScrapeTimeoutDesc, c.collectorScrapeTimeoutDesc,
prometheus.GaugeValue, prometheus.GaugeValue,
timeoutValue, timeoutValue,
status.name, status.name,
@@ -154,13 +98,13 @@ func (p *Prometheus) Collect(ch chan<- prometheus.Metric) {
} }
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
p.scrapeDurationDesc, c.scrapeDurationDesc,
prometheus.GaugeValue, prometheus.GaugeValue,
time.Since(collectorStartTime).Seconds(), time.Since(collectorStartTime).Seconds(),
) )
} }
func (p *Prometheus) execute(name string, c Collector, ch chan<- prometheus.Metric) collectorStatusCode { func (c *Collection) collectCollector(ch chan<- prometheus.Metric, logger *slog.Logger, name string, collector Collector, maxScrapeDuration time.Duration) collectorStatusCode {
var ( var (
err error err error
numMetrics int numMetrics int
@@ -173,10 +117,10 @@ func (p *Prometheus) execute(name string, c Collector, ch chan<- prometheus.Metr
bufCh := make(chan prometheus.Metric, 1000) bufCh := make(chan prometheus.Metric, 1000)
errCh := make(chan error, 1) errCh := make(chan error, 1)
ctx, cancel := context.WithTimeout(context.Background(), p.maxScrapeDuration) ctx, cancel := context.WithTimeout(context.Background(), maxScrapeDuration)
defer cancel() defer cancel()
// Execute the collector // execute the collector
go func() { go func() {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@@ -188,7 +132,7 @@ func (p *Prometheus) execute(name string, c Collector, ch chan<- prometheus.Metr
close(bufCh) close(bufCh)
}() }()
errCh <- c.Collect(bufCh) errCh <- collector.Collect(bufCh)
}() }()
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
@@ -232,7 +176,7 @@ func (p *Prometheus) execute(name string, c Collector, ch chan<- prometheus.Metr
duration = time.Since(t) duration = time.Since(t)
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
p.collectorScrapeDurationDesc, c.collectorScrapeDurationDesc,
prometheus.GaugeValue, prometheus.GaugeValue,
duration.Seconds(), duration.Seconds(),
name, name,
@@ -242,13 +186,13 @@ func (p *Prometheus) execute(name string, c Collector, ch chan<- prometheus.Metr
duration = time.Since(t) duration = time.Since(t)
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
p.collectorScrapeDurationDesc, c.collectorScrapeDurationDesc,
prometheus.GaugeValue, prometheus.GaugeValue,
duration.Seconds(), duration.Seconds(),
name, name,
) )
p.logger.Warn(fmt.Sprintf("collector %s timeouted after %s, resulting in %d metrics", name, p.maxScrapeDuration, numMetrics)) logger.Warn(fmt.Sprintf("collector %s timeouted after %s, resulting in %d metrics", name, maxScrapeDuration, numMetrics))
go func() { go func() {
// Drain channel in case of premature return to not leak a goroutine. // Drain channel in case of premature return to not leak a goroutine.
@@ -260,13 +204,10 @@ func (p *Prometheus) execute(name string, c Collector, ch chan<- prometheus.Metr
return pending return pending
} }
if err != nil { if err != nil && !errors.Is(err, perfdata.ErrNoData) && !errors.Is(err, types.ErrNoData) {
loggerFn := p.logger.Warn loggerFn := logger.Warn
if errors.Is(err, types.ErrNoData) || if errors.Is(err, perfdata.ErrPerformanceCounterNotInitialized) || errors.Is(err, mi.MI_RESULT_INVALID_NAMESPACE) {
errors.Is(err, perfdata.ErrNoData) || loggerFn = logger.Debug
errors.Is(err, perfdata.ErrPerformanceCounterNotInitialized) ||
errors.Is(err, mi.MI_RESULT_INVALID_NAMESPACE) {
loggerFn = p.logger.Debug
} }
loggerFn(fmt.Sprintf("collector %s failed after %s, resulting in %d metrics", name, duration, numMetrics), loggerFn(fmt.Sprintf("collector %s failed after %s, resulting in %d metrics", name, duration, numMetrics),
@@ -276,7 +217,7 @@ func (p *Prometheus) execute(name string, c Collector, ch chan<- prometheus.Metr
return failed return failed
} }
p.logger.Debug(fmt.Sprintf("collector %s succeeded after %s, resulting in %d metrics", name, duration, numMetrics)) logger.Debug(fmt.Sprintf("collector %s succeeded after %s, resulting in %d metrics", name, duration, numMetrics))
return success return success
} }

View File

@@ -19,8 +19,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
"maps"
"slices" "slices"
"sync" "sync"
gotime "time"
"github.com/alecthomas/kingpin/v2" "github.com/alecthomas/kingpin/v2"
"github.com/prometheus-community/windows_exporter/internal/collector/ad" "github.com/prometheus-community/windows_exporter/internal/collector/ad"
@@ -72,10 +74,12 @@ import (
"github.com/prometheus-community/windows_exporter/internal/collector/update" "github.com/prometheus-community/windows_exporter/internal/collector/update"
"github.com/prometheus-community/windows_exporter/internal/collector/vmware" "github.com/prometheus-community/windows_exporter/internal/collector/vmware"
"github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/mi"
"github.com/prometheus-community/windows_exporter/internal/types"
"github.com/prometheus/client_golang/prometheus"
) )
// NewWithFlags To be called by the exporter for collector initialization before running kingpin.Parse. // NewWithFlags To be called by the exporter for collector initialization before running kingpin.Parse.
func NewWithFlags(app *kingpin.Application) *MetricCollectors { func NewWithFlags(app *kingpin.Application) *Collection {
collectors := map[string]Collector{} collectors := map[string]Collector{}
for name, builder := range BuildersWithFlags { for name, builder := range BuildersWithFlags {
@@ -88,7 +92,7 @@ func NewWithFlags(app *kingpin.Application) *MetricCollectors {
// NewWithConfig To be called by the external libraries for collector initialization without running [kingpin.Parse]. // NewWithConfig To be called by the external libraries for collector initialization without running [kingpin.Parse].
// //
//goland:noinspection GoUnusedExportedFunction //goland:noinspection GoUnusedExportedFunction
func NewWithConfig(config Config) *MetricCollectors { func NewWithConfig(config Config) *Collection {
collectors := Map{} collectors := Map{}
collectors[ad.Name] = ad.New(&config.AD) collectors[ad.Name] = ad.New(&config.AD)
collectors[adcs.Name] = adcs.New(&config.ADCS) collectors[adcs.Name] = adcs.New(&config.ADCS)
@@ -143,23 +147,48 @@ func NewWithConfig(config Config) *MetricCollectors {
} }
// New To be called by the external libraries for collector initialization. // New To be called by the external libraries for collector initialization.
func New(collectors Map) *MetricCollectors { func New(collectors Map) *Collection {
return &MetricCollectors{ return &Collection{
Collectors: collectors, collectors: collectors,
concurrencyCh: make(chan struct{}, 1),
scrapeDurationDesc: prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, "exporter", "scrape_duration_seconds"),
"windows_exporter: Total scrape duration.",
nil,
nil,
),
collectorScrapeDurationDesc: prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, "exporter", "collector_duration_seconds"),
"windows_exporter: Duration of a collection.",
[]string{"collector"},
nil,
),
collectorScrapeSuccessDesc: prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, "exporter", "collector_success"),
"windows_exporter: Whether the collector was successful.",
[]string{"collector"},
nil,
),
collectorScrapeTimeoutDesc: prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, "exporter", "collector_timeout"),
"windows_exporter: Whether the collector timed out.",
[]string{"collector"},
nil,
),
} }
} }
// Enable removes all collectors that not enabledCollectors. // Enable removes all collectors that not enabledCollectors.
func (c *MetricCollectors) Enable(enabledCollectors []string) error { func (c *Collection) Enable(enabledCollectors []string) error {
for _, name := range enabledCollectors { for _, name := range enabledCollectors {
if _, ok := c.Collectors[name]; !ok { if _, ok := c.collectors[name]; !ok {
return fmt.Errorf("unknown collector %s", name) return fmt.Errorf("unknown collector %s", name)
} }
} }
for name := range c.Collectors { for name := range c.collectors {
if !slices.Contains(enabledCollectors, name) { if !slices.Contains(enabledCollectors, name) {
delete(c.Collectors, name) delete(c.collectors, name)
} }
} }
@@ -167,23 +196,25 @@ func (c *MetricCollectors) Enable(enabledCollectors []string) error {
} }
// Build To be called by the exporter for collector initialization. // Build To be called by the exporter for collector initialization.
func (c *MetricCollectors) Build(logger *slog.Logger) error { func (c *Collection) Build(logger *slog.Logger) error {
c.startTime = gotime.Now()
err := c.initMI() err := c.initMI()
if err != nil { if err != nil {
return fmt.Errorf("error from initialize MI: %w", err) return fmt.Errorf("error from initialize MI: %w", err)
} }
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(len(c.Collectors)) wg.Add(len(c.collectors))
errCh := make(chan error, len(c.Collectors)) errCh := make(chan error, len(c.collectors))
errs := make([]error, 0, len(c.Collectors)) errs := make([]error, 0, len(c.collectors))
for _, collector := range c.Collectors { for _, collector := range c.collectors {
go func() { go func() {
defer wg.Done() defer wg.Done()
if err = collector.Build(logger, c.MISession); err != nil { if err = collector.Build(logger, c.miSession); err != nil {
errCh <- fmt.Errorf("error build collector %s: %w", collector.GetName(), err) errCh <- fmt.Errorf("error build collector %s: %w", collector.GetName(), err)
} }
}() }()
@@ -201,21 +232,21 @@ func (c *MetricCollectors) Build(logger *slog.Logger) error {
} }
// Close To be called by the exporter for collector cleanup. // Close To be called by the exporter for collector cleanup.
func (c *MetricCollectors) Close() error { func (c *Collection) Close() error {
errs := make([]error, 0, len(c.Collectors)) errs := make([]error, 0, len(c.collectors))
for _, collector := range c.Collectors { for _, collector := range c.collectors {
if err := collector.Close(); err != nil { if err := collector.Close(); err != nil {
errs = append(errs, fmt.Errorf("error from close collector %s: %w", collector.GetName(), err)) errs = append(errs, fmt.Errorf("error from close collector %s: %w", collector.GetName(), err))
} }
} }
app, err := c.MISession.GetApplication() app, err := c.miSession.GetApplication()
if err != nil && !errors.Is(err, mi.ErrNotInitialized) { if err != nil && !errors.Is(err, mi.ErrNotInitialized) {
errs = append(errs, fmt.Errorf("error from get MI application: %w", err)) errs = append(errs, fmt.Errorf("error from get MI application: %w", err))
} }
if err := c.MISession.Close(); err != nil && !errors.Is(err, mi.ErrNotInitialized) { if err := c.miSession.Close(); err != nil && !errors.Is(err, mi.ErrNotInitialized) {
errs = append(errs, fmt.Errorf("error from close MI session: %w", err)) errs = append(errs, fmt.Errorf("error from close MI session: %w", err))
} }
@@ -226,8 +257,8 @@ func (c *MetricCollectors) Close() error {
return errors.Join(errs...) return errors.Join(errs...)
} }
// Close To be called by the exporter for collector cleanup. // initMI To be called by the exporter for collector initialization.
func (c *MetricCollectors) initMI() error { func (c *Collection) initMI() error {
app, err := mi.Application_Initialize() app, err := mi.Application_Initialize()
if err != nil { if err != nil {
return fmt.Errorf("error from initialize MI application: %w", err) return fmt.Errorf("error from initialize MI application: %w", err)
@@ -242,10 +273,34 @@ func (c *MetricCollectors) initMI() error {
return fmt.Errorf("error from set locale: %w", err) return fmt.Errorf("error from set locale: %w", err)
} }
c.MISession, err = app.NewSession(destinationOptions) c.miSession, err = app.NewSession(destinationOptions)
if err != nil { if err != nil {
return fmt.Errorf("error from create NewSession: %w", err) return fmt.Errorf("error from create NewSession: %w", err)
} }
return nil return nil
} }
// WithCollectors To be called by the exporter for collector initialization.
func (c *Collection) WithCollectors(collectors []string) (*Collection, error) {
metricCollectors := &Collection{
miSession: c.miSession,
startTime: c.startTime,
concurrencyCh: c.concurrencyCh,
scrapeDurationDesc: c.scrapeDurationDesc,
collectorScrapeDurationDesc: c.collectorScrapeDurationDesc,
collectorScrapeSuccessDesc: c.collectorScrapeSuccessDesc,
collectorScrapeTimeoutDesc: c.collectorScrapeTimeoutDesc,
collectors: maps.Clone(c.collectors),
}
if err := metricCollectors.Enable(collectors); err != nil {
return nil, err
}
return metricCollectors, nil
}
func (c *Collection) GetStartTime() gotime.Time {
return c.startTime
}

62
pkg/collector/handler.go Normal file
View File

@@ -0,0 +1,62 @@
// 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.
//go:build windows
package collector
import (
"fmt"
"log/slog"
"time"
"github.com/prometheus/client_golang/prometheus"
)
// Interface guard.
var _ prometheus.Collector = (*Handler)(nil)
// Handler implements [prometheus.Collector] for a set of Windows Collection.
type Handler struct {
maxScrapeDuration time.Duration
logger *slog.Logger
collection *Collection
}
// NewHandler returns a new Handler that implements a [prometheus.Collector] for the given metrics Collection.
func (c *Collection) NewHandler(maxScrapeDuration time.Duration, logger *slog.Logger, collectors []string) (*Handler, error) {
collection := c
if len(collectors) != 0 {
var err error
collection, err = c.WithCollectors(collectors)
if err != nil {
return nil, fmt.Errorf("failed to create handler with collectors: %w", err)
}
}
return &Handler{
maxScrapeDuration: maxScrapeDuration,
collection: collection,
logger: logger,
}, nil
}
func (p *Handler) Describe(_ chan<- *prometheus.Desc) {}
// Collect sends the collected metrics from each of the Collection to
// prometheus.
func (p *Handler) Collect(ch chan<- prometheus.Metric) {
p.collection.collectAll(ch, p.logger, p.maxScrapeDuration)
}

View File

@@ -17,6 +17,7 @@ package collector
import ( import (
"log/slog" "log/slog"
"time"
"github.com/alecthomas/kingpin/v2" "github.com/alecthomas/kingpin/v2"
"github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/mi"
@@ -25,10 +26,16 @@ import (
const DefaultCollectors = "cpu,cs,memory,logical_disk,physical_disk,net,os,service,system" const DefaultCollectors = "cpu,cs,memory,logical_disk,physical_disk,net,os,service,system"
type MetricCollectors struct { type Collection struct {
Collectors Map collectors Map
MISession *mi.Session miSession *mi.Session
PerfCounterQuery string startTime time.Time
concurrencyCh chan struct{}
scrapeDurationDesc *prometheus.Desc
collectorScrapeDurationDesc *prometheus.Desc
collectorScrapeSuccessDesc *prometheus.Desc
collectorScrapeTimeoutDesc *prometheus.Desc
} }
type ( type (