1 // Copyright (c) 2021-2023 Apple Inc.  All rights reserved.
2 
3 #include <darwintest.h>
4 #include <stdlib.h>
5 #include <sys/resource_private.h>
6 #include <sys/sysctl.h>
7 
8 #include "test_utils.h"
9 #include "recount_test_utils.h"
10 
11 T_GLOBAL_META(
12 	T_META_RADAR_COMPONENT_NAME("xnu"),
13 	T_META_RADAR_COMPONENT_VERSION("cpu counters"),
14     T_META_OWNER("mwidmann"),
15     T_META_CHECK_LEAKS(false));
16 
17 static void
_check_cpi(struct thsc_cpi * before,struct thsc_cpi * after,const char * name)18 _check_cpi(struct thsc_cpi *before, struct thsc_cpi *after, const char *name)
19 {
20 	T_QUIET;
21 	T_EXPECT_GT(before->tcpi_instructions, UINT64_C(0),
22 	    "%s: instructions non-zero", name);
23 	T_QUIET;
24 	T_EXPECT_GT(before->tcpi_cycles, UINT64_C(0), "%s: cycles non-zero",
25 	    name);
26 
27 	T_EXPECT_GT(after->tcpi_instructions, before->tcpi_instructions,
28 	    "%s: instructions monotonically-increasing", name);
29 	T_EXPECT_GT(after->tcpi_cycles, before->tcpi_cycles,
30 	    "%s: cycles monotonically-increasing", name);
31 }
32 
33 static void
_check_no_cpi(struct thsc_cpi * before,struct thsc_cpi * after,const char * name)34 _check_no_cpi(struct thsc_cpi *before, struct thsc_cpi *after, const char *name)
35 {
36 	T_EXPECT_EQ(after->tcpi_instructions, before->tcpi_instructions,
37 	    "%s: instructions should not increase", name);
38 	T_EXPECT_EQ(after->tcpi_cycles, before->tcpi_cycles,
39 	    "%s: cycles should not increase", name);
40 }
41 
42 static struct thsc_cpi
_remove_time_from_cpi(struct thsc_time_cpi * time_cpi)43 _remove_time_from_cpi(struct thsc_time_cpi *time_cpi)
44 {
45 	return (struct thsc_cpi){
46 		.tcpi_instructions = time_cpi->ttci_instructions,
47 		.tcpi_cycles = time_cpi->ttci_cycles,
48 	};
49 }
50 
51 static void
_check_time_cpi(struct thsc_time_cpi * before,struct thsc_time_cpi * after,const char * name)52 _check_time_cpi(struct thsc_time_cpi *before, struct thsc_time_cpi *after,
53     const char *name)
54 {
55 	struct thsc_cpi before_cpi = _remove_time_from_cpi(before);
56 	struct thsc_cpi after_cpi = _remove_time_from_cpi(after);
57 	_check_cpi(&before_cpi, &after_cpi, name);
58 
59 	T_EXPECT_GT(after->ttci_user_time_mach, before->ttci_user_time_mach,
60 			"%s: user time monotonically-increasing", name);
61 
62 	if (has_user_system_times()) {
63 		T_EXPECT_GT(after->ttci_system_time_mach, before->ttci_system_time_mach,
64 				"%s: system time monotonically-increasing", name);
65 	}
66 }
67 
68 static void
_check_no_time_cpi(struct thsc_time_cpi * before,struct thsc_time_cpi * after,const char * name)69 _check_no_time_cpi(struct thsc_time_cpi *before, struct thsc_time_cpi *after,
70     const char *name)
71 {
72 	struct thsc_cpi before_cpi = _remove_time_from_cpi(before);
73 	struct thsc_cpi after_cpi = _remove_time_from_cpi(after);
74 	_check_no_cpi(&before_cpi, &after_cpi, name);
75 
76 	T_EXPECT_EQ(after->ttci_user_time_mach, before->ttci_user_time_mach,
77 			"%s: user time should not change", name);
78 
79 	if (has_user_system_times()) {
80 		T_EXPECT_EQ(after->ttci_system_time_mach, before->ttci_system_time_mach,
81 				"%s: system time should not change", name);
82 	}
83 }
84 
85 static struct thsc_time_cpi
_remove_energy_from_cpi(struct thsc_time_energy_cpi * energy_cpi)86 _remove_energy_from_cpi(struct thsc_time_energy_cpi *energy_cpi)
87 {
88 	return (struct thsc_time_cpi){
89 		.ttci_instructions = energy_cpi->ttec_instructions,
90 		.ttci_cycles = energy_cpi->ttec_cycles,
91 		.ttci_system_time_mach = energy_cpi->ttec_system_time_mach,
92 		.ttci_user_time_mach = energy_cpi->ttec_user_time_mach,
93 	};
94 }
95 
96 static void
_check_usage(struct thsc_time_energy_cpi * before,struct thsc_time_energy_cpi * after,const char * name)97 _check_usage(struct thsc_time_energy_cpi *before,
98     struct thsc_time_energy_cpi *after, const char *name)
99 {
100 	struct thsc_time_cpi before_time = _remove_energy_from_cpi(before);
101 	struct thsc_time_cpi after_time = _remove_energy_from_cpi(after);
102 	_check_time_cpi(&before_time, &after_time, name);
103 
104 	if (has_energy()) {
105 		T_EXPECT_GT(after->ttec_energy_nj, UINT64_C(0),
106 				"%s: energy monotonically-increasing", name);
107 	}
108 }
109 
110 static void
_check_no_usage(struct thsc_time_energy_cpi * before,struct thsc_time_energy_cpi * after,const char * name)111 _check_no_usage(struct thsc_time_energy_cpi *before,
112     struct thsc_time_energy_cpi *after, const char *name)
113 {
114 	struct thsc_time_cpi before_time = _remove_energy_from_cpi(before);
115 	struct thsc_time_cpi after_time = _remove_energy_from_cpi(after);
116 	_check_no_time_cpi(&before_time, &after_time, name);
117 }
118 
119 T_DECL(thread_selfcounts_cpi_sanity, "check the current thread's CPI",
120     REQUIRE_RECOUNT_PMCS, T_META_TAG_VM_NOT_ELIGIBLE)
121 {
122 	int err;
123 	struct thsc_cpi counts[2] = { 0 };
124 
125 	err = thread_selfcounts(THSC_CPI, &counts[0], sizeof(counts[0]));
126 	T_ASSERT_POSIX_ZERO(err, "thread_selfcounts(THSC_CPI, ...)");
127 	err = thread_selfcounts(THSC_CPI, &counts[1], sizeof(counts[1]));
128 	T_ASSERT_POSIX_ZERO(err, "thread_selfcounts(THSC_CPI, ...)");
129 
130 	_check_cpi(&counts[0], &counts[1], "anywhere");
131 }
132 
133 T_DECL(thread_selfcounts_perf_level_sanity,
134     "check per-perf level time, energy, and CPI",
135     REQUIRE_RECOUNT_PMCS,
136     // REQUIRE_MULTIPLE_PERF_LEVELS, disabled due to rdar://111297938
137     SET_THREAD_BIND_BOOTARG,
138     T_META_ASROOT(true), T_META_TAG_VM_NOT_ELIGIBLE)
139 {
140 	unsigned int level_count = perf_level_count();
141 
142 	// Until rdar://111297938, manually skip the test if there aren't multiple perf levels.
143 	if (level_count < 2) {
144 		T_SKIP("device is not eligible for checking perf levels because it is SMP");
145 	}
146 	struct thsc_time_energy_cpi *before = calloc(level_count, sizeof(*before));
147 	struct thsc_time_energy_cpi *after = calloc(level_count, sizeof(*after));
148 
149 	run_on_all_perf_levels();
150 
151 	int err = thread_selfcounts(THSC_TIME_ENERGY_CPI_PER_PERF_LEVEL, before,
152 			level_count * sizeof(*before));
153 	T_ASSERT_POSIX_ZERO(err,
154 			"thread_selfcounts(THSC_TIME_ENERGY_CPI_PER_PERF_LEVEL, ...)");
155 
156 	run_on_all_perf_levels();
157 
158 	err = thread_selfcounts(THSC_TIME_ENERGY_CPI_PER_PERF_LEVEL, after,
159 			level_count * sizeof(*after));
160 	T_ASSERT_POSIX_ZERO(err,
161 			"thread_selfcounts(THSC_TIME_ENERGY_CPI_PER_PERF_LEVEL, ...)");
162 
163 	for (unsigned int i = 0; i < level_count; i++) {
164 		_check_usage(&before[i], &after[i], perf_level_name(i));
165 	}
166 
167 	free(before);
168 	free(after);
169 }
170 
171 static void
_expect_counts_on_perf_level(unsigned int perf_level_index,struct thsc_time_energy_cpi * before,struct thsc_time_energy_cpi * after)172 _expect_counts_on_perf_level(unsigned int perf_level_index,
173 		struct thsc_time_energy_cpi *before,
174 		struct thsc_time_energy_cpi *after)
175 {
176 	unsigned int level_count = perf_level_count();
177 	int err = thread_selfcounts(THSC_TIME_ENERGY_CPI_PER_PERF_LEVEL, before,
178 			level_count * sizeof(*before));
179 	T_ASSERT_POSIX_ZERO(err,
180 			"thread_selfcounts(THSC_TIME_ENERGY_CPI_PER_PERF_LEVEL, ...)");
181 	(void)getppid();
182 	err = thread_selfcounts(THSC_TIME_ENERGY_CPI_PER_PERF_LEVEL, after,
183 			level_count * sizeof(*after));
184 	T_ASSERT_POSIX_ZERO(err,
185 			"thread_selfcounts(THSC_TIME_ENERGY_CPI_PER_PERF_LEVEL, ...)");
186 
187 	char *name = perf_level_name(perf_level_index);
188 	_check_usage(&before[perf_level_index], &after[perf_level_index], name);
189 }
190 
191 static void
_expect_no_counts_on_perf_level(unsigned int perf_level_index,struct thsc_time_energy_cpi * before,struct thsc_time_energy_cpi * after)192 _expect_no_counts_on_perf_level(unsigned int perf_level_index,
193 		struct thsc_time_energy_cpi *before,
194 		struct thsc_time_energy_cpi *after)
195 {
196 	unsigned int level_count = perf_level_count();
197 	int err = thread_selfcounts(THSC_TIME_ENERGY_CPI_PER_PERF_LEVEL, before,
198 			level_count * sizeof(*before));
199 	T_ASSERT_POSIX_ZERO(err,
200 			"thread_selfcounts(THSC_TIME_ENERGY_CPI_PER_PERF_LEVEL, ...)");
201 	(void)getppid();
202 	err = thread_selfcounts(THSC_TIME_ENERGY_CPI_PER_PERF_LEVEL, after,
203 			level_count * sizeof(*after));
204 	T_ASSERT_POSIX_ZERO(err,
205 			"thread_selfcounts(THSC_TIME_ENERGY_CPI_PER_PERF_LEVEL, ...)");
206 
207 	char *name = perf_level_name(perf_level_index);
208 	_check_no_usage(&before[perf_level_index], &after[perf_level_index], name);
209 }
210 
211 T_DECL(thread_selfcounts_perf_level_correct,
212     "check that runtimes on each perf level match binding request",
213     REQUIRE_RECOUNT_PMCS,
214     // REQUIRE_MULTIPLE_PERF_LEVELS, disabled due to rdar://111297938
215     SET_THREAD_BIND_BOOTARG,
216     T_META_ASROOT(true), T_META_TAG_VM_NOT_ELIGIBLE)
217 {
218 	unsigned int level_count = perf_level_count();
219 
220 	// Until rdar://111297938, manually skip the test if there aren't multiple perf levels.
221 	if (level_count < 2) {
222 		T_SKIP("device is not eligible for checking perf levels because it is SMP");
223 	}
224 	for (unsigned int i = 0; i < level_count; i++) {
225 		T_LOG("Level %d: %s", i, perf_level_name(i));
226 	}
227 
228 	struct thsc_time_energy_cpi *before = calloc(level_count, sizeof(*before));
229 	struct thsc_time_energy_cpi *after = calloc(level_count, sizeof(*after));
230 
231 	T_LOG("Binding to Efficiency cluster, should only see counts from E-cores");
232 	T_SETUPBEGIN;
233 	bind_to_cluster('E');
234 	T_SETUPEND;
235 	_expect_counts_on_perf_level(1, before, after);
236 	_expect_no_counts_on_perf_level(0, before, after);
237 
238 	T_LOG("Binding to Performance cluster, should only see counts from P-cores");
239 	T_SETUPBEGIN;
240 	bind_to_cluster('P');
241 	T_SETUPEND;
242 	_expect_counts_on_perf_level(0, before, after);
243 	_expect_no_counts_on_perf_level(1, before, after);
244 
245 	free(before);
246 	free(after);
247 }
248 
249 T_DECL(thread_selfcounts_cpi_perf,
250     "test the overhead of thread_selfcounts(2) THSC_CPI", T_META_TAG_PERF,
251     REQUIRE_RECOUNT_PMCS, T_META_TAG_VM_NOT_ELIGIBLE)
252 {
253 	struct thsc_cpi counts[2];
254 
255 	T_SETUPBEGIN;
256 	dt_stat_t instrs = dt_stat_create("thread_selfcounts_cpi_instrs",
257 			"instructions");
258 	dt_stat_t cycles = dt_stat_create("thread_selfcounts_cpi_cycles", "cycles");
259 	T_SETUPEND;
260 
261 	while (!dt_stat_stable(instrs) || !dt_stat_stable(cycles)) {
262 		int r1 = thread_selfcounts(THSC_CPI, &counts[0], sizeof(counts[0]));
263 		int r2 = thread_selfcounts(THSC_CPI, &counts[1], sizeof(counts[1]));
264 		T_QUIET; T_ASSERT_POSIX_ZERO(r1, "thread_selfcounts(THSC_CPI, ...)");
265 		T_QUIET; T_ASSERT_POSIX_ZERO(r2, "thread_selfcounts(THSC_CPI, ...)");
266 
267 		dt_stat_add(instrs, counts[1].tcpi_instructions -
268 				counts[0].tcpi_instructions);
269 		dt_stat_add(cycles, counts[1].tcpi_cycles - counts[0].tcpi_cycles);
270 	}
271 
272 	dt_stat_finalize(instrs);
273 	dt_stat_finalize(cycles);
274 }
275