Skip to content

Commit 907bf2f

Browse files
committed
fix(collector): report each thermal sensor once and update e2e fixture
Collecting temperatures on Apple Silicon surfaced a second problem that was previously unreachable, because Update returned before updateTemperatures ever ran. A sensor is identified only by its IOHID "Product" name, and several services report the same name. On an M5 Pro, 76 temperature-capable services carry only 25 distinct names, and the services expose nothing that tells them apart: RegistryID, UniqueID and SerialNumber are all unset, and two services can share both Product and LocationID. Emitting the same label set twice makes the registry reject those samples and log an error on every scrape, so only the first reading for a name is reported now and the rest are counted in a debug message. Also update collector/fixtures/e2e-output-darwin.txt for node_scrape_collector_success{collector="thermal"}, which is now 1 because the collector no longer fails. Two fixes to end-to-end-test.sh so that regenerating that fixture on a Darwin host does the right thing: - The Darwin fixture path was built with "${fixture_metrics::-4}". A negative substring length needs bash 4.2 and macOS still ships bash 3.2, where the expansion fails and fixture_metrics keeps pointing at the Linux file. Running ./end-to-end-test.sh -u on macOS therefore overwrote collector/fixtures/e2e-output.txt with Darwin output. "${fixture_metrics%.txt}" is equivalent and portable. - node_thermal_temperature_celsius is per-machine, both in its sensor names and its values, so it is stripped along with the other non-deterministic metrics. The CI runner reports no sensors at all, but a developer running the suite on real hardware would otherwise see the whole sensor list as a diff. Signed-off-by: SaiPisey2 <piseysai0202@gmail.com>
1 parent 3279e73 commit 907bf2f

4 files changed

Lines changed: 64 additions & 2 deletions

File tree

collector/fixtures/e2e-output-darwin.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ node_scrape_collector_success{collector="netdev"} 1
159159
node_scrape_collector_success{collector="os"} 1
160160
node_scrape_collector_success{collector="powersupplyclass"} 1
161161
node_scrape_collector_success{collector="textfile"} 1
162-
node_scrape_collector_success{collector="thermal"} 0
162+
node_scrape_collector_success{collector="thermal"} 1
163163
node_scrape_collector_success{collector="time"} 1
164164
node_scrape_collector_success{collector="xfrm"} 1
165165
# HELP node_textfile_mtime_seconds Unixtime mtime of textfiles successfully read.

collector/thermal_darwin_arm64.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,16 @@ func (c *thermCollector) updateTemperatures(ch chan<- prometheus.Metric) error {
101101
cfProdKey := C.CFStringCreateWithCString(C.kCFAllocatorDefault, prodKey, C.kCFStringEncodingUTF8)
102102
defer C.CFRelease(C.CFTypeRef(cfProdKey))
103103

104+
// A sensor is identified only by its product name. Several services report
105+
// the same name, either because the same sensor is listed more than once or
106+
// because two distinct sensors share a name, and the services carry no
107+
// property that tells them apart: RegistryID, UniqueID and SerialNumber are
108+
// all unset here. Emitting the same label set twice makes the registry
109+
// reject those samples and fail the whole scrape, so only the first reading
110+
// for a name is reported.
111+
seen := make(map[string]struct{}, int(count))
112+
skipped := 0
113+
104114
for i := 0; i < int(count); i++ {
105115
service := C.CFArrayGetValueAtIndex(services, C.CFIndex(i))
106116

@@ -125,8 +135,19 @@ func (c *thermCollector) updateTemperatures(ch chan<- prometheus.Metric) error {
125135
C.CFRelease(C.CFTypeRef(nameRef))
126136
}
127137

138+
if _, duplicate := seen[name]; duplicate {
139+
skipped++
140+
continue
141+
}
142+
seen[name] = struct{}{}
143+
128144
ch <- c.temperature.mustNewConstMetric(float64(temp), name)
129145
}
146+
147+
if skipped > 0 {
148+
c.logger.Debug("Skipped thermal sensors reporting a duplicate name", "skipped", skipped, "reported", len(seen))
149+
}
150+
130151
return nil
131152
}
132153

collector/thermal_darwin_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"testing"
2323

2424
"github.com/prometheus/client_golang/prometheus"
25+
dto "github.com/prometheus/client_model/go"
2526
)
2627

2728
// Apple Silicon does not implement IOPMCopyCPUPowerStatus, so fetchCPUPowerStatus
@@ -50,3 +51,39 @@ func TestThermalUpdateWithoutCPUPowerStatus(t *testing.T) {
5051
for range ch {
5152
}
5253
}
54+
55+
// Several IOHID services report the same sensor name and carry no property that
56+
// tells them apart, so the collector must report each name once. Duplicate label
57+
// sets are rejected by the registry and fail the whole scrape.
58+
func TestThermalTemperaturesAreUnique(t *testing.T) {
59+
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
60+
61+
c, err := NewThermCollector(logger)
62+
if err != nil {
63+
t.Fatalf("failed to create collector: %v", err)
64+
}
65+
66+
ch := make(chan prometheus.Metric, 4096)
67+
if err := c.Update(ch); err != nil {
68+
t.Fatalf("Update failed: %v", err)
69+
}
70+
close(ch)
71+
72+
seen := make(map[string]struct{})
73+
for m := range ch {
74+
var pb dto.Metric
75+
if err := m.Write(&pb); err != nil {
76+
t.Fatalf("cannot read metric: %v", err)
77+
}
78+
79+
key := m.Desc().String()
80+
for _, l := range pb.GetLabel() {
81+
key += "," + l.GetName() + "=" + l.GetValue()
82+
}
83+
84+
if _, duplicate := seen[key]; duplicate {
85+
t.Errorf("duplicate metric collected: %s", key)
86+
}
87+
seen[key] = struct{}{}
88+
}
89+
}

end-to-end-test.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,10 @@ generated_metrics="${tmpdir}/e2e-output.txt"
279279
for os in freebsd openbsd netbsd solaris dragonfly darwin; do
280280
if [ "${GOHOSTOS}" = "${os}" ]; then
281281
generated_metrics="${tmpdir}/e2e-output-${GOHOSTOS}.txt"
282-
fixture_metrics="${fixture_metrics::-4}-${GOHOSTOS}.txt"
282+
# Not "${fixture_metrics::-4}": a negative length needs bash 4.2, and macOS
283+
# still ships bash 3.2, where the expansion fails and the Linux fixture is
284+
# used instead.
285+
fixture_metrics="${fixture_metrics%.txt}-${GOHOSTOS}.txt"
283286
fi
284287
done
285288

@@ -390,6 +393,7 @@ non_deterministic_metrics=$(cat << METRICS
390393
node_network_receive_bytes_total
391394
node_network_receive_multicast_total
392395
node_network_transmit_multicast_total
396+
node_thermal_temperature_celsius
393397
node_zfs_abdstats_linear_count_total
394398
node_zfs_abdstats_linear_data_bytes
395399
node_zfs_abdstats_scatter_chunk_waste_bytes

0 commit comments

Comments
 (0)