diff --git a/.run/all.run.xml b/.run/all.run.xml
index 65ec0a71d..5b5035b88 100644
--- a/.run/all.run.xml
+++ b/.run/all.run.xml
@@ -2,7 +2,7 @@
-
+
@@ -10,4 +10,4 @@
-
\ No newline at end of file
+
diff --git a/cmd/windows_exporter/main.go b/cmd/windows_exporter/main.go
index f28d3a20c..63a47d10c 100644
--- a/cmd/windows_exporter/main.go
+++ b/cmd/windows_exporter/main.go
@@ -207,9 +207,11 @@ func run() int {
// Initialize collectors before loading
if err = collectors.Build(logger); err != nil {
for _, err := range utils.SplitError(err) {
- logger.Warn("couldn't initialize collector",
+ logger.Error("couldn't initialize collector",
slog.Any("err", err),
)
+
+ return 1
}
}
diff --git a/config.yaml b/config.yaml
index 07222f730..d26e5f893 100644
--- a/config.yaml
+++ b/config.yaml
@@ -1,9 +1,16 @@
# example configuration file for windows_exporter
collectors:
- enabled: cpu,cpu_info,exchange,iis,logical_disk,logon,memory,net,os,process,remote_fx,service,system,tcp,time,terminal_services,textfile
+ enabled: cpu,cpu_info,exchange,iis,logical_disk,logon,memory,net,os,performancecounter,process,remote_fx,service,system,tcp,time,terminal_services,textfile
collector:
service:
include: "windows_exporter"
+ performancecounter:
+ objects: |-
+ - name: memory
+ object: "Memory"
+ counters:
+ - name: "Cache Faults/sec"
+ type: "counter" # optional
log:
level: warn
diff --git a/docs/collector.performancecounter.md b/docs/collector.performancecounter.md
index 778b6f0bd..16c906f02 100644
--- a/docs/collector.performancecounter.md
+++ b/docs/collector.performancecounter.md
@@ -13,9 +13,28 @@ The performancecounter collector exposes any configured metric.
### `--collector.performancecounter.objects`
-Objects is a list of objects to collect metrics from. The value takes the form of a JSON array of strings. YAML is also supported.
+Objects is a list of objects to collect metrics from. The value takes the form of a JSON array of strings.
+YAML is supported.
-The collector supports only english named counter. Localized counter-names are not supported.
+The collector supports only English-named counter. Localized counter-names aren’t supported.
+
+> [!CAUTION]
+> If you are using a configuration file, the value must be kept as a string.
+>
+> Use a `|-` to keep the value as a string.
+
+#### Example
+
+```yaml
+collector:
+ performancecounter:
+ objects: |-
+ - name: memory
+ object: "Memory"
+ counters:
+ - name: "Cache Faults/sec"
+ type: "counter" # optional
+```
#### Schema
@@ -25,7 +44,8 @@ YAML:
Click to expand YAML schema
```yaml
-- object: "Processor Information"
+- name: cpu # free text name
+ object: "Processor Information" # Performance counter object name
instances: ["*"]
instance_label: "core"
counters:
@@ -37,7 +57,8 @@ YAML:
metric: windows_performancecounter_processor_information_processor_time # optional
labels:
state: idle
-- object: "Memory"
+- name: memory
+ object: "Memory"
counters:
- name: "Cache Faults/sec"
type: "counter" # optional
@@ -51,6 +72,7 @@ YAML:
```json
[
{
+ "name": "cpu",
"object": "Processor Information",
"instances": [
"*"
@@ -74,6 +96,7 @@ YAML:
]
},
{
+ "name": "memory",
"object": "Memory",
"counters": [
{
@@ -86,6 +109,11 @@ YAML:
```
+#### name
+
+The name is used to identify the object in the logs and metrics.
+Must unique across all objects.
+
#### object
ObjectName is the Object to query for, like Processor, DirectoryServices, LogicalDisk or similar.
@@ -186,6 +214,17 @@ windows_performancecounter_processor_information_processor_time{core="0,8",state
windows_performancecounter_processor_information_processor_time{core="0,9",state="active"} 1.0059484375e+11
windows_performancecounter_processor_information_processor_time{core="0,9",state="idle"} 10059.484375
```
+> [!NOTE]
+> If you are using a configuration file, the value must be keep as string.
+
+Example:
+
+```yaml
+collector:
+ performancecounter:
+ objects: |
+```
+
## Metrics
diff --git a/internal/collector/mssql/mssql.go b/internal/collector/mssql/mssql.go
index db87764db..6d851b356 100644
--- a/internal/collector/mssql/mssql.go
+++ b/internal/collector/mssql/mssql.go
@@ -387,7 +387,7 @@ func (c *Collector) collect(
slog.Any("err", err),
)
} else {
- c.logger.Debug(fmt.Sprintf("mssql class collector %s for instance %s succeeded after %s.", collector, sqlInstance, duration))
+ c.logger.Debug(fmt.Sprintf("mssql class collector %s for instance %s succeeded after %s", collector, sqlInstance, duration))
}
ch <- prometheus.MustNewConstMetric(
diff --git a/internal/collector/performancecounter/performancecounter.go b/internal/collector/performancecounter/performancecounter.go
index aed4b0c22..99452a07b 100644
--- a/internal/collector/performancecounter/performancecounter.go
+++ b/internal/collector/performancecounter/performancecounter.go
@@ -16,16 +16,19 @@
package performancecounter
import (
- "encoding/json"
+ "errors"
"fmt"
"log/slog"
+ "slices"
"strings"
+ "time"
"github.com/alecthomas/kingpin/v2"
"github.com/prometheus-community/windows_exporter/internal/mi"
"github.com/prometheus-community/windows_exporter/internal/perfdata"
"github.com/prometheus-community/windows_exporter/internal/types"
"github.com/prometheus/client_golang/prometheus"
+ "gopkg.in/yaml.v3"
)
const Name = "performancecounter"
@@ -44,6 +47,14 @@ type Collector struct {
config Config
logger *slog.Logger
+
+ objects []Object
+
+ metricNameReplacer *strings.Replacer
+
+ // meta
+ subCollectorScrapeDurationDesc *prometheus.Desc
+ subCollectorScrapeSuccessDesc *prometheus.Desc
}
func New(config *Config) *Collector {
@@ -79,7 +90,7 @@ func NewWithFlags(app *kingpin.Application) *Collector {
return nil
}
- if err := json.Unmarshal([]byte(objects), &c.config.Objects); err != nil {
+ if err := yaml.Unmarshal([]byte(objects), &c.config.Objects); err != nil {
return fmt.Errorf("failed to parse objects: %w", err)
}
@@ -104,107 +115,199 @@ func (c *Collector) Close() error {
func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
c.logger = logger.With(slog.String("collector", Name))
+ c.metricNameReplacer = strings.NewReplacer(
+ ".", "",
+ "%", "",
+ "/", "_",
+ " ", "_",
+ "-", "_",
+ )
+
+ c.objects = make([]Object, 0, len(c.config.Objects))
+ names := make([]string, 0, len(c.config.Objects))
+
+ var errs []error
+
for i, object := range c.config.Objects {
+ if object.Name == "" {
+ return errors.New("object name is required")
+ }
+
+ if object.Object == "" {
+ errs = append(errs, fmt.Errorf("object %s: object is required", object.Name))
+
+ continue
+ }
+
+ if slices.Contains(names, object.Name) {
+ errs = append(errs, fmt.Errorf("object %s: name is duplicated", object.Name))
+
+ continue
+ }
+
+ names = append(names, object.Name)
counters := make([]string, 0, len(object.Counters))
- for j, counter := range object.Counters {
- counters = append(counters, counter.Name)
+ for j, counter := range object.Counters {
if counter.Metric == "" {
- c.config.Objects[i].Counters[j].Metric = sanitizeMetricName(fmt.Sprintf("%s_%s_%s_%s", types.Namespace, Name, object.Object, counter.Name))
+ c.config.Objects[i].Counters[j].Metric = c.sanitizeMetricName(
+ fmt.Sprintf("%s_%s_%s_%s", types.Namespace, Name, object.Object, counter.Name),
+ )
+ }
+
+ if counter.Name == "" {
+ errs = append(errs, errors.New("counter name is required"))
+ c.config.Objects = slices.Delete(c.config.Objects, i, 1)
+
+ continue
+ }
+
+ if slices.Contains(counters, counter.Name) {
+ errs = append(errs, fmt.Errorf("counter name %s is duplicated", counter.Name))
+
+ continue
}
+
+ counters = append(counters, counter.Name)
}
collector, err := perfdata.NewCollector(object.Object, object.Instances, counters)
if err != nil {
- return fmt.Errorf("failed to create v2 collector: %w", err)
+ errs = append(errs, fmt.Errorf("failed collector for %s: %w", object.Name, err))
}
if object.InstanceLabel == "" {
- c.config.Objects[i].InstanceLabel = "instance"
+ object.InstanceLabel = "instance"
}
- c.config.Objects[i].collector = collector
+ object.collector = collector
+
+ c.objects = append(c.objects, object)
}
- return nil
+ c.subCollectorScrapeDurationDesc = prometheus.NewDesc(
+ prometheus.BuildFQName(types.Namespace, Name, "collector_duration_seconds"),
+ "windows_exporter: Duration of an performancecounter child collection.",
+ []string{"collector"},
+ nil,
+ )
+ c.subCollectorScrapeSuccessDesc = prometheus.NewDesc(
+ prometheus.BuildFQName(types.Namespace, Name, "collector_success"),
+ "windows_exporter: Whether a performancecounter child collector was successful.",
+ []string{"collector"},
+ nil,
+ )
+
+ return errors.Join(errs...)
}
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
- for _, perfDataObject := range c.config.Objects {
- collectedPerfData, err := perfDataObject.collector.Collect()
+ var errs []error
+
+ for _, perfDataObject := range c.objects {
+ startTime := time.Now()
+ err := c.collectObject(ch, perfDataObject)
+ duration := time.Since(startTime)
+ success := 1.0
+
if err != nil {
- return fmt.Errorf("failed to collect data: %w", err)
+ errs = append(errs, fmt.Errorf("failed to collect object %s: %w", perfDataObject.Name, err))
+ success = 0.0
+
+ c.logger.Debug(fmt.Sprintf("performancecounter collector %s failed after %s", perfDataObject.Name, duration),
+ slog.Any("err", err),
+ )
+ } else {
+ c.logger.Debug(fmt.Sprintf("performancecounter collector %s succeeded after %s", perfDataObject.Name, duration))
}
- for collectedInstance, collectedInstanceCounters := range collectedPerfData {
- for _, counter := range perfDataObject.Counters {
- collectedCounterValue, ok := collectedInstanceCounters[counter.Name]
- if !ok {
- c.logger.Warn(fmt.Sprintf("counter %s not found in collected data", counter.Name))
+ ch <- prometheus.MustNewConstMetric(
+ c.subCollectorScrapeSuccessDesc,
+ prometheus.GaugeValue,
+ success,
+ perfDataObject.Name,
+ )
+
+ ch <- prometheus.MustNewConstMetric(
+ c.subCollectorScrapeDurationDesc,
+ prometheus.GaugeValue,
+ duration.Seconds(),
+ perfDataObject.Name,
+ )
+ }
+
+ return errors.Join(errs...)
+}
+
+func (c *Collector) collectObject(ch chan<- prometheus.Metric, perfDataObject Object) error {
+ collectedPerfData, err := perfDataObject.collector.Collect()
+ if err != nil {
+ return fmt.Errorf("failed to collect data: %w", err)
+ }
+
+ var errs []error
- continue
- }
+ for collectedInstance, collectedInstanceCounters := range collectedPerfData {
+ for _, counter := range perfDataObject.Counters {
+ collectedCounterValue, ok := collectedInstanceCounters[counter.Name]
+ if !ok {
+ errs = append(errs, fmt.Errorf("counter %s not found in collected data", counter.Name))
- labels := make(prometheus.Labels, len(counter.Labels)+1)
- if collectedInstance != perfdata.InstanceEmpty {
- labels[perfDataObject.InstanceLabel] = collectedInstance
- }
+ continue
+ }
+
+ labels := make(prometheus.Labels, len(counter.Labels)+1)
+
+ if collectedInstance != perfdata.InstanceEmpty {
+ labels[perfDataObject.InstanceLabel] = collectedInstance
+ }
- for key, value := range counter.Labels {
- labels[key] = value
- }
+ for key, value := range counter.Labels {
+ labels[key] = value
+ }
- var metricType prometheus.ValueType
+ var metricType prometheus.ValueType
- switch counter.Type {
- case "counter":
- metricType = prometheus.CounterValue
- case "gauge":
- metricType = prometheus.GaugeValue
- default:
- metricType = collectedCounterValue.Type
- }
+ switch counter.Type {
+ case "counter":
+ metricType = prometheus.CounterValue
+ case "gauge":
+ metricType = prometheus.GaugeValue
+ default:
+ metricType = collectedCounterValue.Type
+ }
+ ch <- prometheus.MustNewConstMetric(
+ prometheus.NewDesc(
+ counter.Metric,
+ "windows_exporter: custom Performance Counter metric",
+ nil,
+ labels,
+ ),
+ metricType,
+ collectedCounterValue.FirstValue,
+ )
+
+ if collectedCounterValue.SecondValue != 0 {
ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(
- counter.Metric,
+ counter.Metric+"_second",
"windows_exporter: custom Performance Counter metric",
nil,
labels,
),
metricType,
- collectedCounterValue.FirstValue,
+ collectedCounterValue.SecondValue,
)
-
- if collectedCounterValue.SecondValue != 0 {
- ch <- prometheus.MustNewConstMetric(
- prometheus.NewDesc(
- counter.Metric+"_second",
- "windows_exporter: custom Performance Counter metric",
- nil,
- labels,
- ),
- metricType,
- collectedCounterValue.SecondValue,
- )
- }
}
}
}
- return nil
+ return errors.Join(errs...)
}
-func sanitizeMetricName(name string) string {
- replacer := strings.NewReplacer(
- ".", "",
- "%", "",
- "/", "_",
- " ", "_",
- "-", "_",
- )
-
- return strings.Trim(replacer.Replace(strings.ToLower(name)), "_")
+func (c *Collector) sanitizeMetricName(name string) string {
+ return strings.Trim(c.metricNameReplacer.Replace(strings.ToLower(name)), "_")
}
diff --git a/internal/collector/performancecounter/performancecounter_test_test.go b/internal/collector/performancecounter/performancecounter_test_test.go
index 6424156d3..6d482c4bb 100644
--- a/internal/collector/performancecounter/performancecounter_test_test.go
+++ b/internal/collector/performancecounter/performancecounter_test_test.go
@@ -49,38 +49,91 @@ func TestCollector(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
+ name string
object string
instances []string
instanceLabel string
+ buildErr string
counters []performancecounter.Counter
expectedMetrics *regexp.Regexp
}{
{
- object: "Memory",
- instances: nil,
- counters: []performancecounter.Counter{{Name: "Available Bytes", Type: "gauge"}},
- expectedMetrics: regexp.MustCompile(`^# HELP windows_performancecounter_memory_available_bytes windows_exporter: custom Performance Counter metric\S*\s*# TYPE windows_performancecounter_memory_available_bytes gauge\s*windows_performancecounter_memory_available_bytes \d`),
+ name: "memory",
+ object: "Memory",
+ instances: nil,
+ buildErr: "",
+ counters: []performancecounter.Counter{{Name: "Available Bytes", Type: "gauge"}},
+ expectedMetrics: regexp.MustCompile(`^# HELP windows_performancecounter_collector_duration_seconds windows_exporter: Duration of an performancecounter child collection.
+# TYPE windows_performancecounter_collector_duration_seconds gauge
+windows_performancecounter_collector_duration_seconds\{collector="memory"} [0-9.e+-]+
+# HELP windows_performancecounter_collector_success windows_exporter: Whether a performancecounter child collector was successful.
+# TYPE windows_performancecounter_collector_success gauge
+windows_performancecounter_collector_success\{collector="memory"} 1
+# HELP windows_performancecounter_memory_available_bytes windows_exporter: custom Performance Counter metric
+# TYPE windows_performancecounter_memory_available_bytes gauge
+windows_performancecounter_memory_available_bytes [0-9.e+-]+`),
+ },
+ {
+ name: "process",
+ object: "Process",
+ instances: []string{"*"},
+ buildErr: "",
+ counters: []performancecounter.Counter{{Name: "Thread Count", Type: "counter"}},
+ expectedMetrics: regexp.MustCompile(`^# HELP windows_performancecounter_collector_duration_seconds windows_exporter: Duration of an performancecounter child collection.
+# TYPE windows_performancecounter_collector_duration_seconds gauge
+windows_performancecounter_collector_duration_seconds\{collector="process"} [0-9.e+-]+
+# HELP windows_performancecounter_collector_success windows_exporter: Whether a performancecounter child collector was successful.
+# TYPE windows_performancecounter_collector_success gauge
+windows_performancecounter_collector_success\{collector="process"} 1
+# HELP windows_performancecounter_process_thread_count windows_exporter: custom Performance Counter metric
+# TYPE windows_performancecounter_process_thread_count counter
+windows_performancecounter_process_thread_count\{instance=".+"} [0-9.e+-]+
+.*`),
},
{
- object: "Process",
- instances: []string{"*"},
- counters: []performancecounter.Counter{{Name: "Thread Count", Type: "counter"}},
- expectedMetrics: regexp.MustCompile(`^# HELP windows_performancecounter_process_thread_count windows_exporter: custom Performance Counter metric\S*\s*# TYPE windows_performancecounter_process_thread_count counter\s*windows_performancecounter_process_thread_count\{instance=".+"} \d`),
+ name: "processor_information",
+ object: "Processor Information",
+ instances: []string{"*"},
+ instanceLabel: "core",
+ buildErr: "",
+ counters: []performancecounter.Counter{{Name: "% Processor Time", Metric: "windows_performancecounter_processor_information_processor_time", Labels: map[string]string{"state": "active"}}, {Name: "% Idle Time", Metric: "windows_performancecounter_processor_information_processor_time", Labels: map[string]string{"state": "idle"}}},
+ expectedMetrics: regexp.MustCompile(`^# HELP windows_performancecounter_collector_duration_seconds windows_exporter: Duration of an performancecounter child collection.
+# TYPE windows_performancecounter_collector_duration_seconds gauge
+windows_performancecounter_collector_duration_seconds\{collector="processor_information"} [0-9.e+-]+
+# HELP windows_performancecounter_collector_success windows_exporter: Whether a performancecounter child collector was successful.
+# TYPE windows_performancecounter_collector_success gauge
+windows_performancecounter_collector_success\{collector="processor_information"} 1
+# HELP windows_performancecounter_processor_information_processor_time windows_exporter: custom Performance Counter metric
+# TYPE windows_performancecounter_processor_information_processor_time counter
+windows_performancecounter_processor_information_processor_time\{core="0,0",state="active"} [0-9.e+-]+
+windows_performancecounter_processor_information_processor_time\{core="0,0",state="idle"} [0-9.e+-]+
+.*`),
},
{
+ name: "",
object: "Processor Information",
- instances: []string{"*"},
- instanceLabel: "core",
- counters: []performancecounter.Counter{{Name: "% Processor Time", Metric: "windows_performancecounter_processor_information_processor_time", Labels: map[string]string{"state": "active"}}, {Name: "% Idle Time", Metric: "windows_performancecounter_processor_information_processor_time", Labels: map[string]string{"state": "idle"}}},
- expectedMetrics: regexp.MustCompile(`^# HELP windows_performancecounter_processor_information_processor_time windows_exporter: custom Performance Counter metric\s+# TYPE windows_performancecounter_processor_information_processor_time counter\s+windows_performancecounter_processor_information_processor_time\{core="0,0",state="active"} [0-9.e+]+\s+windows_performancecounter_processor_information_processor_time\{core="0,0",state="idle"} [0-9.e+]+`),
+ instances: nil,
+ instanceLabel: "",
+ buildErr: "object name is required",
+ counters: nil,
+ expectedMetrics: nil,
+ },
+ {
+ name: "double_counter",
+ object: "Memory",
+ instances: nil,
+ buildErr: "counter name Available Bytes is duplicated",
+ counters: []performancecounter.Counter{{Name: "Available Bytes", Type: "gauge"}, {Name: "Available Bytes", Type: "gauge"}},
+ expectedMetrics: nil,
},
} {
- t.Run(tc.object, func(t *testing.T) {
+ t.Run(tc.name, func(t *testing.T) {
t.Parallel()
perfDataCollector := performancecounter.New(&performancecounter.Config{
Objects: []performancecounter.Object{
{
+ Name: tc.name,
Object: tc.object,
Instances: tc.instances,
InstanceLabel: tc.instanceLabel,
@@ -91,6 +144,13 @@ func TestCollector(t *testing.T) {
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
err := perfDataCollector.Build(logger, nil)
+
+ if tc.buildErr != "" {
+ require.ErrorContains(t, err, tc.buildErr)
+
+ return
+ }
+
require.NoError(t, err)
registry := prometheus.NewRegistry()
@@ -101,6 +161,7 @@ func TestCollector(t *testing.T) {
got := rw.Body.String()
assert.NotEmpty(t, got)
+ require.NotEmpty(t, tc.expectedMetrics)
assert.Regexp(t, tc.expectedMetrics, got)
})
}
diff --git a/internal/collector/performancecounter/types.go b/internal/collector/performancecounter/types.go
index 3fe91c0a3..a01d56918 100644
--- a/internal/collector/performancecounter/types.go
+++ b/internal/collector/performancecounter/types.go
@@ -18,6 +18,7 @@ package performancecounter
import "github.com/prometheus-community/windows_exporter/internal/perfdata"
type Object struct {
+ Name string `json:"name" yaml:"name"`
Object string `json:"object" yaml:"object"`
Instances []string `json:"instances" yaml:"instances"`
Counters []Counter `json:"counters" yaml:"counters"`
diff --git a/internal/types/errors.go b/internal/types/errors.go
index da4f4c1c1..3694d7cc9 100644
--- a/internal/types/errors.go
+++ b/internal/types/errors.go
@@ -13,9 +13,7 @@
package types
-import (
- "errors"
-)
+import "errors"
var (
ErrCollectorNotInitialized = errors.New("collector not initialized")
diff --git a/pkg/collector/collect.go b/pkg/collector/collect.go
index c684dc691..233bd27a7 100644
--- a/pkg/collector/collect.go
+++ b/pkg/collector/collect.go
@@ -206,8 +206,9 @@ func (c *Collection) collectCollector(ch chan<- prometheus.Metric, logger *slog.
if err != nil && !errors.Is(err, perfdata.ErrNoData) && !errors.Is(err, types.ErrNoData) {
loggerFn := logger.Warn
+
if errors.Is(err, perfdata.ErrPerformanceCounterNotInitialized) || errors.Is(err, mi.MI_RESULT_INVALID_NAMESPACE) {
- loggerFn = logger.Debug
+ err = fmt.Errorf("%w. Check application logs from initialization pharse for more information", err)
}
loggerFn(fmt.Sprintf("collector %s failed after %s, resulting in %d metrics", name, duration, numMetrics),
diff --git a/pkg/collector/collection.go b/pkg/collector/collection.go
index de71a2d68..c79f36386 100644
--- a/pkg/collector/collection.go
+++ b/pkg/collector/collection.go
@@ -74,6 +74,7 @@ import (
"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/mi"
+ "github.com/prometheus-community/windows_exporter/internal/perfdata"
"github.com/prometheus-community/windows_exporter/internal/types"
"github.com/prometheus/client_golang/prometheus"
)
@@ -196,6 +197,8 @@ func (c *Collection) Enable(enabledCollectors []string) error {
}
// Build To be called by the exporter for collector initialization.
+// Instead, fail fast, it will try to build all collectors and return all errors.
+// errors are joined with errors.Join.
func (c *Collection) Build(logger *slog.Logger) error {
c.startTime = gotime.Now()
@@ -208,7 +211,6 @@ func (c *Collection) Build(logger *slog.Logger) error {
wg.Add(len(c.collectors))
errCh := make(chan error, len(c.collectors))
- errs := make([]error, 0, len(c.collectors))
for _, collector := range c.collectors {
go func() {
@@ -224,7 +226,20 @@ func (c *Collection) Build(logger *slog.Logger) error {
close(errCh)
+ errs := make([]error, 0, len(c.collectors))
+
for err := range errCh {
+ if errors.Is(err, perfdata.ErrNoData) ||
+ errors.Is(err, perfdata.NewPdhError(perfdata.PdhCstatusNoObject)) ||
+ errors.Is(err, perfdata.NewPdhError(perfdata.PdhCstatusNoCounter)) ||
+ errors.Is(err, mi.MI_RESULT_INVALID_NAMESPACE) {
+ logger.Warn("couldn't initialize collector",
+ slog.Any("err", err),
+ )
+
+ continue
+ }
+
errs = append(errs, err)
}
diff --git a/pkg/collector/map.go b/pkg/collector/map.go
index ec45e7751..957aebfe9 100644
--- a/pkg/collector/map.go
+++ b/pkg/collector/map.go
@@ -128,6 +128,9 @@ var BuildersWithFlags = map[string]BuilderWithFlags[Collector]{
vmware.Name: NewBuilderWithFlags(vmware.NewWithFlags),
}
+// Available returns a sorted list of available collectors.
+//
+//goland:noinspection GoUnusedExportedFunction
func Available() []string {
return slices.Sorted(maps.Keys(BuildersWithFlags))
}
diff --git a/tools/e2e-output.txt b/tools/e2e-output.txt
index 680ed5e96..c03cfbd02 100644
--- a/tools/e2e-output.txt
+++ b/tools/e2e-output.txt
@@ -319,6 +319,10 @@ windows_exporter_collector_timeout{collector="udp"} 0
# TYPE windows_pagefile_free_bytes gauge
# HELP windows_pagefile_limit_bytes Number of bytes that can be stored in the operating system paging files. 0 (zero) indicates that there are no paging files
# TYPE windows_pagefile_limit_bytes gauge
+# HELP windows_performancecounter_collector_duration_seconds windows_exporter: Duration of an performancecounter child collection.
+# TYPE windows_performancecounter_collector_duration_seconds gauge
+# HELP windows_performancecounter_collector_success windows_exporter: Whether a performancecounter child collector was successful.
+# TYPE windows_performancecounter_collector_success gauge
# HELP windows_performancecounter_memory_cache_faults_sec windows_exporter: custom Performance Counter metric
# TYPE windows_performancecounter_memory_cache_faults_sec counter
# HELP windows_performancecounter_processor_information_processor_time windows_exporter: custom Performance Counter metric
diff --git a/tools/end-to-end-test.ps1 b/tools/end-to-end-test.ps1
index 85e5bc9cb..49de5b5a8 100644
--- a/tools/end-to-end-test.ps1
+++ b/tools/end-to-end-test.ps1
@@ -26,7 +26,7 @@ $exporter_proc = Start-Process `
-PassThru `
-FilePath ..\windows_exporter.exe `
-ArgumentList "--log.level=debug","--web.disable-exporter-metrics","--collectors.enabled=[defaults],cpu_info,textfile,process,pagefile,performancecounter,scheduled_task,tcp,udp,time,system,service,logical_disk,printer,os,net,memory,logon,cache","--collector.process.include=explorer.exe","--collector.scheduled_task.include=.*GAEvents","--collector.service.include=Themes","--collector.textfile.directories=$($textfile_dir)",@"
---collector.performancecounter.objects="[{\"object\":\"Processor Information\",\"instances\":[\"*\"],\"instance_label\":\"core\",\"counters\":[{\"name\":\"% Processor Time\",\"metric\":\"windows_performancecounter_processor_information_processor_time\",\"labels\":{\"state\":\"active\"}},{\"name\":\"% Idle Time\",\"metric\":\"windows_performancecounter_processor_information_processor_time\",\"labels\":{\"state\":\"idle\"}}]},{\"object\":\"Memory\",\"counters\":[{\"name\":\"Cache Faults/sec\",\"type\":\"counter\"}]}]"
+--collector.performancecounter.objects="[{\"name\":\"cpu\",\"object\":\"Processor Information\",\"instances\":[\"*\"],\"instance_label\":\"core\",\"counters\":[{\"name\":\"% Processor Time\",\"metric\":\"windows_performancecounter_processor_information_processor_time\",\"labels\":{\"state\":\"active\"}},{\"name\":\"% Idle Time\",\"metric\":\"windows_performancecounter_processor_information_processor_time\",\"labels\":{\"state\":\"idle\"}}]},{\"name\":\"memory\",\"object\":\"Memory\",\"counters\":[{\"name\":\"Cache Faults/sec\",\"type\":\"counter\"}]}]"
"@ `
-WindowStyle Hidden `
-RedirectStandardOutput "$($temp_dir)/windows_exporter.log" `