diff --git a/sysfs/system_cpu.go b/sysfs/system_cpu.go index 0e96e525..ff510437 100644 --- a/sysfs/system_cpu.go +++ b/sysfs/system_cpu.go @@ -65,6 +65,10 @@ type SystemCPUCpufreqStats struct { Governor string RelatedCpus string SetSpeed string + // Refer `CONFIG_CPU_FREQ_STAT`: https://www.kernel.org/doc/html/latest/cpu-freq/cpufreq-stats.html#configuring-cpufreq-stats + CpuinfoFrequencyDuration *map[uint64]uint64 + CpuinfoFrequencyTransitionsTotal *uint64 + CpuinfoTransitionTable *[][]uint64 } // CPUs returns a slice of all CPUs in `/sys/devices/system/cpu`. @@ -292,19 +296,93 @@ func parseCpufreqCpuinfo(cpuPath string) (*SystemCPUCpufreqStats, error) { } } + // "total_trans" is the total number of times the CPU has changed frequency. + var cpuinfoFrequencyTransitionsTotal *uint64 = nil + cpuinfoFrequencyTransitionsTotalUint, err := util.ReadUintFromFile(filepath.Join(cpuPath, "stats", "total_trans")) + if err != nil { + if !(os.IsNotExist(err) || os.IsPermission(err)) { + return &SystemCPUCpufreqStats{}, err + } + } else { + cpuinfoFrequencyTransitionsTotal = &cpuinfoFrequencyTransitionsTotalUint + } + + // "time_in_state" is the total time spent at each frequency. + var cpuinfoFrequencyDuration *map[uint64]uint64 = nil + cpuinfoFrequencyDurationString, err := util.ReadFileNoStat(filepath.Join(cpuPath, "stats", "time_in_state")) + if err != nil { + if !(os.IsNotExist(err) || os.IsPermission(err)) { + return &SystemCPUCpufreqStats{}, err + } + } else { + cpuinfoFrequencyDuration = &map[uint64]uint64{} + for _, line := range strings.Split(string(cpuinfoFrequencyDurationString), "\n") { + if line == "" { + continue + } + fields := strings.Fields(line) + if len(fields) != 2 { + return &SystemCPUCpufreqStats{}, fmt.Errorf("unexpected number of fields in time_in_state: %v", fields) + } + freq, err := strconv.ParseUint(fields[0], 10, 64) + if err != nil { + return &SystemCPUCpufreqStats{}, err + } + duration, err := strconv.ParseUint(fields[1], 10, 64) + if err != nil { + return &SystemCPUCpufreqStats{}, err + } + (*cpuinfoFrequencyDuration)[freq] = duration + } + } + + // "trans_table" contains information about all the CPU frequency transitions. + var cpuinfoTransitionTable *[][]uint64 = nil + cpuinfoTransitionTableString, err := util.ReadFileNoStat(filepath.Join(cpuPath, "stats", "trans_table")) + if err != nil { + if !(os.IsNotExist(err) || os.IsPermission(err)) { + return &SystemCPUCpufreqStats{}, err + } + } else { + cpuinfoTransitionTable = &[][]uint64{} + for i, line := range strings.Split(string(cpuinfoTransitionTableString), "\n") { + // Skip the "From: To" header. + if i == 0 || line == "" { + continue + } + fields := strings.Fields(line) + fields[0] = strings.TrimSuffix(fields[0], ":") + cpuinfoTransitionTableRow := make([]uint64, len(fields)) + for i := range fields { + if len(fields[i]) == 0 { + continue + } + f, err := strconv.ParseUint(fields[i], 10, 64) + if err != nil { + return &SystemCPUCpufreqStats{}, err + } + cpuinfoTransitionTableRow[i] = f + } + *cpuinfoTransitionTable = append(*cpuinfoTransitionTable, cpuinfoTransitionTableRow) + } + } + return &SystemCPUCpufreqStats{ - CpuinfoCurrentFrequency: uintOut[0], - CpuinfoMaximumFrequency: uintOut[1], - CpuinfoMinimumFrequency: uintOut[2], - CpuinfoTransitionLatency: uintOut[3], - ScalingCurrentFrequency: uintOut[4], - ScalingMaximumFrequency: uintOut[5], - ScalingMinimumFrequency: uintOut[6], - AvailableGovernors: stringOut[0], - Driver: stringOut[1], - Governor: stringOut[2], - RelatedCpus: stringOut[3], - SetSpeed: stringOut[4], + CpuinfoCurrentFrequency: uintOut[0], + CpuinfoMaximumFrequency: uintOut[1], + CpuinfoMinimumFrequency: uintOut[2], + CpuinfoTransitionLatency: uintOut[3], + ScalingCurrentFrequency: uintOut[4], + ScalingMaximumFrequency: uintOut[5], + ScalingMinimumFrequency: uintOut[6], + AvailableGovernors: stringOut[0], + Driver: stringOut[1], + Governor: stringOut[2], + RelatedCpus: stringOut[3], + SetSpeed: stringOut[4], + CpuinfoFrequencyDuration: cpuinfoFrequencyDuration, + CpuinfoFrequencyTransitionsTotal: cpuinfoFrequencyTransitionsTotal, + CpuinfoTransitionTable: cpuinfoTransitionTable, }, nil } diff --git a/sysfs/system_cpu_test.go b/sysfs/system_cpu_test.go index 9fee2a1a..577c1188 100644 --- a/sysfs/system_cpu_test.go +++ b/sysfs/system_cpu_test.go @@ -104,23 +104,38 @@ func TestSystemCpufreq(t *testing.T) { } systemCpufreq := []SystemCPUCpufreqStats{ - // Has missing `cpuinfo_cur_freq` file. + // The following files are missing for the first CPU: + // * `cpuinfo_cur_freq` + // * `time_in_state` + // * `total_trans` { - Name: "0", - CpuinfoCurrentFrequency: nil, - CpuinfoMinimumFrequency: makeUint64(800000), - CpuinfoMaximumFrequency: makeUint64(2400000), - CpuinfoTransitionLatency: makeUint64(0), - ScalingCurrentFrequency: makeUint64(1219917), - ScalingMinimumFrequency: makeUint64(800000), - ScalingMaximumFrequency: makeUint64(2400000), - AvailableGovernors: "performance powersave", - Driver: "intel_pstate", - Governor: "powersave", - RelatedCpus: "0", - SetSpeed: "", + Name: "0", + CpuinfoCurrentFrequency: nil, + CpuinfoMinimumFrequency: makeUint64(800000), + CpuinfoMaximumFrequency: makeUint64(2400000), + CpuinfoTransitionLatency: makeUint64(0), + ScalingCurrentFrequency: makeUint64(1219917), + ScalingMinimumFrequency: makeUint64(800000), + ScalingMaximumFrequency: makeUint64(2400000), + AvailableGovernors: "performance powersave", + Driver: "intel_pstate", + Governor: "powersave", + RelatedCpus: "0", + SetSpeed: "", + CpuinfoFrequencyDuration: nil, + CpuinfoFrequencyTransitionsTotal: nil, + CpuinfoTransitionTable: &[][]uint64{ + {0, 3600000, 3400000, 3200000, 3000000, 2800000}, + {3600000, 0, 5, 0, 0, 0}, + {3400000, 4, 0, 2, 0, 0}, + {3200000, 0, 1, 0, 2, 0}, + {3000000, 0, 0, 1, 0, 3}, + {2800000, 0, 0, 0, 2, 0}, + }, }, - // Has missing `scaling_cur_freq` file. + // The following files are missing for the second CPU: + // * `scaling_cur_freq` + // * `trans_table` { Name: "1", CpuinfoCurrentFrequency: makeUint64(1200195), @@ -135,6 +150,15 @@ func TestSystemCpufreq(t *testing.T) { Governor: "powersave", RelatedCpus: "1", SetSpeed: "", + CpuinfoFrequencyDuration: &map[uint64]uint64{ + 3600000: 2089, + 3400000: 136, + 3200000: 34, + 3000000: 67, + 2800000: 172488, + }, + CpuinfoFrequencyTransitionsTotal: makeUint64(20), + CpuinfoTransitionTable: nil, }, } diff --git a/testdata/fixtures.ttar b/testdata/fixtures.ttar index 170a5268..a998b271 100644 --- a/testdata/fixtures.ttar +++ b/testdata/fixtures.ttar @@ -13548,6 +13548,23 @@ Lines: 1 Mode: 664 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Directory: fixtures/sys/devices/system/cpu/cpu1/cpufreq/stats +Mode: 755 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/sys/devices/system/cpu/cpu1/cpufreq/stats/time_in_state +Lines: 5 +3600000 2089 +3400000 136 +3200000 34 +3000000 67 +2800000 172488 +Mode: 644 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/sys/devices/system/cpu/cpu1/cpufreq/stats/total_trans +Lines: 1 +20 +Mode: 644 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/sys/devices/system/cpu/cpu1/thermal_throttle Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -13663,6 +13680,20 @@ Lines: 1 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Directory: fixtures/sys/devices/system/cpu/cpufreq/policy0/stats +Mode: 755 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/sys/devices/system/cpu/cpufreq/policy0/stats/trans_table +Lines: 7 +From : To + : 3600000 3400000 3200000 3000000 2800000 +3600000: 0 5 0 0 0 +3400000: 4 0 2 0 0 +3200000: 0 1 0 2 0 +3000000: 0 0 1 0 3 +2800000: 0 0 0 2 0 +Mode: 644 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/sys/devices/system/cpu/cpufreq/policy1 Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -