├── VERSION ├── testdata ├── die-id-valid │ └── cpu1 │ │ └── topology │ │ └── die_id ├── symlink ├── cpu-msr-cpuID-msr-not-exist │ └── 0 │ │ └── dummy ├── cpu-msr-directories-not-exist │ └── dummy ├── intel-rapl │ ├── intel-rapl{colon}4 │ │ ├── name │ │ └── energy_uj │ ├── intel-rapl{colon}5 │ │ ├── name │ │ └── energy_uj │ ├── intel-rapl{colon}0 │ │ ├── name │ │ ├── energy_uj │ │ ├── constraint_0_max_power_uw │ │ ├── max_energy_range_uj │ │ ├── intel-rapl{colon}0{colon}0 │ │ │ ├── energy_uj │ │ │ ├── name │ │ │ └── max_energy_range_uj │ │ └── intel-rapl{colon}0{colon}1 │ │ │ ├── name │ │ │ ├── energy_uj │ │ │ └── max_energy_range_uj │ ├── intel-rapl{colon}1 │ │ ├── name │ │ ├── energy_uj │ │ ├── max_energy_range_uj │ │ ├── constraint_0_max_power_uw │ │ └── intel-rapl{colon}1{colon}0 │ │ │ └── name │ ├── intel-rapl{colon}3 │ │ ├── name │ │ └── energy_uj │ └── intel-rapl{colon}2 │ │ ├── name │ │ ├── energy_uj │ │ ├── constraint_0_max_power_uw │ │ └── intel-rapl{colon}2{colon}0 │ │ ├── name │ │ ├── max_energy_range_uj │ │ └── energy_uj ├── die-id-invalid │ └── cpu1 │ │ └── topology │ │ └── die_id ├── cpu-freq-invalid │ └── cpu0 │ │ └── cpufreq │ │ └── scaling_cur_freq ├── cpu-freq │ └── cpu0 │ │ └── cpufreq │ │ └── scaling_cur_freq ├── intel-rapl-domain-name-empty │ └── intel-rapl{colon}1 │ │ └── name ├── intel_uncore_frequency │ ├── package_09_die_12 │ │ ├── max_freq_khz │ │ ├── min_freq_khz │ │ ├── initial_max_freq_khz │ │ └── initial_min_freq_khz │ └── package_10_die_03 │ │ ├── current_freq_khz │ │ ├── max_freq_khz │ │ ├── min_freq_khz │ │ ├── initial_max_freq_khz │ │ └── initial_min_freq_khz ├── intel-rapl-invalid-package-domain-name-id │ └── intel-rapl{colon}1 │ │ └── name ├── intel-rapl-dram-domain-name-not-exist │ └── intel-rapl{colon}0 │ │ ├── name │ │ └── intel-rapl{colon}0{colon}0 │ │ └── invalid-name ├── cpu-msr-cpuID-msr-softlink │ ├── 0 │ │ └── msr │ └── 1 │ │ └── msr ├── intel-rapl-dram-curr-energy-attr-file-not-exist │ └── intel-rapl{colon}0 │ │ ├── name │ │ ├── energy_uj │ │ ├── max_energy_range_uj │ │ └── intel-rapl{colon}0{colon}1 │ │ ├── name │ │ └── max_energy_range_uj ├── intel-rapl-package-curr-energy-attr-file-not-exist │ └── intel-rapl{colon}0 │ │ ├── name │ │ ├── max_energy_range_uj │ │ └── intel-rapl{colon}0{colon}1 │ │ ├── name │ │ ├── energy_uj │ │ └── max_energy_range_uj ├── intel-rapl-package-domain-name-not-exist │ └── intel-rapl{colon}0 │ │ └── domain ├── cpu-msr │ ├── 0 │ │ └── msr │ ├── 1 │ │ └── msr │ ├── 10 │ │ └── msr │ └── 100 │ │ └── msr ├── cpu-msr-invalid-cpuID-directories │ ├── 01 │ │ └── msr │ └── 1invalid │ │ └── msr ├── cpuinfo_bad3 │ └── cpuinfo ├── cpuinfo_bad1 │ └── cpuinfo ├── cpuinfo_bad2 │ └── cpuinfo ├── cpuinfo_good │ └── cpuinfo ├── proc_modules_msr_loaded ├── proc_modules_msr_not_loaded ├── proc_modules_rapl_not_loaded ├── proc_modules_rapl_loaded ├── alderlake_goldencove_core.json └── sapphirerapids_core.json ├── internal ├── cpuid │ ├── cpuid_count_amd64.go │ ├── cpuid_count_amd64.s │ └── hybrid.go ├── log │ ├── default.go │ └── logger.go ├── version │ └── version.go └── cpumodel │ └── intel_family.go ├── .gitignore ├── unit_converter.go ├── security.md ├── clock.go ├── LICENSE_OF_DEPENDENCIES.md ├── go.mod ├── Makefile ├── errors.go ├── msr_mock_test.go ├── cpufreq.go ├── CONTRIBUTING.md ├── file.go ├── cpufreq_test.go ├── testdata_setup_test.go ├── go.sum ├── uncorefreq.go ├── CODE_OF_CONDUCT.md ├── uncorefreq_test.go ├── metrics.go ├── topology.go ├── metrics_test.go ├── cmd └── example │ └── main.go ├── .golangci.yml ├── LICENSE ├── turbofreq.go ├── topology_test.go └── msr.go /VERSION: -------------------------------------------------------------------------------- 1 | 1.0.1 2 | -------------------------------------------------------------------------------- /testdata/die-id-valid/cpu1/topology/die_id: -------------------------------------------------------------------------------- 1 | 1 -------------------------------------------------------------------------------- /testdata/symlink: -------------------------------------------------------------------------------- 1 | ./proc_modules_msr_loaded -------------------------------------------------------------------------------- /testdata/cpu-msr-cpuID-msr-not-exist/0/dummy: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testdata/cpu-msr-directories-not-exist/dummy: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}4/name: -------------------------------------------------------------------------------- 1 | psys -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}5/name: -------------------------------------------------------------------------------- 1 | core -------------------------------------------------------------------------------- /testdata/die-id-invalid/cpu1/topology/die_id: -------------------------------------------------------------------------------- 1 | badValue -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}0/name: -------------------------------------------------------------------------------- 1 | package-0 -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}1/name: -------------------------------------------------------------------------------- 1 | package-1 -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}3/name: -------------------------------------------------------------------------------- 1 | package-3 -------------------------------------------------------------------------------- /testdata/cpu-freq-invalid/cpu0/cpufreq/scaling_cur_freq: -------------------------------------------------------------------------------- 1 | a -------------------------------------------------------------------------------- /testdata/cpu-freq/cpu0/cpufreq/scaling_cur_freq: -------------------------------------------------------------------------------- 1 | 888888 2 | -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}2/name: -------------------------------------------------------------------------------- 1 | package-2 2 | -------------------------------------------------------------------------------- /testdata/intel-rapl-domain-name-empty/intel-rapl{colon}1/name: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}1/energy_uj: -------------------------------------------------------------------------------- 1 | 206999075695 -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}2/energy_uj: -------------------------------------------------------------------------------- 1 | 205999075695 -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}3/energy_uj: -------------------------------------------------------------------------------- 1 | 205888075695 -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}4/energy_uj: -------------------------------------------------------------------------------- 1 | 205888075695 -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}5/energy_uj: -------------------------------------------------------------------------------- 1 | 205888075695 -------------------------------------------------------------------------------- /testdata/intel_uncore_frequency/package_09_die_12/max_freq_khz: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}0/energy_uj: -------------------------------------------------------------------------------- 1 | 206999074695 2 | -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}1/max_energy_range_uj: -------------------------------------------------------------------------------- 1 | abcdef -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}2/constraint_0_max_power_uw: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testdata/intel_uncore_frequency/package_09_die_12/min_freq_khz: -------------------------------------------------------------------------------- 1 | / 2 | -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}0/constraint_0_max_power_uw: -------------------------------------------------------------------------------- 1 | 250000000 -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}0/max_energy_range_uj: -------------------------------------------------------------------------------- 1 | 262143328850 -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}1/constraint_0_max_power_uw: -------------------------------------------------------------------------------- 1 | abcdef -------------------------------------------------------------------------------- /testdata/intel_uncore_frequency/package_10_die_03/current_freq_khz: -------------------------------------------------------------------------------- 1 | 1500000 -------------------------------------------------------------------------------- /testdata/intel_uncore_frequency/package_10_die_03/max_freq_khz: -------------------------------------------------------------------------------- 1 | 1900000 2 | -------------------------------------------------------------------------------- /testdata/intel_uncore_frequency/package_10_die_03/min_freq_khz: -------------------------------------------------------------------------------- 1 | 1100000 2 | -------------------------------------------------------------------------------- /testdata/intel-rapl-invalid-package-domain-name-id/intel-rapl{colon}1/name: -------------------------------------------------------------------------------- 1 | package-0 -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}0/intel-rapl{colon}0{colon}0/energy_uj: -------------------------------------------------------------------------------- 1 | 0 2 | -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}0/intel-rapl{colon}0{colon}0/name: -------------------------------------------------------------------------------- 1 | domain 2 | -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}0/intel-rapl{colon}0{colon}1/name: -------------------------------------------------------------------------------- 1 | dram 2 | -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}1/intel-rapl{colon}1{colon}0/name: -------------------------------------------------------------------------------- 1 | socket 2 | -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}2/intel-rapl{colon}2{colon}0/name: -------------------------------------------------------------------------------- 1 | dram 2 | -------------------------------------------------------------------------------- /testdata/intel_uncore_frequency/package_09_die_12/initial_max_freq_khz: -------------------------------------------------------------------------------- 1 | abac 2 | -------------------------------------------------------------------------------- /testdata/intel_uncore_frequency/package_10_die_03/initial_max_freq_khz: -------------------------------------------------------------------------------- 1 | 2000000 2 | -------------------------------------------------------------------------------- /testdata/intel_uncore_frequency/package_10_die_03/initial_min_freq_khz: -------------------------------------------------------------------------------- 1 | 1000000 2 | -------------------------------------------------------------------------------- /testdata/intel-rapl-dram-domain-name-not-exist/intel-rapl{colon}0/name: -------------------------------------------------------------------------------- 1 | package-0 2 | -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}0/intel-rapl{colon}0{colon}0/max_energy_range_uj: -------------------------------------------------------------------------------- 1 | 0 -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}2/intel-rapl{colon}2{colon}0/max_energy_range_uj: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testdata/cpu-msr-cpuID-msr-softlink/1/msr: -------------------------------------------------------------------------------- 1 | iapower/testdata/cpu-msr-cpuID-msr-softlink/0/msr -------------------------------------------------------------------------------- /testdata/intel-rapl-dram-curr-energy-attr-file-not-exist/intel-rapl{colon}0/name: -------------------------------------------------------------------------------- 1 | package-0 -------------------------------------------------------------------------------- /testdata/intel-rapl-package-curr-energy-attr-file-not-exist/intel-rapl{colon}0/name: -------------------------------------------------------------------------------- 1 | package-0 -------------------------------------------------------------------------------- /testdata/intel-rapl-package-domain-name-not-exist/intel-rapl{colon}0/domain: -------------------------------------------------------------------------------- 1 | package-0 2 | -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}0/intel-rapl{colon}0{colon}1/energy_uj: -------------------------------------------------------------------------------- 1 | 64155753419 2 | -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}2/intel-rapl{colon}2{colon}0/energy_uj: -------------------------------------------------------------------------------- 1 | 66155553419 2 | -------------------------------------------------------------------------------- /testdata/intel-rapl/intel-rapl{colon}0/intel-rapl{colon}0{colon}1/max_energy_range_uj: -------------------------------------------------------------------------------- 1 | 65712999613 -------------------------------------------------------------------------------- /testdata/cpu-msr/0/msr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/powertelemetry/HEAD/testdata/cpu-msr/0/msr -------------------------------------------------------------------------------- /testdata/cpu-msr/1/msr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/powertelemetry/HEAD/testdata/cpu-msr/1/msr -------------------------------------------------------------------------------- /testdata/cpu-msr/10/msr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/powertelemetry/HEAD/testdata/cpu-msr/10/msr -------------------------------------------------------------------------------- /testdata/intel-rapl-dram-curr-energy-attr-file-not-exist/intel-rapl{colon}0/energy_uj: -------------------------------------------------------------------------------- 1 | 206999074695 2 | -------------------------------------------------------------------------------- /testdata/cpu-msr/100/msr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/powertelemetry/HEAD/testdata/cpu-msr/100/msr -------------------------------------------------------------------------------- /testdata/intel-rapl-dram-curr-energy-attr-file-not-exist/intel-rapl{colon}0/max_energy_range_uj: -------------------------------------------------------------------------------- 1 | 262143328850 -------------------------------------------------------------------------------- /testdata/intel-rapl-package-curr-energy-attr-file-not-exist/intel-rapl{colon}0/max_energy_range_uj: -------------------------------------------------------------------------------- 1 | 262143328850 -------------------------------------------------------------------------------- /testdata/intel_uncore_frequency/package_09_die_12/initial_min_freq_khz: -------------------------------------------------------------------------------- 1 | 200000 2 | -------------------------------------------------------------------------------- /testdata/intel-rapl-dram-curr-energy-attr-file-not-exist/intel-rapl{colon}0/intel-rapl{colon}0{colon}1/name: -------------------------------------------------------------------------------- 1 | dram 2 | -------------------------------------------------------------------------------- /testdata/intel-rapl-dram-domain-name-not-exist/intel-rapl{colon}0/intel-rapl{colon}0{colon}0/invalid-name: -------------------------------------------------------------------------------- 1 | dram 2 | -------------------------------------------------------------------------------- /testdata/intel-rapl-package-curr-energy-attr-file-not-exist/intel-rapl{colon}0/intel-rapl{colon}0{colon}1/name: -------------------------------------------------------------------------------- 1 | dram 2 | -------------------------------------------------------------------------------- /testdata/intel-rapl-dram-curr-energy-attr-file-not-exist/intel-rapl{colon}0/intel-rapl{colon}0{colon}1/max_energy_range_uj: -------------------------------------------------------------------------------- 1 | 65712999613 -------------------------------------------------------------------------------- /testdata/intel-rapl-package-curr-energy-attr-file-not-exist/intel-rapl{colon}0/intel-rapl{colon}0{colon}1/energy_uj: -------------------------------------------------------------------------------- 1 | 64155753419 2 | -------------------------------------------------------------------------------- /testdata/cpu-msr-cpuID-msr-softlink/0/msr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/powertelemetry/HEAD/testdata/cpu-msr-cpuID-msr-softlink/0/msr -------------------------------------------------------------------------------- /testdata/intel-rapl-package-curr-energy-attr-file-not-exist/intel-rapl{colon}0/intel-rapl{colon}0{colon}1/max_energy_range_uj: -------------------------------------------------------------------------------- 1 | 65712999613 -------------------------------------------------------------------------------- /testdata/cpu-msr-invalid-cpuID-directories/01/msr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/powertelemetry/HEAD/testdata/cpu-msr-invalid-cpuID-directories/01/msr -------------------------------------------------------------------------------- /testdata/cpu-msr-invalid-cpuID-directories/1invalid/msr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/powertelemetry/HEAD/testdata/cpu-msr-invalid-cpuID-directories/1invalid/msr -------------------------------------------------------------------------------- /internal/cpuid/cpuid_count_amd64.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package cpuid 7 | 8 | //nolint:revive // This is to keep the function name same as in Linux kernel sources 9 | func cpuid_count(level, count uint32) (eax, ebx, ecx, edx uint32) // implemented in cpuid_count_amd64.s 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Go template 2 | # Binaries for programs and plugins 3 | /example 4 | *.exe 5 | *.exe~ 6 | *.dll 7 | *.so 8 | *.dylib 9 | /coverage.out 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # VSCode config 18 | .vscode 19 | 20 | # Idea files 21 | *.iml 22 | .idea/ 23 | -------------------------------------------------------------------------------- /unit_converter.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package powertelemetry 7 | 8 | const ( 9 | fromKiloHertzToMegaHertzRatio = 1e-3 10 | fromMicrojoulesToJoulesRatio = 1e-6 11 | fromMicrowattsToWatts = 1e-6 12 | fromProcessorCyclesToHertz = 1e-6 13 | fromNanosecondsToSecondsRatio = 1e-9 14 | ) 15 | -------------------------------------------------------------------------------- /security.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | Intel is committed to rapidly addressing security vulnerabilities affecting our customers and providing clear guidance on the solution, impact, severity and mitigation. 3 | 4 | ## Reporting a Vulnerability 5 | Please report any security vulnerabilities in this project utilizing the guidelines [here](https://www.intel.com/content/www/us/en/security-center/vulnerability-handling-guidelines.html). 6 | -------------------------------------------------------------------------------- /internal/cpuid/cpuid_count_amd64.s: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #include "textflag.h" 5 | 6 | // func cpuid_count(level, count uint32) (eax, ebx, ecx, edx uint32) 7 | TEXT ·cpuid_count(SB), NOSPLIT, $0-24 8 | MOVL level+0(FP), AX 9 | MOVL count+4(FP), CX 10 | CPUID 11 | MOVL AX, eax+8(FP) 12 | MOVL BX, ebx+12(FP) 13 | MOVL CX, ecx+16(FP) 14 | MOVL DX, edx+20(FP) 15 | RET 16 | -------------------------------------------------------------------------------- /testdata/cpuinfo_bad3/cpuinfo: -------------------------------------------------------------------------------- 1 | processor : 0 2 | vendor_id : IdOfVendor 3 | cpu family : 13 4 | model : 23 5 | model name : NameOfModel 6 | stepping : 1 7 | microcode : 111 8 | cpu MHz : 55.5 9 | cache size : + 10 | physical id : 2 11 | siblings : 5 12 | core id : 66 13 | cpu cores : 2 14 | apicid : 22 15 | initial apicid : 1 16 | fpu : no 17 | fpu_exception : no 18 | cpuid level : 0 19 | wp : no 20 | flags : no flags 21 | bugs : no bugs 22 | bogomips : 2.2222 23 | clflush size : 64 24 | cache_alignment : 64 25 | address sizes : 10 bits physical, 607 bits virtual 26 | power management: -------------------------------------------------------------------------------- /testdata/cpuinfo_bad1/cpuinfo: -------------------------------------------------------------------------------- 1 | processor : + 2 | vendor_id : IdOfVendor 3 | cpu family : 13 4 | model : 23 5 | model name : NameOfModel 6 | stepping : 1 7 | microcode : 111 8 | cpu MHz : 55.5 9 | cache size : 12 KB 10 | physical id : 2 11 | siblings : 5 12 | core id : 66 13 | cpu cores : 2 14 | apicid : 22 15 | initial apicid : 1 16 | fpu : no 17 | fpu_exception : no 18 | cpuid level : 0 19 | wp : no 20 | flags : no flags 21 | bugs : no bugs 22 | bogomips : 2.2222 23 | clflush size : 64 24 | cache_alignment : 64 25 | address sizes : 10 bits physical, 607 bits virtual 26 | power management: -------------------------------------------------------------------------------- /testdata/cpuinfo_bad2/cpuinfo: -------------------------------------------------------------------------------- 1 | processor : 0 2 | vendor_id : IdOfVendor 3 | cpu family : 13 4 | model : 23 5 | model name : NameOfModel 6 | stepping : + 7 | microcode : 111 8 | cpu MHz : 55.5 9 | cache size : 12 KB 10 | physical id : 2 11 | siblings : 5 12 | core id : 66 13 | cpu cores : 2 14 | apicid : 22 15 | initial apicid : 1 16 | fpu : no 17 | fpu_exception : no 18 | cpuid level : 0 19 | wp : no 20 | flags : no flags 21 | bugs : no bugs 22 | bogomips : 2.2222 23 | clflush size : 64 24 | cache_alignment : 64 25 | address sizes : 10 bits physical, 607 bits virtual 26 | power management: -------------------------------------------------------------------------------- /testdata/cpuinfo_good/cpuinfo: -------------------------------------------------------------------------------- 1 | processor : 1 2 | vendor_id : IdOfVendor 3 | cpu family : 13 4 | model : 23 5 | model name : NameOfModel 6 | stepping : 1 7 | microcode : 111 8 | cpu MHz : 55.5 9 | cache size : 12 KB 10 | physical id : 2 11 | siblings : 5 12 | core id : 66 13 | cpu cores : 2 14 | apicid : 22 15 | initial apicid : 1 16 | fpu : no 17 | fpu_exception : no 18 | cpuid level : 0 19 | wp : no 20 | flags : no flags 21 | bugs : no bugs 22 | bogomips : 2.2222 23 | clflush size : 64 24 | cache_alignment : 64 25 | address sizes : 10 bits physical, 607 bits virtual 26 | power management: -------------------------------------------------------------------------------- /internal/log/default.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package log 7 | 8 | // defaultLogger defines a default no-op logging structure. 9 | type defaultLogger struct{} 10 | 11 | func (l *defaultLogger) Errorf(_ string, _ ...interface{}) {} 12 | func (l *defaultLogger) Error(_ ...interface{}) {} 13 | func (l *defaultLogger) Debugf(_ string, _ ...interface{}) {} 14 | func (l *defaultLogger) Debug(_ ...interface{}) {} 15 | func (l *defaultLogger) Warnf(_ string, _ ...interface{}) {} 16 | func (l *defaultLogger) Warn(_ ...interface{}) {} 17 | func (l *defaultLogger) Infof(_ string, _ ...interface{}) {} 18 | func (l *defaultLogger) Info(_ ...interface{}) {} 19 | -------------------------------------------------------------------------------- /internal/cpuid/hybrid.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package cpuid 7 | 8 | var ( 9 | cpuIsHybrid bool 10 | cpuIsHybridCheckedOnce bool 11 | ) 12 | 13 | // IsCPUHybrid checks if CPU is hybrid (Alder Lake, Raptor Lake, Meteor Lake, etc.) 14 | // The function ensures the actual cpuid reading is done only once. 15 | // The cpuid value handling is aligned with the process_cpuid() function of turbostat. 16 | func IsCPUHybrid() bool { 17 | if !cpuIsHybridCheckedOnce { 18 | cpuIsHybridCheckedOnce = true 19 | maxLevel, _, _, _ := cpuid_count(0, 0) 20 | if maxLevel >= 0x7 { 21 | _, _, _, edx := cpuid_count(7, 0) 22 | if (edx & (1 << 15)) != 0 { 23 | cpuIsHybrid = true 24 | } 25 | } 26 | } 27 | return cpuIsHybrid 28 | } 29 | -------------------------------------------------------------------------------- /clock.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package powertelemetry 7 | 8 | import ( 9 | "time" 10 | 11 | "github.com/jmhodges/clock" 12 | ) 13 | 14 | var ( 15 | // holds function definition to retrieve the current local time. 16 | timeNowFn func() time.Time 17 | 18 | // holds a fake clock used to test time-sensitive code. 19 | fakeClock clock.FakeClock 20 | ) 21 | 22 | // setFakeClock gates the use of a fake clock for unit tests to retrieve 23 | // the current local time. 24 | func setFakeClock() { 25 | timeNowFn = fakeClock.Now 26 | } 27 | 28 | // unsetFakeClock restores timeNowFn function to retrieve the current time from the host. 29 | func unsetFakeClock() { 30 | timeNowFn = time.Now 31 | } 32 | 33 | func init() { 34 | timeNowFn = time.Now 35 | fakeClock = clock.NewFake() 36 | } 37 | -------------------------------------------------------------------------------- /internal/version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package version 7 | 8 | import ( 9 | "fmt" 10 | "strings" 11 | ) 12 | 13 | // Set via LDFLAGS -X. 14 | var ( 15 | LibName = "powertelemetry" 16 | Version = "unknown" 17 | Branch = "" 18 | Commit = "" 19 | ) 20 | 21 | func GetFullVersion() string { 22 | var parts = []string{LibName} 23 | 24 | if Version != "" { 25 | parts = append(parts, Version) 26 | } else { 27 | parts = append(parts, "unknown") 28 | } 29 | 30 | if Branch != "" || Commit != "" { 31 | if Branch == "" { 32 | Branch = "unknown" 33 | } 34 | if Commit == "" { 35 | Commit = "unknown" 36 | } 37 | git := fmt.Sprintf("(git: %s@%s)", Branch, Commit) 38 | parts = append(parts, git) 39 | } 40 | 41 | return strings.Join(parts, " ") 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE_OF_DEPENDENCIES.md: -------------------------------------------------------------------------------- 1 | # Licenses of dependencies 2 | 3 | `powertelemetry` may contain portions of the following works: 4 | 5 | - github.com/intel/iaevents [Apache License 2.0](https://github.com/intel/iaevents/blob/main/LICENSE) 6 | - github.com/jmhodges/clock [MIT License](https://github.com/jmhodges/clock/blob/main/LICENSE) 7 | - github.com/shirou/gopsutil [BSD License](https://github.com/shirou/gopsutil/blob/master/LICENSE) 8 | - github.com/tklauser/go-sysconf [BSD 3-Clause "New" or "Revised" License](https://github.com/tklauser/go-sysconf/blob/main/LICENSE) 9 | - github.com/tklauser/numcpus [Apache License 2.0](https://github.com/tklauser/numcpus/blob/main/LICENSE) 10 | - golang.org/x/exp [BSD-3-Clause](https://pkg.go.dev/golang.org/x/exp?tab=licenses) 11 | - golang.org/x/sync [BSD-3-Clause](https://pkg.go.dev/golang.org/x/exp?tab=licenses) 12 | - golang.org/x/sys [BSD-3-Clause](https://pkg.go.dev/golang.org/x/exp?tab=licenses) 13 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/intel/powertelemetry 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/intel/iaevents v1.1.0 7 | github.com/jmhodges/clock v1.2.0 8 | github.com/shirou/gopsutil/v4 v4.24.9 9 | github.com/stretchr/testify v1.9.0 10 | golang.org/x/sync v0.5.0 11 | ) 12 | 13 | require ( 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/ebitengine/purego v0.8.0 // indirect 16 | github.com/go-ole/go-ole v1.2.6 // indirect 17 | github.com/lufia/plan9stats v0.0.0-20230110061619-bbe2e5e100de // indirect 18 | github.com/pmezard/go-difflib v1.0.0 // indirect 19 | github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect 20 | github.com/stretchr/objx v0.5.2 // indirect 21 | github.com/tklauser/go-sysconf v0.3.12 // indirect 22 | github.com/tklauser/numcpus v0.6.1 // indirect 23 | github.com/yusufpapurcu/wmi v1.2.4 // indirect 24 | golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect 25 | golang.org/x/sys v0.25.0 // indirect 26 | gopkg.in/yaml.v3 v3.0.1 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2023 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | package_version_name := github.com/intel/powertelemetry/internal/version 5 | version := $(shell cat VERSION) 6 | tag := $(shell git describe --exact-match --tags 2>/dev/null) 7 | 8 | branch := $(shell git rev-parse --abbrev-ref HEAD) 9 | commit := $(shell git rev-parse --short=8 HEAD) 10 | 11 | LDFLAGS := $(LDFLAGS) -X $(package_version_name).Commit=$(commit) -X $(package_version_name).Branch=$(branch) 12 | ifneq ($(tag),) 13 | LDFLAGS += -X $(package_version_name).Version=$(version) 14 | else 15 | LDFLAGS += -X $(package_version_name).Version=$(version)-$(commit) 16 | endif 17 | 18 | GOFILES ?= $(shell git ls-files '*.go') 19 | GOFMT ?= $(shell gofmt -l -s $(GOFILES)) 20 | 21 | build: 22 | go build -ldflags "$(LDFLAGS)" ./cmd/example 23 | 24 | test: 25 | go test -race -cover -v ./... 26 | 27 | coverage: 28 | go test ./... -coverprofile=coverage.out 29 | 30 | fmtcheck: 31 | @if [ ! -z "$(GOFMT)" ]; then \ 32 | echo "[ERROR] gofmt has found errors in the following files:" ; \ 33 | echo "$(GOFMT)" ; \ 34 | echo "" ;\ 35 | echo "Run make fmt to fix them." ; \ 36 | exit 1 ;\ 37 | fi 38 | 39 | tidy: 40 | go mod verify 41 | go mod tidy 42 | go fix ./... 43 | 44 | .PHONY : build test coverage fmtcheck tidy 45 | -------------------------------------------------------------------------------- /testdata/proc_modules_msr_loaded: -------------------------------------------------------------------------------- 1 | msr 16384 0 - Live 0x0000000000000000 2 | intel_uncore_frequency 16384 0 - Live 0x0000000000000000 3 | binfmt_misc 24576 1 - Live 0x0000000000000000 4 | xt_conntrack 16384 3 - Live 0x0000000000000000 5 | xt_tcpudp 20480 3 - Live 0x0000000000000000 6 | xt_REDIRECT 20480 1 - Live 0x0000000000000000 7 | xt_comment 16384 872 - Live 0x0000000000000000 8 | nft_compat 20480 879 - Live 0x0000000000000000 9 | nft_counter 16384 874 - Live 0x0000000000000000 10 | nft_chain_nat 16384 2 - Live 0x0000000000000000 11 | nf_nat 49152 2 xt_REDIRECT,nft_chain_nat, Live 0x0000000000000000 12 | nf_conntrack 172032 3 xt_conntrack,xt_REDIRECT,nf_nat, Live 0x0000000000000000 13 | nf_defrag_ipv6 24576 1 nf_conntrack, Live 0x0000000000000000 14 | nf_defrag_ipv4 16384 1 nf_conntrack, Live 0x0000000000000000 15 | nf_tables 249856 3085 nft_compat,nft_counter,nft_chain_nat, Live 0x0000000000000000 16 | nfnetlink 20480 2 nft_compat,nf_tables, Live 0x0000000000000000 17 | 8021q 36864 0 - Live 0x0000000000000000 18 | garp 20480 1 8021q, Live 0x0000000000000000 19 | mrp 20480 1 8021q, Live 0x0000000000000000 20 | stp 16384 1 garp, Live 0x0000000000000000 21 | llc 16384 2 garp,stp, Live 0x0000000000000000 22 | bonding 196608 0 - Live 0x0000000000000000 23 | tls 114688 1 bonding, Live 0x0000000000000000 24 | ipmi_ssif 40960 0 - Live 0x0000000000000000 25 | -------------------------------------------------------------------------------- /testdata/proc_modules_msr_not_loaded: -------------------------------------------------------------------------------- 1 | binfmt_misc 24576 1 - Live 0x0000000000000000 2 | xt_conntrack 16384 3 - Live 0x0000000000000000 3 | xt_tcpudp 20480 3 - Live 0x0000000000000000 4 | xt_REDIRECT 20480 1 - Live 0x0000000000000000 5 | xt_comment 16384 872 - Live 0x0000000000000000 6 | nft_compat 20480 879 - Live 0x0000000000000000 7 | nft_counter 16384 874 - Live 0x0000000000000000 8 | nft_chain_nat 16384 2 - Live 0x0000000000000000 9 | nf_nat 49152 2 xt_REDIRECT,nft_chain_nat, Live 0x0000000000000000 10 | nf_conntrack 172032 3 xt_conntrack,xt_REDIRECT,nf_nat, Live 0x0000000000000000 11 | nf_defrag_ipv6 24576 1 nf_conntrack, Live 0x0000000000000000 12 | nf_defrag_ipv4 16384 1 nf_conntrack, Live 0x0000000000000000 13 | nf_tables 249856 3085 nft_compat,nft_counter,nft_chain_nat, Live 0x0000000000000000 14 | nfnetlink 20480 2 nft_compat,nf_tables, Live 0x0000000000000000 15 | 8021q 36864 0 - Live 0x0000000000000000 16 | garp 20480 1 8021q, Live 0x0000000000000000 17 | mrp 20480 1 8021q, Live 0x0000000000000000 18 | stp 16384 1 garp, Live 0x0000000000000000 19 | llc 16384 2 garp,stp, Live 0x0000000000000000 20 | bonding 196608 0 - Live 0x0000000000000000 21 | tls 114688 1 bonding, Live 0x0000000000000000 22 | ipmi_ssif 40960 0 - Live 0x0000000000000000 23 | intel_rapl_msr 20480 0 - Live 0x0000000000000000 24 | intel_rapl_common 40960 1 intel_rapl_msr, Live 0x0000000000000000 25 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package powertelemetry 7 | 8 | import ( 9 | "fmt" 10 | "strings" 11 | ) 12 | 13 | // MultiError holds a slice of error descriptions. Implements Error interface. 14 | // It is used to mark errors that happened during the initialization of PowerTelemetry dependencies. 15 | type MultiError struct { 16 | errs []string 17 | } 18 | 19 | // add takes an error message and appends it to the receiver's slice of error descriptions. 20 | func (e *MultiError) add(errMsg string) { 21 | e.errs = append(e.errs, errMsg) 22 | } 23 | 24 | // Error returns a string with all error descriptions. Implements error.Error. 25 | func (e *MultiError) Error() string { 26 | return strings.Join(e.errs, "; ") 27 | } 28 | 29 | // ModuleNotInitializedError indicates that a module has not been initialized. 30 | type ModuleNotInitializedError struct { 31 | Name string //holds name of not initialized module 32 | } 33 | 34 | // Error returns a reason of this error. 35 | func (e *ModuleNotInitializedError) Error() string { 36 | return fmt.Sprintf("module %q is not initialized", e.Name) 37 | } 38 | 39 | // MetricNotSupportedError indicates that a metric is not supported. 40 | type MetricNotSupportedError struct { 41 | reason string 42 | } 43 | 44 | // Error returns a reason of this error. 45 | func (e *MetricNotSupportedError) Error() string { 46 | return e.reason 47 | } 48 | -------------------------------------------------------------------------------- /testdata/proc_modules_rapl_not_loaded: -------------------------------------------------------------------------------- 1 | msr 16384 0 - Live 0x0000000000000000 2 | intel_uncore_frequency 16384 0 - Live 0x0000000000000000 3 | binfmt_misc 24576 1 - Live 0x0000000000000000 4 | xt_conntrack 16384 3 - Live 0x0000000000000000 5 | xt_tcpudp 20480 3 - Live 0x0000000000000000 6 | 7 | xt_REDIRECT 20480 1 - Live 0x0000000000000000 8 | xt_comment 16384 872 - Live 0x0000000000000000 9 | nft_compat 20480 879 - Live 0x0000000000000000 10 | nft_counter 16384 874 - Live 0x0000000000000000 11 | nft_chain_nat 16384 2 - Live 0x0000000000000000 12 | nf_nat 49152 2 xt_REDIRECT,nft_chain_nat, Live 0x0000000000000000 13 | nf_conntrack 172032 3 xt_conntrack,xt_REDIRECT,nf_nat, Live 0x0000000000000000 14 | nf_defrag_ipv6 24576 1 nf_conntrack, Live 0x0000000000000000 15 | nf_defrag_ipv4 16384 1 nf_conntrack, Live 0x0000000000000000 16 | nf_tables 249856 3085 nft_compat,nft_counter,nft_chain_nat, Live 0x0000000000000000 17 | nfnetlink 20480 2 nft_compat,nf_tables, Live 0x0000000000000000 18 | 8021q 36864 0 - Live 0x0000000000000000 19 | garp 20480 1 8021q, Live 0x0000000000000000 20 | mrp 20480 1 8021q, Live 0x0000000000000000 21 | stp 16384 1 garp, Live 0x0000000000000000 22 | llc 16384 2 garp,stp, Live 0x0000000000000000 23 | bonding 196608 0 - Live 0x0000000000000000 24 | tls 114688 1 bonding, Live 0x0000000000000000 25 | ipmi_ssif 40960 0 - Live 0x0000000000000000 26 | sb_edac 36864 0 - Live 0x0000000000000000 27 | nls_iso8859_1 16384 1 - Live 0x0000000000000000 28 | x86_pkg_temp_thermal 20480 0 - Live 0x0000000000000000 29 | intel_powerclamp 20480 0 - Live 0x0000000000000000 30 | rapl 20480 0 - Live 0x0000000000000000 31 | intel_cstate 20480 0 - Live 0x0000000000000000 32 | -------------------------------------------------------------------------------- /testdata/proc_modules_rapl_loaded: -------------------------------------------------------------------------------- 1 | msr 16384 0 - Live 0x0000000000000000 2 | intel_uncore_frequency 16384 0 - Live 0x0000000000000000 3 | binfmt_misc 24576 1 - Live 0x0000000000000000 4 | xt_conntrack 16384 3 - Live 0x0000000000000000 5 | xt_tcpudp 20480 3 - Live 0x0000000000000000 6 | xt_REDIRECT 20480 1 - Live 0x0000000000000000 7 | xt_comment 16384 872 - Live 0x0000000000000000 8 | nft_compat 20480 879 - Live 0x0000000000000000 9 | nft_counter 16384 874 - Live 0x0000000000000000 10 | nft_chain_nat 16384 2 - Live 0x0000000000000000 11 | nf_nat 49152 2 xt_REDIRECT,nft_chain_nat, Live 0x0000000000000000 12 | nf_conntrack 172032 3 xt_conntrack,xt_REDIRECT,nf_nat, Live 0x0000000000000000 13 | nf_defrag_ipv6 24576 1 nf_conntrack, Live 0x0000000000000000 14 | nf_defrag_ipv4 16384 1 nf_conntrack, Live 0x0000000000000000 15 | nf_tables 249856 3085 nft_compat,nft_counter,nft_chain_nat, Live 0x0000000000000000 16 | nfnetlink 20480 2 nft_compat,nf_tables, Live 0x0000000000000000 17 | 8021q 36864 0 - Live 0x0000000000000000 18 | garp 20480 1 8021q, Live 0x0000000000000000 19 | mrp 20480 1 8021q, Live 0x0000000000000000 20 | stp 16384 1 garp, Live 0x0000000000000000 21 | llc 16384 2 garp,stp, Live 0x0000000000000000 22 | bonding 196608 0 - Live 0x0000000000000000 23 | tls 114688 1 bonding, Live 0x0000000000000000 24 | ipmi_ssif 40960 0 - Live 0x0000000000000000 25 | intel_rapl_msr 20480 0 - Live 0x0000000000000000 26 | intel_rapl_common 40960 1 intel_rapl_msr, Live 0x0000000000000000 27 | sb_edac 36864 0 - Live 0x0000000000000000 28 | nls_iso8859_1 16384 1 - Live 0x0000000000000000 29 | x86_pkg_temp_thermal 20480 0 - Live 0x0000000000000000 30 | intel_powerclamp 20480 0 - Live 0x0000000000000000 31 | rapl 20480 0 - Live 0x0000000000000000 32 | intel_cstate 20480 0 - Live 0x0000000000000000 33 | -------------------------------------------------------------------------------- /internal/log/logger.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package log 7 | 8 | // Logger defines an interface for logging. 9 | type Logger interface { 10 | Errorf(format string, args ...interface{}) 11 | Error(args ...interface{}) 12 | Debugf(format string, args ...interface{}) 13 | Debug(args ...interface{}) 14 | Warnf(format string, args ...interface{}) 15 | Warn(args ...interface{}) 16 | Infof(format string, args ...interface{}) 17 | Info(args ...interface{}) 18 | } 19 | 20 | // log defines a variable that stores the actual logger pointer. 21 | var log Logger = &defaultLogger{} 22 | 23 | // SetLogger sets a user provided logger structure to be used to log messages. 24 | // If the provided logger is a nil pointer, a default no-op logger will be set. 25 | func SetLogger(l Logger) { 26 | if l != nil { 27 | log = l 28 | } else { 29 | log = &defaultLogger{} 30 | } 31 | } 32 | 33 | // Errorf logs an error message. 34 | func Errorf(format string, args ...interface{}) { 35 | log.Errorf(format, args...) 36 | } 37 | 38 | // Error logs an error message. 39 | func Error(args ...interface{}) { 40 | log.Error(args...) 41 | } 42 | 43 | // Debugf logs a debug message. 44 | func Debugf(format string, args ...interface{}) { 45 | log.Debugf(format, args...) 46 | } 47 | 48 | // Debug logs a debug message. 49 | func Debug(args ...interface{}) { 50 | log.Debug(args...) 51 | } 52 | 53 | // Warnf logs a warning message. 54 | func Warnf(format string, args ...interface{}) { 55 | log.Warnf(format, args...) 56 | } 57 | 58 | // Warn logs a warning message. 59 | func Warn(args ...interface{}) { 60 | log.Warn(args...) 61 | } 62 | 63 | // Infof logs an information message. 64 | func Infof(format string, args ...interface{}) { 65 | log.Infof(format, args...) 66 | } 67 | 68 | // Info logs an information message. 69 | func Info(args ...interface{}) { 70 | log.Info(args...) 71 | } 72 | -------------------------------------------------------------------------------- /msr_mock_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package powertelemetry 7 | 8 | import ( 9 | "math/big" 10 | "time" 11 | 12 | "github.com/stretchr/testify/mock" 13 | ) 14 | 15 | // msrRegMock represents a mock for msr type. Implements msrReg interface. 16 | type msrRegMock struct { 17 | mock.Mock 18 | } 19 | 20 | func (m *msrRegMock) getPath() string { 21 | args := m.Called() 22 | return args.String(0) 23 | } 24 | 25 | func (m *msrRegMock) getCPUID() int { 26 | args := m.Called() 27 | return args.Int(0) 28 | } 29 | 30 | func (m *msrRegMock) read(offset uint32) (uint64, error) { 31 | args := m.Called(offset) 32 | return args.Get(0).(uint64), args.Error(1) 33 | } 34 | 35 | func (m *msrRegMock) readAll(offsets []uint32) (map[uint32]uint64, error) { 36 | args := m.Called(offsets) 37 | if args.Get(0) == nil { 38 | return nil, args.Error(1) 39 | } 40 | return args.Get(0).(map[uint32]uint64), args.Error(1) 41 | } 42 | 43 | // msrMock represents a mock for msrDataWithStorage type. Implements msrReaderWithStorage interface. 44 | type msrMock struct { 45 | mock.Mock 46 | } 47 | 48 | func (m *msrMock) initMsrMap(cpuIDs []int, timeout time.Duration) error { 49 | args := m.Called(cpuIDs, timeout) 50 | return args.Error(0) 51 | } 52 | 53 | func (m *msrMock) read(offset uint32, cpuID int) (uint64, error) { 54 | args := m.Called(offset, cpuID) 55 | return args.Get(0).(uint64), args.Error(1) 56 | } 57 | 58 | func (m *msrMock) isMsrLoaded(modulesPath string) (bool, error) { 59 | args := m.Called(modulesPath) 60 | return args.Bool(0), args.Error(1) 61 | } 62 | 63 | func (m *msrMock) update(cpuID int) error { 64 | args := m.Called(cpuID) 65 | return args.Error(0) 66 | } 67 | 68 | func (m *msrMock) scaleOffsetDeltas(cpuID int, offsets []uint32, f *big.Float) error { 69 | args := m.Called(cpuID, offsets, f) 70 | return args.Error(0) 71 | } 72 | 73 | func (m *msrMock) getOffsetDeltas(cpuID int) (map[uint32]uint64, error) { 74 | args := m.Called(cpuID) 75 | if args.Get(0) == nil { 76 | return nil, args.Error(1) 77 | } 78 | return args.Get(0).(map[uint32]uint64), args.Error(1) 79 | } 80 | 81 | func (m *msrMock) getTimestampDelta(cpuID int) (time.Duration, error) { 82 | args := m.Called(cpuID) 83 | return args.Get(0).(time.Duration), args.Error(1) 84 | } 85 | -------------------------------------------------------------------------------- /cpufreq.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package powertelemetry 7 | 8 | import ( 9 | "fmt" 10 | "path/filepath" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | const ( 16 | // Path to folder with a collection of both global and individual CPU attributes. 17 | defaultCPUFreqBasePath = "/sys/devices/system/cpu" 18 | 19 | // Path to a file which frequency provides the current operating frequency of the CPU. 20 | cpuFrequencyPath = "cpufreq/scaling_cur_freq" 21 | ) 22 | 23 | // cpuFreqReader represents a mechanism for reading core frequency values exposed via filesystem. 24 | type cpuFreqReader interface { 25 | init() error 26 | 27 | getCPUFrequencyMhz(cpuID int) (float64, error) 28 | } 29 | 30 | // cpuFreqData allows to get core frequency values exposed via filesystem. Implements cpuFreqReader interface. 31 | type cpuFreqData struct { 32 | cpuFrequencyFilePath string 33 | } 34 | 35 | // getCPUFrequencyMhz returns CPU's current frequency read from a file. 36 | func (c *cpuFreqData) getCPUFrequencyMhz(cpuID int) (float64, error) { 37 | cpuFrequencyFile := c.getCPUFrequencyFilePath(cpuID) 38 | 39 | fileContent, err := readFile(cpuFrequencyFile) 40 | if err != nil { 41 | return 0, fmt.Errorf("error reading file %q: %w", cpuFrequencyFile, err) 42 | } 43 | 44 | cpuFrequency, err := strconv.ParseFloat(strings.TrimRight(string(fileContent), "\n"), 64) 45 | if err != nil { 46 | return 0, fmt.Errorf("error while converting value from file %q: %w", cpuFrequencyFile, err) 47 | } 48 | return cpuFrequency * fromKiloHertzToMegaHertzRatio, nil 49 | } 50 | 51 | // init checks if cpuFrequencyFilePath is a valid path. 52 | // TODO: Consider to remove this method. 53 | func (c *cpuFreqData) init() error { 54 | if len(c.cpuFrequencyFilePath) == 0 { 55 | return fmt.Errorf("base path of CPU core frequency cannot be empty") 56 | } 57 | if err := checkFile(c.cpuFrequencyFilePath); err != nil { 58 | return fmt.Errorf("invalid base path of CPU core frequency: %w", err) 59 | } 60 | return nil 61 | } 62 | 63 | // getCPUFrequencyFilePath returns the file path, from which the CPU's current frequency can be read. 64 | func (c *cpuFreqData) getCPUFrequencyFilePath(cpuID int) string { 65 | cpuFrequencyFilePath := filepath.Join(c.cpuFrequencyFilePath, "cpu%d", cpuFrequencyPath) 66 | return fmt.Sprintf(cpuFrequencyFilePath, cpuID) 67 | } 68 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ### License 4 | 5 | Power Telemetry Library is licensed under the terms in [LICENSE](LICENSE). By contributing to the project, you agree to the license and copyright terms therein and release your contribution under these terms. 6 | 7 | ### Sign your work 8 | 9 | Please use the sign-off line at the end of the patch. Your signature certifies that you wrote the patch or otherwise have the right to pass it on as an open-source patch. The rules are pretty simple: if you can certify 10 | the below (from [developercertificate.org](http://developercertificate.org/)): 11 | 12 | ``` 13 | Developer Certificate of Origin 14 | Version 1.1 15 | 16 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 17 | 660 York Street, Suite 102, 18 | San Francisco, CA 94110 USA 19 | 20 | Everyone is permitted to copy and distribute verbatim copies of this 21 | license document, but changing it is not allowed. 22 | 23 | Developer's Certificate of Origin 1.1 24 | 25 | By making a contribution to this project, I certify that: 26 | 27 | (a) The contribution was created in whole or in part by me and I 28 | have the right to submit it under the open source license 29 | indicated in the file; or 30 | 31 | (b) The contribution is based upon previous work that, to the best 32 | of my knowledge, is covered under an appropriate open source 33 | license and I have the right under that license to submit that 34 | work with modifications, whether created in whole or in part 35 | by me, under the same open source license (unless I am 36 | permitted to submit under a different license), as indicated 37 | in the file; or 38 | 39 | (c) The contribution was provided directly to me by some other 40 | person who certified (a), (b) or (c) and I have not modified 41 | it. 42 | 43 | (d) I understand and agree that this project and the contribution 44 | are public and that a record of the contribution (including all 45 | personal information I submit with it, including my sign-off) is 46 | maintained indefinitely and may be redistributed consistent with 47 | this project or the open source license(s) involved. 48 | ``` 49 | 50 | Then you just add a line to every git commit message: 51 | 52 | Signed-off-by: Joe Smith 53 | 54 | Use your real name (sorry, no pseudonyms or anonymous contributions.) 55 | 56 | If you set your `user.name` and `user.email` git configs, you can sign your 57 | commit automatically with `git commit -s`. 58 | -------------------------------------------------------------------------------- /file.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package powertelemetry 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "io/fs" 12 | "os" 13 | "time" 14 | ) 15 | 16 | // readFile reads the contents of a file at the given path and returns them as a byte slice. 17 | // If the file doesn't exist or can't be read, an error is returned. 18 | func readFile(filePath string) ([]byte, error) { 19 | // Check if the file exists and can be read. 20 | if err := checkFile(filePath); err != nil { 21 | return nil, err 22 | } 23 | 24 | // Read the entire contents of the file into a byte slice. 25 | fileContent, err := os.ReadFile(filePath) 26 | if err != nil { 27 | return nil, fmt.Errorf("error while reading file from path %q: %w", filePath, err) 28 | } 29 | 30 | return fileContent, nil 31 | } 32 | 33 | // readFileWithTimestamp reads the content of the given file specified as argument. 34 | // If no error occurred, it returns a slice of bytes with file content and a timestamp. 35 | // Otherwise, returns an error. 36 | func readFileWithTimestamp(filePath string) ([]byte, time.Time, error) { 37 | // Check if the file exists and can be read. 38 | if err := checkFile(filePath); err != nil { 39 | return nil, time.Time{}, err 40 | } 41 | 42 | // Read the entire contents of the file into a byte slice. 43 | timestamp := timeNowFn() 44 | fileContent, err := os.ReadFile(filePath) 45 | if err != nil { 46 | return nil, time.Time{}, fmt.Errorf("error while reading file from path %q: %w", filePath, err) 47 | } 48 | return fileContent, timestamp, nil 49 | } 50 | 51 | // checkFile is a helper function that returns nil if the given file path exists, 52 | // and it is not a symlink. Otherwise, it returns an error. 53 | func checkFile(path string) error { 54 | if len(path) == 0 { 55 | return errors.New("file path is empty") 56 | } 57 | fInfo, err := os.Lstat(path) 58 | if err != nil { 59 | if errors.Is(err, fs.ErrNotExist) { 60 | return fmt.Errorf("file %q does not exist", path) 61 | } 62 | return fmt.Errorf("could not get the info for file %q: %w", path, err) 63 | } 64 | if fMode := fInfo.Mode(); fMode&os.ModeSymlink != 0 { 65 | return fmt.Errorf("file %q is a symlink", path) 66 | } 67 | return nil 68 | } 69 | 70 | // fileExists checks if a file exists at the given filePath. 71 | // It returns true if the file exists, and false otherwise. 72 | func fileExists(filePath string) (bool, error) { 73 | if len(filePath) == 0 { 74 | return false, errors.New("file path is empty") 75 | } 76 | _, err := os.Stat(filePath) 77 | return !os.IsNotExist(err), nil 78 | } 79 | -------------------------------------------------------------------------------- /cpufreq_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package powertelemetry 7 | 8 | import ( 9 | "errors" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestGetCPUFrequencyMhz(t *testing.T) { 16 | testCases := []struct { 17 | name string 18 | baseCPUFrequencyPath string 19 | cpuID int 20 | expected float64 21 | err error 22 | }{ 23 | { 24 | name: "Correct value of cpu's frequency has been returned.", 25 | baseCPUFrequencyPath: "testdata/cpu-freq", 26 | cpuID: 0, 27 | expected: 888.888, 28 | err: nil, 29 | }, 30 | { 31 | name: "NonNumericContent", 32 | baseCPUFrequencyPath: "testdata/cpu-freq-invalid", 33 | cpuID: 0, 34 | expected: 0, 35 | err: errors.New("error while converting value from file \"testdata/cpu-freq-invalid/cpu0/cpufreq/scaling_cur_freq\""), 36 | }, 37 | { 38 | name: "InvalidPath", 39 | baseCPUFrequencyPath: "testdata/cpu-freq-invalid-path", 40 | cpuID: 0, 41 | expected: 0, 42 | err: errors.New("error reading file \"testdata/cpu-freq-invalid-path/cpu0" + 43 | "/cpufreq/scaling_cur_freq\": file \"testdata/cpu-freq-invalid-path/cpu0/cpufreq/scaling_cur_freq\" does not exist"), 44 | }, 45 | } 46 | for _, tc := range testCases { 47 | t.Run(tc.name, func(t *testing.T) { 48 | c := cpuFreqData{ 49 | cpuFrequencyFilePath: tc.baseCPUFrequencyPath, 50 | } 51 | cpuFrequencyValue, err := c.getCPUFrequencyMhz(tc.cpuID) 52 | if tc.err != nil { 53 | require.Error(t, err) 54 | require.ErrorContains(t, err, tc.err.Error()) 55 | } else { 56 | require.NoError(t, err) 57 | require.Equal(t, tc.expected, cpuFrequencyValue) 58 | } 59 | }) 60 | } 61 | } 62 | 63 | func TestCPUFreqData_Init(t *testing.T) { 64 | testCases := []struct { 65 | name string 66 | cpuFreqPath string 67 | err error 68 | }{ 69 | { 70 | name: "Initialized", 71 | cpuFreqPath: "testdata/cpu-freq", 72 | err: nil, 73 | }, 74 | { 75 | name: "EmptyString", 76 | cpuFreqPath: "", 77 | err: errors.New("base path of CPU core frequency cannot be empty"), 78 | }, 79 | { 80 | name: "WrongPath", 81 | cpuFreqPath: "/dummy/path", 82 | err: errors.New("invalid base path of CPU core frequency"), 83 | }, 84 | } 85 | for _, tc := range testCases { 86 | t.Run(tc.name, func(t *testing.T) { 87 | cpuFreq := &cpuFreqData{ 88 | cpuFrequencyFilePath: tc.cpuFreqPath, 89 | } 90 | 91 | err := cpuFreq.init() 92 | if tc.err != nil { 93 | require.ErrorContains(t, err, tc.err.Error()) 94 | } else { 95 | require.NoError(t, err) 96 | } 97 | }) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /testdata_setup_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package powertelemetry 7 | 8 | import ( 9 | "fmt" 10 | "io/fs" 11 | "os" 12 | "path/filepath" 13 | "strings" 14 | "testing" 15 | ) 16 | 17 | // TestMain rolls out testdata in a temporary directory, runs all tests, and cleans up. 18 | func TestMain(m *testing.M) { 19 | err := setupTestData() 20 | if err == nil { 21 | m.Run() 22 | } 23 | 24 | teardownTestData() 25 | 26 | if err != nil { 27 | fmt.Printf("TestData setup failed: %v\n", err) 28 | os.Exit(1) 29 | } 30 | } 31 | 32 | var tempTestDataDir string 33 | 34 | // makeTestDataPath returns an absolute path created from the temporary directory and 35 | // the relative path. 36 | func makeTestDataPath(rel string) string { 37 | return filepath.Join(tempTestDataDir, rel) 38 | } 39 | 40 | // setupTestData copies all testdata directory structure and files to a temporary directory. 41 | // While copying, files and directories are renamed to replace a colon placeholder "{colon}" 42 | // with a regular colon character. 43 | // 44 | // E.g. the directory "intel-rapl{colon}0{colon}0" will be renamed to "intel-rapl:0:0". 45 | // 46 | // This workaround eliminates the issue with the ZIP archiver, which is used by the Go 47 | // toolchain when importing a library. The ZIP archiver doesn't allow several characters 48 | // including colon to be used in file or directory names. 49 | // 50 | // The workaround requires files and directories in the testdata directory to be named 51 | // the way that colon characters are replaced with a "{colon}" placeholder. 52 | // 53 | // E.g. an "intel-rapl:0:0" directory should be named "intel-rapl{colon}0{colon}0". 54 | // 55 | // TODO: this whole implementation of the ZIP archiver workaround has to be moved under 56 | // the rapl package along with rapl related unit tests when the powertelemetry library architecture 57 | // is reworked to be package based. 58 | func setupTestData() error { 59 | var err error 60 | tempTestDataDir, err = os.MkdirTemp("", "sampledir") 61 | if err != nil { 62 | return err 63 | } 64 | 65 | srcDir := "testdata" 66 | 67 | return filepath.WalkDir(srcDir, func(path string, d fs.DirEntry, _ error) error { 68 | v := path 69 | colonPlaceholder := "{colon}" 70 | colonCharacter := ":" 71 | if strings.Contains(v, colonPlaceholder) { 72 | v = strings.ReplaceAll(v, colonPlaceholder, colonCharacter) 73 | } 74 | 75 | destPath := filepath.Join(tempTestDataDir, v) 76 | 77 | if d.IsDir() { 78 | return os.Mkdir(destPath, 0750) 79 | } else if d.Type() == fs.ModeSymlink { 80 | link, err := os.Readlink(path) 81 | if err != nil { 82 | return err 83 | } 84 | return os.Symlink(link, destPath) 85 | } 86 | 87 | data, err := os.ReadFile(path) 88 | if err != nil { 89 | return err 90 | } 91 | return os.WriteFile(destPath, data, 0640) 92 | }) 93 | } 94 | 95 | // teardownTestData removes the temporary directory with all its contents. 96 | func teardownTestData() { 97 | _ = os.RemoveAll(tempTestDataDir) 98 | } 99 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/ebitengine/purego v0.8.0 h1:JbqvnEzRvPpxhCJzJJ2y0RbiZ8nyjccVUrSM3q+GvvE= 4 | github.com/ebitengine/purego v0.8.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= 5 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 6 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 7 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 8 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 9 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 10 | github.com/intel/iaevents v1.1.0 h1:FzxMBfXk/apG2EUXUCfaq3gUQ+q+TgZ1HNMjjUILUGE= 11 | github.com/intel/iaevents v1.1.0/go.mod h1:CyUUzXw0lHRCsmyyF7Pwco9Y7NiTNQUUlcJ7RJAazKs= 12 | github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs= 13 | github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI= 14 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 15 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 16 | github.com/lufia/plan9stats v0.0.0-20230110061619-bbe2e5e100de h1:V53FWzU6KAZVi1tPp5UIsMoUWJ2/PNwYIDXnu7QuBCE= 17 | github.com/lufia/plan9stats v0.0.0-20230110061619-bbe2e5e100de/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= 18 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 19 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 20 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 21 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 22 | github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig= 23 | github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 24 | github.com/shirou/gopsutil/v4 v4.24.9 h1:KIV+/HaHD5ka5f570RZq+2SaeFsb/pq+fp2DGNWYoOI= 25 | github.com/shirou/gopsutil/v4 v4.24.9/go.mod h1:3fkaHNeYsUFCGZ8+9vZVWtbyM1k2eRnlL+bWO8Bxa/Q= 26 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 27 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 28 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 29 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 30 | github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= 31 | github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= 32 | github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= 33 | github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= 34 | github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= 35 | github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 36 | golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= 37 | golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= 38 | golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= 39 | golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 40 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 41 | golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 42 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 43 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 44 | golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= 45 | golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 46 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 47 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 48 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 49 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 50 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 51 | -------------------------------------------------------------------------------- /uncorefreq.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package powertelemetry 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "path/filepath" 12 | "strconv" 13 | "strings" 14 | ) 15 | 16 | const ( 17 | // base path comprising the uncore frequency type files. 18 | defaultUncoreFreqBasePath = "/sys/devices/system/cpu/intel_uncore_frequency" 19 | 20 | // pattern string to identify the specific package ID of an uncore frequency path. 21 | packageIDPattern = "package_%s" 22 | 23 | // pattern string to identify the specific die ID of an uncore frequency path. 24 | dieIDPattern = "die_%s" 25 | 26 | // pattern to identify file name of the uncore frequency type path. 27 | freqTypeFilePattern = "%s_freq_khz" 28 | ) 29 | 30 | // uncoreFreqType is an enum type to identify specific uncore frequency parameters. 31 | type uncoreFreqType int 32 | 33 | // uncoreFreqType enum defines supported uncore frequency parameters. 34 | const ( 35 | initialMax uncoreFreqType = iota 36 | initialMin 37 | customizedMax 38 | customizedMin 39 | current 40 | ) 41 | 42 | // Helper function to return a string representation of uncoreFreqType. 43 | func (f uncoreFreqType) String() string { 44 | switch f { 45 | case initialMax: 46 | return "initial_max" 47 | case initialMin: 48 | return "initial_min" 49 | case customizedMax: 50 | return "max" 51 | case customizedMin: 52 | return "min" 53 | case current: 54 | return "current" 55 | default: 56 | return "" 57 | } 58 | } 59 | 60 | // Helper function takes a string representation of uncoreFreqType and returns its equivalent 61 | // uncoreFreqType. If the string is not a valid frequency type, an error is returned. 62 | func toUncoreFreqType(freqType string) (uncoreFreqType, error) { 63 | switch freqType { 64 | case "initial_max": 65 | return initialMax, nil 66 | case "initial_min": 67 | return initialMin, nil 68 | case "max": 69 | return customizedMax, nil 70 | case "min": 71 | return customizedMin, nil 72 | case "current": 73 | return current, nil 74 | default: 75 | return 0, fmt.Errorf("unsupported uncore frequency type %q", freqType) 76 | } 77 | } 78 | 79 | // getUncoreFreqPath is a helper function that takes a package ID, die ID, a frequency type and returns 80 | // a string with the corresponding file name of the frequency type parameter. 81 | func getUncoreFreqPath(packageID, dieID int, freqType string) (string, error) { 82 | uFreqType, err := toUncoreFreqType(freqType) 83 | if err != nil { 84 | return "", err 85 | } 86 | freqTypeFile := fmt.Sprintf(freqTypeFilePattern, uFreqType) 87 | 88 | // format packageID and dieID as two-digit strings 89 | packageIDPrefix := fmt.Sprintf(packageIDPattern, fmt.Sprintf("%02d", packageID)) 90 | dieIDPrefix := fmt.Sprintf(dieIDPattern, fmt.Sprintf("%02d", dieID)) 91 | prefix := fmt.Sprintf("%s_%s", packageIDPrefix, dieIDPrefix) 92 | 93 | return filepath.Join(prefix, freqTypeFile), nil 94 | } 95 | 96 | // uncoreFreqReader represents a mechanism for reading uncore frequency values exposed via filesystem. 97 | type uncoreFreqReader interface { 98 | init() error 99 | 100 | // getUncoreFrequencyMhz takes a package ID, die ID and a frequency type and returns its value. 101 | getUncoreFrequencyMhz(packageID, dieID int, freqType string) (float64, error) 102 | } 103 | 104 | // uncoreFreqData allows to get uncore frequency values exposed via filesystem. Implements uncoreFreqReader interface. 105 | type uncoreFreqData struct { 106 | uncoreFreqBasePath string 107 | } 108 | 109 | // init checks if uncoreFreqBasePath is a valid path. 110 | // TODO: Consider to remove this method. 111 | func (u *uncoreFreqData) init() error { 112 | if len(u.uncoreFreqBasePath) == 0 { 113 | return errors.New("base path of uncore frequency cannot be empty") 114 | } 115 | if err := checkFile(u.uncoreFreqBasePath); err != nil { 116 | return fmt.Errorf("invalid base path of uncore frequency: %w", err) 117 | } 118 | return nil 119 | } 120 | 121 | // getUncoreFrequencyMhz retrieves the uncore frequency value, in MHz, for the given package ID and die ID 122 | // and the specified frequency type. 123 | func (u *uncoreFreqData) getUncoreFrequencyMhz(packageID, dieID int, freqType string) (float64, error) { 124 | // Create the path to the frequency file based on the input parameters. 125 | freqPath, err := getUncoreFreqPath(packageID, dieID, freqType) 126 | if err != nil { 127 | return 0, fmt.Errorf("failed to get frequency path: %w", err) 128 | } 129 | 130 | // Read the contents of the frequency file. 131 | path := filepath.Join(u.uncoreFreqBasePath, freqPath) 132 | content, err := readFile(path) 133 | if err != nil { 134 | return 0, fmt.Errorf("failed to read frequency file: %w", err) 135 | } 136 | 137 | // Convert the file contents to a float64 value. 138 | freqKhz, err := strconv.ParseFloat(strings.TrimRight(string(content), "\n"), 64) 139 | if err != nil { 140 | return 0, fmt.Errorf("failed to convert frequency file content to float64: %w", err) 141 | } 142 | return freqKhz * fromKiloHertzToMegaHertzRatio, nil 143 | } 144 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | CommunityCodeOfConduct AT intel DOT com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 126 | [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | -------------------------------------------------------------------------------- /internal/cpumodel/intel_family.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package cpumodel 7 | 8 | // Model definitions for CPU models are taken from the following kernel source link: 9 | // https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/intel-family.h 10 | 11 | /* 12 | * "Big Core" Processors (Branded as Core, Xeon, etc...) 13 | * 14 | * While adding a new CPUID for a new microarchitecture, add a new 15 | * group to keep logically sorted out in chronological order. Within 16 | * that group keep the CPUID for the variants sorted by model number. 17 | * 18 | * The defined symbol names have the following form: 19 | * INTEL_FAM6{OPTFAMILY}_{MICROARCH}{OPTDIFF} 20 | * where: 21 | * OPTFAMILY Describes the family of CPUs that this belongs to. Default 22 | * is assumed to be "_CORE" (and should be omitted). Other values 23 | * currently in use are _ATOM and _XEON_PHI 24 | * MICROARCH Is the code name for the micro-architecture for this core. 25 | * N.B. Not the platform name. 26 | * OPTDIFF If needed, a short string to differentiate by market segment. 27 | * 28 | * Common OPTDIFFs: 29 | * 30 | * - regular client parts 31 | * _L - regular mobile parts 32 | * _G - parts with extra graphics on 33 | * _X - regular server parts 34 | * _D - micro server parts 35 | * _N,_P - other mobile parts 36 | * _H - premium mobile parts 37 | * _S - other client parts 38 | * 39 | * Historical OPTDIFFs: 40 | * 41 | * _EP - 2 socket server parts 42 | * _EX - 4+ socket server parts 43 | * 44 | * Lines may optionally include a comment including platform or core 45 | * names. An exception is made for skylake/kabylake where steppings seem to have gotten 46 | * their own names :-( 47 | */ 48 | 49 | //nolint:revive,godot // this is to keep CPU model definitions same as in Linux kernel sources 50 | const ( 51 | INTEL_FAM6_CORE_YONAH = 0x0E 52 | 53 | INTEL_FAM6_CORE2_MEROM = 0x0F 54 | INTEL_FAM6_CORE2_MEROM_L = 0x16 55 | INTEL_FAM6_CORE2_PENRYN = 0x17 56 | INTEL_FAM6_CORE2_DUNNINGTON = 0x1D 57 | 58 | INTEL_FAM6_NEHALEM = 0x1E 59 | INTEL_FAM6_NEHALEM_G = 0x1F /* Auburndale / Havendale */ 60 | INTEL_FAM6_NEHALEM_EP = 0x1A 61 | INTEL_FAM6_NEHALEM_EX = 0x2E 62 | 63 | INTEL_FAM6_WESTMERE = 0x25 64 | INTEL_FAM6_WESTMERE_EP = 0x2C 65 | INTEL_FAM6_WESTMERE_EX = 0x2F 66 | 67 | INTEL_FAM6_SANDYBRIDGE = 0x2A 68 | INTEL_FAM6_SANDYBRIDGE_X = 0x2D 69 | INTEL_FAM6_IVYBRIDGE = 0x3A 70 | INTEL_FAM6_IVYBRIDGE_X = 0x3E 71 | 72 | INTEL_FAM6_HASWELL = 0x3C 73 | INTEL_FAM6_HASWELL_X = 0x3F 74 | INTEL_FAM6_HASWELL_L = 0x45 75 | INTEL_FAM6_HASWELL_G = 0x46 76 | 77 | INTEL_FAM6_BROADWELL = 0x3D 78 | INTEL_FAM6_BROADWELL_G = 0x47 79 | INTEL_FAM6_BROADWELL_X = 0x4F 80 | INTEL_FAM6_BROADWELL_D = 0x56 81 | 82 | INTEL_FAM6_SKYLAKE_L = 0x4E /* Sky Lake */ 83 | INTEL_FAM6_SKYLAKE = 0x5E /* Sky Lake */ 84 | INTEL_FAM6_SKYLAKE_X = 0x55 /* Sky Lake */ 85 | /* CASCADELAKE_X = 0x55 Sky Lake -- s: 7 */ 86 | /* COOPERLAKE_X = 0x55 Sky Lake -- s: 11 */ 87 | 88 | INTEL_FAM6_KABYLAKE_L = 0x8E /* Sky Lake */ 89 | /* AMBERLAKE_L = 0x8E Sky Lake -- s: 9 */ 90 | /* COFFEELAKE_L = 0x8E Sky Lake -- s: 10 */ 91 | /* WHISKEYLAKE_L = 0x8E Sky Lake -- s: 11,12 */ 92 | 93 | INTEL_FAM6_KABYLAKE = 0x9E /* Sky Lake */ 94 | /* COFFEELAKE = 0x9E Sky Lake -- s: 10-13 */ 95 | 96 | INTEL_FAM6_COMETLAKE = 0xA5 /* Sky Lake */ 97 | INTEL_FAM6_COMETLAKE_L = 0xA6 /* Sky Lake */ 98 | 99 | INTEL_FAM6_CANNONLAKE_L = 0x66 /* Palm Cove */ 100 | 101 | INTEL_FAM6_ICELAKE_X = 0x6A /* Sunny Cove */ 102 | INTEL_FAM6_ICELAKE_D = 0x6C /* Sunny Cove */ 103 | INTEL_FAM6_ICELAKE = 0x7D /* Sunny Cove */ 104 | INTEL_FAM6_ICELAKE_L = 0x7E /* Sunny Cove */ 105 | INTEL_FAM6_ICELAKE_NNPI = 0x9D /* Sunny Cove */ 106 | 107 | INTEL_FAM6_ROCKETLAKE = 0xA7 /* Cypress Cove */ 108 | 109 | INTEL_FAM6_TIGERLAKE_L = 0x8C /* Willow Cove */ 110 | INTEL_FAM6_TIGERLAKE = 0x8D /* Willow Cove */ 111 | 112 | INTEL_FAM6_SAPPHIRERAPIDS_X = 0x8F /* Golden Cove */ 113 | 114 | INTEL_FAM6_EMERALDRAPIDS_X = 0xCF 115 | 116 | INTEL_FAM6_GRANITERAPIDS_X = 0xAD 117 | INTEL_FAM6_GRANITERAPIDS_D = 0xAE 118 | 119 | /* "Hybrid" Processors (P-Core/E-Core) */ 120 | 121 | INTEL_FAM6_LAKEFIELD = 0x8A /* Sunny Cove / Tremont */ 122 | 123 | INTEL_FAM6_ALDERLAKE = 0x97 /* Golden Cove / Gracemont */ 124 | INTEL_FAM6_ALDERLAKE_L = 0x9A /* Golden Cove / Gracemont */ 125 | 126 | INTEL_FAM6_RAPTORLAKE = 0xB7 /* Raptor Cove / Enhanced Gracemont */ 127 | INTEL_FAM6_RAPTORLAKE_P = 0xBA 128 | INTEL_FAM6_RAPTORLAKE_S = 0xBF 129 | 130 | INTEL_FAM6_METEORLAKE = 0xAC 131 | INTEL_FAM6_METEORLAKE_L = 0xAA 132 | 133 | INTEL_FAM6_ARROWLAKE_H = 0xC5 134 | INTEL_FAM6_ARROWLAKE = 0xC6 135 | 136 | INTEL_FAM6_LUNARLAKE_M = 0xBD 137 | 138 | /* "Small Core" Processors (Atom/E-Core) */ 139 | 140 | INTEL_FAM6_ATOM_BONNELL = 0x1C /* Diamondville, Pineview */ 141 | INTEL_FAM6_ATOM_BONNELL_MID = 0x26 /* Silverthorne, Lincroft */ 142 | 143 | INTEL_FAM6_ATOM_SALTWELL = 0x36 /* Cedarview */ 144 | INTEL_FAM6_ATOM_SALTWELL_MID = 0x27 /* Penwell */ 145 | INTEL_FAM6_ATOM_SALTWELL_TABLET = 0x35 /* Cloverview */ 146 | 147 | INTEL_FAM6_ATOM_SILVERMONT = 0x37 /* Bay Trail, Valleyview */ 148 | INTEL_FAM6_ATOM_SILVERMONT_D = 0x4D /* Avaton, Rangely */ 149 | INTEL_FAM6_ATOM_SILVERMONT_MID = 0x4A /* Merriefield */ 150 | INTEL_FAM6_ATOM_SILVERMONT_SMARTPHONE = 0x5A // INTEL_FAM6_ATOM_AIRMONT_MID in turbostat 151 | 152 | INTEL_FAM6_ATOM_AIRMONT = 0x4C /* Cherry Trail, Braswell */ 153 | INTEL_FAM6_ATOM_AIRMONT_NP = 0x75 /* Lightning Mountain */ 154 | 155 | INTEL_FAM6_ATOM_GOLDMONT = 0x5C /* Apollo Lake */ 156 | INTEL_FAM6_ATOM_GOLDMONT_D = 0x5F /* Denverton */ 157 | 158 | /* Note: the micro-architecture is "Goldmont Plus" */ 159 | INTEL_FAM6_ATOM_GOLDMONT_PLUS = 0x7A /* Gemini Lake */ 160 | 161 | INTEL_FAM6_ATOM_TREMONT_D = 0x86 /* Jacobsville */ 162 | INTEL_FAM6_ATOM_TREMONT = 0x96 /* Elkhart Lake */ 163 | INTEL_FAM6_ATOM_TREMONT_L = 0x9C /* Jasper Lake */ 164 | 165 | INTEL_FAM6_ATOM_GRACEMONT = 0xBE /* Alderlake N */ 166 | 167 | INTEL_FAM6_ATOM_CRESTMONT_X = 0xAF /* Sierra Forest */ 168 | INTEL_FAM6_ATOM_CRESTMONT = 0xB6 /* Grand Ridge */ 169 | 170 | /* Xeon Phi */ 171 | 172 | INTEL_FAM6_XEON_PHI_KNL = 0x57 /* Knights Landing */ 173 | INTEL_FAM6_XEON_PHI_KNM = 0x85 /* Knights Mill */ 174 | ) 175 | -------------------------------------------------------------------------------- /uncorefreq_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package powertelemetry 7 | 8 | import ( 9 | "errors" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestUncoreFreqTypeToString(t *testing.T) { 16 | t.Run("InitialMax", func(t *testing.T) { 17 | freqType := uncoreFreqType(0) 18 | require.Equal(t, "initial_max", freqType.String()) 19 | }) 20 | t.Run("InitialMin", func(t *testing.T) { 21 | freqType := uncoreFreqType(1) 22 | require.Equal(t, "initial_min", freqType.String()) 23 | }) 24 | t.Run("CustomizedMax", func(t *testing.T) { 25 | freqType := uncoreFreqType(2) 26 | require.Equal(t, "max", freqType.String()) 27 | }) 28 | t.Run("CustomizedMin", func(t *testing.T) { 29 | freqType := uncoreFreqType(3) 30 | require.Equal(t, "min", freqType.String()) 31 | }) 32 | t.Run("Current", func(t *testing.T) { 33 | freqType := uncoreFreqType(4) 34 | require.Equal(t, "current", freqType.String()) 35 | }) 36 | t.Run("Invalid", func(t *testing.T) { 37 | freqType := uncoreFreqType(5) 38 | require.Equal(t, "", freqType.String()) 39 | }) 40 | } 41 | 42 | func TestGetUncoreFrequencyPath(t *testing.T) { 43 | testCases := []struct { 44 | name string 45 | packageID int 46 | dieID int 47 | freqType string 48 | expected string 49 | err error 50 | }{ 51 | { 52 | name: "InvalidFreqType", 53 | packageID: 1, 54 | dieID: 0, 55 | freqType: "invalid", 56 | expected: "", 57 | err: errors.New("unsupported uncore frequency type \"invalid\""), 58 | }, 59 | { 60 | name: "InitialMax", 61 | packageID: 1, 62 | dieID: 0, 63 | freqType: "initial_max", 64 | expected: "package_01_die_00/initial_max_freq_khz", 65 | err: nil, 66 | }, 67 | { 68 | name: "InitialMin", 69 | packageID: 1, 70 | dieID: 0, 71 | freqType: "initial_min", 72 | expected: "package_01_die_00/initial_min_freq_khz", 73 | err: nil, 74 | }, 75 | { 76 | name: "CustomizedMax", 77 | packageID: 1, 78 | dieID: 0, 79 | freqType: "max", 80 | expected: "package_01_die_00/max_freq_khz", 81 | err: nil, 82 | }, 83 | { 84 | name: "CustomizedMin", 85 | packageID: 1, 86 | dieID: 0, 87 | freqType: "min", 88 | expected: "package_01_die_00/min_freq_khz", 89 | err: nil, 90 | }, 91 | { 92 | name: "Current", 93 | packageID: 0, 94 | dieID: 1, 95 | freqType: "current", 96 | expected: "package_00_die_01/current_freq_khz", 97 | err: nil, 98 | }, 99 | } 100 | 101 | for _, tc := range testCases { 102 | t.Run(tc.name, func(t *testing.T) { 103 | uncoreFreqPath, err := getUncoreFreqPath(tc.packageID, tc.dieID, tc.freqType) 104 | if tc.err != nil { 105 | require.Error(t, err) 106 | require.ErrorContains(t, err, tc.err.Error()) 107 | } else { 108 | require.NoError(t, err) 109 | require.Equal(t, tc.expected, uncoreFreqPath) 110 | } 111 | }) 112 | } 113 | } 114 | 115 | func TestUncoreFreqData_Init(t *testing.T) { 116 | testCases := []struct { 117 | name string 118 | uncoreFreqPath string 119 | err error 120 | }{ 121 | { 122 | name: "UncoreFreqPathEmpty", 123 | uncoreFreqPath: "", 124 | err: errors.New("base path of uncore frequency cannot be empty"), 125 | }, 126 | { 127 | name: "UncoreFreqPathNotExist", 128 | uncoreFreqPath: "/dummy/path", 129 | err: errors.New("invalid base path of uncore frequency"), 130 | }, 131 | { 132 | name: "UncoreFreqPathValid", 133 | uncoreFreqPath: "testdata/intel_uncore_frequency", 134 | err: nil, 135 | }, 136 | } 137 | 138 | for _, tc := range testCases { 139 | t.Run(tc.name, func(t *testing.T) { 140 | uFreqData := &uncoreFreqData{ 141 | uncoreFreqBasePath: tc.uncoreFreqPath, 142 | } 143 | 144 | err := uFreqData.init() 145 | if tc.err != nil { 146 | require.ErrorContains(t, err, tc.err.Error()) 147 | } else { 148 | require.NoError(t, err) 149 | } 150 | }) 151 | } 152 | } 153 | 154 | func TestGetUncoreFrequencyMhz(t *testing.T) { 155 | testCases := []struct { 156 | name string 157 | packageID int 158 | dieID int 159 | freqType string 160 | expected float64 161 | err error 162 | }{ 163 | { 164 | name: "InitialMax", 165 | packageID: 10, 166 | dieID: 3, 167 | freqType: "initial_max", 168 | expected: 2000, 169 | err: nil, 170 | }, 171 | { 172 | name: "InitialMin", 173 | packageID: 10, 174 | dieID: 3, 175 | freqType: "initial_min", 176 | expected: 1000, 177 | err: nil, 178 | }, 179 | { 180 | name: "CustomizedMax", 181 | packageID: 10, 182 | dieID: 3, 183 | freqType: "max", 184 | expected: 1900, 185 | err: nil, 186 | }, 187 | { 188 | name: "CustomizedMin", 189 | packageID: 10, 190 | dieID: 3, 191 | freqType: "min", 192 | expected: 1100, 193 | err: nil, 194 | }, 195 | { 196 | name: "Current", 197 | packageID: 10, 198 | dieID: 3, 199 | freqType: "current", 200 | expected: 1500, 201 | err: nil, 202 | }, 203 | { 204 | name: "InvalidInitialMaxValue", 205 | packageID: 9, 206 | dieID: 12, 207 | freqType: "initial_max", 208 | expected: 0, 209 | err: errors.New("failed to convert frequency file content to float64"), 210 | }, 211 | { 212 | name: "InvalidInitialMinValue", 213 | packageID: 9, 214 | dieID: 12, 215 | freqType: "initial_min", 216 | expected: 0, 217 | err: errors.New("failed to convert frequency file content to float64"), 218 | }, 219 | { 220 | name: "InvalidCustomizedMaxValue", 221 | packageID: 9, 222 | dieID: 12, 223 | freqType: "max", 224 | expected: 0, 225 | err: errors.New("failed to convert frequency file content to float64"), 226 | }, 227 | { 228 | name: "InvalidCustomizedMinValue", 229 | packageID: 9, 230 | dieID: 12, 231 | freqType: "min", 232 | expected: 0, 233 | err: errors.New("failed to convert frequency file content to float64"), 234 | }, 235 | { 236 | name: "InvalidFreqType", 237 | packageID: 9, 238 | dieID: 12, 239 | freqType: "invalid", 240 | expected: 0, 241 | err: errors.New("failed to get frequency path: unsupported uncore frequency type \"invalid\""), 242 | }, 243 | { 244 | name: "FreqTypeFileNotExist", 245 | packageID: 9, 246 | dieID: 12, 247 | freqType: "current", 248 | expected: 0, 249 | err: errors.New("failed to read frequency file: file \"testdata/intel_uncore_frequency/package_09_die_12/current_freq_khz\" does not exist"), 250 | }, 251 | } 252 | 253 | u := &uncoreFreqData{ 254 | uncoreFreqBasePath: "testdata/intel_uncore_frequency", 255 | } 256 | require.NoError(t, u.init()) 257 | 258 | for _, tc := range testCases { 259 | t.Run(tc.name, func(t *testing.T) { 260 | uncoreFreq, err := u.getUncoreFrequencyMhz(tc.packageID, tc.dieID, tc.freqType) 261 | if tc.err != nil { 262 | require.Error(t, err) 263 | require.ErrorContains(t, err, tc.err.Error()) 264 | } else { 265 | require.NoError(t, err) 266 | require.Equal(t, tc.expected, uncoreFreq) 267 | } 268 | }) 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /metrics.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package powertelemetry 7 | 8 | import ( 9 | "fmt" 10 | 11 | "github.com/intel/powertelemetry/internal/cpumodel" 12 | ) 13 | 14 | // CheckIfCPUC1StateResidencySupported checks if CPU C1 state residency metric is supported by CPU model. 15 | // Returns MetricNotSupportedError if metric is not supported by the CPU model; otherwise, returns nil. 16 | func CheckIfCPUC1StateResidencySupported(cpuModel int) error { 17 | if !isC1C6BaseTempSupported(cpuModel) { 18 | return &MetricNotSupportedError{fmt.Sprintf("c1 state residency metric not supported by CPU model: 0x%X", cpuModel)} 19 | } 20 | 21 | return nil 22 | } 23 | 24 | // CheckIfCPUC3StateResidencySupported checks if CPU C3 state residency metric is supported by CPU model. 25 | // Returns MetricNotSupportedError if metric is not supported by the CPU model; otherwise, returns nil. 26 | func CheckIfCPUC3StateResidencySupported(cpuModel int) error { 27 | if !isC3Supported(cpuModel) { 28 | return &MetricNotSupportedError{fmt.Sprintf("c3 state residency metric not supported by CPU model: 0x%X", cpuModel)} 29 | } 30 | 31 | return nil 32 | } 33 | 34 | // CheckIfCPUC6StateResidencySupported checks if CPU C6 state residency metric is supported by CPU model. 35 | // Returns MetricNotSupportedError if metric is not supported by the CPU model; otherwise, returns nil. 36 | func CheckIfCPUC6StateResidencySupported(cpuModel int) error { 37 | if !isC1C6BaseTempSupported(cpuModel) { 38 | return &MetricNotSupportedError{fmt.Sprintf("c6 state residency metric not supported by CPU model: 0x%X", cpuModel)} 39 | } 40 | 41 | return nil 42 | } 43 | 44 | // CheckIfCPUC7StateResidencySupported checks if CPU C7 state residency metric is supported by CPU model. 45 | // Returns MetricNotSupportedError if metric is not supported by the CPU model; otherwise, returns nil. 46 | func CheckIfCPUC7StateResidencySupported(cpuModel int) error { 47 | if !isC7Supported(cpuModel) { 48 | return &MetricNotSupportedError{fmt.Sprintf("c7 state residency metric not supported by CPU model: 0x%X", cpuModel)} 49 | } 50 | 51 | return nil 52 | } 53 | 54 | // CheckIfCPUBaseFrequencySupported checks if CPU base frequency metric is supported by CPU model. 55 | // Returns MetricNotSupportedError if metric is not supported by the CPU model; otherwise, returns nil. 56 | func CheckIfCPUBaseFrequencySupported(cpuModel int) error { 57 | if !isC1C6BaseTempSupported(cpuModel) { 58 | return &MetricNotSupportedError{fmt.Sprintf("cpu base frequency metric not supported by CPU model: 0x%X", cpuModel)} 59 | } 60 | 61 | return nil 62 | } 63 | 64 | // CheckIfCPUTemperatureSupported checks if CPU temperature metric is supported by CPU model. 65 | // Returns MetricNotSupportedError if metric is not supported by the CPU model; otherwise, returns nil. 66 | func CheckIfCPUTemperatureSupported(cpuModel int) error { 67 | if !isC1C6BaseTempSupported(cpuModel) { 68 | return &MetricNotSupportedError{fmt.Sprintf("cpu temperature metric not supported by CPU model: 0x%X", cpuModel)} 69 | } 70 | 71 | return nil 72 | } 73 | 74 | func isC1C6BaseTempSupported(cpuModel int) bool { 75 | switch cpuModel { 76 | case 77 | cpumodel.INTEL_FAM6_NEHALEM, 78 | cpumodel.INTEL_FAM6_NEHALEM_G, 79 | cpumodel.INTEL_FAM6_NEHALEM_EP, 80 | cpumodel.INTEL_FAM6_NEHALEM_EX, 81 | cpumodel.INTEL_FAM6_WESTMERE, 82 | cpumodel.INTEL_FAM6_WESTMERE_EP, 83 | cpumodel.INTEL_FAM6_WESTMERE_EX, 84 | cpumodel.INTEL_FAM6_SANDYBRIDGE, 85 | cpumodel.INTEL_FAM6_SANDYBRIDGE_X, 86 | cpumodel.INTEL_FAM6_IVYBRIDGE, 87 | cpumodel.INTEL_FAM6_IVYBRIDGE_X, 88 | cpumodel.INTEL_FAM6_HASWELL, 89 | cpumodel.INTEL_FAM6_HASWELL_X, 90 | cpumodel.INTEL_FAM6_HASWELL_L, 91 | cpumodel.INTEL_FAM6_HASWELL_G, 92 | cpumodel.INTEL_FAM6_BROADWELL, 93 | cpumodel.INTEL_FAM6_BROADWELL_G, 94 | cpumodel.INTEL_FAM6_BROADWELL_X, 95 | cpumodel.INTEL_FAM6_BROADWELL_D, 96 | cpumodel.INTEL_FAM6_SKYLAKE_L, 97 | cpumodel.INTEL_FAM6_SKYLAKE, 98 | cpumodel.INTEL_FAM6_SKYLAKE_X, 99 | cpumodel.INTEL_FAM6_KABYLAKE_L, 100 | cpumodel.INTEL_FAM6_KABYLAKE, 101 | cpumodel.INTEL_FAM6_COMETLAKE, 102 | cpumodel.INTEL_FAM6_COMETLAKE_L, 103 | cpumodel.INTEL_FAM6_CANNONLAKE_L, 104 | cpumodel.INTEL_FAM6_ICELAKE_X, 105 | cpumodel.INTEL_FAM6_ICELAKE_D, 106 | cpumodel.INTEL_FAM6_ICELAKE, 107 | cpumodel.INTEL_FAM6_ICELAKE_L, 108 | cpumodel.INTEL_FAM6_ICELAKE_NNPI, 109 | cpumodel.INTEL_FAM6_ROCKETLAKE, 110 | cpumodel.INTEL_FAM6_TIGERLAKE_L, 111 | cpumodel.INTEL_FAM6_TIGERLAKE, 112 | cpumodel.INTEL_FAM6_SAPPHIRERAPIDS_X, 113 | cpumodel.INTEL_FAM6_EMERALDRAPIDS_X, 114 | cpumodel.INTEL_FAM6_GRANITERAPIDS_X, 115 | cpumodel.INTEL_FAM6_GRANITERAPIDS_D, 116 | cpumodel.INTEL_FAM6_LAKEFIELD, 117 | cpumodel.INTEL_FAM6_ALDERLAKE, 118 | cpumodel.INTEL_FAM6_ALDERLAKE_L, 119 | cpumodel.INTEL_FAM6_RAPTORLAKE, 120 | cpumodel.INTEL_FAM6_RAPTORLAKE_P, 121 | cpumodel.INTEL_FAM6_RAPTORLAKE_S, 122 | cpumodel.INTEL_FAM6_METEORLAKE, 123 | cpumodel.INTEL_FAM6_METEORLAKE_L, 124 | cpumodel.INTEL_FAM6_ARROWLAKE, 125 | cpumodel.INTEL_FAM6_LUNARLAKE_M, 126 | cpumodel.INTEL_FAM6_ATOM_SILVERMONT, 127 | cpumodel.INTEL_FAM6_ATOM_SILVERMONT_D, 128 | cpumodel.INTEL_FAM6_ATOM_SILVERMONT_MID, 129 | cpumodel.INTEL_FAM6_ATOM_SILVERMONT_SMARTPHONE, 130 | cpumodel.INTEL_FAM6_ATOM_AIRMONT, 131 | cpumodel.INTEL_FAM6_ATOM_GOLDMONT, 132 | cpumodel.INTEL_FAM6_ATOM_GOLDMONT_D, 133 | cpumodel.INTEL_FAM6_ATOM_GOLDMONT_PLUS, 134 | cpumodel.INTEL_FAM6_ATOM_TREMONT_D, 135 | cpumodel.INTEL_FAM6_ATOM_TREMONT, 136 | cpumodel.INTEL_FAM6_ATOM_TREMONT_L, 137 | cpumodel.INTEL_FAM6_ATOM_GRACEMONT, 138 | cpumodel.INTEL_FAM6_ATOM_CRESTMONT_X, 139 | cpumodel.INTEL_FAM6_ATOM_CRESTMONT, 140 | cpumodel.INTEL_FAM6_XEON_PHI_KNL, 141 | cpumodel.INTEL_FAM6_XEON_PHI_KNM: 142 | return true 143 | } 144 | return false 145 | } 146 | 147 | func isC3Supported(cpuModel int) bool { 148 | switch cpuModel { 149 | case 150 | cpumodel.INTEL_FAM6_NEHALEM, 151 | cpumodel.INTEL_FAM6_NEHALEM_G, 152 | cpumodel.INTEL_FAM6_NEHALEM_EP, 153 | cpumodel.INTEL_FAM6_NEHALEM_EX, 154 | cpumodel.INTEL_FAM6_WESTMERE, 155 | cpumodel.INTEL_FAM6_WESTMERE_EP, 156 | cpumodel.INTEL_FAM6_WESTMERE_EX, 157 | cpumodel.INTEL_FAM6_SANDYBRIDGE, 158 | cpumodel.INTEL_FAM6_SANDYBRIDGE_X, 159 | cpumodel.INTEL_FAM6_IVYBRIDGE, 160 | cpumodel.INTEL_FAM6_IVYBRIDGE_X, 161 | cpumodel.INTEL_FAM6_HASWELL, 162 | cpumodel.INTEL_FAM6_HASWELL_X, 163 | cpumodel.INTEL_FAM6_HASWELL_L, 164 | cpumodel.INTEL_FAM6_HASWELL_G, 165 | cpumodel.INTEL_FAM6_BROADWELL, 166 | cpumodel.INTEL_FAM6_BROADWELL_G, 167 | cpumodel.INTEL_FAM6_BROADWELL_X, 168 | cpumodel.INTEL_FAM6_BROADWELL_D, 169 | cpumodel.INTEL_FAM6_SKYLAKE_L, 170 | cpumodel.INTEL_FAM6_SKYLAKE, 171 | cpumodel.INTEL_FAM6_KABYLAKE_L, 172 | cpumodel.INTEL_FAM6_KABYLAKE, 173 | cpumodel.INTEL_FAM6_COMETLAKE, 174 | cpumodel.INTEL_FAM6_COMETLAKE_L, 175 | cpumodel.INTEL_FAM6_ATOM_AIRMONT, 176 | cpumodel.INTEL_FAM6_ATOM_GOLDMONT, 177 | cpumodel.INTEL_FAM6_ATOM_GOLDMONT_PLUS: 178 | return true 179 | } 180 | return false 181 | } 182 | 183 | func isC7Supported(cpuModel int) bool { 184 | switch cpuModel { 185 | case 186 | cpumodel.INTEL_FAM6_SANDYBRIDGE, 187 | cpumodel.INTEL_FAM6_SANDYBRIDGE_X, 188 | cpumodel.INTEL_FAM6_IVYBRIDGE, 189 | cpumodel.INTEL_FAM6_IVYBRIDGE_X, 190 | cpumodel.INTEL_FAM6_HASWELL, 191 | cpumodel.INTEL_FAM6_HASWELL_X, 192 | cpumodel.INTEL_FAM6_HASWELL_L, 193 | cpumodel.INTEL_FAM6_HASWELL_G, 194 | cpumodel.INTEL_FAM6_BROADWELL, 195 | cpumodel.INTEL_FAM6_BROADWELL_G, 196 | cpumodel.INTEL_FAM6_SKYLAKE_L, 197 | cpumodel.INTEL_FAM6_SKYLAKE, 198 | cpumodel.INTEL_FAM6_KABYLAKE_L, 199 | cpumodel.INTEL_FAM6_KABYLAKE, 200 | cpumodel.INTEL_FAM6_COMETLAKE, 201 | cpumodel.INTEL_FAM6_COMETLAKE_L, 202 | cpumodel.INTEL_FAM6_CANNONLAKE_L, 203 | cpumodel.INTEL_FAM6_ICELAKE_L, 204 | cpumodel.INTEL_FAM6_ICELAKE_NNPI, 205 | cpumodel.INTEL_FAM6_ROCKETLAKE, 206 | cpumodel.INTEL_FAM6_TIGERLAKE_L, 207 | cpumodel.INTEL_FAM6_TIGERLAKE, 208 | cpumodel.INTEL_FAM6_LAKEFIELD, 209 | cpumodel.INTEL_FAM6_ALDERLAKE, 210 | cpumodel.INTEL_FAM6_ALDERLAKE_L, 211 | cpumodel.INTEL_FAM6_RAPTORLAKE, 212 | cpumodel.INTEL_FAM6_RAPTORLAKE_P, 213 | cpumodel.INTEL_FAM6_RAPTORLAKE_S, 214 | cpumodel.INTEL_FAM6_METEORLAKE, 215 | cpumodel.INTEL_FAM6_METEORLAKE_L, 216 | cpumodel.INTEL_FAM6_ARROWLAKE, 217 | cpumodel.INTEL_FAM6_LUNARLAKE_M, 218 | cpumodel.INTEL_FAM6_ATOM_GOLDMONT, 219 | cpumodel.INTEL_FAM6_ATOM_GOLDMONT_PLUS, 220 | cpumodel.INTEL_FAM6_ATOM_TREMONT, 221 | cpumodel.INTEL_FAM6_ATOM_TREMONT_L, 222 | cpumodel.INTEL_FAM6_ATOM_GRACEMONT: 223 | return true 224 | } 225 | return false 226 | } 227 | -------------------------------------------------------------------------------- /topology.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package powertelemetry 7 | 8 | import ( 9 | "fmt" 10 | "path/filepath" 11 | "slices" 12 | "strconv" 13 | "strings" 14 | 15 | cpuUtil "github.com/shirou/gopsutil/v4/cpu" 16 | ) 17 | 18 | const ( 19 | // base path which holds global and individual CPU attributes. 20 | defaultDieBasePath = "/sys/devices/system/cpu" 21 | 22 | // name for die ID attribute file, corresponding to a specific CPU ID. 23 | dieFilename = "topology/die_id" 24 | ) 25 | 26 | // topologyGetter gets topology information of the host. 27 | type topologyGetter interface { 28 | getCPUVendor(cpuID int) (string, error) 29 | getCPUFamily(cpuID int) (string, error) 30 | getCPUDieID(cpuID int) (int, error) 31 | getCPUPackageID(cpuID int) (int, error) 32 | getCPUCoreID(cpuID int) (int, error) 33 | getCPUModel() int 34 | getCPUFlags(cpuID int) ([]string, error) 35 | getCPUsNumber() int 36 | getPackageDieIDs(packageID int) ([]int, error) 37 | getPackageIDs() []int 38 | } 39 | 40 | // topologyReader provides per-CPU ID attribute information of the host. 41 | type topologyReader interface { 42 | // initTopology parses topology information from the host. 43 | initTopology() error 44 | 45 | topologyGetter 46 | } 47 | 48 | // cpuInfo represents attribute information of a CPU. 49 | type cpuInfo struct { 50 | vendorID string 51 | family string 52 | dieID int 53 | packageID int 54 | coreID int 55 | flags []string 56 | } 57 | 58 | // topologyData provides information about the processor of the host, including the number of CPUs present, 59 | // CPUs details, CPU model, mapping between packages and dies and all package IDs. 60 | // Implements topologyReader interface. 61 | type topologyData struct { 62 | dieIDPath string 63 | 64 | topologyMap map[int]*cpuInfo 65 | packageDies map[int][]int 66 | packageIDs []int 67 | model int 68 | } 69 | 70 | // initTopology initializes information about the processor of the host, including the number of CPUs present, 71 | // CPUs details, CPU model, mapping between packages and dies and all package IDs. 72 | func (t *topologyData) initTopology() error { 73 | cpus, err := cpuUtil.Info() 74 | if err != nil { 75 | return fmt.Errorf("error occurred while parsing CPU information: %w", err) 76 | } 77 | if len(cpus) == 0 { 78 | return fmt.Errorf("no CPUs were found") 79 | } 80 | 81 | modelParsed := false 82 | cpuInfoMap := make(map[int]*cpuInfo, len(cpus)) 83 | for _, singleCPUInfo := range cpus { 84 | info, err := parseCPUInfo(singleCPUInfo) 85 | if err != nil { 86 | return err 87 | } 88 | 89 | if !modelParsed { 90 | t.model, err = strconv.Atoi(singleCPUInfo.Model) 91 | if err != nil { 92 | return fmt.Errorf("error parsing model: %w", err) 93 | } 94 | modelParsed = true 95 | } 96 | 97 | cpuInfoMap[int(singleCPUInfo.CPU)] = info 98 | } 99 | 100 | t.packageDies = make(map[int][]int) 101 | // Attempt to retrieve die ID for each CPU ID from sysfs 102 | // If not retrieved, default value is zero, as in turbostat. 103 | for cpuID, cInfo := range cpuInfoMap { 104 | cpuDir := "cpu" + strconv.Itoa(cpuID) 105 | dieName := filepath.Join(t.dieIDPath, cpuDir, dieFilename) 106 | dieID, err := extractDieID(dieName) 107 | if err != nil { 108 | continue 109 | } 110 | cpuInfoMap[cpuID].dieID = dieID 111 | t.addDieToPackageDies(cInfo.packageID, dieID) 112 | } 113 | t.topologyMap = cpuInfoMap 114 | 115 | // slices.Compact replaces consecutive runs of equal elements with a single copy 116 | // (therefore, the slice must be sorted earlier to remove duplicates) 117 | for packageID, dies := range t.packageDies { 118 | slices.Sort(dies) 119 | t.packageDies[packageID] = slices.Compact(dies) 120 | } 121 | 122 | // Get ordered slice of unique package IDs. 123 | t.packageIDs = packageIDs(t.topologyMap) 124 | 125 | return nil 126 | } 127 | 128 | // parseCPUInfo parses information from single CPU. 129 | func parseCPUInfo(infoStat cpuUtil.InfoStat) (*cpuInfo, error) { 130 | physicalID, err := strconv.Atoi(infoStat.PhysicalID) 131 | if err != nil { 132 | return nil, fmt.Errorf("error parsing physical ID: %w", err) 133 | } 134 | 135 | coreID, err := strconv.Atoi(infoStat.CoreID) 136 | if err != nil { 137 | return nil, fmt.Errorf("error parsing core ID: %w", err) 138 | } 139 | 140 | return &cpuInfo{ 141 | vendorID: infoStat.VendorID, 142 | family: infoStat.Family, 143 | packageID: physicalID, 144 | coreID: coreID, 145 | flags: infoStat.Flags, 146 | }, nil 147 | } 148 | 149 | // packageIDs takes a topology map and returns a sorted slice with unique package IDs. 150 | func packageIDs(topologyMap map[int]*cpuInfo) []int { 151 | pkgIDs := make([]int, 0, len(topologyMap)) 152 | for _, info := range topologyMap { 153 | pkgIDs = append(pkgIDs, info.packageID) 154 | } 155 | slices.Sort(pkgIDs) 156 | return slices.Compact(pkgIDs) 157 | } 158 | 159 | // extractDieID extracts id of die from dieFile. 160 | func extractDieID(dieFile string) (dieID int, err error) { 161 | // Return 0 in case die_id does not exist 162 | exists, err := fileExists(dieFile) 163 | if err != nil { 164 | return 0, fmt.Errorf("error opening file %q: %w", dieFile, err) 165 | } 166 | if !exists { 167 | return 0, nil 168 | } 169 | 170 | fileContent, err := readFile(dieFile) 171 | if err != nil { 172 | return 0, fmt.Errorf("error reading file %q: %w", dieFile, err) 173 | } 174 | 175 | dieID, err = strconv.Atoi(strings.TrimSpace(string(fileContent))) 176 | if err != nil { 177 | return 0, fmt.Errorf("error converting die ID value from the file %q to int: %w", dieFile, err) 178 | } 179 | 180 | return dieID, nil 181 | } 182 | 183 | func (t *topologyData) addDieToPackageDies(packageID int, dieID int) { 184 | dies, ok := t.packageDies[packageID] 185 | if ok { 186 | dies = append(dies, dieID) 187 | } else { 188 | dies = []int{dieID} 189 | } 190 | t.packageDies[packageID] = dies 191 | } 192 | 193 | // getCPUVendor gets cpu's vendorID value. If no cpu is found for the corresponding cpuID 194 | // an error is returned. 195 | func (t *topologyData) getCPUVendor(cpuID int) (string, error) { 196 | if info, ok := t.topologyMap[cpuID]; ok { 197 | return info.vendorID, nil 198 | } 199 | return "", fmt.Errorf("cpu: %d doesn't exist", cpuID) 200 | } 201 | 202 | // getCPUFamily gets cpu's family value. If no cpu is found for the corresponding cpuID 203 | // an error is returned. 204 | func (t *topologyData) getCPUFamily(cpuID int) (string, error) { 205 | if info, ok := t.topologyMap[cpuID]; ok { 206 | return info.family, nil 207 | } 208 | return "", fmt.Errorf("cpu: %d doesn't exist", cpuID) 209 | } 210 | 211 | // getCPUDieID gets cpu's dieID value. If no cpu is found for the corresponding cpuID 212 | // an error is returned. 213 | func (t *topologyData) getCPUDieID(cpuID int) (int, error) { 214 | if info, ok := t.topologyMap[cpuID]; ok { 215 | return info.dieID, nil 216 | } 217 | return 0, fmt.Errorf("cpu: %d doesn't exist", cpuID) 218 | } 219 | 220 | // getCPUPackageID gets cpu's package ID value. If no cpu is found for the corresponding cpuID 221 | // an error is returned. 222 | func (t *topologyData) getCPUPackageID(cpuID int) (int, error) { 223 | if info, ok := t.topologyMap[cpuID]; ok { 224 | return info.packageID, nil 225 | } 226 | return 0, fmt.Errorf("cpu: %d doesn't exist", cpuID) 227 | } 228 | 229 | // getCPUCoreID gets cpu's core ID value. If no cpu is found for the corresponding cpuID 230 | // an error is returned. 231 | func (t *topologyData) getCPUCoreID(cpuID int) (int, error) { 232 | if info, ok := t.topologyMap[cpuID]; ok { 233 | return info.coreID, nil 234 | } 235 | return 0, fmt.Errorf("cpu: %d doesn't exist", cpuID) 236 | } 237 | 238 | // getCPUModel gets model value of CPU. 239 | func (t *topologyData) getCPUModel() int { 240 | return t.model 241 | } 242 | 243 | // getCPUFlags gets cpu's flags' values. If no cpu is found for the corresponding cpuID 244 | // an error is returned. 245 | func (t *topologyData) getCPUFlags(cpuID int) ([]string, error) { 246 | if info, ok := t.topologyMap[cpuID]; ok { 247 | return info.flags, nil 248 | } 249 | return nil, fmt.Errorf("cpu: %d doesn't exist", cpuID) 250 | } 251 | 252 | // getCPUsNumber returns the number of logical CPUs on a server. 253 | func (t *topologyData) getCPUsNumber() int { 254 | return len(t.topologyMap) 255 | } 256 | 257 | // getCPUDieID gets cpu's dieID value. If no cpu is found for the corresponding cpuID 258 | // an error is returned. 259 | func (t *topologyData) getPackageDieIDs(packageID int) ([]int, error) { 260 | if dies, ok := t.packageDies[packageID]; ok { 261 | return dies, nil 262 | } 263 | return nil, fmt.Errorf("package: %d doesn't exist", packageID) 264 | } 265 | 266 | // getPackageIDs returns a slice with ordered package IDs of the host topology. 267 | func (t *topologyData) getPackageIDs() []int { 268 | return t.packageIDs 269 | } 270 | -------------------------------------------------------------------------------- /metrics_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package powertelemetry 7 | 8 | import ( 9 | "fmt" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestCheckIfCPUC1StateResidencySupported(t *testing.T) { 16 | m := make(map[int]interface{}) 17 | for _, v := range c1c6BaseTempModels { 18 | m[v] = struct{}{} 19 | } 20 | 21 | for model := 0; model < 0xFF; model++ { 22 | err := CheckIfCPUC1StateResidencySupported(model) 23 | if m[model] != nil { 24 | require.NoError(t, err, "CPU model 0x%X should support c1 state residency", model) 25 | } else { 26 | require.ErrorContains(t, err, fmt.Sprintf("c1 state residency metric not supported by CPU model: 0x%X", model), 27 | "CPU model 0x%X shouldn't support c1 state residency", model) 28 | } 29 | } 30 | } 31 | 32 | func TestCheckIfCPUC3StateResidencySupported(t *testing.T) { 33 | m := make(map[int]interface{}) 34 | for _, v := range c3Models { 35 | m[v] = struct{}{} 36 | } 37 | 38 | for model := 0; model < 0xFF; model++ { 39 | err := CheckIfCPUC3StateResidencySupported(model) 40 | if m[model] != nil { 41 | require.NoError(t, err, "CPU model 0x%X should support c3 state residency", model) 42 | } else { 43 | require.ErrorContains(t, err, fmt.Sprintf("c3 state residency metric not supported by CPU model: 0x%X", model), 44 | "CPU model 0x%X shouldn't support c3 state residency", model) 45 | } 46 | } 47 | } 48 | 49 | func TestCheckIfCPUC6StateResidencySupported(t *testing.T) { 50 | m := make(map[int]interface{}) 51 | for _, v := range c1c6BaseTempModels { 52 | m[v] = struct{}{} 53 | } 54 | 55 | for model := 0; model < 0xFF; model++ { 56 | err := CheckIfCPUC6StateResidencySupported(model) 57 | if m[model] != nil { 58 | require.NoError(t, err, "CPU model 0x%X should support c6 state residency", model) 59 | } else { 60 | require.ErrorContains(t, err, fmt.Sprintf("c6 state residency metric not supported by CPU model: 0x%X", model), 61 | "CPU model 0x%X shouldn't support c6 state residency", model) 62 | } 63 | } 64 | } 65 | 66 | func TestCheckIfCPUC7StateResidencySupported(t *testing.T) { 67 | m := make(map[int]interface{}) 68 | for _, v := range c7Models { 69 | m[v] = struct{}{} 70 | } 71 | 72 | for model := 0; model < 0xFF; model++ { 73 | err := CheckIfCPUC7StateResidencySupported(model) 74 | if m[model] != nil { 75 | require.NoError(t, err, "CPU model 0x%X should support c7 state residency", model) 76 | } else { 77 | require.ErrorContains(t, err, fmt.Sprintf("c7 state residency metric not supported by CPU model: 0x%X", model), 78 | "CPU model 0x%X shouldn't support c7 state residency", model) 79 | } 80 | } 81 | } 82 | 83 | func TestCheckIfCPUBaseFrequencySupported(t *testing.T) { 84 | m := make(map[int]interface{}) 85 | for _, v := range c1c6BaseTempModels { 86 | m[v] = struct{}{} 87 | } 88 | 89 | for model := 0; model < 0xFF; model++ { 90 | err := CheckIfCPUBaseFrequencySupported(model) 91 | if m[model] != nil { 92 | require.NoError(t, err, "CPU model 0x%X should support cpu base frequency", model) 93 | } else { 94 | require.ErrorContains(t, err, fmt.Sprintf("cpu base frequency metric not supported by CPU model: 0x%X", model), 95 | "CPU model 0x%X shouldn't support cpu base frequency", model) 96 | } 97 | } 98 | } 99 | 100 | func TestCheckIfCPUTemperatureSupported(t *testing.T) { 101 | m := make(map[int]interface{}) 102 | for _, v := range c1c6BaseTempModels { 103 | m[v] = struct{}{} 104 | } 105 | 106 | for model := 0; model < 0xFF; model++ { 107 | err := CheckIfCPUTemperatureSupported(model) 108 | if m[model] != nil { 109 | require.NoError(t, err, "CPU model 0x%X should support cpu temperature", model) 110 | } else { 111 | require.ErrorContains(t, err, fmt.Sprintf("cpu temperature metric not supported by CPU model: 0x%X", model), 112 | "CPU model 0x%X shouldn't support cpu temperature", model) 113 | } 114 | } 115 | } 116 | 117 | var ( 118 | c1c6BaseTempModels = []int{ 119 | 0x1E, // INTEL_FAM6_NEHALEM 120 | 0x1F, // INTEL_FAM6_NEHALEM_G 121 | 0x1A, // INTEL_FAM6_NEHALEM_EP 122 | 0x2E, // INTEL_FAM6_NEHALEM_EX 123 | 0x25, // INTEL_FAM6_WESTMERE 124 | 0x2C, // INTEL_FAM6_WESTMERE_EP 125 | 0x2F, // INTEL_FAM6_WESTMERE_EX 126 | 0x2A, // INTEL_FAM6_SANDYBRIDGE 127 | 0x2D, // INTEL_FAM6_SANDYBRIDGE_X 128 | 0x3A, // INTEL_FAM6_IVYBRIDGE 129 | 0x3E, // INTEL_FAM6_IVYBRIDGE_X 130 | 0x3C, // INTEL_FAM6_HASWELL 131 | 0x3F, // INTEL_FAM6_HASWELL_X 132 | 0x45, // INTEL_FAM6_HASWELL_L 133 | 0x46, // INTEL_FAM6_HASWELL_G 134 | 0x3D, // INTEL_FAM6_BROADWELL 135 | 0x47, // INTEL_FAM6_BROADWELL_G 136 | 0x4F, // INTEL_FAM6_BROADWELL_X 137 | 0x56, // INTEL_FAM6_BROADWELL_D 138 | 0x4E, // INTEL_FAM6_SKYLAKE_L 139 | 0x5E, // INTEL_FAM6_SKYLAKE 140 | 0x55, // INTEL_FAM6_SKYLAKE_X 141 | 0x8E, // INTEL_FAM6_KABYLAKE_L 142 | 0x9E, // INTEL_FAM6_KABYLAKE 143 | 0xA5, // INTEL_FAM6_COMETLAKE 144 | 0xA6, // INTEL_FAM6_COMETLAKE_L 145 | 0x66, // INTEL_FAM6_CANNONLAKE_L 146 | 0x6A, // INTEL_FAM6_ICELAKE_X 147 | 0x6C, // INTEL_FAM6_ICELAKE_D 148 | 0x7D, // INTEL_FAM6_ICELAKE 149 | 0x7E, // INTEL_FAM6_ICELAKE_L 150 | 0x9D, // INTEL_FAM6_ICELAKE_NNPI 151 | 0xA7, // INTEL_FAM6_ROCKETLAKE 152 | 0x8C, // INTEL_FAM6_TIGERLAKE_L 153 | 0x8D, // INTEL_FAM6_TIGERLAKE 154 | 0x8F, // INTEL_FAM6_SAPPHIRERAPIDS_X 155 | 0xCF, // INTEL_FAM6_EMERALDRAPIDS_X 156 | 0xAD, // INTEL_FAM6_GRANITERAPIDS_X 157 | 0xAE, // INTEL_FAM6_GRANITERAPIDS_D 158 | 0x8A, // INTEL_FAM6_LAKEFIELD 159 | 0x97, // INTEL_FAM6_ALDERLAKE 160 | 0x9A, // INTEL_FAM6_ALDERLAKE_L 161 | 0xB7, // INTEL_FAM6_RAPTORLAKE 162 | 0xBA, // INTEL_FAM6_RAPTORLAKE_P 163 | 0xBF, // INTEL_FAM6_RAPTORLAKE_S 164 | 0xAC, // INTEL_FAM6_METEORLAKE 165 | 0xAA, // INTEL_FAM6_METEORLAKE_L 166 | 0xC6, // INTEL_FAM6_ARROWLAKE 167 | 0xBD, // INTEL_FAM6_LUNARLAKE_M 168 | 0x37, // INTEL_FAM6_ATOM_SILVERMONT 169 | 0x4D, // INTEL_FAM6_ATOM_SILVERMONT_D 170 | 0x4A, // INTEL_FAM6_ATOM_SILVERMONT_MID 171 | 0x5A, // INTEL_FAM6_ATOM_SILVERMONT_SMARTPHONE 172 | 0x4C, // INTEL_FAM6_ATOM_AIRMONT 173 | 0x5C, // INTEL_FAM6_ATOM_GOLDMONT 174 | 0x5F, // INTEL_FAM6_ATOM_GOLDMONT_D 175 | 0x7A, // INTEL_FAM6_ATOM_GOLDMONT_PLUS 176 | 0x86, // INTEL_FAM6_ATOM_TREMONT_D 177 | 0x96, // INTEL_FAM6_ATOM_TREMONT 178 | 0x9C, // INTEL_FAM6_ATOM_TREMONT_L 179 | 0xBE, // INTEL_FAM6_ATOM_GRACEMONT 180 | 0xAF, // INTEL_FAM6_ATOM_CRESTMONT_X 181 | 0xB6, // INTEL_FAM6_ATOM_CRESTMONT 182 | 0x57, // INTEL_FAM6_XEON_PHI_KNL 183 | 0x85, // INTEL_FAM6_XEON_PHI_KNM 184 | } 185 | 186 | c3Models = []int{ 187 | 0x1E, // INTEL_FAM6_NEHALEM 188 | 0x1F, // INTEL_FAM6_NEHALEM_G 189 | 0x1A, // INTEL_FAM6_NEHALEM_EP 190 | 0x2E, // INTEL_FAM6_NEHALEM_EX 191 | 0x25, // INTEL_FAM6_WESTMERE 192 | 0x2C, // INTEL_FAM6_WESTMERE_EP 193 | 0x2F, // INTEL_FAM6_WESTMERE_EX 194 | 0x2A, // INTEL_FAM6_SANDYBRIDGE 195 | 0x2D, // INTEL_FAM6_SANDYBRIDGE_X 196 | 0x3A, // INTEL_FAM6_IVYBRIDGE 197 | 0x3E, // INTEL_FAM6_IVYBRIDGE_X 198 | 0x3C, // INTEL_FAM6_HASWELL 199 | 0x3F, // INTEL_FAM6_HASWELL_X 200 | 0x45, // INTEL_FAM6_HASWELL_L 201 | 0x46, // INTEL_FAM6_HASWELL_G 202 | 0x3D, // INTEL_FAM6_BROADWELL 203 | 0x47, // INTEL_FAM6_BROADWELL_G 204 | 0x4F, // INTEL_FAM6_BROADWELL_X 205 | 0x56, // INTEL_FAM6_BROADWELL_D 206 | 0x4E, // INTEL_FAM6_SKYLAKE_L 207 | 0x5E, // INTEL_FAM6_SKYLAKE 208 | 0x8E, // INTEL_FAM6_KABYLAKE_L 209 | 0x9E, // INTEL_FAM6_KABYLAKE 210 | 0xA5, // INTEL_FAM6_COMETLAKE 211 | 0xA6, // INTEL_FAM6_COMETLAKE_L 212 | 0x4C, // INTEL_FAM6_ATOM_AIRMONT 213 | 0x5C, // INTEL_FAM6_ATOM_GOLDMONT 214 | 0x7A, // INTEL_FAM6_ATOM_GOLDMONT_PLUS 215 | } 216 | 217 | c7Models = []int{ 218 | 0x2A, // INTEL_FAM6_SANDYBRIDGE 219 | 0x2D, // INTEL_FAM6_SANDYBRIDGE_X 220 | 0x3A, // INTEL_FAM6_IVYBRIDGE 221 | 0x3E, // INTEL_FAM6_IVYBRIDGE_X 222 | 0x3C, // INTEL_FAM6_HASWELL 223 | 0x3F, // INTEL_FAM6_HASWELL_X 224 | 0x45, // INTEL_FAM6_HASWELL_L 225 | 0x46, // INTEL_FAM6_HASWELL_G 226 | 0x3D, // INTEL_FAM6_BROADWELL 227 | 0x47, // INTEL_FAM6_BROADWELL_G 228 | 0x4E, // INTEL_FAM6_SKYLAKE_L 229 | 0x5E, // INTEL_FAM6_SKYLAKE 230 | 0x8E, // INTEL_FAM6_KABYLAKE_L 231 | 0x9E, // INTEL_FAM6_KABYLAKE 232 | 0xA5, // INTEL_FAM6_COMETLAKE 233 | 0xA6, // INTEL_FAM6_COMETLAKE_L 234 | 0x66, // INTEL_FAM6_CANNONLAKE_L 235 | 0x7E, // INTEL_FAM6_ICELAKE_L 236 | 0x9D, // INTEL_FAM6_ICELAKE_NNPI 237 | 0xA7, // INTEL_FAM6_ROCKETLAKE 238 | 0x8C, // INTEL_FAM6_TIGERLAKE_L 239 | 0x8D, // INTEL_FAM6_TIGERLAKE 240 | 0x8A, // INTEL_FAM6_LAKEFIELD 241 | 0x97, // INTEL_FAM6_ALDERLAKE 242 | 0x9A, // INTEL_FAM6_ALDERLAKE_L 243 | 0xB7, // INTEL_FAM6_RAPTORLAKE 244 | 0xBA, // INTEL_FAM6_RAPTORLAKE_P 245 | 0xBF, // INTEL_FAM6_RAPTORLAKE_S 246 | 0xAC, // INTEL_FAM6_METEORLAKE 247 | 0xAA, // INTEL_FAM6_METEORLAKE_L 248 | 0xC6, // INTEL_FAM6_ARROWLAKE 249 | 0xBD, // INTEL_FAM6_LUNARLAKE_M 250 | 0x5C, // INTEL_FAM6_ATOM_GOLDMONT 251 | 0x7A, // INTEL_FAM6_ATOM_GOLDMONT_PLUS 252 | 0x96, // INTEL_FAM6_ATOM_TREMONT 253 | 0x9C, // INTEL_FAM6_ATOM_TREMONT_L 254 | 0xBE, // INTEL_FAM6_ATOM_GRACEMONT 255 | } 256 | ) 257 | -------------------------------------------------------------------------------- /cmd/example/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package main 7 | 8 | import ( 9 | "context" 10 | "errors" 11 | "fmt" 12 | "log" 13 | "os" 14 | "time" 15 | 16 | powertelemetry "github.com/intel/powertelemetry" 17 | "github.com/intel/powertelemetry/internal/version" 18 | ) 19 | 20 | const ( 21 | interval = 5 * time.Second // sample interval in seconds 22 | duration = 26 * time.Second // duration of each experiment in seconds 23 | ) 24 | 25 | func main() { 26 | logger := simpleLogger{} 27 | 28 | // Print the current version of the application 29 | logger.Infof("Using: %s", version.GetFullVersion()) 30 | 31 | // TODO: Add logic to parse CPU IDs (and package IDs?) from command line 32 | cpuID := 0 33 | packageID := 0 34 | dieID := 0 35 | includedCPUs := []int{cpuID} 36 | 37 | pt, err := powertelemetry.New( 38 | powertelemetry.WithLogger(&logger), 39 | // powertelemetry.WithExcludedCPUs(excludedCPUs), 40 | powertelemetry.WithIncludedCPUs(includedCPUs), 41 | powertelemetry.WithMsr(), 42 | powertelemetry.WithRapl(), 43 | powertelemetry.WithCoreFrequency(), 44 | powertelemetry.WithUncoreFrequency(), 45 | //powertelemetry.WithPerf(""), 46 | ) 47 | 48 | var initErr *powertelemetry.MultiError 49 | if err != nil { 50 | if !errors.As(err, &initErr) { 51 | logger.Errorf("Failed to build powertelemetry instance: %v", err) 52 | os.Exit(1) 53 | } 54 | logger.Warn(err) 55 | } 56 | 57 | // 58 | // Per CPU ID metrics 59 | // 60 | logger.Info("=== Per CPU ID metrics ===") 61 | 62 | // CPU current frequency metric 63 | cpuFreq, err := pt.GetCPUFrequency(cpuID) 64 | if err != nil { 65 | logger.Errorf("Error getting current frequency for CPU ID %v: %v", cpuID, err) 66 | } else { 67 | logger.Infof("CPU ID: %v, CPU current frequency[MHz]: %0.1f", cpuID, cpuFreq) 68 | } 69 | 70 | // CPU temperature metric 71 | cpuTemp, err := pt.GetCPUTemperature(cpuID) 72 | if err != nil { 73 | logger.Errorf("Error getting temperature for CPU ID %v: %v", cpuID, err) 74 | } else { 75 | logger.Infof("CPU ID: %v, CPU temperature[°C]: %v", cpuID, cpuTemp) 76 | } 77 | 78 | // 79 | // CPU MSR time-based metrics: 80 | // 81 | // * CPU C0/C1/C3/C6/C7 state residency 82 | // * CPU busy frequency 83 | // 84 | logger.Info("=== CPU time-based metrics ===") 85 | 86 | ctx, cancel := context.WithCancel(context.Background()) 87 | defer cancel() 88 | 89 | getCPUMSRMetrics := func() (string, error) { 90 | return func(cpuID int) (string, error) { 91 | if err := pt.UpdatePerCPUMetrics(cpuID); err != nil { 92 | return "", fmt.Errorf("error gathering per CPU metrics for CPU ID %v: %w", cpuID, err) 93 | } 94 | c0State, err := pt.GetCPUC0StateResidency(cpuID) 95 | if err != nil { 96 | return "", fmt.Errorf("error getting CPU C0 state residency for CPU ID %v: %w", cpuID, err) 97 | } 98 | c1State, err := pt.GetCPUC1StateResidency(cpuID) 99 | if err != nil { 100 | return "", fmt.Errorf("error getting CPU C1 state residency for CPU ID %v: %w", cpuID, err) 101 | } 102 | c3State, err := pt.GetCPUC3StateResidency(cpuID) 103 | if err != nil { 104 | return "", fmt.Errorf("error getting CPU C3 state residency for CPU ID %v: %w", cpuID, err) 105 | } 106 | c6State, err := pt.GetCPUC6StateResidency(cpuID) 107 | if err != nil { 108 | return "", fmt.Errorf("error getting CPU C6 state residency for CPU ID %v: %w", cpuID, err) 109 | } 110 | c7State, err := pt.GetCPUC7StateResidency(cpuID) 111 | if err != nil { 112 | return "", fmt.Errorf("error getting CPU C7 state residency for CPU ID %v: %w", cpuID, err) 113 | } 114 | busyFreq, err := pt.GetCPUBusyFrequencyMhz(cpuID) 115 | if err != nil { 116 | return "", fmt.Errorf("error getting CPU busy frequency for CPU ID %v: %w", cpuID, err) 117 | } 118 | return fmt.Sprintf("CPU ID: %v, C0[%%]: %.4f, C1[%%]: %.4f, C3[%%]: %.4f, C6[%%]: %.4f, C7[%%]: %.4f, Busy freq[MHz]: %.4f", 119 | cpuID, c0State, c1State, c3State, c6State, c7State, busyFreq), nil 120 | }(cpuID) 121 | } 122 | 123 | err = printMetricsIteratively(ctx, logger, getCPUMSRMetrics) 124 | if err != nil { 125 | logger.Errorf("Error while getting CPU MSR metrics: %v", err) 126 | } 127 | 128 | // 129 | // Per package ID and die ID metrics 130 | // 131 | logger.Info("=== Per package ID metrics ===") 132 | 133 | // CPU base frequency metric 134 | cpuBaseFreq, err := pt.GetCPUBaseFrequency(packageID) 135 | if err != nil { 136 | logger.Errorf("Error getting CPU base frequency: %v", err) 137 | } else { 138 | logger.Infof("Package ID: %v, CPU base frequency[MHz]: %v", packageID, cpuBaseFreq) 139 | } 140 | 141 | // Package uncore frequency limits 142 | // Package customized uncore maximum frequency 143 | customizedMaxFreq, err := pt.GetCustomizedUncoreFrequencyMax(packageID, dieID) 144 | if err != nil { 145 | logger.Errorf("Error reading customized max frequency of packageID: %v, dieID: %v: %v", packageID, dieID, err) 146 | } else { 147 | logger.Infof("Package ID: %v, die ID: %v, customized uncore frequency max[MHz]: %v", packageID, dieID, customizedMaxFreq) 148 | } 149 | 150 | // Package customized uncore minimum frequency 151 | customizedMinFreq, err := pt.GetCustomizedUncoreFrequencyMin(packageID, dieID) 152 | if err != nil { 153 | logger.Errorf("Error reading customized min frequency of packageID: %v, dieID: %v: %v", packageID, dieID, err) 154 | } else { 155 | logger.Infof("Package ID: %v, die ID: %v, customized uncore frequency min[MHz]: %v", packageID, dieID, customizedMinFreq) 156 | } 157 | 158 | // Package initial uncore maximum frequency 159 | initialMaxFreq, err := pt.GetInitialUncoreFrequencyMax(packageID, dieID) 160 | if err != nil { 161 | logger.Errorf("Error reading initial max frequency of packageID: %v, dieID: %v: %v", packageID, dieID, err) 162 | } else { 163 | logger.Infof("Package ID: %v, die ID: %v, initial uncore frequency max[MHz]: %v", packageID, dieID, initialMaxFreq) 164 | } 165 | 166 | // Package initial uncore minimum frequency 167 | initialMinFreq, err := pt.GetInitialUncoreFrequencyMin(packageID, dieID) 168 | if err != nil { 169 | logger.Errorf("Error reading initial min frequency of packageID: %v, dieID: %v: %v", packageID, dieID, err) 170 | } else { 171 | logger.Infof("Package ID: %v, die ID: %v, initial uncore frequency min[MHz]: %v", packageID, dieID, initialMinFreq) 172 | } 173 | 174 | // Package current uncore frequency metric 175 | currentFreq, err := pt.GetCurrentUncoreFrequency(packageID, dieID) 176 | if err != nil { 177 | logger.Errorf("Error reading current frequency of packageID: %v, dieID: %v: %v", packageID, dieID, err) 178 | } else { 179 | logger.Infof("Package ID: %v, die ID: %v, current uncore frequency[MHz]: %v", packageID, dieID, currentFreq) 180 | } 181 | 182 | // Package thermal design power metric 183 | thermalDesignPower, err := pt.GetPackageThermalDesignPowerWatts(packageID) 184 | if err != nil { 185 | logger.Errorf("Error getting thermal design power for package ID %v: %v", packageID, err) 186 | } else { 187 | logger.Infof("Package ID: %v, thermal design power[W]: %v", packageID, thermalDesignPower) 188 | } 189 | 190 | maxTurboFreqList, err := pt.GetMaxTurboFreqList(packageID) 191 | if err != nil { 192 | logger.Errorf("Error getting max turbo frequency limit list: %v", err) 193 | } else { 194 | for _, v := range maxTurboFreqList { 195 | str := fmt.Sprintf("Package ID: %v, die ID: %v, max turbo frequency: %v MHz, active cores: %v", packageID, dieID, v.Value, v.ActiveCores) 196 | if v.Secondary { 197 | str += ", secondary" 198 | } 199 | logger.Info(str) 200 | } 201 | } 202 | 203 | // 204 | // Current power consumption metrics: 205 | // 206 | // * Package current power consumption 207 | // * Package DRAM current power consumption 208 | // 209 | logger.Info("=== Current Power Consumption ===") 210 | 211 | getPowerConsumptionMetrics := func() (string, error) { 212 | return func(packageID int) (string, error) { 213 | packageCurrPower, err := pt.GetCurrentPackagePowerConsumptionWatts(packageID) 214 | if err != nil { 215 | return "", fmt.Errorf("error getting package power consumption for package ID %v: %w", packageID, err) 216 | } 217 | dramCurrPower, err := pt.GetCurrentDramPowerConsumptionWatts(packageID) 218 | if err != nil { 219 | return "", fmt.Errorf("error getting dram power consumption for package ID %v: %w", packageID, err) 220 | } 221 | return fmt.Sprintf("PackageID: %v, package[W]: %.4f, dram[W]: %.4f", packageID, packageCurrPower, dramCurrPower), nil 222 | }(packageID) 223 | } 224 | 225 | err = printMetricsIteratively(ctx, logger, getPowerConsumptionMetrics) 226 | if err != nil { 227 | logger.Errorf("Error while getting power consumption metrics: %v", err) 228 | } 229 | 230 | // CPU flag support 231 | supported, err := pt.IsFlagSupported("msr") 232 | if err != nil { 233 | logger.Errorf("Error while checking if flag is supported by first CPU: %v", err) 234 | } else { 235 | logger.Infof("Is flag 'msr' supported for first CPU: %t", supported) 236 | } 237 | } 238 | 239 | func printMetricsIteratively(ctx context.Context, logger simpleLogger, getMetrics func() (string, error)) error { 240 | tInterval := time.NewTicker(interval) 241 | tDuration := time.NewTicker(duration) 242 | count := 0 243 | 244 | for { 245 | select { 246 | case <-ctx.Done(): 247 | tInterval.Stop() 248 | tDuration.Stop() 249 | return ctx.Err() 250 | case <-tDuration.C: 251 | tInterval.Stop() 252 | tDuration.Stop() 253 | return nil 254 | case <-tInterval.C: 255 | count++ 256 | if line, err := getMetrics(); err != nil { 257 | logger.Errorf("Error fetching metrics: %v", err) 258 | } else { 259 | logger.Infof("Sample %v: %q", count, line) 260 | } 261 | } 262 | } 263 | } 264 | 265 | type simpleLogger struct { 266 | } 267 | 268 | func (l *simpleLogger) Debugf(format string, args ...interface{}) { 269 | log.Printf("D! "+format, args...) 270 | } 271 | 272 | func (l *simpleLogger) Debug(args ...interface{}) { 273 | log.Print(append([]interface{}{"D! "}, args...)...) 274 | } 275 | 276 | func (l *simpleLogger) Infof(format string, args ...interface{}) { 277 | log.Printf("I! "+format, args...) 278 | } 279 | 280 | func (l *simpleLogger) Info(args ...interface{}) { 281 | log.Print(append([]interface{}{"I! "}, args...)...) 282 | } 283 | 284 | func (l *simpleLogger) Warnf(format string, args ...interface{}) { 285 | log.Printf("W! "+format, args...) 286 | } 287 | 288 | func (l *simpleLogger) Warn(args ...interface{}) { 289 | log.Print(append([]interface{}{"W! "}, args...)...) 290 | } 291 | 292 | func (l *simpleLogger) Errorf(format string, args ...interface{}) { 293 | log.Printf("E! "+format, args...) 294 | } 295 | 296 | func (l *simpleLogger) Error(args ...interface{}) { 297 | log.Print(append([]interface{}{"E! "}, args...)...) 298 | } 299 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable-all: true 3 | enable: 4 | - asasalint 5 | - asciicheck 6 | - bidichk 7 | - bodyclose 8 | - decorder 9 | - dogsled 10 | - dupword 11 | - durationcheck 12 | - errchkjson 13 | - errcheck 14 | - errname 15 | - errorlint 16 | - exhaustive 17 | - exportloopref 18 | - gci 19 | - gocheckcompilerdirectives 20 | - gochecksumtype 21 | - gocritic 22 | - godot 23 | - goprintffuncname 24 | - gosec 25 | - gosimple 26 | - govet 27 | - inamedparam 28 | - ineffassign 29 | - interfacebloat 30 | - lll 31 | - makezero 32 | - mirror 33 | - misspell 34 | - musttag 35 | - nakedret 36 | - nestif 37 | - nilerr 38 | - nolintlint 39 | - perfsprint 40 | - prealloc 41 | - predeclared 42 | - reassign 43 | - revive 44 | - sqlclosecheck 45 | - staticcheck 46 | - tenv 47 | - testifylint 48 | - tparallel 49 | - typecheck 50 | - unconvert 51 | - unparam 52 | - usestdlibvars 53 | - unused 54 | - wastedassign 55 | - whitespace 56 | 57 | linters-settings: 58 | errcheck: 59 | # List of functions to exclude from checking, where each entry is a single function to exclude. 60 | # See https://github.com/kisielk/errcheck#excluding-functions for details. 61 | exclude-functions: 62 | - "(*hash/maphash.Hash).Write" 63 | - "(*hash/maphash.Hash).WriteByte" 64 | - "(*hash/maphash.Hash).WriteString" 65 | gci: 66 | # Section configuration to compare against. 67 | # Section names are case-insensitive and may contain parameters in (). 68 | # The default order of sections is `standard > default > custom > blank > dot`, 69 | # If `custom-order` is `true`, it follows the order of `sections` option. 70 | # Default: ["standard", "default"] 71 | sections: 72 | - standard # Standard section: captures all standard packages. 73 | - default # Default section: contains all imports that could not be matched to another section type. 74 | - prefix(github.com/intel/powertelemetry) # Custom section: groups all imports with the specified Prefix. 75 | gocritic: 76 | # Which checks should be enabled; can't be combined with 'disabled-checks'. 77 | # See https://go-critic.github.io/overview#checks-overview. 78 | # To check which checks are enabled run `GL_DEBUG=gocritic golangci-lint run`. 79 | # By default, list of stable checks is used. 80 | enabled-checks: 81 | - argOrder 82 | - badCall 83 | - badCond 84 | - badLock 85 | - badRegexp 86 | - badSorting 87 | - builtinShadowDecl 88 | - caseOrder 89 | - codegenComment 90 | - commentedOutCode 91 | - deferInLoop 92 | - dupArg 93 | - deprecatedComment 94 | - dupBranchBody 95 | - dupCase 96 | - dupSubExpr 97 | - dynamicFmtString 98 | - emptyDecl 99 | - evalOrder 100 | - exitAfterDefer 101 | - externalErrorReassign 102 | - filepathJoin 103 | - flagName 104 | - mapKey 105 | - nilValReturn 106 | - offBy1 107 | - regexpPattern 108 | - sloppyTestFuncName 109 | - sloppyReassign 110 | - sloppyTypeAssert 111 | - sortSlice 112 | - sprintfQuotedString 113 | - sqlQuery 114 | - syncMapLoadAndDelete 115 | - truncateCmp 116 | - uncheckedInlineErr 117 | - unnecessaryDefer 118 | - weakCond 119 | gosec: 120 | # To select a subset of rules to run. 121 | # Available rules: https://github.com/securego/gosec#available-rules 122 | # Default: [] - means include all rules 123 | includes: 124 | - G101 125 | - G102 126 | - G103 127 | - G106 128 | - G107 129 | - G108 130 | - G109 131 | - G110 132 | - G111 133 | - G112 134 | - G114 135 | - G201 136 | - G202 137 | - G203 138 | - G301 139 | - G302 140 | - G303 141 | - G305 142 | - G306 143 | - G401 144 | - G403 145 | - G404 146 | - G501 147 | - G502 148 | - G503 149 | - G505 150 | - G601 151 | # G104, G105, G113, G204, G304, G307, G402, G504 were not enabled intentionally 152 | # To specify the configuration of rules. 153 | config: 154 | # Maximum allowed permissions mode for os.OpenFile and os.Chmod 155 | # Default: "0600" 156 | G302: "0640" 157 | # Maximum allowed permissions mode for os.WriteFile and ioutil.WriteFile 158 | # Default: "0600" 159 | G306: "0640" 160 | govet: 161 | settings: 162 | ## Check the logging function like it would be a printf 163 | printf: 164 | funcs: 165 | - (github.com/influxdata/telegraf.Logger).Debugf 166 | - (github.com/influxdata/telegraf.Logger).Infof 167 | - (github.com/influxdata/telegraf.Logger).Warnf 168 | - (github.com/influxdata/telegraf.Logger).Errorf 169 | - (github.com/influxdata/telegraf.Logger).Debug 170 | - (github.com/influxdata/telegraf.Logger).Info 171 | - (github.com/influxdata/telegraf.Logger).Warn 172 | - (github.com/influxdata/telegraf.Logger).Error 173 | lll: 174 | # Max line length, lines longer will be reported. 175 | # '\t' is counted as 1 character by default, and can be changed with the tab-width option. 176 | # Default: 120. 177 | line-length: 160 178 | # Tab width in spaces. 179 | # Default: 1 180 | tab-width: 4 181 | nolintlint: 182 | # Enable to require an explanation of nonzero length after each nolint directive. 183 | # Default: false 184 | require-explanation: true 185 | # Enable to require nolint directives to mention the specific linter being suppressed. 186 | # Default: false 187 | require-specific: true 188 | prealloc: 189 | # Report pre-allocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them. 190 | # Default: true 191 | simple: false 192 | revive: 193 | rules: 194 | - name: argument-limit 195 | arguments: [ 6 ] 196 | - name: atomic 197 | - name: bare-return 198 | - name: blank-imports 199 | - name: bool-literal-in-expr 200 | - name: call-to-gc 201 | - name: confusing-naming 202 | - name: confusing-results 203 | - name: constant-logical-expr 204 | - name: context-as-argument 205 | - name: context-keys-type 206 | - name: deep-exit 207 | - name: defer 208 | - name: dot-imports 209 | - name: duplicated-imports 210 | - name: early-return 211 | - name: empty-block 212 | - name: empty-lines 213 | - name: error-naming 214 | - name: error-return 215 | - name: error-strings 216 | - name: errorf 217 | - name: flag-parameter 218 | - name: function-result-limit 219 | arguments: [ 4 ] 220 | - name: identical-branches 221 | - name: if-return 222 | - name: import-shadowing 223 | - name: increment-decrement 224 | - name: indent-error-flow 225 | - name: modifies-parameter 226 | - name: modifies-value-receiver 227 | - name: package-comments 228 | - name: range 229 | - name: range-val-address 230 | - name: range-val-in-closure 231 | - name: receiver-naming 232 | - name: redefines-builtin-id 233 | - name: string-of-int 234 | - name: struct-tag 235 | - name: superfluous-else 236 | - name: time-naming 237 | - name: unconditional-recursion 238 | - name: unexported-naming 239 | - name: unnecessary-stmt 240 | - name: unreachable-code 241 | - name: unused-parameter 242 | - name: var-declaration 243 | - name: var-naming 244 | arguments: [[""], ["PMU"]] 245 | - name: waitgroup-by-value 246 | nakedret: 247 | # make an issue if func has more lines of code than this setting and it has naked returns; default is 30 248 | max-func-lines: 1 249 | tenv: 250 | # The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures. 251 | # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked. 252 | # Default: false 253 | all: true 254 | testifylint: 255 | # Enable specific checkers. 256 | # https://github.com/Antonboom/testifylint#checkers 257 | # Default: ["bool-compare", "compares", "empty", "error-is-as", "error-nil", "expected-actual", "float-compare", "len", "require-error", "suite-dont-use-pkg", "suite-extra-assert-call"] 258 | enable: 259 | - bool-compare 260 | - compares 261 | - empty 262 | - error-is-as 263 | - error-nil 264 | - expected-actual 265 | - len 266 | - require-error 267 | - suite-dont-use-pkg 268 | - suite-extra-assert-call 269 | - suite-thelper 270 | 271 | run: 272 | # timeout for analysis, e.g. 30s, 5m, default is 1m 273 | timeout: 10m 274 | 275 | issues: 276 | # Maximum issues count per one linter. Set to 0 to disable. Default is 50. 277 | max-issues-per-linter: 0 278 | 279 | # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. 280 | max-same-issues: 0 281 | 282 | # List of regexps of issue texts to exclude. 283 | # 284 | # But independently of this option we use default exclude patterns, 285 | # it can be disabled by `exclude-use-default: false`. 286 | # To list all excluded by default patterns execute `golangci-lint run --help` 287 | # 288 | # Default: https://golangci-lint.run/usage/false-positives/#default-exclusions 289 | exclude: 290 | # revive:var-naming 291 | - don't use an underscore in package name 292 | # EXC0001 errcheck: Almost all programs ignore errors on these functions and in most cases it's ok 293 | - Error return value of .((os\.)?std(out|err)\..*|.*Close.*|.*Flush|.*Disconnect|.*Clear|os\.Remove(All)?|.*print(f|ln)?|os\.(Un)?Setenv). is not checked 294 | # EXC0013 revive: Annoying issue about not having a comment. The rare codebase has such comments 295 | - package comment should be of the form "(.+)... 296 | # EXC0015 revive: Annoying issue about not having a comment. The rare codebase has such comments 297 | - should have a package comment 298 | 299 | # Excluding configuration per-path, per-linter, per-text and per-source 300 | exclude-rules: 301 | - path: _test\.go 302 | text: "Potential hardcoded credentials" #gosec:G101 303 | 304 | - path: _test\.go 305 | text: "Use of weak random number generator" #gosec:G404 306 | 307 | # Independently of option `exclude` we use default exclude patterns, 308 | # it can be disabled by this option. 309 | # To list all excluded by default patterns execute `golangci-lint run --help`. 310 | # Default: true. 311 | exclude-use-default: false 312 | 313 | # output configuration options 314 | output: 315 | # Format: colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions 316 | # 317 | # Multiple can be specified by separating them by comma, output can be provided 318 | # for each of them by separating format name and path by colon symbol. 319 | # Output path can be either `stdout`, `stderr` or path to the file to write to. 320 | # Example: "checkstyle:report.json,colored-line-number" 321 | # 322 | # Default: colored-line-number 323 | format: tab 324 | # Make issues output unique by line. 325 | # Default: true 326 | uniq-by-line: false 327 | # Sort results by: filepath, line and column. 328 | sort-results: true 329 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /turbofreq.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package powertelemetry 7 | 8 | import ( 9 | "fmt" 10 | 11 | "github.com/intel/powertelemetry/internal/cpuid" 12 | "github.com/intel/powertelemetry/internal/cpumodel" 13 | ) 14 | 15 | // MaxTurboFreq is an item of a list of max turbo frequencies and related active cores. 16 | type MaxTurboFreq struct { 17 | Value uint64 // Maximum reachable turbo frequency in MHz 18 | ActiveCores uint32 // Maximum number of active cores for the reachable turbo frequency 19 | Secondary bool // Attribute indicating if the list item is related to secondary cores of a hybrid architecture 20 | } 21 | 22 | // isHybrid points to a function that checks if CPU is hybrid. 23 | var isHybrid = cpuid.IsCPUHybrid 24 | 25 | // GetMaxTurboFreqList returns a list of max turbo frequencies and related active cores 26 | // according to the package ID. 27 | func (pt *PowerTelemetry) GetMaxTurboFreqList(packageID int) ([]MaxTurboFreq, error) { 28 | if pt.msr == nil { 29 | return nil, &ModuleNotInitializedError{Name: "msr"} 30 | } 31 | 32 | cpuID, err := pt.getCPUIDFromPackageID(packageID) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | model := pt.topology.getCPUModel() 38 | list := []MaxTurboFreq{} 39 | if hasHswTurboRatioLimit(model) { 40 | out, err := pt.dumpHswTurboRatioLimits(cpuID) 41 | if err != nil { 42 | return nil, fmt.Errorf("dump hsw: %w", err) 43 | } 44 | list = append(list, out...) 45 | } 46 | 47 | if hasIvtTurboRatioLimit(model) { 48 | out, err := pt.dumpIvtTurboRatioLimits(cpuID) 49 | if err != nil { 50 | return nil, fmt.Errorf("dump ivt: %w", err) 51 | } 52 | list = append(list, out...) 53 | } 54 | 55 | if hasTurboRatioLimit(model) { 56 | out, err := pt.dumpTurboRatioLimits(turboRatioLimit, model, cpuID) 57 | if err != nil { 58 | return nil, fmt.Errorf("dump turbo ratio limits: %w", err) 59 | } 60 | list = append(list, out...) 61 | 62 | if isHybrid != nil && isHybrid() { 63 | out, err := pt.dumpTurboRatioLimits(secondaryTurboRatioLimit, model, cpuID) 64 | if err != nil { 65 | return nil, err 66 | } 67 | list = append(list, out...) 68 | } 69 | } 70 | 71 | if hasAtomTurboRatioLimit(model) { 72 | out, err := pt.dumpAtomTurboRatioLimits(cpuID) 73 | if err != nil { 74 | return nil, fmt.Errorf("dump atom: %w", err) 75 | } 76 | list = append(list, out...) 77 | } 78 | 79 | if hasKnlTurboRatioLimit(model) { 80 | out, err := pt.dumpKnlTurboRatioLimits(cpuID) 81 | if err != nil { 82 | return nil, fmt.Errorf("dump knl: %w", err) 83 | } 84 | list = append(list, out...) 85 | } 86 | 87 | return list, nil 88 | } 89 | 90 | // hasHswTurboRatioLimit checks if the model supports the Haswell turbo ratio limit. 91 | func hasHswTurboRatioLimit(model int) bool { 92 | return model == cpumodel.INTEL_FAM6_HASWELL_X // HSW Xeon 93 | } 94 | 95 | // hasKnlTurboRatioLimit checks if the model supports the Knights Landing turbo ratio limit. 96 | func hasKnlTurboRatioLimit(model int) bool { 97 | switch model { 98 | case 99 | cpumodel.INTEL_FAM6_XEON_PHI_KNL, // Knights Landing 100 | cpumodel.INTEL_FAM6_XEON_PHI_KNM: // Knights Mill 101 | return true 102 | } 103 | return false 104 | } 105 | 106 | // hasIvtTurboRatioLimit checks if the model supports the Ivy Bridge turbo ratio limit. 107 | func hasIvtTurboRatioLimit(model int) bool { 108 | switch model { 109 | case 110 | cpumodel.INTEL_FAM6_IVYBRIDGE_X, // IVB Xeon 111 | cpumodel.INTEL_FAM6_HASWELL_X: // HSW Xeon 112 | return true 113 | } 114 | return false 115 | } 116 | 117 | // hasSlvMsrs checks if the model supports Silvermont MSRs. 118 | func hasSlvMsrs(model int) bool { 119 | switch model { 120 | case 121 | cpumodel.INTEL_FAM6_ATOM_SILVERMONT, 122 | cpumodel.INTEL_FAM6_ATOM_SILVERMONT_MID, 123 | cpumodel.INTEL_FAM6_ATOM_SILVERMONT_SMARTPHONE: // INTEL_FAM6_ATOM_AIRMONT_MID in turbostat 124 | return true 125 | } 126 | return false 127 | } 128 | 129 | // hasAtomTurboRatioLimit checks if the model supports the Atom turbo ratio limit. 130 | func hasAtomTurboRatioLimit(model int) bool { 131 | return hasSlvMsrs(model) 132 | } 133 | 134 | // hasTurboRatioLimit checks if the model has turbo ratio limit support. 135 | func hasTurboRatioLimit(model int) bool { 136 | if hasSlvMsrs(model) { 137 | return false 138 | } 139 | 140 | switch model { 141 | // Nehalem compatible, but do not include turbo-ratio limit support 142 | case 143 | cpumodel.INTEL_FAM6_NEHALEM_EX, // Nehalem-EX Xeon - Beckton 144 | cpumodel.INTEL_FAM6_WESTMERE_EX, 145 | cpumodel.INTEL_FAM6_XEON_PHI_KNL, // PHI - Knights Landing (different MSR definition) 146 | cpumodel.INTEL_FAM6_XEON_PHI_KNM: // Knights Mill 147 | return false 148 | } 149 | 150 | return true 151 | } 152 | 153 | // hasTurboRatioGroupLimits checks if the model supports turbo ratio group limits. 154 | func hasTurboRatioGroupLimits(model int) bool { 155 | switch model { 156 | case 157 | cpumodel.INTEL_FAM6_ATOM_GOLDMONT, 158 | cpumodel.INTEL_FAM6_SKYLAKE_X, 159 | cpumodel.INTEL_FAM6_ICELAKE_X, 160 | cpumodel.INTEL_FAM6_ICELAKE_D, 161 | cpumodel.INTEL_FAM6_SAPPHIRERAPIDS_X, 162 | cpumodel.INTEL_FAM6_EMERALDRAPIDS_X, 163 | cpumodel.INTEL_FAM6_ATOM_GOLDMONT_D, 164 | cpumodel.INTEL_FAM6_ATOM_TREMONT_D: 165 | return true 166 | default: 167 | return false 168 | } 169 | } 170 | 171 | // dumpHswTurboRatioLimits returns a list of max turbo frequencies and related active cores 172 | // of a Haswell based CPU. 173 | func (pt *PowerTelemetry) dumpHswTurboRatioLimits(cpuID int) ([]MaxTurboFreq, error) { 174 | msrValue, err := pt.msr.read(turboRatioLimit2, cpuID) 175 | if err != nil { 176 | return nil, fmt.Errorf("can't read MSR 0x%X: %w", turboRatioLimit2, err) 177 | } 178 | 179 | list := []MaxTurboFreq{} 180 | 181 | // Get two least significant octets of the 64-bit MSR value, which represent ratios, and add items with positive ratios to the list. 182 | ratio := (msrValue >> 8) & 0xFF 183 | if ratio > 0 { 184 | list = append(list, MaxTurboFreq{ActiveCores: 18, Value: uint64(float64(ratio) * pt.busClock)}) 185 | } 186 | ratio = (msrValue >> 0) & 0xFF 187 | if ratio > 0 { 188 | list = append(list, MaxTurboFreq{ActiveCores: 17, Value: uint64(float64(ratio) * pt.busClock)}) 189 | } 190 | 191 | return list, nil 192 | } 193 | 194 | // dumpIvtTurboRatioLimits returns a list of max turbo frequencies and related active cores 195 | // of an Ivy Bridge based CPU. 196 | func (pt *PowerTelemetry) dumpIvtTurboRatioLimits(cpuID int) ([]MaxTurboFreq, error) { 197 | msrValue, err := pt.msr.read(turboRatioLimit1, cpuID) 198 | if err != nil { 199 | return nil, fmt.Errorf("can't read MSR 0x%X: %w", turboRatioLimit1, err) 200 | } 201 | 202 | list := []MaxTurboFreq{} 203 | 204 | // Get 8 octets of the 64-bit MSR value, which represent ratios, and add items with positive ratios to the list. 205 | ratio := (msrValue >> 56) & 0xFF 206 | if ratio > 0 { 207 | list = append(list, MaxTurboFreq{ActiveCores: 16, Value: uint64(float64(ratio) * pt.busClock)}) 208 | } 209 | ratio = (msrValue >> 48) & 0xFF 210 | if ratio > 0 { 211 | list = append(list, MaxTurboFreq{ActiveCores: 15, Value: uint64(float64(ratio) * pt.busClock)}) 212 | } 213 | ratio = (msrValue >> 40) & 0xFF 214 | if ratio > 0 { 215 | list = append(list, MaxTurboFreq{ActiveCores: 14, Value: uint64(float64(ratio) * pt.busClock)}) 216 | } 217 | ratio = (msrValue >> 32) & 0xFF 218 | if ratio > 0 { 219 | list = append(list, MaxTurboFreq{ActiveCores: 13, Value: uint64(float64(ratio) * pt.busClock)}) 220 | } 221 | ratio = (msrValue >> 24) & 0xFF 222 | if ratio > 0 { 223 | list = append(list, MaxTurboFreq{ActiveCores: 12, Value: uint64(float64(ratio) * pt.busClock)}) 224 | } 225 | ratio = (msrValue >> 16) & 0xFF 226 | if ratio > 0 { 227 | list = append(list, MaxTurboFreq{ActiveCores: 11, Value: uint64(float64(ratio) * pt.busClock)}) 228 | } 229 | ratio = (msrValue >> 8) & 0xFF 230 | if ratio > 0 { 231 | list = append(list, MaxTurboFreq{ActiveCores: 10, Value: uint64(float64(ratio) * pt.busClock)}) 232 | } 233 | ratio = (msrValue >> 0) & 0xFF 234 | if ratio > 0 { 235 | list = append(list, MaxTurboFreq{ActiveCores: 9, Value: uint64(float64(ratio) * pt.busClock)}) 236 | } 237 | 238 | return list, nil 239 | } 240 | 241 | // dumpTurboRatioLimits returns a list of max turbo frequencies and related active cores 242 | // of a CPU supporting turbo ratio limits. 243 | func (pt *PowerTelemetry) dumpTurboRatioLimits(trlMsrOffset uint32, model int, cpuID int) ([]MaxTurboFreq, error) { 244 | msrValue, err := pt.msr.read(trlMsrOffset, cpuID) 245 | if err != nil { 246 | return nil, fmt.Errorf("can't read MSR 0x%X: %w", trlMsrOffset, err) 247 | } 248 | 249 | secondary := trlMsrOffset == secondaryTurboRatioLimit 250 | 251 | var coreCounts uint64 252 | if hasTurboRatioGroupLimits(model) { 253 | coreCounts, err = pt.msr.read(turboRatioLimit1, cpuID) 254 | if err != nil { 255 | return nil, fmt.Errorf("can't read MSR 0x%X: %w", turboRatioLimit1, err) 256 | } 257 | } else { 258 | coreCounts = 0x0807060504030201 259 | } 260 | 261 | list := []MaxTurboFreq{} 262 | 263 | // Iterate over 8 octets of the 64-bit MSR value and the core counts value, get the ratio and the group size, 264 | // then add items with positive ratios to the list. 265 | for shift := 56; shift >= 0; shift -= 8 { 266 | ratio := (msrValue >> shift) & 0xFF 267 | groupSize := (coreCounts >> shift) & 0xFF 268 | if ratio > 0 { 269 | list = append(list, MaxTurboFreq{ 270 | ActiveCores: uint32(groupSize), 271 | Value: uint64(float64(ratio) * pt.busClock), 272 | Secondary: secondary, 273 | }) 274 | } 275 | } 276 | 277 | return list, nil 278 | } 279 | 280 | // dumpAtomTurboRatioLimits returns a list of max turbo frequencies and related active cores 281 | // of an Atom based CPU. 282 | func (pt *PowerTelemetry) dumpAtomTurboRatioLimits(cpuID int) ([]MaxTurboFreq, error) { 283 | msrValue, err := pt.msr.read(atomCoreTurboRatios, cpuID) 284 | if err != nil { 285 | return nil, fmt.Errorf("can't read MSR 0x%X: %w", atomCoreTurboRatios, err) 286 | } 287 | 288 | list := []MaxTurboFreq{} 289 | 290 | // Get 4 least significant octets of the 64-bit MSR value, which represent ratios, 291 | // and add items with positive ratios to the list. 292 | ratio := (msrValue >> 24) & 0x3F 293 | if ratio > 0 { 294 | list = append(list, MaxTurboFreq{ActiveCores: 4, Value: uint64(float64(ratio) * pt.busClock)}) 295 | } 296 | ratio = (msrValue >> 16) & 0x3F 297 | if ratio > 0 { 298 | list = append(list, MaxTurboFreq{ActiveCores: 3, Value: uint64(float64(ratio) * pt.busClock)}) 299 | } 300 | ratio = (msrValue >> 8) & 0x3F 301 | if ratio > 0 { 302 | list = append(list, MaxTurboFreq{ActiveCores: 2, Value: uint64(float64(ratio) * pt.busClock)}) 303 | } 304 | ratio = (msrValue >> 0) & 0x3F 305 | if ratio > 0 { 306 | list = append(list, MaxTurboFreq{ActiveCores: 1, Value: uint64(float64(ratio) * pt.busClock)}) 307 | } 308 | 309 | return list, nil 310 | } 311 | 312 | // dumpKnlTurboRatioLimits returns a list of max turbo frequencies and related active cores 313 | // of a Knights Landing based CPU. 314 | func (pt *PowerTelemetry) dumpKnlTurboRatioLimits(cpuID int) ([]MaxTurboFreq, error) { 315 | msrValue, err := pt.msr.read(turboRatioLimit, cpuID) 316 | if err != nil { 317 | return nil, fmt.Errorf("can't read MSR 0x%X: %w", turboRatioLimit, err) 318 | } 319 | 320 | list := []MaxTurboFreq{} 321 | 322 | const bucketsNo = 7 323 | cores := [bucketsNo]uint64{} 324 | ratio := [bucketsNo]uint64{} 325 | 326 | bNr := 0 327 | cores[bNr] = (msrValue & 0xFF) >> 1 // Get maximum number of cores in Group 0 328 | ratio[bNr] = (msrValue >> 8) & 0xFF // Get maximum ratio limit for Group 0 329 | 330 | // Iterate over octets 3..8 of the 64-bit MSR value, get the number of incremental cores added to Group 1..6 331 | // and get the group ratio delta for Group 1..6 332 | for i := 16; i < 64; i += 8 { 333 | deltaCores := (msrValue >> i) & 0x1F 334 | deltaRatio := (msrValue >> (i + 5)) & 0x7 335 | 336 | cores[bNr+1] = cores[bNr] + deltaCores 337 | ratio[bNr+1] = ratio[bNr] - deltaRatio 338 | bNr++ 339 | } 340 | 341 | // Iterate over the pairs of cores and ratios, add the first pair along with others with unique ratios to the list 342 | for i := bucketsNo - 1; i >= 0; i-- { 343 | if ((i > 0) && (ratio[i] != ratio[i-1])) || (i == 0) { 344 | list = append(list, MaxTurboFreq{ 345 | ActiveCores: uint32(cores[i]), 346 | Value: uint64(float64(ratio[i]) * pt.busClock), 347 | }) 348 | } 349 | } 350 | 351 | return list, nil 352 | } 353 | -------------------------------------------------------------------------------- /topology_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package powertelemetry 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "testing" 12 | 13 | "github.com/stretchr/testify/mock" 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | type topologyMock struct { 18 | mock.Mock 19 | } 20 | 21 | func (m *topologyMock) initTopology() error { 22 | args := m.Called() 23 | return args.Error(0) 24 | } 25 | 26 | func (m *topologyMock) getCPUVendor(cpuID int) (string, error) { 27 | args := m.Called(cpuID) 28 | return args.String(0), args.Error(1) 29 | } 30 | 31 | func (m *topologyMock) getCPUFamily(cpuID int) (string, error) { 32 | args := m.Called(cpuID) 33 | return args.String(0), args.Error(1) 34 | } 35 | 36 | func (m *topologyMock) getCPUDieID(cpuID int) (int, error) { 37 | args := m.Called(cpuID) 38 | return args.Int(0), args.Error(1) 39 | } 40 | 41 | func (m *topologyMock) getCPUPackageID(cpuID int) (int, error) { 42 | args := m.Called(cpuID) 43 | return args.Int(0), args.Error(1) 44 | } 45 | 46 | func (m *topologyMock) getCPUCoreID(cpuID int) (int, error) { 47 | args := m.Called(cpuID) 48 | return args.Int(0), args.Error(1) 49 | } 50 | 51 | func (m *topologyMock) getCPUModel() int { 52 | args := m.Called() 53 | return args.Int(0) 54 | } 55 | 56 | func (m *topologyMock) getCPUFlags(cpuID int) ([]string, error) { 57 | args := m.Called(cpuID) 58 | if args.Get(0) == nil { 59 | return nil, args.Error(1) 60 | } 61 | return args.Get(0).([]string), args.Error(1) 62 | } 63 | 64 | func (m *topologyMock) getCPUsNumber() int { 65 | args := m.Called() 66 | return args.Int(0) 67 | } 68 | 69 | func (m *topologyMock) getPackageDieIDs(packageID int) ([]int, error) { 70 | args := m.Called(packageID) 71 | if args.Get(0) == nil { 72 | return nil, args.Error(1) 73 | } 74 | return args.Get(0).([]int), args.Error(1) 75 | } 76 | 77 | func (m *topologyMock) getPackageIDs() []int { 78 | args := m.Called() 79 | if args.Get(0) == nil { 80 | return nil 81 | } 82 | return args.Get(0).([]int) 83 | } 84 | 85 | func TestCpuFields(t *testing.T) { 86 | flagsExp := []string{"msr", "dts"} 87 | topology := &topologyData{ 88 | topologyMap: map[int]*cpuInfo{ 89 | 0: { 90 | vendorID: "vendorID", 91 | family: "family", 92 | flags: flagsExp, 93 | dieID: 2, 94 | packageID: 2, 95 | }, 96 | }, 97 | } 98 | 99 | t.Run("Validate cpu's family value", func(t *testing.T) { 100 | cpuID := 0 101 | expected := "family" 102 | res, err := topology.getCPUFamily(cpuID) 103 | require.NoError(t, err) 104 | require.EqualValues(t, expected, res) 105 | }) 106 | 107 | t.Run("Validate cpu's vendorID value", func(t *testing.T) { 108 | cpuID := 0 109 | expected := "vendorID" 110 | res, err := topology.getCPUVendor(cpuID) 111 | require.NoError(t, err) 112 | require.EqualValues(t, expected, res) 113 | }) 114 | 115 | t.Run("Family's value doesn't exist in map for given key", func(t *testing.T) { 116 | cpuID := 1 117 | expected := "" 118 | res, err := topology.getCPUFamily(cpuID) 119 | require.EqualValues(t, expected, res) 120 | require.Error(t, err) 121 | require.ErrorContains(t, err, fmt.Sprintf("cpu: %d doesn't exist", cpuID)) 122 | }) 123 | 124 | t.Run("VendorID's value doesn't exist in map for given key", func(t *testing.T) { 125 | cpuID := 1 126 | expected := "" 127 | res, err := topology.getCPUVendor(cpuID) 128 | require.Error(t, err) 129 | require.ErrorContains(t, err, fmt.Sprintf("cpu: %d doesn't exist", cpuID)) 130 | require.EqualValues(t, expected, res) 131 | }) 132 | 133 | t.Run("PackageID value doesn't exist in map for given key", func(t *testing.T) { 134 | cpuID := 1 135 | expected := 0 136 | res, err := topology.getCPUPackageID(cpuID) 137 | require.Error(t, err) 138 | require.ErrorContains(t, err, fmt.Sprintf("cpu: %d doesn't exist", cpuID)) 139 | require.EqualValues(t, expected, res) 140 | }) 141 | 142 | t.Run("CoreID value doesn't exist in map for given key", func(t *testing.T) { 143 | cpuID := 1000 144 | expected := 0 145 | res, err := topology.getCPUCoreID(cpuID) 146 | require.Error(t, err) 147 | require.ErrorContains(t, err, fmt.Sprintf("cpu: %d doesn't exist", cpuID)) 148 | require.EqualValues(t, expected, res) 149 | }) 150 | 151 | t.Run("DieID's value doesn't exist in map for given key", func(t *testing.T) { 152 | cpuID := 1 153 | expected := 0 154 | res, err := topology.getCPUDieID(cpuID) 155 | require.Error(t, err) 156 | require.ErrorContains(t, err, fmt.Sprintf("cpu: %d doesn't exist", cpuID)) 157 | require.EqualValues(t, expected, res) 158 | }) 159 | 160 | t.Run("Validate cpu's packageID value", func(t *testing.T) { 161 | cpuID := 0 162 | expected := 2 163 | res, err := topology.getCPUPackageID(cpuID) 164 | require.NoError(t, err) 165 | require.EqualValues(t, expected, res) 166 | }) 167 | 168 | t.Run("Validate cpu's coreID value", func(t *testing.T) { 169 | cpuID := 0 170 | expected := 0 171 | res, err := topology.getCPUCoreID(cpuID) 172 | require.NoError(t, err) 173 | require.EqualValues(t, expected, res) 174 | }) 175 | 176 | t.Run("Validate cpu's dieID value", func(t *testing.T) { 177 | cpuID := 0 178 | expected := 2 179 | res, err := topology.getCPUDieID(cpuID) 180 | require.NoError(t, err) 181 | require.EqualValues(t, expected, res) 182 | }) 183 | 184 | t.Run("ValidCPUID", func(t *testing.T) { 185 | cpuID := 0 186 | expected := flagsExp 187 | res, err := topology.getCPUFlags(cpuID) 188 | require.NoError(t, err) 189 | require.EqualValues(t, expected, res) 190 | }) 191 | 192 | t.Run("InvalidCPUID", func(t *testing.T) { 193 | cpuID := 1 194 | expected := []string(nil) 195 | res, err := topology.getCPUFlags(cpuID) 196 | require.Error(t, err) 197 | require.ErrorContains(t, err, fmt.Sprintf("cpu: %d doesn't exist", cpuID)) 198 | require.EqualValues(t, expected, res) 199 | }) 200 | } 201 | 202 | // TestExtractDieID checks if dieID value extracted from file is correct in different test cases. 203 | func TestExtractDieID(t *testing.T) { 204 | testCases := []struct { 205 | desc string 206 | filePath string 207 | expectedDieID int 208 | err error 209 | }{ 210 | { 211 | desc: "Extracted", 212 | filePath: "testdata/die-id-valid/cpu1/topology/die_id", 213 | expectedDieID: 1, 214 | err: nil, 215 | }, 216 | { 217 | desc: "EmptyFilename", 218 | filePath: "", 219 | expectedDieID: 0, 220 | err: errors.New("error opening file \"\""), 221 | }, 222 | { 223 | desc: "DirectoryInsteadOfFile", 224 | filePath: "testdata", 225 | expectedDieID: 0, 226 | err: errors.New("error reading file \"testdata\": error while reading file from path \"testdata\""), 227 | }, 228 | { 229 | desc: "FileNotExist", 230 | filePath: "testdata/die-id-valid/cpu1/topology/die_id_badPath", 231 | expectedDieID: 0, 232 | err: nil, 233 | }, 234 | { 235 | desc: "NotExtracted", 236 | filePath: "testdata/die-id-invalid/cpu1/topology/die_id", 237 | expectedDieID: 0, 238 | err: errors.New("error converting die ID value from the file \"testdata/die-id-invalid/cpu1/topology/die_id\" to int"), 239 | }, 240 | } 241 | 242 | for _, testCase := range testCases { 243 | t.Run(testCase.desc, func(t *testing.T) { 244 | resDieID, err := extractDieID(testCase.filePath) 245 | require.Equal(t, testCase.expectedDieID, resDieID) 246 | if testCase.err != nil { 247 | require.ErrorContains(t, err, testCase.err.Error()) 248 | } else { 249 | require.NoError(t, err) 250 | } 251 | }) 252 | } 253 | } 254 | 255 | // TestInitTopology checks if cpuInfo value of topology struct is correct in different test cases. 256 | func TestInitTopology(t *testing.T) { 257 | testCases := []struct { 258 | name string 259 | cpuInfoPath string 260 | diePath string 261 | topologyMapExp map[int]*cpuInfo 262 | packageDiesExp map[int][]int 263 | packageIDsExp []int 264 | err error 265 | }{ 266 | { 267 | name: "InitializedWithValidDieIDPath", 268 | diePath: "testdata/die-id-valid", 269 | cpuInfoPath: "testdata/cpuinfo_good/", 270 | topologyMapExp: map[int]*cpuInfo{ 271 | 1: { 272 | vendorID: "IdOfVendor", 273 | family: "13", 274 | dieID: 1, 275 | packageID: 2, 276 | coreID: 66, 277 | flags: []string{"no", "flags"}, 278 | }, 279 | }, 280 | packageDiesExp: map[int][]int{ 281 | 2: { 282 | 1, 283 | }, 284 | }, 285 | packageIDsExp: []int{2}, 286 | err: nil, 287 | }, 288 | { 289 | name: "InitializedWithInvalidDieIDPath", 290 | diePath: "testdata/die-id-invalid", 291 | cpuInfoPath: "testdata/cpuinfo_good/", 292 | topologyMapExp: map[int]*cpuInfo{ 293 | 1: { 294 | vendorID: "IdOfVendor", 295 | family: "13", 296 | dieID: 0, 297 | packageID: 2, 298 | coreID: 66, 299 | flags: []string{"no", "flags"}, 300 | }, 301 | }, 302 | packageDiesExp: map[int][]int{}, 303 | packageIDsExp: []int{2}, 304 | err: nil, 305 | }, 306 | { 307 | name: "InitializedWithoutDieIDPath", 308 | cpuInfoPath: "testdata/cpuinfo_good/", 309 | topologyMapExp: map[int]*cpuInfo{ 310 | 1: { 311 | vendorID: "IdOfVendor", 312 | family: "13", 313 | dieID: 0, 314 | packageID: 2, 315 | coreID: 66, 316 | flags: []string{"no", "flags"}, 317 | }, 318 | }, 319 | packageDiesExp: map[int][]int{ 320 | 2: { 321 | 0, 322 | }, 323 | }, 324 | packageIDsExp: []int{2}, 325 | err: nil, 326 | }, 327 | { 328 | name: "InvalidProcessorField", 329 | diePath: "testdata/die-id-valid", 330 | cpuInfoPath: "testdata/cpuinfo_bad1/", 331 | topologyMapExp: nil, 332 | err: errors.New("error occurred while parsing CPU information"), 333 | }, 334 | { 335 | name: "InvalidSteppingField", 336 | diePath: "testdata/die-id-valid", 337 | cpuInfoPath: "testdata/cpuinfo_bad2/", 338 | topologyMapExp: nil, 339 | err: errors.New("error occurred while parsing CPU information"), 340 | }, 341 | { 342 | name: "InvalidCacheSizeField", 343 | diePath: "testdata/die-id-valid", 344 | cpuInfoPath: "testdata/cpuinfo_bad3/", 345 | topologyMapExp: nil, 346 | err: errors.New("error occurred while parsing CPU information"), 347 | }, 348 | { 349 | name: "InvalidCpuInfoPath", 350 | diePath: "testdata/die-id-valid", 351 | cpuInfoPath: "testdata/cpuinfo_bad_path", 352 | topologyMapExp: nil, 353 | err: errors.New("no CPUs were found"), 354 | }, 355 | } 356 | 357 | for _, tc := range testCases { 358 | t.Run(tc.name, func(t *testing.T) { 359 | t.Setenv("HOST_PROC", tc.cpuInfoPath) 360 | 361 | newTopology := &topologyData{ 362 | dieIDPath: tc.diePath, 363 | } 364 | 365 | err := newTopology.initTopology() 366 | require.Equal(t, tc.topologyMapExp, newTopology.topologyMap) 367 | require.Equal(t, tc.packageDiesExp, newTopology.packageDies) 368 | require.Equal(t, tc.packageIDsExp, newTopology.getPackageIDs()) 369 | if tc.err != nil { 370 | require.ErrorContains(t, err, tc.err.Error()) 371 | } else { 372 | require.NoError(t, err) 373 | } 374 | }) 375 | } 376 | } 377 | 378 | func TestGetCPUsNumber(t *testing.T) { 379 | testCases := []struct { 380 | name string 381 | topology topologyReader 382 | numberOfCPUs int 383 | }{ 384 | { 385 | name: "0_CPUs", 386 | topology: &topologyData{ 387 | topologyMap: make(map[int]*cpuInfo), 388 | }, 389 | numberOfCPUs: 0, 390 | }, 391 | { 392 | name: "3_CPUs", 393 | topology: &topologyData{ 394 | topologyMap: map[int]*cpuInfo{ 395 | 0: nil, 396 | 1: nil, 397 | 2: nil, 398 | }, 399 | }, 400 | numberOfCPUs: 3, 401 | }, 402 | } 403 | 404 | for _, tc := range testCases { 405 | t.Run(tc.name, func(t *testing.T) { 406 | actual := tc.topology.getCPUsNumber() 407 | require.Equal(t, tc.numberOfCPUs, actual) 408 | }) 409 | } 410 | } 411 | 412 | func TestPackageIDs(t *testing.T) { 413 | testCases := []struct { 414 | name string 415 | topologyMap map[int]*cpuInfo 416 | packageIDs []int 417 | }{ 418 | { 419 | name: "EmptyTopologyMap", 420 | topologyMap: map[int]*cpuInfo{}, 421 | packageIDs: []int{}, 422 | }, 423 | { 424 | name: "Found", 425 | topologyMap: map[int]*cpuInfo{ 426 | 0: { 427 | packageID: 0, 428 | }, 429 | 1: { 430 | packageID: 1, 431 | }, 432 | 2: { 433 | packageID: 0, 434 | }, 435 | 3: { 436 | packageID: 0, 437 | }, 438 | 4: { 439 | packageID: 1, 440 | }, 441 | 5: { 442 | packageID: 2, 443 | }, 444 | }, 445 | packageIDs: []int{0, 1, 2}, 446 | }, 447 | } 448 | 449 | for _, tc := range testCases { 450 | t.Run(tc.name, func(t *testing.T) { 451 | require.Equal(t, tc.packageIDs, packageIDs(tc.topologyMap)) 452 | }) 453 | } 454 | } 455 | -------------------------------------------------------------------------------- /testdata/alderlake_goldencove_core.json: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "Copyright": "Copyright (c) 2001 - 2023 Intel Corporation. All rights reserved.", 4 | "Info": "Performance Monitoring Events for 12th and 13th Generation Intel(R) Core(TM) Processor - V1.21", 5 | "DatePublished": "04/25/2023", 6 | "Version": "1.21", 7 | "Legend": "" 8 | }, 9 | "Events": [ 10 | { 11 | "EventCode": "0x00", 12 | "UMask": "0x02", 13 | "EventName": "CPU_CLK_UNHALTED.THREAD", 14 | "BriefDescription": "Core cycles when the thread is not in halt state", 15 | "PublicDescription": "Counts the number of core cycles while the thread is not in a halt state. The thread enters the halt state when it is running the HLT instruction. This event is a component in many key event ratios. The core frequency may change from time to time due to transitions associated with Enhanced Intel SpeedStep Technology or TM2. For this reason this event may have a changing ratio with regards to time. When the core frequency is constant, this event can approximate elapsed time while the core was not in the halt state. It is counted on a dedicated fixed counter, leaving the eight programmable counters available for other events.", 16 | "Counter": "Fixed counter 1", 17 | "PEBScounters": "33", 18 | "SampleAfterValue": "2000003", 19 | "MSRIndex": "0x00", 20 | "MSRValue": "0x00", 21 | "CollectPEBSRecord": "2", 22 | "TakenAlone": "0", 23 | "CounterMask": "0", 24 | "Invert": "0", 25 | "EdgeDetect": "0", 26 | "PEBS": "0", 27 | "Data_LA": "0", 28 | "L1_Hit_Indication": "0", 29 | "Errata": "null", 30 | "Offcore": "0", 31 | "Deprecated": "0", 32 | "Speculative": "1" 33 | }, 34 | { 35 | "EventCode": "0x00", 36 | "UMask": "0x03", 37 | "EventName": "CPU_CLK_UNHALTED.REF_TSC", 38 | "BriefDescription": "Reference cycles when the core is not in halt state.", 39 | "PublicDescription": "Counts the number of reference cycles when the core is not in a halt state. The core enters the halt state when it is running the HLT instruction or the MWAIT instruction. This event is not affected by core frequency changes (for example, P states, TM2 transitions) but has the same incrementing frequency as the time stamp counter. This event can approximate elapsed time while the core was not in a halt state. It is counted on a dedicated fixed counter, leaving the eight programmable counters available for other events. Note: On all current platforms this event stops counting during 'throttling (TM)' states duty off periods the processor is 'halted'. The counter update is done at a lower clock rate then the core clock the overflow status bit for this counter may appear 'sticky'. After the counter has overflowed and software clears the overflow status bit and resets the counter to less than MAX. The reset value to the counter is not clocked immediately so the overflow status bit will flip 'high (1)' and generate another PMI (if enabled) after which the reset value gets clocked into the counter. Therefore, software will get the interrupt, read the overflow status bit '1 for bit 34 while the counter value is less than MAX. Software should ignore this case.", 40 | "Counter": "Fixed counter 2", 41 | "PEBScounters": "34", 42 | "SampleAfterValue": "2000003", 43 | "MSRIndex": "0x00", 44 | "MSRValue": "0x00", 45 | "CollectPEBSRecord": "2", 46 | "TakenAlone": "0", 47 | "CounterMask": "0", 48 | "Invert": "0", 49 | "EdgeDetect": "0", 50 | "PEBS": "0", 51 | "Data_LA": "0", 52 | "L1_Hit_Indication": "0", 53 | "Errata": "null", 54 | "Offcore": "0", 55 | "Deprecated": "0", 56 | "Speculative": "1" 57 | }, 58 | { 59 | "EventCode": "0x3c", 60 | "UMask": "0x00", 61 | "EventName": "CPU_CLK_UNHALTED.THREAD_P", 62 | "BriefDescription": "Thread cycles when thread is not in halt state", 63 | "PublicDescription": "This is an architectural event that counts the number of thread cycles while the thread is not in a halt state. The thread enters the halt state when it is running the HLT instruction. The core frequency may change from time to time due to power or thermal throttling. For this reason, this event may have a changing ratio with regards to wall clock time.", 64 | "Counter": "0,1,2,3,4,5,6,7", 65 | "PEBScounters": "0,1,2,3,4,5,6,7", 66 | "SampleAfterValue": "2000003", 67 | "MSRIndex": "0x00", 68 | "MSRValue": "0x00", 69 | "CollectPEBSRecord": "2", 70 | "TakenAlone": "0", 71 | "CounterMask": "0", 72 | "Invert": "0", 73 | "EdgeDetect": "0", 74 | "PEBS": "0", 75 | "Data_LA": "0", 76 | "L1_Hit_Indication": "0", 77 | "Errata": "null", 78 | "Offcore": "0", 79 | "Deprecated": "0", 80 | "Speculative": "1" 81 | }, 82 | { 83 | "EventCode": "0x3c", 84 | "UMask": "0x01", 85 | "EventName": "CPU_CLK_UNHALTED.REF_TSC_P", 86 | "BriefDescription": "Reference cycles when the core is not in halt state.", 87 | "PublicDescription": "Counts the number of reference cycles when the core is not in a halt state. The core enters the halt state when it is running the HLT instruction or the MWAIT instruction. This event is not affected by core frequency changes (for example, P states, TM2 transitions) but has the same incrementing frequency as the time stamp counter. This event can approximate elapsed time while the core was not in a halt state. It is counted on a dedicated fixed counter, leaving the four (eight when Hyperthreading is disabled) programmable counters available for other events. Note: On all current platforms this event stops counting during 'throttling (TM)' states duty off periods the processor is 'halted'. The counter update is done at a lower clock rate then the core clock the overflow status bit for this counter may appear 'sticky'. After the counter has overflowed and software clears the overflow status bit and resets the counter to less than MAX. The reset value to the counter is not clocked immediately so the overflow status bit will flip 'high (1)' and generate another PMI (if enabled) after which the reset value gets clocked into the counter. Therefore, software will get the interrupt, read the overflow status bit '1 for bit 34 while the counter value is less than MAX. Software should ignore this case.", 88 | "Counter": "0,1,2,3,4,5,6,7", 89 | "PEBScounters": "0,1,2,3,4,5,6,7", 90 | "SampleAfterValue": "2000003", 91 | "MSRIndex": "0x00", 92 | "MSRValue": "0x00", 93 | "CollectPEBSRecord": "2", 94 | "TakenAlone": "0", 95 | "CounterMask": "0", 96 | "Invert": "0", 97 | "EdgeDetect": "0", 98 | "PEBS": "0", 99 | "Data_LA": "0", 100 | "L1_Hit_Indication": "0", 101 | "Errata": "null", 102 | "Offcore": "0", 103 | "Deprecated": "0", 104 | "Speculative": "1" 105 | }, 106 | { 107 | "EventCode": "0x3c", 108 | "UMask": "0x02", 109 | "EventName": "CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE", 110 | "BriefDescription": "Core crystal clock cycles when this thread is unhalted and the other thread is halted.", 111 | "PublicDescription": "Counts Core crystal clock cycles when current thread is unhalted and the other thread is halted.", 112 | "Counter": "0,1,2,3,4,5,6,7", 113 | "PEBScounters": "0,1,2,3,4,5,6,7", 114 | "SampleAfterValue": "25003", 115 | "MSRIndex": "0x00", 116 | "MSRValue": "0x00", 117 | "CollectPEBSRecord": "2", 118 | "TakenAlone": "0", 119 | "CounterMask": "0", 120 | "Invert": "0", 121 | "EdgeDetect": "0", 122 | "PEBS": "0", 123 | "Data_LA": "0", 124 | "L1_Hit_Indication": "0", 125 | "Errata": "null", 126 | "Offcore": "0", 127 | "Deprecated": "0", 128 | "Speculative": "1" 129 | }, 130 | { 131 | "EventCode": "0x3c", 132 | "UMask": "0x08", 133 | "EventName": "CPU_CLK_UNHALTED.REF_DISTRIBUTED", 134 | "BriefDescription": "Core crystal clock cycles. Cycle counts are evenly distributed between active threads in the Core.", 135 | "PublicDescription": "This event distributes Core crystal clock cycle counts between active hyperthreads, i.e., those in C0 sleep-state. A hyperthread becomes inactive when it executes the HLT or MWAIT instructions. If one thread is active in a core, all counts are attributed to this hyperthread. To obtain the full count when the Core is active, sum the counts from each hyperthread.", 136 | "Counter": "0,1,2,3,4,5,6,7", 137 | "PEBScounters": "0,1,2,3,4,5,6,7", 138 | "SampleAfterValue": "2000003", 139 | "MSRIndex": "0x00", 140 | "MSRValue": "0x00", 141 | "CollectPEBSRecord": "2", 142 | "TakenAlone": "0", 143 | "CounterMask": "0", 144 | "Invert": "0", 145 | "EdgeDetect": "0", 146 | "PEBS": "0", 147 | "Data_LA": "0", 148 | "L1_Hit_Indication": "0", 149 | "Errata": "null", 150 | "Offcore": "0", 151 | "Deprecated": "0", 152 | "Speculative": "1" 153 | }, 154 | { 155 | "EventCode": "0xec", 156 | "UMask": "0x02", 157 | "EventName": "CPU_CLK_UNHALTED.DISTRIBUTED", 158 | "BriefDescription": "Cycle counts are evenly distributed between active threads in the Core.", 159 | "PublicDescription": "This event distributes cycle counts between active hyperthreads, i.e., those in C0. A hyperthread becomes inactive when it executes the HLT or MWAIT instructions. If all other hyperthreads are inactive (or disabled or do not exist), all counts are attributed to this hyperthread. To obtain the full count when the Core is active, sum the counts from each hyperthread.", 160 | "Counter": "0,1,2,3,4,5,6,7", 161 | "PEBScounters": "0,1,2,3,4,5,6,7", 162 | "SampleAfterValue": "2000003", 163 | "MSRIndex": "0x00", 164 | "MSRValue": "0x00", 165 | "CollectPEBSRecord": "2", 166 | "TakenAlone": "0", 167 | "CounterMask": "0", 168 | "Invert": "0", 169 | "EdgeDetect": "0", 170 | "PEBS": "0", 171 | "Data_LA": "0", 172 | "L1_Hit_Indication": "0", 173 | "Errata": "null", 174 | "Offcore": "0", 175 | "Deprecated": "0", 176 | "Speculative": "1" 177 | }, 178 | { 179 | "EventCode": "0xec", 180 | "UMask": "0x10", 181 | "EventName": "CPU_CLK_UNHALTED.C01", 182 | "BriefDescription": "Core clocks when the thread is in the C0.1 light-weight slower wakeup time but more power saving optimized state.", 183 | "PublicDescription": "Counts core clocks when the thread is in the C0.1 light-weight slower wakeup time but more power saving optimized state. This state can be entered via the TPAUSE or UMWAIT instructions.", 184 | "Counter": "0,1,2,3,4,5,6,7", 185 | "PEBScounters": "0,1,2,3,4,5,6,7", 186 | "SampleAfterValue": "2000003", 187 | "MSRIndex": "0x00", 188 | "MSRValue": "0x00", 189 | "CollectPEBSRecord": "2", 190 | "TakenAlone": "0", 191 | "CounterMask": "0", 192 | "Invert": "0", 193 | "EdgeDetect": "0", 194 | "PEBS": "0", 195 | "Data_LA": "0", 196 | "L1_Hit_Indication": "0", 197 | "Errata": "null", 198 | "Offcore": "0", 199 | "Deprecated": "0", 200 | "Speculative": "1" 201 | }, 202 | { 203 | "EventCode": "0xec", 204 | "UMask": "0x20", 205 | "EventName": "CPU_CLK_UNHALTED.C02", 206 | "BriefDescription": "Core clocks when the thread is in the C0.2 light-weight faster wakeup time but less power saving optimized state.", 207 | "PublicDescription": "Counts core clocks when the thread is in the C0.2 light-weight faster wakeup time but less power saving optimized state. This state can be entered via the TPAUSE or UMWAIT instructions.", 208 | "Counter": "0,1,2,3,4,5,6,7", 209 | "PEBScounters": "0,1,2,3,4,5,6,7", 210 | "SampleAfterValue": "2000003", 211 | "MSRIndex": "0x00", 212 | "MSRValue": "0x00", 213 | "CollectPEBSRecord": "2", 214 | "TakenAlone": "0", 215 | "CounterMask": "0", 216 | "Invert": "0", 217 | "EdgeDetect": "0", 218 | "PEBS": "0", 219 | "Data_LA": "0", 220 | "L1_Hit_Indication": "0", 221 | "Errata": "null", 222 | "Offcore": "0", 223 | "Deprecated": "0", 224 | "Speculative": "1" 225 | }, 226 | { 227 | "EventCode": "0xec", 228 | "UMask": "0x40", 229 | "EventName": "CPU_CLK_UNHALTED.PAUSE", 230 | "BriefDescription": "CPU_CLK_UNHALTED.PAUSE", 231 | "PublicDescription": "CPU_CLK_UNHALTED.PAUSE", 232 | "Counter": "0,1,2,3,4,5,6,7", 233 | "PEBScounters": "0,1,2,3,4,5,6,7", 234 | "SampleAfterValue": "2000003", 235 | "MSRIndex": "0x00", 236 | "MSRValue": "0x00", 237 | "CollectPEBSRecord": "2", 238 | "TakenAlone": "0", 239 | "CounterMask": "0", 240 | "Invert": "0", 241 | "EdgeDetect": "0", 242 | "PEBS": "0", 243 | "Data_LA": "0", 244 | "L1_Hit_Indication": "0", 245 | "Errata": "null", 246 | "Offcore": "0", 247 | "Deprecated": "0", 248 | "Speculative": "1" 249 | }, 250 | { 251 | "EventCode": "0xec", 252 | "UMask": "0x40", 253 | "EventName": "CPU_CLK_UNHALTED.PAUSE_INST", 254 | "BriefDescription": "CPU_CLK_UNHALTED.PAUSE_INST", 255 | "PublicDescription": "CPU_CLK_UNHALTED.PAUSE_INST", 256 | "Counter": "0,1,2,3,4,5,6,7", 257 | "PEBScounters": "0,1,2,3,4,5,6,7", 258 | "SampleAfterValue": "2000003", 259 | "MSRIndex": "0x00", 260 | "MSRValue": "0x00", 261 | "CollectPEBSRecord": "2", 262 | "TakenAlone": "0", 263 | "CounterMask": "1", 264 | "Invert": "0", 265 | "EdgeDetect": "1", 266 | "PEBS": "0", 267 | "Data_LA": "0", 268 | "L1_Hit_Indication": "0", 269 | "Errata": "null", 270 | "Offcore": "0", 271 | "Deprecated": "0", 272 | "Speculative": "1" 273 | }, 274 | { 275 | "EventCode": "0xec", 276 | "UMask": "0x70", 277 | "EventName": "CPU_CLK_UNHALTED.C0_WAIT", 278 | "BriefDescription": "Core clocks when the thread is in the C0.1 or C0.2 or running a PAUSE in C0 ACPI state.", 279 | "PublicDescription": "Counts core clocks when the thread is in the C0.1 or C0.2 power saving optimized states (TPAUSE or UMWAIT instructions) or running the PAUSE instruction.", 280 | "Counter": "0,1,2,3,4,5,6,7", 281 | "PEBScounters": "0,1,2,3,4,5,6,7", 282 | "SampleAfterValue": "2000003", 283 | "MSRIndex": "0x00", 284 | "MSRValue": "0x00", 285 | "CollectPEBSRecord": "2", 286 | "TakenAlone": "0", 287 | "CounterMask": "0", 288 | "Invert": "0", 289 | "EdgeDetect": "0", 290 | "PEBS": "0", 291 | "Data_LA": "0", 292 | "L1_Hit_Indication": "0", 293 | "Errata": "null", 294 | "Offcore": "0", 295 | "Deprecated": "0", 296 | "Speculative": "1" 297 | } 298 | ] 299 | } -------------------------------------------------------------------------------- /testdata/sapphirerapids_core.json: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "Copyright": "Copyright (c) 2001 - 2023 Intel Corporation. All rights reserved.", 4 | "Info": "Performance Monitoring Events for 4th Generation Intel(R) Xeon(R) Processor Scalable Family based on Sapphire Rapids microarchitecture - V1.15", 5 | "DatePublished": "06/28/2023", 6 | "Version": "1.15", 7 | "Legend": "" 8 | }, 9 | "Events": [ 10 | { 11 | "EventCode": "0x00", 12 | "UMask": "0x02", 13 | "EventName": "CPU_CLK_UNHALTED.THREAD", 14 | "BriefDescription": "Core cycles when the thread is not in halt state", 15 | "PublicDescription": "Counts the number of core cycles while the thread is not in a halt state. The thread enters the halt state when it is running the HLT instruction. This event is a component in many key event ratios. The core frequency may change from time to time due to transitions associated with Enhanced Intel SpeedStep Technology or TM2. For this reason this event may have a changing ratio with regards to time. When the core frequency is constant, this event can approximate elapsed time while the core was not in the halt state. It is counted on a dedicated fixed counter, leaving the eight programmable counters available for other events.", 16 | "Counter": "Fixed counter 1", 17 | "PEBScounters": "33", 18 | "SampleAfterValue": "2000003", 19 | "MSRIndex": "0x00", 20 | "MSRValue": "0x00", 21 | "CollectPEBSRecord": "2", 22 | "TakenAlone": "0", 23 | "CounterMask": "0", 24 | "Invert": "0", 25 | "EdgeDetect": "0", 26 | "PEBS": "0", 27 | "Data_LA": "0", 28 | "L1_Hit_Indication": "0", 29 | "Errata": "null", 30 | "Offcore": "0", 31 | "Deprecated": "0", 32 | "Speculative": "1" 33 | }, 34 | { 35 | "EventCode": "0x00", 36 | "UMask": "0x03", 37 | "EventName": "CPU_CLK_UNHALTED.REF_TSC", 38 | "BriefDescription": "Reference cycles when the core is not in halt state.", 39 | "PublicDescription": "Counts the number of reference cycles when the core is not in a halt state. The core enters the halt state when it is running the HLT instruction or the MWAIT instruction. This event is not affected by core frequency changes (for example, P states, TM2 transitions) but has the same incrementing frequency as the time stamp counter. This event can approximate elapsed time while the core was not in a halt state. It is counted on a dedicated fixed counter, leaving the eight programmable counters available for other events. Note: On all current platforms this event stops counting during 'throttling (TM)' states duty off periods the processor is 'halted'. The counter update is done at a lower clock rate then the core clock the overflow status bit for this counter may appear 'sticky'. After the counter has overflowed and software clears the overflow status bit and resets the counter to less than MAX. The reset value to the counter is not clocked immediately so the overflow status bit will flip 'high (1)' and generate another PMI (if enabled) after which the reset value gets clocked into the counter. Therefore, software will get the interrupt, read the overflow status bit '1 for bit 34 while the counter value is less than MAX. Software should ignore this case.", 40 | "Counter": "Fixed counter 2", 41 | "PEBScounters": "34", 42 | "SampleAfterValue": "2000003", 43 | "MSRIndex": "0x00", 44 | "MSRValue": "0x00", 45 | "CollectPEBSRecord": "2", 46 | "TakenAlone": "0", 47 | "CounterMask": "0", 48 | "Invert": "0", 49 | "EdgeDetect": "0", 50 | "PEBS": "0", 51 | "Data_LA": "0", 52 | "L1_Hit_Indication": "0", 53 | "Errata": "null", 54 | "Offcore": "0", 55 | "Deprecated": "0", 56 | "Speculative": "1" 57 | }, 58 | { 59 | "EventCode": "0x3c", 60 | "UMask": "0x00", 61 | "EventName": "CPU_CLK_UNHALTED.THREAD_P", 62 | "BriefDescription": "Thread cycles when thread is not in halt state", 63 | "PublicDescription": "This is an architectural event that counts the number of thread cycles while the thread is not in a halt state. The thread enters the halt state when it is running the HLT instruction. The core frequency may change from time to time due to power or thermal throttling. For this reason, this event may have a changing ratio with regards to wall clock time.", 64 | "Counter": "0,1,2,3,4,5,6,7", 65 | "PEBScounters": "0,1,2,3,4,5,6,7", 66 | "SampleAfterValue": "2000003", 67 | "MSRIndex": "0x00", 68 | "MSRValue": "0x00", 69 | "CollectPEBSRecord": "2", 70 | "TakenAlone": "0", 71 | "CounterMask": "0", 72 | "Invert": "0", 73 | "EdgeDetect": "0", 74 | "PEBS": "0", 75 | "Data_LA": "0", 76 | "L1_Hit_Indication": "0", 77 | "Errata": "null", 78 | "Offcore": "0", 79 | "Deprecated": "0", 80 | "Speculative": "1" 81 | }, 82 | { 83 | "EventCode": "0x3c", 84 | "UMask": "0x01", 85 | "EventName": "CPU_CLK_UNHALTED.REF_TSC_P", 86 | "BriefDescription": "Reference cycles when the core is not in halt state.", 87 | "PublicDescription": "Counts the number of reference cycles when the core is not in a halt state. The core enters the halt state when it is running the HLT instruction or the MWAIT instruction. This event is not affected by core frequency changes (for example, P states, TM2 transitions) but has the same incrementing frequency as the time stamp counter. This event can approximate elapsed time while the core was not in a halt state. It is counted on a dedicated fixed counter, leaving the four (eight when Hyperthreading is disabled) programmable counters available for other events. Note: On all current platforms this event stops counting during 'throttling (TM)' states duty off periods the processor is 'halted'. The counter update is done at a lower clock rate then the core clock the overflow status bit for this counter may appear 'sticky'. After the counter has overflowed and software clears the overflow status bit and resets the counter to less than MAX. The reset value to the counter is not clocked immediately so the overflow status bit will flip 'high (1)' and generate another PMI (if enabled) after which the reset value gets clocked into the counter. Therefore, software will get the interrupt, read the overflow status bit '1 for bit 34 while the counter value is less than MAX. Software should ignore this case.", 88 | "Counter": "0,1,2,3,4,5,6,7", 89 | "PEBScounters": "0,1,2,3,4,5,6,7", 90 | "SampleAfterValue": "2000003", 91 | "MSRIndex": "0x00", 92 | "MSRValue": "0x00", 93 | "CollectPEBSRecord": "2", 94 | "TakenAlone": "0", 95 | "CounterMask": "0", 96 | "Invert": "0", 97 | "EdgeDetect": "0", 98 | "PEBS": "0", 99 | "Data_LA": "0", 100 | "L1_Hit_Indication": "0", 101 | "Errata": "null", 102 | "Offcore": "0", 103 | "Deprecated": "0", 104 | "Speculative": "1" 105 | }, 106 | { 107 | "EventCode": "0x3c", 108 | "UMask": "0x02", 109 | "EventName": "CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE", 110 | "BriefDescription": "Core crystal clock cycles when this thread is unhalted and the other thread is halted.", 111 | "PublicDescription": "Counts Core crystal clock cycles when current thread is unhalted and the other thread is halted.", 112 | "Counter": "0,1,2,3,4,5,6,7", 113 | "PEBScounters": "0,1,2,3,4,5,6,7", 114 | "SampleAfterValue": "25003", 115 | "MSRIndex": "0x00", 116 | "MSRValue": "0x00", 117 | "CollectPEBSRecord": "2", 118 | "TakenAlone": "0", 119 | "CounterMask": "0", 120 | "Invert": "0", 121 | "EdgeDetect": "0", 122 | "PEBS": "0", 123 | "Data_LA": "0", 124 | "L1_Hit_Indication": "0", 125 | "Errata": "null", 126 | "Offcore": "0", 127 | "Deprecated": "0", 128 | "Speculative": "1" 129 | }, 130 | { 131 | "EventCode": "0x3c", 132 | "UMask": "0x08", 133 | "EventName": "CPU_CLK_UNHALTED.REF_DISTRIBUTED", 134 | "BriefDescription": "Core crystal clock cycles. Cycle counts are evenly distributed between active threads in the Core.", 135 | "PublicDescription": "This event distributes Core crystal clock cycle counts between active hyperthreads, i.e., those in C0 sleep-state. A hyperthread becomes inactive when it executes the HLT or MWAIT instructions. If one thread is active in a core, all counts are attributed to this hyperthread. To obtain the full count when the Core is active, sum the counts from each hyperthread.", 136 | "Counter": "0,1,2,3,4,5,6,7", 137 | "PEBScounters": "0,1,2,3,4,5,6,7", 138 | "SampleAfterValue": "2000003", 139 | "MSRIndex": "0x00", 140 | "MSRValue": "0x00", 141 | "CollectPEBSRecord": "2", 142 | "TakenAlone": "0", 143 | "CounterMask": "0", 144 | "Invert": "0", 145 | "EdgeDetect": "0", 146 | "PEBS": "0", 147 | "Data_LA": "0", 148 | "L1_Hit_Indication": "0", 149 | "Errata": "null", 150 | "Offcore": "0", 151 | "Deprecated": "0", 152 | "Speculative": "1" 153 | }, 154 | { 155 | "EventCode": "0xec", 156 | "UMask": "0x02", 157 | "EventName": "CPU_CLK_UNHALTED.DISTRIBUTED", 158 | "BriefDescription": "Cycle counts are evenly distributed between active threads in the Core.", 159 | "PublicDescription": "This event distributes cycle counts between active hyperthreads, i.e., those in C0. A hyperthread becomes inactive when it executes the HLT or MWAIT instructions. If all other hyperthreads are inactive (or disabled or do not exist), all counts are attributed to this hyperthread. To obtain the full count when the Core is active, sum the counts from each hyperthread.", 160 | "Counter": "0,1,2,3,4,5,6,7", 161 | "PEBScounters": "0,1,2,3,4,5,6,7", 162 | "SampleAfterValue": "2000003", 163 | "MSRIndex": "0x00", 164 | "MSRValue": "0x00", 165 | "CollectPEBSRecord": "2", 166 | "TakenAlone": "0", 167 | "CounterMask": "0", 168 | "Invert": "0", 169 | "EdgeDetect": "0", 170 | "PEBS": "0", 171 | "Data_LA": "0", 172 | "L1_Hit_Indication": "0", 173 | "Errata": "null", 174 | "Offcore": "0", 175 | "Deprecated": "0", 176 | "Speculative": "1" 177 | }, 178 | { 179 | "EventCode": "0xec", 180 | "UMask": "0x10", 181 | "EventName": "CPU_CLK_UNHALTED.C01", 182 | "BriefDescription": "Core clocks when the thread is in the C0.1 light-weight slower wakeup time but more power saving optimized state.", 183 | "PublicDescription": "Counts core clocks when the thread is in the C0.1 light-weight slower wakeup time but more power saving optimized state. This state can be entered via the TPAUSE or UMWAIT instructions.", 184 | "Counter": "0,1,2,3,4,5,6,7", 185 | "PEBScounters": "0,1,2,3,4,5,6,7", 186 | "SampleAfterValue": "2000003", 187 | "MSRIndex": "0x00", 188 | "MSRValue": "0x00", 189 | "CollectPEBSRecord": "2", 190 | "TakenAlone": "0", 191 | "CounterMask": "0", 192 | "Invert": "0", 193 | "EdgeDetect": "0", 194 | "PEBS": "0", 195 | "Data_LA": "0", 196 | "L1_Hit_Indication": "0", 197 | "Errata": "null", 198 | "Offcore": "0", 199 | "Deprecated": "0", 200 | "Speculative": "1" 201 | }, 202 | { 203 | "EventCode": "0xec", 204 | "UMask": "0x20", 205 | "EventName": "CPU_CLK_UNHALTED.C02", 206 | "BriefDescription": "Core clocks when the thread is in the C0.2 light-weight faster wakeup time but less power saving optimized state.", 207 | "PublicDescription": "Counts core clocks when the thread is in the C0.2 light-weight faster wakeup time but less power saving optimized state. This state can be entered via the TPAUSE or UMWAIT instructions.", 208 | "Counter": "0,1,2,3,4,5,6,7", 209 | "PEBScounters": "0,1,2,3,4,5,6,7", 210 | "SampleAfterValue": "2000003", 211 | "MSRIndex": "0x00", 212 | "MSRValue": "0x00", 213 | "CollectPEBSRecord": "2", 214 | "TakenAlone": "0", 215 | "CounterMask": "0", 216 | "Invert": "0", 217 | "EdgeDetect": "0", 218 | "PEBS": "0", 219 | "Data_LA": "0", 220 | "L1_Hit_Indication": "0", 221 | "Errata": "null", 222 | "Offcore": "0", 223 | "Deprecated": "0", 224 | "Speculative": "1" 225 | }, 226 | { 227 | "EventCode": "0xec", 228 | "UMask": "0x40", 229 | "EventName": "CPU_CLK_UNHALTED.PAUSE", 230 | "BriefDescription": "CPU_CLK_UNHALTED.PAUSE", 231 | "PublicDescription": "CPU_CLK_UNHALTED.PAUSE", 232 | "Counter": "0,1,2,3,4,5,6,7", 233 | "PEBScounters": "0,1,2,3,4,5,6,7", 234 | "SampleAfterValue": "2000003", 235 | "MSRIndex": "0x00", 236 | "MSRValue": "0x00", 237 | "CollectPEBSRecord": "2", 238 | "TakenAlone": "0", 239 | "CounterMask": "0", 240 | "Invert": "0", 241 | "EdgeDetect": "0", 242 | "PEBS": "0", 243 | "Data_LA": "0", 244 | "L1_Hit_Indication": "0", 245 | "Errata": "null", 246 | "Offcore": "0", 247 | "Deprecated": "0", 248 | "Speculative": "1" 249 | }, 250 | { 251 | "EventCode": "0xec", 252 | "UMask": "0x40", 253 | "EventName": "CPU_CLK_UNHALTED.PAUSE_INST", 254 | "BriefDescription": "CPU_CLK_UNHALTED.PAUSE_INST", 255 | "PublicDescription": "CPU_CLK_UNHALTED.PAUSE_INST", 256 | "Counter": "0,1,2,3,4,5,6,7", 257 | "PEBScounters": "0,1,2,3,4,5,6,7", 258 | "SampleAfterValue": "2000003", 259 | "MSRIndex": "0x00", 260 | "MSRValue": "0x00", 261 | "CollectPEBSRecord": "2", 262 | "TakenAlone": "0", 263 | "CounterMask": "1", 264 | "Invert": "0", 265 | "EdgeDetect": "1", 266 | "PEBS": "0", 267 | "Data_LA": "0", 268 | "L1_Hit_Indication": "0", 269 | "Errata": "null", 270 | "Offcore": "0", 271 | "Deprecated": "0", 272 | "Speculative": "1" 273 | }, 274 | { 275 | "EventCode": "0xec", 276 | "UMask": "0x70", 277 | "EventName": "CPU_CLK_UNHALTED.C0_WAIT", 278 | "BriefDescription": "Core clocks when the thread is in the C0.1 or C0.2 or running a PAUSE in C0 ACPI state.", 279 | "PublicDescription": "Counts core clocks when the thread is in the C0.1 or C0.2 power saving optimized states (TPAUSE or UMWAIT instructions) or running the PAUSE instruction.", 280 | "Counter": "0,1,2,3,4,5,6,7", 281 | "PEBScounters": "0,1,2,3,4,5,6,7", 282 | "SampleAfterValue": "2000003", 283 | "MSRIndex": "0x00", 284 | "MSRValue": "0x00", 285 | "CollectPEBSRecord": "2", 286 | "TakenAlone": "0", 287 | "CounterMask": "0", 288 | "Invert": "0", 289 | "EdgeDetect": "0", 290 | "PEBS": "0", 291 | "Data_LA": "0", 292 | "L1_Hit_Indication": "0", 293 | "Errata": "null", 294 | "Offcore": "0", 295 | "Deprecated": "0", 296 | "Speculative": "1" 297 | } 298 | ] 299 | } -------------------------------------------------------------------------------- /msr.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build linux && amd64 5 | 6 | package powertelemetry 7 | 8 | import ( 9 | "encoding/binary" 10 | "errors" 11 | "fmt" 12 | "io" 13 | "math/big" 14 | "os" 15 | "path/filepath" 16 | "regexp" 17 | "strconv" 18 | "time" 19 | 20 | "github.com/intel/powertelemetry/internal/log" 21 | ) 22 | 23 | const ( 24 | // base path comprising all per-CPU ID MSR files. 25 | defaultMsrBasePath = "/dev/cpu" 26 | 27 | // file name of the binary MSR file specific for each CPU ID. 28 | msrFile = "msr" 29 | ) 30 | 31 | var ( 32 | // regex used to check CPU ID format as numeric value without leading zeroes. 33 | cpuIDRegex = regexp.MustCompile("^(0|[1-9][0-9]*)$") 34 | 35 | // regex used to check MSR module within the loaded kernel modules list. 36 | msrModuleRegex = regexp.MustCompile(`\bmsr\b`) 37 | ) 38 | 39 | // msrReg represents a CPU ID specific MSR register with the ability to read offset 40 | // values. 41 | type msrReg interface { 42 | // getPath gets the absolute path of the MSR file. 43 | getPath() string 44 | 45 | // getCPUID gets the CPU ID corresponding to the MSR register. 46 | getCPUID() int 47 | 48 | // read returns the MSR value of the given offset. 49 | read(offset uint32) (uint64, error) 50 | 51 | // readAll takes a slice of offsets and returns a map with offset key 52 | // and content of the MSR offset value. 53 | readAll(offsets []uint32) (map[uint32]uint64, error) 54 | } 55 | 56 | // msr represents a CPU ID specific MSR register. Implements msrReg interface. 57 | type msr struct { 58 | path string 59 | cpuID int 60 | timeout time.Duration 61 | } 62 | 63 | // resultError is used for transmitting a value or an err through the channel. 64 | type resultError struct { 65 | value uint64 66 | err error 67 | } 68 | 69 | // newMsr creates a new MSR register, initializing the CPU ID for this specific 70 | // register and the path where to find the MSR file. 71 | func newMsr(path string, timeout time.Duration) (msrReg, error) { 72 | cpuIDStr := filepath.Base(path) 73 | if !cpuIDRegex.MatchString(cpuIDStr) { 74 | return nil, fmt.Errorf("invalid format for CPU ID in path %q", path) 75 | } 76 | cpuID, err := strconv.Atoi(cpuIDStr) 77 | if err != nil { 78 | return nil, fmt.Errorf("error converting parsed CPU ID from path to numeric") 79 | } 80 | cpuMsr := filepath.Join(path, msrFile) 81 | if err := checkFile(cpuMsr); err != nil { 82 | return nil, fmt.Errorf("invalid MSR file for cpu ID %v: %w", cpuID, err) 83 | } 84 | return &msr{ 85 | path: cpuMsr, 86 | cpuID: cpuID, 87 | timeout: timeout, 88 | }, nil 89 | } 90 | 91 | // getPath returns the MSR file path of the receiver. 92 | func (m *msr) getPath() string { 93 | return m.path 94 | } 95 | 96 | // getCPUID returns the CPU ID corresponding to the receiver. 97 | func (m *msr) getCPUID() int { 98 | return m.cpuID 99 | } 100 | 101 | // read takes an address, specified as offset, and returns an 8-byte value with 102 | // the address content of the given CPU ID's MSR. 103 | func (m *msr) read(offset uint32) (uint64, error) { 104 | f, err := os.OpenFile(m.path, os.O_RDONLY, 0400) 105 | if err != nil { 106 | return 0, err 107 | } 108 | defer f.Close() 109 | return readOffset(offset, f, m.timeout) 110 | } 111 | 112 | // readAll takes a slice of addresses, specified as offsets, and returns a map 113 | // with offset key and the offset content of the given CPU ID's MSR as value. 114 | // Each read offset operation is performed in a separate goroutine. In case an 115 | // error occurs, the function returns a nil map with the corresponding error. 116 | func (m *msr) readAll(offsets []uint32) (map[uint32]uint64, error) { 117 | f, err := os.OpenFile(m.path, os.O_RDONLY, 0400) 118 | if err != nil { 119 | return nil, err 120 | } 121 | defer f.Close() 122 | 123 | errCh := make(chan error) 124 | msrOffsetChannels := make(map[uint32]chan uint64) 125 | for _, offset := range offsets { 126 | msrOffsetChannels[offset] = make(chan uint64) 127 | } 128 | 129 | for offset, ch := range msrOffsetChannels { 130 | go func(off uint32, ch chan uint64, errCh chan error) { 131 | v, err := readOffset(off, f, m.timeout) 132 | if err != nil { 133 | errCh <- err 134 | return 135 | } 136 | ch <- v 137 | }(offset, ch, errCh) 138 | } 139 | 140 | valuesMap := make(map[uint32]uint64) 141 | for offset, ch := range msrOffsetChannels { 142 | select { 143 | case err := <-errCh: 144 | return nil, fmt.Errorf("error reading MSR offsets: %w", err) 145 | case v := <-ch: 146 | valuesMap[offset] = v 147 | } 148 | } 149 | return valuesMap, nil 150 | } 151 | 152 | // readOffset is a helper function that takes an address, specified as offset, an io.ReaderAt interface, and timeout. 153 | // It returns an 8-byte value with the address content of the given reader argument. 154 | func readOffset(offset uint32, reader io.ReaderAt, timeout time.Duration) (uint64, error) { 155 | // read without timeout 156 | if timeout <= 0 { 157 | return readOffsetWithoutTimeout(offset, reader) 158 | } 159 | 160 | // read with timeout 161 | resultCh := make(chan resultError, 1) 162 | go func(resCh chan<- resultError) { 163 | buf := make([]byte, 8) 164 | if _, err := reader.ReadAt(buf, int64(offset)); err != nil { 165 | if errors.Is(err, io.EOF) { 166 | resCh <- resultError{value: 0, err: fmt.Errorf("offset 0x%x is out-of-bounds", offset)} 167 | } else { 168 | resCh <- resultError{value: 0, err: fmt.Errorf("error when reading file at offset 0x%x: %w", offset, err)} 169 | } 170 | } else { 171 | resCh <- resultError{value: binary.LittleEndian.Uint64(buf), err: nil} 172 | } 173 | 174 | close(resCh) 175 | }(resultCh) 176 | 177 | t := time.NewTimer(timeout) 178 | select { 179 | case <-t.C: 180 | return 0, fmt.Errorf("timeout when reading file at offset 0x%x", offset) 181 | case result := <-resultCh: 182 | if !t.Stop() { 183 | <-t.C 184 | } 185 | if result.err != nil { 186 | return 0, result.err 187 | } 188 | 189 | return result.value, nil 190 | } 191 | } 192 | 193 | // readOffsetWithoutTimeout is a helper function that takes an address, specified as offset and an io.ReaderAt interface. 194 | // It returns an 8-byte value with the address content of the given reader argument. 195 | func readOffsetWithoutTimeout(offset uint32, reader io.ReaderAt) (uint64, error) { 196 | buf := make([]byte, 8) 197 | if _, err := reader.ReadAt(buf, int64(offset)); err != nil { 198 | if errors.Is(err, io.EOF) { 199 | return 0, fmt.Errorf("offset 0x%x is out-of-bounds", offset) 200 | } 201 | return 0, fmt.Errorf("error when reading file at offset 0x%x: %w", offset, err) 202 | } 203 | 204 | return binary.LittleEndian.Uint64(buf), nil 205 | } 206 | 207 | // msrRegWithStorage represents a CPU ID specific MSR register with the ability to read and 208 | // store offset values. Two types of stored offset values are supported: 209 | // - offset values from the last read operation. 210 | // - delta offset values defined as the subtraction between offset values from last read 211 | // and offset values from the previous read operation. 212 | type msrRegWithStorage interface { 213 | msrReg 214 | 215 | // getOffsetValues gets a map with offset key and offset value of the latest read operation. 216 | getOffsetValues() map[uint32]uint64 217 | 218 | // getOffsetDeltas gets a map with offset key and delta offset value between the latest and 219 | // the previous read operation. 220 | getOffsetDeltas() map[uint32]uint64 221 | 222 | // setOffsetDeltas sets the given map of offset key and delta offset values to the receiver. 223 | setOffsetDeltas(offsets map[uint32]uint64) 224 | 225 | // getTimestampDelta gets the timestamp delta between the last offset values reading operation 226 | // and its previous reading operation. 227 | getTimestampDelta() time.Duration 228 | 229 | // update gets MSR values and updates the storage. 230 | update() error 231 | } 232 | 233 | // msrWithStorage represents a CPU ID specific MSR register with the ability to read and 234 | // store offset values. Implements msrRegWithStorage interface. 235 | // The offset values in the storage correspond to values for offsets specified in offsets 236 | // field. 237 | type msrWithStorage struct { 238 | msrReg 239 | offsets []uint32 240 | offsetValues map[uint32]uint64 // offset values from the last read operation 241 | offsetDeltas map[uint32]uint64 // delta offset values between the latest and its previous reading operation 242 | timestamp time.Time // timestamp of the last reading operation 243 | timestampDelta time.Duration // timestamp delta between the last read and its previous reading operation 244 | } 245 | 246 | // newMsrWithStorage creates a new MSR register with the ability to read and store multiple MSR 247 | // offset values, provided as argument. First creates an MSR register, then decorates it adding 248 | // storage for both offset values from the last read operation and delta offset values between 249 | // the latest and its previous reading operation. 250 | func newMsrWithStorage(path string, offsets []uint32, timeout time.Duration) (msrRegWithStorage, error) { 251 | if len(offsets) == 0 { 252 | return nil, errors.New("no offsets were provided") 253 | } 254 | 255 | msr, err := newMsr(path, timeout) 256 | if err != nil { 257 | return nil, fmt.Errorf("error creating MSR register for CPU path %q: %w", path, err) 258 | } 259 | 260 | return &msrWithStorage{ 261 | msrReg: msr, 262 | offsets: offsets, 263 | offsetValues: make(map[uint32]uint64), 264 | offsetDeltas: make(map[uint32]uint64), 265 | }, nil 266 | } 267 | 268 | // getOffsetValues returns a map with offset key and offset values from the last read operation 269 | // of the receiver. 270 | func (m *msrWithStorage) getOffsetValues() map[uint32]uint64 { 271 | return m.offsetValues 272 | } 273 | 274 | // setOffsetValues sets the given map of offset key and offset values to the receiver. 275 | func (m *msrWithStorage) setOffsetValues(offsetValues map[uint32]uint64) { 276 | m.offsetValues = offsetValues 277 | } 278 | 279 | // getOffsetDeltas returns a map with offset key and delta offset values from the last read operation 280 | // of the receiver. 281 | func (m *msrWithStorage) getOffsetDeltas() map[uint32]uint64 { 282 | return m.offsetDeltas 283 | } 284 | 285 | // setOffsetDeltas sets the given map of offset key and delta offset values to the receiver. 286 | func (m *msrWithStorage) setOffsetDeltas(offsetDeltas map[uint32]uint64) { 287 | m.offsetDeltas = offsetDeltas 288 | } 289 | 290 | // update performs reading operations along the offsets specified by the receiver. It updates 291 | // last read offset values and delta offset values of the receiver. 292 | func (m *msrWithStorage) update() error { 293 | latest, err := m.msrReg.readAll(m.offsets) 294 | if err != nil { 295 | return err 296 | } 297 | 298 | // Get time interval between offset MSR read and its previous reading operation 299 | newTimestamp := timeNowFn() 300 | m.timestampDelta, m.timestamp = newTimestamp.Sub(m.timestamp), newTimestamp 301 | 302 | prev := m.getOffsetValues() 303 | deltasMap := make(map[uint32]uint64, len(latest)) 304 | for offset := range latest { 305 | if latest[offset] < prev[offset] { 306 | deltasMap[offset] = 0 307 | log.Warnf("A negative delta for the offset 0x%X and CPU ID %v", offset, m.msrReg.getCPUID()) 308 | } else { 309 | deltasMap[offset] = latest[offset] - prev[offset] 310 | } 311 | } 312 | 313 | m.setOffsetDeltas(deltasMap) 314 | m.setOffsetValues(latest) 315 | return nil 316 | } 317 | 318 | // getTimestampDelta returns the timestamp delta between the offset values last reading operations 319 | // and its previous reading operation. 320 | func (m *msrWithStorage) getTimestampDelta() time.Duration { 321 | return m.timestampDelta 322 | } 323 | 324 | // msrReaderWithStorage represents per-CPU ID MSR registers of the host with the ability to read single 325 | // MSR offset values, read and store multiple MSR offset values, and eventually provide the MSR delta 326 | // offset values between latest and its previous reading operation. 327 | type msrReaderWithStorage interface { 328 | // initMsrMap initializes a map of CPU ID key and MSR register value with storage. 329 | initMsrMap(cpuIDs []int, timeout time.Duration) error 330 | 331 | // isMsrLoaded check if MSR kernel module is loaded. 332 | isMsrLoaded(modulesPath string) (bool, error) 333 | 334 | // read returns the MSR value for a given offset and CPU ID. 335 | read(offset uint32, cpuID int) (uint64, error) 336 | 337 | // update takes a CPU ID, reads multiple MSR offset values and updates the storage. 338 | update(cpuID int) error 339 | 340 | // getOffsetDeltas takes a CPU ID and returns MSR delta offset values between latest and its previous reading operation. 341 | getOffsetDeltas(cpuID int) (map[uint32]uint64, error) 342 | 343 | // scaleOffsetDeltas takes a CPU ID and a slice of msr offsets. It scales all offset deltas of the msr storage by multiplying 344 | // each offset delta by the given factor f. 345 | scaleOffsetDeltas(cpuID int, offsets []uint32, f *big.Float) error 346 | 347 | // getTimestampDelta takes a CPU ID and returns the time interval between the last offset value reading operation 348 | // and its previous reading operation. 349 | getTimestampDelta(cpuID int) (time.Duration, error) 350 | } 351 | 352 | // msrDataWithStorage represents per-CPU ID MSR registers of the host with offset values storage capabilities. 353 | // 354 | // It represents the hierarchy tree for /dev/cpu directory: 355 | // 356 | // /dev/cpu 357 | // ├── 0 (CPU ID) 358 | // │ └── msr (MSR binary file) 359 | // ├── 1 360 | // │ └── msr 361 | // ├── 2 362 | // │ └── msr 363 | // ∙ 364 | // └── n 365 | // 366 | // Each map entry corresponds to an MSR register, which allows reading operations 367 | // to specific addresses (offsets), as well as storage of the offset values read. 368 | type msrDataWithStorage struct { 369 | msrPath string 370 | msrOffsets []uint32 371 | 372 | msrMap map[int]msrRegWithStorage 373 | } 374 | 375 | // initMsrMap initializes a map of CPU ID key and MSR register value with storage. Each MSR register is able to update 376 | // the storage values and deltas of provided offsets in the offsets slice field. Field msrCPUIDs holds values of CPU IDs 377 | // for which an MSR register is initialized. In case msrCPUIDs is nil, MSR registers for all CPU IDs found in system 378 | // file will be initialized. It ensures that each CPU ID directory is properly formatted and binary MSR file exists. 379 | // In case of malformed base path tree, an error is returned. 380 | func (m *msrDataWithStorage) initMsrMap(cpuIDs []int, timeout time.Duration) error { 381 | if len(m.msrOffsets) == 0 { 382 | return errors.New("MSR offsets argument cannot be empty") 383 | } 384 | 385 | if len(m.msrPath) == 0 { 386 | return errors.New("base path for MSR files cannot be an empty string") 387 | } 388 | if err := checkFile(m.msrPath); err != nil { 389 | return fmt.Errorf("invalid MSR base path %q: %w", m.msrPath, err) 390 | } 391 | 392 | cpuDirs, err := os.ReadDir(m.msrPath) 393 | if err != nil { 394 | return fmt.Errorf("error reading directory %q: %w", m.msrPath, err) 395 | } 396 | 397 | // Declaring map for constant time search 398 | filterCPUIDsMap := make(map[string]struct{}) 399 | for _, cpuID := range cpuIDs { 400 | cpuIDStr := strconv.FormatUint(uint64(cpuID), 10) 401 | filterCPUIDsMap[cpuIDStr] = struct{}{} 402 | } 403 | isFilterEmpty := len(filterCPUIDsMap) == 0 404 | 405 | msrMap := make(map[int]msrRegWithStorage) 406 | for _, cpuDirEntry := range cpuDirs { 407 | cpuDir := cpuDirEntry.Name() 408 | if !cpuDirEntry.IsDir() || !cpuIDRegex.MatchString(cpuDir) { 409 | continue 410 | } 411 | 412 | // Skip only if filterCPUIDs are not empty and there is no corresponding value in the map to cpuDir. 413 | // if filterCPUIDs are empty, then we read all values from the directory. 414 | if _, isCPUIDFiltered := filterCPUIDsMap[cpuDir]; !isFilterEmpty && !isCPUIDFiltered { 415 | continue 416 | } 417 | 418 | cpuPath := filepath.Join(m.msrPath, cpuDir) 419 | cpuMsrWithStorage, err := newMsrWithStorage(cpuPath, m.msrOffsets, timeout) 420 | if err != nil { 421 | return fmt.Errorf("error creating MSR register with storage for CPU path %q: %w", cpuPath, err) 422 | } 423 | 424 | err = cpuMsrWithStorage.update() 425 | if err != nil { 426 | return fmt.Errorf("error initializing the MSR register storage for CPU ID %v: %w", cpuMsrWithStorage.getCPUID(), err) 427 | } 428 | msrMap[cpuMsrWithStorage.getCPUID()] = cpuMsrWithStorage 429 | } 430 | 431 | if len(msrMap) == 0 { 432 | return fmt.Errorf("could not find valid CPU MSR files for path: %q", m.msrPath) 433 | } 434 | 435 | m.msrMap = msrMap 436 | return nil 437 | } 438 | 439 | // isMsrLoaded returns true if MSR kernel module is loaded, otherwise returns false. 440 | func (m *msrDataWithStorage) isMsrLoaded(modulesPath string) (bool, error) { 441 | if err := checkFile(modulesPath); err != nil { 442 | return false, err 443 | } 444 | data, err := os.ReadFile(modulesPath) 445 | if err != nil { 446 | return false, fmt.Errorf("could not read file %q: %w", modulesPath, err) 447 | } 448 | matches := msrModuleRegex.FindAll(data, -1) 449 | return len(matches) > 0, nil 450 | } 451 | 452 | // read takes a CPU ID and offset and returns an 8-byte value with the contents 453 | // of the associated MSR register. 454 | func (m *msrDataWithStorage) read(offset uint32, cpuID int) (uint64, error) { 455 | reg, ok := m.msrMap[cpuID] 456 | if !ok { 457 | return 0, fmt.Errorf("could not find MSR register for CPU ID: %v", cpuID) 458 | } 459 | return reg.read(offset) 460 | } 461 | 462 | // update takes a CPU ID, performs reading operations along the offsets, storing the results 463 | // within the storage. 464 | func (m *msrDataWithStorage) update(cpuID int) error { 465 | reg, ok := m.msrMap[cpuID] 466 | if !ok { 467 | return fmt.Errorf("could not find MSR register for CPU ID: %v", cpuID) 468 | } 469 | return reg.update() 470 | } 471 | 472 | // getOffsetDeltas takes a CPU ID and returns a map with offset keys and delta offset values between 473 | // latest and its previous reading offsets operation. 474 | func (m *msrDataWithStorage) getOffsetDeltas(cpuID int) (map[uint32]uint64, error) { 475 | reg, ok := m.msrMap[cpuID] 476 | if !ok { 477 | return nil, fmt.Errorf("could not find MSR register for CPU ID: %v", cpuID) 478 | } 479 | return reg.getOffsetDeltas(), nil 480 | } 481 | 482 | // scaleOffsetDeltas takes a CPU ID and a slice of msr offsets. It scales all offset deltas of the msr storage by multiplying 483 | // each offset delta by the given factor f. 484 | func (m *msrDataWithStorage) scaleOffsetDeltas(cpuID int, offsets []uint32, f *big.Float) error { 485 | reg, ok := m.msrMap[cpuID] 486 | if !ok { 487 | return fmt.Errorf("could not find MSR register for CPU ID: %v", cpuID) 488 | } 489 | 490 | deltas := reg.getOffsetDeltas() 491 | for _, offset := range offsets { 492 | if v, ok := deltas[offset]; ok { 493 | deltaBig := new(big.Float).SetUint64(v) 494 | scaledDeltaBig := new(big.Float).Mul(deltaBig, f) 495 | scaledDelta, _ := scaledDeltaBig.Uint64() 496 | deltas[offset] = scaledDelta 497 | } 498 | } 499 | 500 | reg.setOffsetDeltas(deltas) 501 | return nil 502 | } 503 | 504 | // getTimestampDelta takes a CPU ID and returns the time interval between the last offset value reading 505 | // operation and its previous reading operation. 506 | func (m *msrDataWithStorage) getTimestampDelta(cpuID int) (time.Duration, error) { 507 | reg, ok := m.msrMap[cpuID] 508 | if !ok { 509 | return time.Duration(0), fmt.Errorf("could not find MSR register for CPU ID: %v", cpuID) 510 | } 511 | return reg.getTimestampDelta(), nil 512 | } 513 | --------------------------------------------------------------------------------