perf_linux_test.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. //go:build linux
  2. package cpupick
  3. import (
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "slices"
  8. "testing"
  9. )
  10. // fakeSysfs builds a cpuDir tree with the given per-CPU file values.
  11. // A nil map for a file means "file absent on every CPU".
  12. func fakeSysfs(t *testing.T, capacity, maxFreq map[int]int) string {
  13. t.Helper()
  14. dir := t.TempDir()
  15. write := func(cpu int, rel string, v int) {
  16. p := filepath.Join(dir, fmt.Sprintf("cpu%d", cpu), rel)
  17. if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
  18. t.Fatal(err)
  19. }
  20. if err := os.WriteFile(p, fmt.Appendf(nil, "%d\n", v), 0o644); err != nil {
  21. t.Fatal(err)
  22. }
  23. }
  24. for cpu, v := range capacity {
  25. write(cpu, "cpu_capacity", v)
  26. }
  27. for cpu, v := range maxFreq {
  28. write(cpu, "cpufreq/cpuinfo_max_freq", v)
  29. }
  30. return dir
  31. }
  32. func writeCoreMask(t *testing.T, mask string) string {
  33. t.Helper()
  34. p := filepath.Join(t.TempDir(), "cpus")
  35. if err := os.WriteFile(p, []byte(mask+"\n"), 0o644); err != nil {
  36. t.Fatal(err)
  37. }
  38. return p
  39. }
  40. func TestPerfCPUsBigLittleCapacity(t *testing.T) {
  41. // 4 big (1024) + 4 LITTLE (~290): capacity is authoritative on ARM.
  42. dir := fakeSysfs(t, map[int]int{
  43. 0: 1024, 1: 1024, 2: 1024, 3: 1024,
  44. 4: 290, 5: 290, 6: 290, 7: 290,
  45. }, nil)
  46. got, signal := perfCPUsFrom(dir, filepath.Join(dir, "nope"), []int{0, 1, 2, 3, 4, 5, 6, 7})
  47. if signal != "cpu_capacity" {
  48. t.Fatalf("signal = %q", signal)
  49. }
  50. if !slices.Equal(got, []int{0, 1, 2, 3}) {
  51. t.Errorf("got %v", got)
  52. }
  53. }
  54. func TestPerfCPUsThreeTierKeepsMid(t *testing.T) {
  55. // prime (1024) + mid (~780) + little (~280): 50% keeps prime+mid.
  56. dir := fakeSysfs(t, map[int]int{
  57. 0: 280, 1: 280, 2: 280, 3: 280,
  58. 4: 780, 5: 780, 6: 780,
  59. 7: 1024,
  60. }, nil)
  61. got, _ := perfCPUsFrom(dir, filepath.Join(dir, "nope"), []int{0, 1, 2, 3, 4, 5, 6, 7})
  62. if !slices.Equal(got, []int{4, 5, 6, 7}) {
  63. t.Errorf("got %v", got)
  64. }
  65. }
  66. func TestPerfCPUsIntelHybridMask(t *testing.T) {
  67. // No cpu_capacity on x86; the P-core PMU mask decides.
  68. dir := fakeSysfs(t, nil, nil)
  69. mask := writeCoreMask(t, "0-7")
  70. got, signal := perfCPUsFrom(dir, mask, []int{0, 1, 2, 3, 8, 9, 10, 11})
  71. if signal != "intel_core_pmu" {
  72. t.Fatalf("signal = %q", signal)
  73. }
  74. if !slices.Equal(got, []int{0, 1, 2, 3}) {
  75. t.Errorf("got %v", got)
  76. }
  77. }
  78. func TestPerfCPUsIntelMaskDisjointFallsThrough(t *testing.T) {
  79. // Confined to E-cores only: the mask can't help, and equal freqs below
  80. // mean nothing else distinguishes them either -> allowed unchanged.
  81. dir := fakeSysfs(t, nil, map[int]int{8: 4300000, 9: 4300000})
  82. mask := writeCoreMask(t, "0-7")
  83. got, signal := perfCPUsFrom(dir, mask, []int{8, 9})
  84. if signal != "" || !slices.Equal(got, []int{8, 9}) {
  85. t.Errorf("got %v signal %q", got, signal)
  86. }
  87. }
  88. func TestPerfCPUsMaxFreqCompactCores(t *testing.T) {
  89. // AMD-style compact cores: no capacity, no Intel mask; 3.3 vs 5.7 GHz.
  90. dir := fakeSysfs(t, nil, map[int]int{
  91. 0: 5700000, 1: 5700000, 2: 3300000, 3: 3300000,
  92. })
  93. got, signal := perfCPUsFrom(dir, filepath.Join(dir, "nope"), []int{0, 1, 2, 3})
  94. if signal != "max_freq" {
  95. t.Fatalf("signal = %q", signal)
  96. }
  97. if !slices.Equal(got, []int{0, 1}) {
  98. t.Errorf("got %v", got)
  99. }
  100. }
  101. func TestPerfCPUsFavoredCoreSkewKept(t *testing.T) {
  102. // Turbo Boost Max favored cores run a few percent hot; they must not
  103. // shrink the candidate set to one or two cores.
  104. dir := fakeSysfs(t, nil, map[int]int{
  105. 0: 5800000, 1: 5700000, 2: 5700000, 3: 5600000,
  106. })
  107. got, _ := perfCPUsFrom(dir, filepath.Join(dir, "nope"), []int{0, 1, 2, 3})
  108. if !slices.Equal(got, []int{0, 1, 2, 3}) {
  109. t.Errorf("favored-core skew filtered CPUs: %v", got)
  110. }
  111. }
  112. func TestPerfCPUsHomogeneousInconclusive(t *testing.T) {
  113. dir := fakeSysfs(t, nil, map[int]int{0: 3000000, 1: 3000000})
  114. got, signal := perfCPUsFrom(dir, filepath.Join(dir, "nope"), []int{0, 1})
  115. if signal != "" || !slices.Equal(got, []int{0, 1}) {
  116. t.Errorf("got %v signal %q", got, signal)
  117. }
  118. }
  119. func TestPerfCPUsNoSysfs(t *testing.T) {
  120. dir := t.TempDir()
  121. got, signal := perfCPUsFrom(dir, filepath.Join(dir, "nope"), []int{0, 1, 2})
  122. if signal != "" || !slices.Equal(got, []int{0, 1, 2}) {
  123. t.Errorf("got %v signal %q", got, signal)
  124. }
  125. }
  126. func TestParseCPUList(t *testing.T) {
  127. cases := []struct {
  128. in string
  129. want []int
  130. wantErr bool
  131. }{
  132. {"0-3", []int{0, 1, 2, 3}, false},
  133. {"0-1,16-17", []int{0, 1, 16, 17}, false},
  134. {"5", []int{5}, false},
  135. {"", nil, false},
  136. {"3-1", nil, true},
  137. {"a-b", nil, true},
  138. {"1,x", nil, true},
  139. }
  140. for _, c := range cases {
  141. got, err := parseCPUList(c.in)
  142. if (err != nil) != c.wantErr {
  143. t.Errorf("%q: err=%v wantErr=%v", c.in, err, c.wantErr)
  144. continue
  145. }
  146. if !c.wantErr && !slices.Equal(got, c.want) {
  147. t.Errorf("%q: got %v want %v", c.in, got, c.want)
  148. }
  149. }
  150. }