Skip to content

Commit e958c21

Browse files
rchatreshuahkh
authored andcommitted
selftests/resctrl: Make benchmark parameter passing robust
The benchmark used during the CMT, MBM, and MBA tests can be provided by the user via (-b) parameter, if not provided the default "fill_buf" benchmark is used. The user is additionally able to override any of the "fill_buf" default parameters when running the tests with "-b fill_buf <fill_buf parameters>". The "fill_buf" parameters are managed as an array of strings. Using an array of strings is complex because it requires transformations to/from strings at every producer and consumer. This is made worse for the individual tests where the default benchmark parameters values may not be appropriate and additional data wrangling is required. For example, the CMT test duplicates the entire array of strings in order to replace one of the parameters. More issues appear when combining the usage of an array of strings with the use case of user overriding default parameters by specifying "-b fill_buf <parameters>". This use case is fragile with opportunities to trigger a SIGSEGV because of opportunities for NULL pointers to exist in the array of strings. For example, by running below (thus by specifying "fill_buf" should be used but all parameters are NULL): $ sudo resctrl_tests -t mbm -b fill_buf Replace the "array of strings" parameters used for "fill_buf" with new struct fill_buf_param that contains the "fill_buf" parameters that can be used directly without transformations to/from strings. Two instances of struct fill_buf_param may exist at any point in time: * If the user provides new parameters to "fill_buf", the user parameter structure (struct user_params) will point to a fully initialized and immutable struct fill_buf_param containing the user provided parameters. * If "fill_buf" is the benchmark that should be used by a test, then the test parameter structure (struct resctrl_val_param) will point to a fully initialized struct fill_buf_param. The latter may contain (a) the user provided parameters verbatim, (b) user provided parameters adjusted to be appropriate for the test, or (c) the default parameters for "fill_buf" that is appropriate for the test if the user did not provide "fill_buf" parameters nor an alternate benchmark. The existing behavior of CMT test is to use test defined value for the buffer size even if the user provides another value via command line. This behavior is maintained since the test requires that the buffer size matches the size of the cache allocated, and the amount of cache allocated can instead be changed by the user with the "-n" command line parameter. Signed-off-by: Reinette Chatre <[email protected]> Reviewed-by: Ilpo Järvinen <[email protected]> Signed-off-by: Shuah Khan <[email protected]>
1 parent 76f8f00 commit e958c21

File tree

7 files changed

+178
-96
lines changed

7 files changed

+178
-96
lines changed

tools/testing/selftests/resctrl/cmt_test.c

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,13 @@ static void cmt_test_cleanup(void)
116116

