prometheus/node_exporter · error

failed to open sysfs

Error message

failed to open sysfs: %w

What it means

NewThermalZoneCollector wraps the error returned by sysfs.NewFS when the /sys mount point (path given by --path.sysfs) cannot be opened. sysfs.NewFS fails when the path does not exist or is not a directory, so the collector cannot be constructed at registration time. This is a constructor error, not a scrape-time error.

Solutions

  1. Verify /sys exists and is a directory on the target host (ls -ld /sys).
  2. Correct the --path.sysfs flag to point at the real sysfs mount.
  3. In containers/Kubernetes, mount /sys from the host into the exporter container.
  4. If the platform has no sysfs, don't enable the thermal_zone collector.

Example fix

// before
node_exporter --path.sysfs=/host/wrong-sys
// after
node_exporter --path.sysfs=/sys  # or the correctly mounted host path
Defensive patterns

Strategy: validation

Validate before calling

if fi, err := os.Stat(sysfsPath); err != nil || !fi.IsDir() {
    // sysfs unavailable: don't enable the thermal_zone collector
}

Try / catch

c, err := collector.NewThermalZoneCollector(logger)
if err != nil {
    logger.Warn("thermal_zone collector disabled", "err", err)
} else {
    registry.MustRegister(c)
}

Prevention

When it happens

Trigger: Calling NewThermalZoneCollector (node_exporter --collector.thermal_zone) when *sysPath points to a nonexistent or non-directory path, e.g. --path.sysfs=/wrong/path.

Common situations: Running node_exporter in a container where /sys is not mounted, misconfigured --path.sysfs flag in systemd unit files, or host-path remapping under Kubernetes that hides the real /sys.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of prometheus/node_exporter@17ddd77c59 (2026-09-07). Data as JSON: /api/errors/30666aa5ed5f7095. Report an issue: GitHub.

Appendix: source

Thrown at collector/thermal_zone_linux.go:48

const thermalZone = "thermal_zone"

type thermalZoneCollector struct {
	fs                    sysfs.FS
	coolingDeviceCurState *prometheus.Desc
	coolingDeviceMaxState *prometheus.Desc
	zoneTemp              *prometheus.Desc
	logger                *slog.Logger
}

func init() {
	registerCollector("thermal_zone", defaultEnabled, NewThermalZoneCollector)
}

// NewThermalZoneCollector returns a new Collector exposing kernel/system statistics.
func NewThermalZoneCollector(logger *slog.Logger) (Collector, error) {
	fs, err := sysfs.NewFS(*sysPath)
	if err != nil {
		return nil, fmt.Errorf("failed to open sysfs: %w", err)
	}

	return &thermalZoneCollector{
		fs: fs,
		zoneTemp: prometheus.NewDesc(
			prometheus.BuildFQName(namespace, thermalZone, "temp"),
			"Zone temperature in Celsius",
			[]string{"zone", "type"}, nil,
		),
		coolingDeviceCurState: prometheus.NewDesc(
			prometheus.BuildFQName(namespace, coolingDevice, "cur_state"),
			"Current throttle state of the cooling device",
			[]string{"name", "type"}, nil,
		),
		coolingDeviceMaxState: prometheus.NewDesc(
			prometheus.BuildFQName(namespace, coolingDevice, "max_state"),
			"Maximum throttle state of the cooling device",
			[]string{"name", "type"}, nil,

View on GitHub (pinned to 17ddd77c59)