diff --git a/internal/collector/performancecounter/performancecounter.go b/internal/collector/performancecounter/performancecounter.go index 1924abc8..b4f5697b 100644 --- a/internal/collector/performancecounter/performancecounter.go +++ b/internal/collector/performancecounter/performancecounter.go @@ -20,6 +20,7 @@ import ( "fmt" "log/slog" "reflect" + "regexp" "slices" "strings" "time" @@ -34,6 +35,8 @@ import ( const Name = "performancecounter" +var reNonAlphaNum = regexp.MustCompile(`[^a-zA-Z0-9]`) + type Config struct { Objects []Object `yaml:"objects"` } @@ -51,8 +54,6 @@ type Collector struct { objects []Object - metricNameReplacer *strings.Replacer - // meta subCollectorScrapeDurationDesc *prometheus.Desc subCollectorScrapeSuccessDesc *prometheus.Desc @@ -115,15 +116,6 @@ 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)) @@ -152,7 +144,7 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { for j, counter := range object.Counters { if counter.Metric == "" { - c.config.Objects[i].Counters[j].Metric = c.sanitizeMetricName( + c.config.Objects[i].Counters[j].Metric = sanitizeMetricName( fmt.Sprintf("%s_%s_%s_%s", types.Namespace, Name, object.Object, counter.Name), ) } @@ -171,11 +163,27 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { } counters = append(counters, counter.Name) - fields = append(fields, reflect.StructField{ - Name: strings.ToUpper(c.sanitizeMetricName(counter.Name)), - Type: reflect.TypeOf(float64(0)), - Tag: reflect.StructTag(fmt.Sprintf(`perfdata:"%s"`, counter.Name)), - }) + + field, err := func(name string) (_ reflect.StructField, err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("failed to create field for %s: %v", name, r) + } + }() + + return reflect.StructField{ + Name: strings.ToUpper(sanitizeMetricName(name)), + Type: reflect.TypeOf(float64(0)), + Tag: reflect.StructTag(fmt.Sprintf(`perfdata:"%s"`, name)), + }, nil + }(counter.Name) + if err != nil { + errs = append(errs, err) + + continue + } + + fields = append(fields, field) } if object.Instances != nil { @@ -276,7 +284,7 @@ func (c *Collector) collectObject(ch chan<- prometheus.Metric, perfDataObject Ob for _, counter := range perfDataObject.Counters { val := reflect.ValueOf(sliceValue).Index(i) - field := val.FieldByName(strings.ToUpper(c.sanitizeMetricName(counter.Name))) + field := val.FieldByName(strings.ToUpper(sanitizeMetricName(counter.Name))) if !field.IsValid() { errs = append(errs, fmt.Errorf("%s not found in collected data", counter.Name)) @@ -355,6 +363,6 @@ func (c *Collector) collectObject(ch chan<- prometheus.Metric, perfDataObject Ob return errors.Join(errs...) } -func (c *Collector) sanitizeMetricName(name string) string { - return strings.Trim(c.metricNameReplacer.Replace(strings.ToLower(name)), "_") +func sanitizeMetricName(name string) string { + return strings.Trim(reNonAlphaNum.ReplaceAllString(strings.ToLower(name), "_"), "_") } diff --git a/internal/collector/performancecounter/performancecounter_test_test.go b/internal/collector/performancecounter/performancecounter_test_test.go index 6d482c4b..a9c54195 100644 --- a/internal/collector/performancecounter/performancecounter_test_test.go +++ b/internal/collector/performancecounter/performancecounter_test_test.go @@ -25,6 +25,7 @@ import ( "testing" "github.com/prometheus-community/windows_exporter/internal/collector/performancecounter" + "github.com/prometheus-community/windows_exporter/internal/pdh" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/stretchr/testify/assert" @@ -126,6 +127,14 @@ windows_performancecounter_processor_information_processor_time\{core="0,0",stat counters: []performancecounter.Counter{{Name: "Available Bytes", Type: "gauge"}, {Name: "Available Bytes", Type: "gauge"}}, expectedMetrics: nil, }, + { + name: "counter with spaces and brackets", + object: "invalid", + instances: nil, + buildErr: pdh.NewPdhError(pdh.CstatusNoObject).Error(), + counters: []performancecounter.Counter{{Name: "Total Memory Usage --- Non-Paged Pool", Type: "counter"}, {Name: "Max Session Input Delay (ms)", Type: "counter"}}, + expectedMetrics: nil, + }, } { t.Run(tc.name, func(t *testing.T) { t.Parallel() diff --git a/internal/collector/vmware/vmware.go b/internal/collector/vmware/vmware.go index c61b8e60..a34ec44d 100644 --- a/internal/collector/vmware/vmware.go +++ b/internal/collector/vmware/vmware.go @@ -93,11 +93,19 @@ func (c *Collector) Close() error { } func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { - var err error + var ( + err error + errs []error + ) c.perfDataCollectorCPU, err = pdh.NewCollector[perfDataCounterValuesCPU]("VM Processor", pdh.InstancesTotal) if err != nil { - return fmt.Errorf("failed to create VM Processor collector: %w", err) + errs = append(errs, fmt.Errorf("failed to create VM Processor collector: %w", err)) + } + + c.perfDataCollectorMemory, err = pdh.NewCollector[perfDataCounterValuesMemory]("VM Memory", nil) + if err != nil { + errs = append(errs, fmt.Errorf("failed to create VM Memory collector: %w", err)) } c.cpuLimitMHz = prometheus.NewDesc( @@ -143,11 +151,6 @@ func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { nil, ) - c.perfDataCollectorMemory, err = pdh.NewCollector[perfDataCounterValuesMemory]("VM Memory", nil) - if err != nil { - return fmt.Errorf("failed to create VM Memory collector: %w", err) - } - c.memActive = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "mem_active_bytes"), "The estimated amount of memory the virtual machine is actively using.", @@ -221,7 +224,7 @@ func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { nil, ) - return nil + return errors.Join(errs...) } // Collect sends the metric values for each metric