117117
static int cmt_run_test(const struct resctrl_test *test, const struct user_params *uparams)
118118
{
119-
const char * const *cmd = uparams->benchmark_cmd;
120-
const char *new_cmd[BENCHMARK_ARGS];
119+
struct fill_buf_param fill_buf = {};
121120
unsigned long cache_total_size = 0;
122121
int n = uparams->bits ? : 5;
123122
unsigned long long_mask;
124-
char *span_str = NULL;
125123
int count_of_bits;
126124
size_t span;
127-
int ret, i;
125+
int ret;
128126

129127
ret = get_full_cbm("L3", &long_mask);
130128
if (ret)
@@ -155,32 +153,26 @@ static int cmt_run_test(const struct resctrl_test *test, const struct user_param
155153

156154
span = cache_portion_size(cache_total_size, param.mask, long_mask);
157155

158-
if (strcmp(cmd[0], "fill_buf") == 0) {
159-
/* Duplicate the command to be able to replace span in it */
160-
for (i = 0; uparams->benchmark_cmd[i]; i++)
161-
new_cmd[i] = uparams->benchmark_cmd[i];
162-
new_cmd[i] = NULL;
163-
164-
ret = asprintf(&span_str, "%zu", span);
165-
if (ret < 0)
166-
return -1;
167-
new_cmd[1] = span_str;
168-
cmd = new_cmd;
156+
if (uparams->fill_buf) {
157+
fill_buf.buf_size = span;
158+
fill_buf.memflush = uparams->fill_buf->memflush;
159+
param.fill_buf = &fill_buf;
160+
} else if (!uparams->benchmark_cmd[0]) {
161+
fill_buf.buf_size = span;
162+
fill_buf.memflush = true;
163+
param.fill_buf = &fill_buf;
169164
}
170165

171166
remove(RESULT_FILE_NAME);
172167

173-
ret = resctrl_val(test, uparams, cmd, &param);
168+
ret = resctrl_val(test, uparams, &param);
174169
if (ret)
175-
goto out;
170+
return ret;
176171

177172
ret = check_results(&param, span, n);
178173
if (ret && (get_vendor() == ARCH_INTEL))
179174
ksft_print_msg("Intel CMT may be inaccurate when Sub-NUMA Clustering is enabled. Check BIOS configuration.\n");
180175

181-
out:
182-
free(span_str);
183-
184176
return ret;
185177
}
186178

tools/testing/selftests/resctrl/fill_buf.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ void fill_cache_read(unsigned char *buf, size_t buf_size, bool once)
102102
*value_sink = ret;
103103
}
104104

105-
unsigned char *alloc_buffer(size_t buf_size, int memflush)
105+
unsigned char *alloc_buffer(size_t buf_size, bool memflush)
106106
{
107107
void *buf = NULL;
108108
uint64_t *p64;
@@ -130,7 +130,7 @@ unsigned char *alloc_buffer(size_t buf_size, int memflush)
130130
return buf;
131131
}
132132

133-
int run_fill_buf(size_t buf_size, int memflush)
133+
int run_fill_buf(size_t buf_size, bool memflush)
134134
{
135135
unsigned char *buf;
136136

tools/testing/selftests/resctrl/mba_test.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,22 @@ static int mba_run_test(const struct resctrl_test *test, const struct user_param
172172
.setup = mba_setup,
173173
.measure = mba_measure,
174174
};
175+
struct fill_buf_param fill_buf = {};
175176
int ret;
176177

177178
remove(RESULT_FILE_NAME);
178179

179-
ret = resctrl_val(test, uparams, uparams->benchmark_cmd, &param);
180+
if (uparams->fill_buf) {
181+
fill_buf.buf_size = uparams->fill_buf->buf_size;
182+
fill_buf.memflush = uparams->fill_buf->memflush;
183+
param.fill_buf = &fill_buf;
184+
} else if (!uparams->benchmark_cmd[0]) {
185+
fill_buf.buf_size = DEFAULT_SPAN;
186+
fill_buf.memflush = true;
187+
param.fill_buf = &fill_buf;
188+
}
189+
190+
ret = resctrl_val(test, uparams, &param);
180191
if (ret)
181192
return ret;
182193

tools/testing/selftests/resctrl/mbm_test.c

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -139,26 +139,26 @@ static int mbm_run_test(const struct resctrl_test *test, const struct user_param
139139
.setup = mbm_setup,
140140
.measure = mbm_measure,
141141
};
142-
char *endptr = NULL;
143-
size_t span = 0;
142+
struct fill_buf_param fill_buf = {};
144143
int ret;
145144

146145
remove(RESULT_FILE_NAME);
147146

148-
if (uparams->benchmark_cmd[0] && strcmp(uparams->benchmark_cmd[0], "fill_buf") == 0) {
149-
if (uparams->benchmark_cmd[1] && *uparams->benchmark_cmd[1] != '\0') {
150-
errno = 0;
151-
span = strtoul(uparams->benchmark_cmd[1], &endptr, 10);
152-
if (errno || *endptr != '\0')
153-
return -EINVAL;
154-
}
147+
if (uparams->fill_buf) {
148+
fill_buf.buf_size = uparams->fill_buf->buf_size;
149+
fill_buf.memflush = uparams->fill_buf->memflush;
150+
param.fill_buf = &fill_buf;
151+
} else if (!uparams->benchmark_cmd[0]) {
152+
fill_buf.buf_size = DEFAULT_SPAN;
153+
fill_buf.memflush = true;
154+
param.fill_buf = &fill_buf;
155155
}
156156

157-
ret = resctrl_val(test, uparams, uparams->benchmark_cmd, &param);
157+
ret = resctrl_val(test, uparams, &param);
158158
if (ret)
159159
return ret;
160160

161-
ret = check_results(span);
161+
ret = check_results(param.fill_buf ? param.fill_buf->buf_size : 0);
162162
if (ret && (get_vendor() == ARCH_INTEL))
163163
ksft_print_msg("Intel MBM may be inaccurate when Sub-NUMA Clustering is enabled. Check BIOS configuration.\n");
164164

tools/testing/selftests/resctrl/resctrl.h

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,36 @@
4343

4444
#define DEFAULT_SPAN (250 * MB)
4545

46+
/*
47+
* fill_buf_param: "fill_buf" benchmark parameters
48+
* @buf_size: Size (in bytes) of buffer used in benchmark.
49+
* "fill_buf" allocates and initializes buffer of
50+
* @buf_size. User can change value via command line.
51+
* @memflush: If false the buffer will not be flushed after
52+
* allocation and initialization, otherwise the
53+
* buffer will be flushed. User can change value via
54+
* command line (via integers with 0 interpreted as
55+
* false and anything else as true).
56+
*/
57+
struct fill_buf_param {
58+
size_t buf_size;
59+
bool memflush;
60+
};
61+
4662
/*
4763
* user_params: User supplied parameters
4864
* @cpu: CPU number to which the benchmark will be bound to
4965
* @bits: Number of bits used for cache allocation size
5066
* @benchmark_cmd: Benchmark command to run during (some of the) tests
67+
* @fill_buf: Pointer to user provided parameters for "fill_buf",
68+
* NULL if user did not provide parameters and test
69+
* specific defaults should be used.
5170
*/
5271
struct user_params {
5372
int cpu;
5473
int bits;
5574
const char *benchmark_cmd[BENCHMARK_ARGS];
75+
const struct fill_buf_param *fill_buf;
5676
};
5777

5878
/*
@@ -87,21 +107,29 @@ struct resctrl_test {
87107
* @init: Callback function to initialize test environment
88108
* @setup: Callback function to setup per test run environment
89109
* @measure: Callback that performs the measurement (a single test)
110+
* @fill_buf: Parameters for default "fill_buf" benchmark.
111+
* Initialized with user provided parameters, possibly
112+
* adapted to be relevant to the test. If user does
113+
* not provide parameters for "fill_buf" nor a
114+
* replacement benchmark then initialized with defaults
115+
* appropriate for test. NULL if user provided
116+
* benchmark.
90117
*/
91118
struct resctrl_val_param {
92-
const char *ctrlgrp;
93-
const char *mongrp;
94-
char filename[64];
95-
unsigned long mask;
96-
int num_of_runs;
97-
int (*init)(const struct resctrl_val_param *param,
98-
int domain_id);
99-
int (*setup)(const struct resctrl_test *test,
100-
const struct user_params *uparams,
101-
struct resctrl_val_param *param);
102-
int (*measure)(const struct user_params *uparams,
103-
struct resctrl_val_param *param,
104-
pid_t bm_pid);
119+
const char *ctrlgrp;
120+
const char *mongrp;
121+
char filename[64];
122+
unsigned long mask;
123+
int num_of_runs;
124+
int (*init)(const struct resctrl_val_param *param,
125+
int domain_id);
126+
int (*setup)(const struct resctrl_test *test,
127+
const struct user_params *uparams,
128+
struct resctrl_val_param *param);
129+
int (*measure)(const struct user_params *uparams,
130+
struct resctrl_val_param *param,
131+
pid_t bm_pid);
132+
struct fill_buf_param *fill_buf;
105133
};
106134

107135
struct perf_event_read {
@@ -138,18 +166,17 @@ int write_schemata(const char *ctrlgrp, char *schemata, int cpu_no,
138166
int write_bm_pid_to_resctrl(pid_t bm_pid, const char *ctrlgrp, const char *mongrp);
139167
int perf_event_open(struct perf_event_attr *hw_event, pid_t pid, int cpu,
140168
int group_fd, unsigned long flags);
141-
unsigned char *alloc_buffer(size_t buf_size, int memflush);
169+
unsigned char *alloc_buffer(size_t buf_size, bool memflush);
142170
void mem_flush(unsigned char *buf, size_t buf_size);
143171
void fill_cache_read(unsigned char *buf, size_t buf_size, bool once);
144-
int run_fill_buf(size_t buf_size, int memflush);
172+
int run_fill_buf(size_t buf_size, bool memflush);
145173
int initialize_read_mem_bw_imc(void);
146174
int measure_read_mem_bw(const struct user_params *uparams,
147175
struct resctrl_val_param *param, pid_t bm_pid);
148176
void initialize_mem_bw_resctrl(const struct resctrl_val_param *param,
149177
int domain_id);
150178
int resctrl_val(const struct resctrl_test *test,
151179
const struct user_params *uparams,
152-
const char * const *benchmark_cmd,
153180
struct resctrl_val_param *param);
154181
unsigned long create_bit_mask(unsigned int start, unsigned int len);
155182
unsigned int count_contiguous_bits(unsigned long val, unsigned int *start);

tools/testing/selftests/resctrl/resctrl_tests.c

Lines changed: 79 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,78 @@ static void run_single_test(const struct resctrl_test *test, const struct user_p
148148
test_cleanup(test);
149149
}
150150

151+
/*
152+
* Allocate and initialize a struct fill_buf_param with user provided
153+
* (via "-b fill_buf <fill_buf parameters>") parameters.
154+
*
155+
* Use defaults (that may not be appropriate for all tests) for any
156+
* fill_buf parameters omitted by the user.
157+
*
158+
* Historically it may have been possible for user space to provide
159+
* additional parameters, "operation" ("read" vs "write") in
160+
* benchmark_cmd[3] and "once" (run "once" or until terminated) in
161+
* benchmark_cmd[4]. Changing these parameters have never been
162+
* supported with the default of "read" operation and running until
163+
* terminated built into the tests. Any unsupported values for
164+
* (original) "fill_buf" parameters are treated as failure.
165+
*
166+
* Return: On failure, forcibly exits the test on any parsing failure,
167+
* returns NULL if no parsing needed (user did not actually provide
168+
* "-b fill_buf").
169+
* On success, returns pointer to newly allocated and fully
170+
* initialized struct fill_buf_param that caller must free.
171+
*/
172+
static struct fill_buf_param *alloc_fill_buf_param(struct user_params *uparams)
173+
{
174+
struct fill_buf_param *fill_param = NULL;
175+
char *endptr = NULL;
176+
177+
if (!uparams->benchmark_cmd[0] || strcmp(uparams->benchmark_cmd[0], "fill_buf"))
178+
return NULL;
179+
180+
fill_param = malloc(sizeof(*fill_param));
181+
if (!fill_param)
182+
ksft_exit_skip("Unable to allocate memory for fill_buf parameters.\n");
183+
184+
if (uparams->benchmark_cmd[1] && *uparams->benchmark_cmd[1] != '\0') {
185+
errno = 0;
186+
fill_param->buf_size = strtoul(uparams->benchmark_cmd[1], &endptr, 10);
187+
if (errno || *endptr != '\0') {
188+
free(fill_param);
189+
ksft_exit_skip("Unable to parse benchmark buffer size.\n");
190+
}
191+
} else {
192+
fill_param->buf_size = DEFAULT_SPAN;
193+
}
194+
195+
if (uparams->benchmark_cmd[2] && *uparams->benchmark_cmd[2] != '\0') {
196+
errno = 0;
197+
fill_param->memflush = strtol(uparams->benchmark_cmd[2], &endptr, 10) != 0;
198+
if (errno || *endptr != '\0') {
199+
free(fill_param);
200+
ksft_exit_skip("Unable to parse benchmark memflush parameter.\n");
201+
}
202+
} else {
203+
fill_param->memflush = true;
204+
}
205+
206+
if (uparams->benchmark_cmd[3] && *uparams->benchmark_cmd[3] != '\0') {
207+
if (strcmp(uparams->benchmark_cmd[3], "0")) {
208+
free(fill_param);
209+
ksft_exit_skip("Only read operations supported.\n");
210+
}
211+
}
212+
213+
if (uparams->benchmark_cmd[4] && *uparams->benchmark_cmd[4] != '\0') {
214+
if (strcmp(uparams->benchmark_cmd[4], "false")) {
215+
free(fill_param);
216+
ksft_exit_skip("fill_buf is required to run until termination.\n");
217+
}
218+
}
219+
220+
return fill_param;
221+
}
222+
151223
static void init_user_params(struct user_params *uparams)
152224
{
153225
memset(uparams, 0, sizeof(*uparams));
@@ -158,11 +230,11 @@ static void init_user_params(struct user_params *uparams)
158230

159231
int main(int argc, char **argv)
160232
{
233+
struct fill_buf_param *fill_param = NULL;
161234
int tests = ARRAY_SIZE(resctrl_tests);
162235
bool test_param_seen = false;
163236
struct user_params uparams;
164-
char *span_str = NULL;
165-
int ret, c, i;
237+
int c, i;
166238

167239
init_user_params(&uparams);
168240

@@ -239,6 +311,10 @@ int main(int argc, char **argv)
239311
}
240312
last_arg:
241313

314+
fill_param = alloc_fill_buf_param(&uparams);
315+
if (fill_param)
316+
uparams.fill_buf = fill_param;
317+
242318
ksft_print_header();
243319

244320
/*
@@ -257,32 +333,11 @@ int main(int argc, char **argv)
257333

258334
filter_dmesg();
259335

260-
if (!uparams.benchmark_cmd[0]) {
261-
/* If no benchmark is given by "-b" argument, use fill_buf. */
262-
uparams.benchmark_cmd[0] = "fill_buf";
263-
ret = asprintf(&span_str, "%u", DEFAULT_SPAN);
264-
if (ret < 0)
265-
ksft_exit_fail_msg("Out of memory!\n");
266-
uparams.benchmark_cmd[1] = span_str;
267-
uparams.benchmark_cmd[2] = "1";
268-
/*
269-
* Third parameter was previously used for "operation"
270-
* (read/write) of which only (now default) "read"/"0"
271-
* works.
272-
* Fourth parameter was previously used to indicate
273-
* how long "fill_buf" should run for, with "false"
274-
* ("fill_buf" will keep running until terminated)
275-
* the only option that works.
276-
*/
277-
uparams.benchmark_cmd[3] = NULL;
278-
uparams.benchmark_cmd[4] = NULL;
279-
}
280-
281336
ksft_set_plan(tests);
282337

283338
for (i = 0; i < ARRAY_SIZE(resctrl_tests); i++)
284339
run_single_test(resctrl_tests[i], &uparams);
285340

286-
free(span_str);
341+
free(fill_param);
287342
ksft_finished();
288343
}

0 commit comments

Comments
 (0)