feat: track battery levels per device

This commit is contained in:
henrygd
2026-08-15 18:16:03 -04:00
parent b4e1f3fafe
commit 68f417575f
18 changed files with 431 additions and 373 deletions

View File

@@ -186,6 +186,9 @@ func AverageSystemStatsSlice(records []system.Stats) system.Stats {
// necessary because uint8 is not big enough for the sum
batterySum := 0
batteryCount := 0
batterySums := make(map[string]uint64)
batteryCounts := make(map[string]uint64)
// accumulate per-core usage across records
var cpuCoresSums []uint64
// accumulate cpu breakdown [user, system, iowait, steal, idle]
@@ -232,8 +235,15 @@ func AverageSystemStatsSlice(records []system.Stats) system.Stats {
for i := range stats.DiskIoStats {
sum.DiskIoStats[i] += stats.DiskIoStats[i]
}
batterySum += int(stats.Battery[0])
sum.Battery[1] = stats.Battery[1]
if hasBattery(stats.Battery, stats.Batteries) {
batterySum += int(stats.Battery[0])
batteryCount++
sum.Battery[1] = stats.Battery[1]
}
for name, percent := range stats.Batteries {
batterySums[name] += uint64(percent)
batteryCounts[name]++
}
// accumulate per-core usage if present
if stats.CpuCoresUsage != nil {
@@ -379,7 +389,15 @@ func AverageSystemStatsSlice(records []system.Stats) system.Stats {
sum.LoadAvg[2] = twoDecimals(sum.LoadAvg[2] / count)
sum.Bandwidth[0] = sum.Bandwidth[0] / uint64(count)
sum.Bandwidth[1] = sum.Bandwidth[1] / uint64(count)
sum.Battery[0] = uint8(batterySum / int(count))
if batteryCount > 0 {
sum.Battery[0] = uint8(batterySum / batteryCount)
}
if len(batterySums) > 0 {
sum.Batteries = make(map[string]uint8, len(batterySums))
for name, total := range batterySums {
sum.Batteries[name] = uint8(total / batteryCounts[name])
}
}
// Average network interfaces
if sum.NetworkInterfaces != nil {
@@ -467,6 +485,10 @@ func AverageSystemStatsSlice(records []system.Stats) system.Stats {
return sum
}
func hasBattery(legacy [2]uint8, batteries map[string]uint8) bool {
return legacy != [2]uint8{} || len(batteries) > 0
}
// Calculate the average stats of a list of container_stats records
func (rm *RecordManager) AverageContainerStats(db dbx.Builder, records RecordIds) []container.Stats {
allStats := make([][]container.Stats, 0, len(records))

View File

@@ -602,6 +602,28 @@ func TestAverageSystemStatsSlice_BatteryLastChargeState(t *testing.T) {
assert.Equal(t, uint8(0), result.Battery[1]) // last record's charge state
}
func TestAverageSystemStatsSlice_BatteriesIndependentSamples(t *testing.T) {
input := []system.Stats{
{Battery: [2]uint8{80, 4}, Batteries: map[string]uint8{"Primary": 80, "Mouse": 0}},
{Battery: [2]uint8{60, 3}, Batteries: map[string]uint8{"Primary": 60}},
{Battery: [2]uint8{30, 4}, Batteries: map[string]uint8{"Mouse": 40}},
{},
}
result := records.AverageSystemStatsSlice(input)
assert.Equal(t, map[string]uint8{"Primary": 70, "Mouse": 20}, result.Batteries)
assert.Equal(t, uint8(56), result.Battery[0], "representative battery excludes absent samples")
assert.Equal(t, uint8(4), result.Battery[1], "representative state comes from its latest sample")
}
func TestAverageSystemStatsSlice_ZeroRepresentativeBattery(t *testing.T) {
result := records.AverageSystemStatsSlice([]system.Stats{
{Battery: [2]uint8{0, 1}, Batteries: map[string]uint8{"Primary": 0}},
{},
})
assert.Equal(t, [2]uint8{0, 1}, result.Battery)
assert.Equal(t, map[string]uint8{"Primary": 0}, result.Batteries)
}
func TestAverageSystemStatsSlice_ThreeRecordsRounding(t *testing.T) {
input := []system.Stats{
{Cpu: 10.0, Mem: 8.0},