Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/alecthomas/kingpin/v2"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/procfs"
)

// Namespace defines the common namespace to be used by all metrics.
Expand Down Expand Up @@ -53,7 +54,8 @@ var (
initiatedCollectorsMtx = sync.Mutex{}
initiatedCollectors = make(map[string]Collector)
collectorState = make(map[string]*bool)
forcedCollectors = map[string]bool{} // collectors which have been explicitly enabled or disabled
forcedCollectors = map[string]bool{} // collectors which have been explicitly enabled or disabled
collectorDefaults = make(map[string]bool) // tracks default enabled/disabled state for OTEL context
)

func registerCollector(collector string, isDefaultEnabled bool, factory func(logger *slog.Logger) (Collector, error)) {
Expand All @@ -64,6 +66,9 @@ func registerCollector(collector string, isDefaultEnabled bool, factory func(log
helpDefaultState = "disabled"
}

// Store the default state for OTEL context initialization
collectorDefaults[collector] = isDefaultEnabled

flagName := fmt.Sprintf("collector.%s", collector)
flagHelp := fmt.Sprintf("Enable the %s collector (default: %s).", collector, helpDefaultState)
defaultValue := fmt.Sprintf("%v", isDefaultEnabled)
Expand Down Expand Up @@ -243,3 +248,43 @@ func pushMetric(ch chan<- prometheus.Metric, fieldDesc *prometheus.Desc, name st

ch <- prometheus.MustNewConstMetric(fieldDesc, valueType, fVal, labelValues...)
}

// InitializeCollectorStateForOTEL initializes the collector state for OTEL context
// without relying on kingpin flag parsing. This should be called before creating
// any NodeCollector instances in the OTEL context.
func InitializeCollectorStateForOTEL() {
// Initialize collector enable/disable state from stored defaults
for collectorName, defaultEnabled := range collectorDefaults {
enabled := defaultEnabled
collectorState[collectorName] = &enabled
}

// Initialize collector-specific configuration flags with their defaults
initializeCollectorFlags()
}

// initializeCollectorFlags sets default values for collector-specific flags
// that are normally initialized by kingpin parsing
func initializeCollectorFlags() {
// Initialize path flags with their defaults (from paths.go)
if procPath == nil {
defaultProcPath := procfs.DefaultMountPoint
procPath = &defaultProcPath
}
if sysPath == nil {
defaultSysPath := "/sys"
sysPath = &defaultSysPath
}
if rootfsPath == nil {
defaultRootfsPath := "/"
rootfsPath = &defaultRootfsPath
}
if udevDataPath == nil {
defaultUdevDataPath := "/run/udev/data"
udevDataPath = &defaultUdevDataPath
}

// Initialize other collector-specific flags with empty/default values
// These will be nil pointers until kingpin.Parse() is called, but collectors
// should handle nil gracefully or we can initialize them as needed
}
29 changes: 29 additions & 0 deletions otelcollector/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 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.

package otelcollector

import "fmt"

type Config struct {
DisableDefaults bool `mapstructure:"disable_defaults"`
EnableCollectors []string `mapstructure:"enable_collectors"`
ExcludeCollectors []string `mapstructure:"exclude_collectors"`
}

func (c Config) Validate() error {
if len(c.EnableCollectors) > 0 && len(c.ExcludeCollectors) > 0 {
return fmt.Errorf("%q and %q can't be used at the same time", "EnableCollectors", "ExcludeCollectors")
}
return nil
}
19 changes: 19 additions & 0 deletions otelcollector/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 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.

// Package otelcollector implements the OpenTelemetry Collector receiver interface
// for the Prometheus Node Exporter.
//
// Use this go module to embed the Prometheus Node Exporter in your OpenTelemetry Collector
// using OCB (OpenTelemetry Collector Builder).
package otelcollector
131 changes: 131 additions & 0 deletions otelcollector/exporter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 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.

package otelcollector

import (
"context"
"errors"
"fmt"
"log/slog"

"github.com/prometheus/client_golang/prometheus"
versioncollector "github.com/prometheus/client_golang/prometheus/collectors/version"
"github.com/prometheus/exporter-toolkit/otlpreceiver"
"github.com/prometheus/node_exporter/collector"
)

type NodeExporter struct {
config *Config
registry *prometheus.Registry
}

func NewNodeExporter(config *Config) *NodeExporter {
return &NodeExporter{
config: config,
registry: prometheus.NewRegistry(),
}
}

func (ne *NodeExporter) Initialize(ctx context.Context, cfg otlpreceiver.Config) (*prometheus.Registry, error) {
if cfg != nil {
var ok bool
ne.config, ok = cfg.(*Config)
if !ok {
return nil, errors.New("error reading configuration")
}
} else {
// Use default configuration when none is provided
ne.config = &Config{
DisableDefaults: false,
EnableCollectors: []string{},
ExcludeCollectors: []string{},
}
}

// Create logger for collectors
logger := slog.Default().With("component", "node_exporter")

collector.InitializeCollectorStateForOTEL()

// Apply disable defaults configuration
if ne.config.DisableDefaults {
collector.DisableDefaultCollectors()
}

// Determine which collectors to create based on configuration
var filters []string

// Validate that both EnableCollectors and ExcludeCollectors are not used together
if len(ne.config.EnableCollectors) > 0 && len(ne.config.ExcludeCollectors) > 0 {
return nil, fmt.Errorf("enable_collectors and exclude_collectors cannot be used together")
}

if len(ne.config.EnableCollectors) > 0 {
// If specific collectors are enabled, use only those
filters = ne.config.EnableCollectors
} else if len(ne.config.ExcludeCollectors) > 0 {
// If excludes are specified, we need to create a list of all enabled collectors
// minus the excluded ones, similar to how the main node_exporter handles it
filters = []string{}

// First, create a temporary NodeCollector to get the list of enabled collectors
tempNC, err := collector.NewNodeCollector(logger)
if err != nil {
return nil, fmt.Errorf("failed to get available collectors: %w", err)
}

// Get all enabled collector names
for collectorName := range tempNC.Collectors {
// Check if this collector is not in the exclude list
excluded := false
for _, excludeName := range ne.config.ExcludeCollectors {
if collectorName == excludeName {
excluded = true
break
}
}
if !excluded {
filters = append(filters, collectorName)
}
}
}
// If neither EnableCollectors nor ExcludeCollectors are specified,
// filters remains empty and NewNodeCollector will use all enabled collectors

// Create the node collector with all enabled collectors
nc, err := collector.NewNodeCollector(logger, filters...)
if err != nil {
return nil, fmt.Errorf("failed to create node collector: %w", err)
}

// Register version collector
ne.registry.MustRegister(versioncollector.NewCollector("node_exporter"))

// Register the node collector
if err := ne.registry.Register(nc); err != nil {
return nil, fmt.Errorf("failed to register node collector: %w", err)
}

logger.Info("Node exporter initialized successfully",
"disable_defaults", ne.config.DisableDefaults,
"enable_collectors", ne.config.EnableCollectors,
"exclude_collectors", ne.config.ExcludeCollectors)

return ne.registry, nil
}

func (ne *NodeExporter) Shutdown(_ context.Context) error {
// There's nothing special needed to shutdown node-exporter.
return nil
}
36 changes: 36 additions & 0 deletions otelcollector/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 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.

package otelcollector

import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/receiver"

"github.com/prometheus/exporter-toolkit/otlpreceiver"
)

func NewFactory() receiver.Factory {
defaults := map[string]interface{}{
"disable_defaults": false,
"enable_collectors": []string{},
"exclude_collectors": []string{},
}

return otlpreceiver.NewFactory(
otlpreceiver.WithType(component.MustNewType("prometheus_node_exporter")),
otlpreceiver.WithInitializer(NewNodeExporter(&Config{})),
otlpreceiver.WithConfigUnmarshaler(&ConfigUnmarshaler{}),
otlpreceiver.WithComponentDefaults(defaults),
)
}
81 changes: 81 additions & 0 deletions otelcollector/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
module github.com/prometheus/node_exporter/otel_collector

go 1.25.0

require (
github.com/prometheus/client_golang v1.23.2
github.com/prometheus/exporter-toolkit v0.14.1
github.com/prometheus/node_exporter v1.10.2
go.opentelemetry.io/collector/component v1.44.0
go.opentelemetry.io/collector/receiver v1.44.0
)

require (
github.com/alecthomas/kingpin/v2 v2.4.0 // indirect
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
github.com/beevik/ntp v1.5.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.6.0 // indirect
github.com/dennwc/btrfs v0.0.0-20241002142654-12ae127e0bf6 // indirect
github.com/dennwc/ioctl v1.0.0 // indirect
github.com/ema/qdisc v1.0.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/hashicorp/go-envparse v0.1.0 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hodgesds/perf-utils v0.7.0 // indirect
github.com/illumos/go-kstat v0.0.0-20210513183136-173c9b0a9973 // indirect
github.com/jsimonetti/rtnetlink/v2 v2.0.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/lufia/iostat v1.2.1 // indirect
github.com/mattn/go-xmlrpc v0.0.3 // indirect
github.com/mdlayher/ethtool v0.5.0 // indirect
github.com/mdlayher/genetlink v1.3.2 // indirect
github.com/mdlayher/netlink v1.8.0 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/mdlayher/wifi v0.6.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/selinux v1.12.0 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus-community/go-runit v0.1.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.1 // indirect
github.com/prometheus/procfs v0.19.0 // indirect
github.com/safchain/ethtool v0.6.2 // indirect
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/collector/consumer v1.44.0 // indirect
go.opentelemetry.io/collector/featuregate v1.44.0 // indirect
go.opentelemetry.io/collector/internal/telemetry v0.138.0 // indirect
go.opentelemetry.io/collector/pdata v1.44.0 // indirect
go.opentelemetry.io/collector/pipeline v1.44.0 // indirect
go.opentelemetry.io/contrib/bridges/otelzap v0.13.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/log v0.14.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect
golang.org/x/net v0.45.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.30.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/grpc v1.76.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
howett.net/plist v1.0.1 // indirect
)

replace github.com/prometheus/exporter-toolkit => ../../exporter-toolkit

replace github.com/prometheus/node_exporter => ../
Loading
Loading