performancecounter: rename collector (#1787)

Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
Jan-Otto Kröpke
2024-11-30 11:24:01 +01:00
committed by GitHub
parent a359acb3d1
commit 9db94aa66a
13 changed files with 452 additions and 323 deletions

View 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)), "_")
}

View File

@@ -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)
}

View File

@@ -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)
})
}
}

View 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