mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-03-10 04:26:35 +00:00
performancecounter: rename collector (#1787)
Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
210
internal/collector/performancecounter/performancecounter.go
Normal file
210
internal/collector/performancecounter/performancecounter.go
Normal file
@@ -0,0 +1,210 @@
|
||||
// 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 performancecounter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
const Name = "performancecounter"
|
||||
|
||||
type Config struct {
|
||||
Objects []Object `yaml:"objects"`
|
||||
}
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var ConfigDefaults = Config{
|
||||
Objects: make([]Object, 0),
|
||||
}
|
||||
|
||||
// A Collector is a Prometheus collector for performance counter metrics.
|
||||
type Collector struct {
|
||||
config Config
|
||||
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func New(config *Config) *Collector {
|
||||
if config == nil {
|
||||
config = &ConfigDefaults
|
||||
}
|
||||
|
||||
if config.Objects == nil {
|
||||
config.Objects = ConfigDefaults.Objects
|
||||
}
|
||||
|
||||
c := &Collector{
|
||||
config: *config,
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func NewWithFlags(app *kingpin.Application) *Collector {
|
||||
c := &Collector{
|
||||
config: ConfigDefaults,
|
||||
}
|
||||
|
||||
var objects string
|
||||
|
||||
app.Flag(
|
||||
"collector.performancecounter.objects",
|
||||
"Objects of performance data to observe. See docs for more information on how to use this flag. By default, no objects are observed.",
|
||||
).Default("").StringVar(&objects)
|
||||
|
||||
app.Action(func(*kingpin.ParseContext) error {
|
||||
if objects == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(objects), &c.config.Objects); err != nil {
|
||||
return fmt.Errorf("failed to parse objects: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Collector) GetName() string {
|
||||
return Name
|
||||
}
|
||||
|
||||
func (c *Collector) Close() error {
|
||||
for _, object := range c.config.Objects {
|
||||
object.collector.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
||||
c.logger = logger.With(slog.String("collector", Name))
|
||||
|
||||
for i, object := range c.config.Objects {
|
||||
counters := make([]string, 0, len(object.Counters))
|
||||
for j, counter := range object.Counters {
|
||||
counters = append(counters, counter.Name)
|
||||
|
||||
if counter.Metric == "" {
|
||||
c.config.Objects[i].Counters[j].Metric = sanitizeMetricName(fmt.Sprintf("%s_%s_%s_%s", types.Namespace, Name, object.Object, counter.Name))
|
||||
}
|
||||
}
|
||||
|
||||
collector, err := perfdata.NewCollector(object.Object, object.Instances, counters)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create v2 collector: %w", err)
|
||||
}
|
||||
|
||||
if object.InstanceLabel == "" {
|
||||
c.config.Objects[i].InstanceLabel = "instance"
|
||||
}
|
||||
|
||||
c.config.Objects[i].collector = collector
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to collect data: %w", err)
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
var metricType prometheus.ValueType
|
||||
|
||||
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+"_second",
|
||||
"windows_exporter: custom Performance Counter metric",
|
||||
nil,
|
||||
labels,
|
||||
),
|
||||
metricType,
|
||||
collectedCounterValue.SecondValue,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func sanitizeMetricName(name string) string {
|
||||
replacer := strings.NewReplacer(
|
||||
".", "",
|
||||
"%", "",
|
||||
"/", "_",
|
||||
" ", "_",
|
||||
"-", "_",
|
||||
)
|
||||
|
||||
return strings.Trim(replacer.Replace(strings.ToLower(name)), "_")
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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 performancecounter_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/performancecounter"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils/testutils"
|
||||
)
|
||||
|
||||
func BenchmarkCollector(b *testing.B) {
|
||||
perfDataObjects := `[{"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"}]}]`
|
||||
kingpin.CommandLine.GetArg("collector.perfdata.objects").StringVar(&perfDataObjects)
|
||||
|
||||
testutils.FuncBenchmarkCollector(b, performancecounter.Name, performancecounter.NewWithFlags)
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// 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 performancecounter_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/performancecounter"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type collectorAdapter struct {
|
||||
performancecounter.Collector
|
||||
}
|
||||
|
||||
// Describe implements the prometheus.Collector interface.
|
||||
func (a collectorAdapter) Describe(_ chan<- *prometheus.Desc) {}
|
||||
|
||||
// Collect implements the prometheus.Collector interface.
|
||||
func (a collectorAdapter) Collect(ch chan<- prometheus.Metric) {
|
||||
if err := a.Collector.Collect(ch); err != nil {
|
||||
panic(fmt.Sprintf("failed to update collector: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range []struct {
|
||||
object string
|
||||
instances []string
|
||||
instanceLabel 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`),
|
||||
},
|
||||
{
|
||||
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`),
|
||||
},
|
||||
{
|
||||
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+]+`),
|
||||
},
|
||||
} {
|
||||
t.Run(tc.object, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
perfDataCollector := performancecounter.New(&performancecounter.Config{
|
||||
Objects: []performancecounter.Object{
|
||||
{
|
||||
Object: tc.object,
|
||||
Instances: tc.instances,
|
||||
InstanceLabel: tc.instanceLabel,
|
||||
Counters: tc.counters,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||
err := perfDataCollector.Build(logger, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
registry := prometheus.NewRegistry()
|
||||
registry.MustRegister(collectorAdapter{*perfDataCollector})
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
promhttp.HandlerFor(registry, promhttp.HandlerOpts{ErrorHandling: promhttp.ContinueOnError}).ServeHTTP(rw, &http.Request{})
|
||||
got := rw.Body.String()
|
||||
|
||||
assert.NotEmpty(t, got)
|
||||
assert.Regexp(t, tc.expectedMetrics, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
36
internal/collector/performancecounter/types.go
Normal file
36
internal/collector/performancecounter/types.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// 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 performancecounter
|
||||
|
||||
import "github.com/prometheus-community/windows_exporter/internal/perfdata"
|
||||
|
||||
type Object struct {
|
||||
Object string `json:"object" yaml:"object"`
|
||||
Instances []string `json:"instances" yaml:"instances"`
|
||||
Counters []Counter `json:"counters" yaml:"counters"`
|
||||
InstanceLabel string `json:"instance_label" yaml:"instance_label"` //nolint:tagliatelle
|
||||
|
||||
collector *perfdata.Collector
|
||||
}
|
||||
|
||||
type Counter struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Type string `json:"type" yaml:"type"`
|
||||
Metric string `json:"metric" yaml:"metric"`
|
||||
Labels map[string]string `json:"labels" yaml:"labels"`
|
||||
}
|
||||
|
||||
// https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/54691ebe11bb9ec32b4e35cd31fcb94a352de134/receiver/windowsperfcountersreceiver/README.md?plain=1#L150
|
||||
Reference in New Issue
Block a user