mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-02-08 05:56:37 +00:00
performancecounter: support yaml documents and tolerate collector errors (#1809)
Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
<configuration default="false" name="all" type="GoApplicationRunConfiguration" factoryName="Go Application" folderName="run">
|
||||
<module name="windows_exporter" />
|
||||
<working_directory value="$PROJECT_DIR$" />
|
||||
<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,performancecounter,physical_disk,printer,process,remote_fx,scheduled_task,service,smb,smbclient,smtp,system,tcp,terminal_services,thermalzone,time,udp,update,vmware --debug.enabled" />
|
||||
<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,performancecounter,physical_disk,printer,process,remote_fx,scheduled_task,service,smb,smbclient,smtp,system,tcp,terminal_services,thermalzone,time,udp,update,vmware,performancecounter --debug.enabled --collector.performancecounter.objects='[{ "name": "memory", "object": "Memory", "counters": [{ "name":"Cache Faults/sc", "type":"counter" }]}]'" />
|
||||
<sudo value="true" />
|
||||
<kind value="PACKAGE" />
|
||||
<package value="github.com/prometheus-community/windows_exporter/cmd/windows_exporter" />
|
||||
@@ -10,4 +10,4 @@
|
||||
<filePath value="$PROJECT_DIR$/exporter.go" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
</component>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
<summary>Click to expand YAML schema</summary>
|
||||
|
||||
```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:
|
||||
```
|
||||
</details>
|
||||
|
||||
#### 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
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,101 +115,7 @@ func (c *Collector) Close() error {
|
||||
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(
|
||||
c.metricNameReplacer = strings.NewReplacer(
|
||||
".", "",
|
||||
"%", "",
|
||||
"/", "_",
|
||||
@@ -206,5 +123,191 @@ func sanitizeMetricName(name string) string {
|
||||
"-", "_",
|
||||
)
|
||||
|
||||
return strings.Trim(replacer.Replace(strings.ToLower(name)), "_")
|
||||
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 {
|
||||
if counter.Metric == "" {
|
||||
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 {
|
||||
errs = append(errs, fmt.Errorf("failed collector for %s: %w", object.Name, err))
|
||||
}
|
||||
|
||||
if object.InstanceLabel == "" {
|
||||
object.InstanceLabel = "instance"
|
||||
}
|
||||
|
||||
object.collector = collector
|
||||
|
||||
c.objects = append(c.objects, object)
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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))
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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))
|
||||
|
||||
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 errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (c *Collector) sanitizeMetricName(name string) string {
|
||||
return strings.Trim(c.metricNameReplacer.Replace(strings.ToLower(name)), "_")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}{
|
||||
{
|
||||
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+-]+
|
||||
.*`),
|
||||
},
|
||||
{
|
||||
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: nil,
|
||||
instanceLabel: "",
|
||||
buildErr: "object name is required",
|
||||
counters: nil,
|
||||
expectedMetrics: nil,
|
||||
},
|
||||
{
|
||||
name: "double_counter",
|
||||
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+]+`),
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -13,9 +13,7 @@
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrCollectorNotInitialized = errors.New("collector not initialized")
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" `
|
||||
|
||||
Reference in New Issue
Block a user