1 /*
2 * Copyright (C) 2013-2021 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 * This code is a complete clean re-write of the stress tool by
19 * Colin Ian King <colin.king@canonical.com> and attempts to be
20 * backwardly compatible with the stress tool by Amos Waterland
21 * <apw@rossby.metr.ou.edu> but has more stress tests and more
22 * functionality.
23 *
24 */
25 #include "stress-ng.h"
26
27 #if defined(HAVE_LIB_BSD) && \
28 defined(__linux__)
29
30 #define MAX_MOUNTS (256)
31 #if !defined(DEBUGFS_MAGIC)
32 #define DEBUGFS_MAGIC (0x64626720)
33 #endif
34
35 struct rb_node {
36 RB_ENTRY(rb_node) rb; /* red/black node entry */
37 char *func_name; /* ftrace'd kernel function name */
38 int64_t start_count; /* start number of calls to func */
39 int64_t end_count; /* end number of calls to func */
40 double start_time_us; /* start time used by func in microsecs */
41 double end_time_us; /* end time used by func microsecs */
42 };
43
44 static bool tracing_enabled;
45
46 /*
47 * rb_node_cmp()
48 * used for sorting functions by name
49 */
rb_node_cmp(struct rb_node * n1,struct rb_node * n2)50 static int rb_node_cmp(struct rb_node *n1, struct rb_node *n2)
51 {
52 return strcmp(n1->func_name, n2->func_name);
53 }
54
55 static RB_HEAD(rb_tree, rb_node) rb_root;
56 RB_PROTOTYPE(rb_tree, rb_node, rb, rb_node_cmp);
57 RB_GENERATE(rb_tree, rb_node, rb, rb_node_cmp);
58
59 /*
60 * stress_ftrace_get_debugfs_path()
61 * find debugfs mount path, returns NULL if not found
62 */
stress_ftrace_get_debugfs_path(void)63 static char *stress_ftrace_get_debugfs_path(void)
64 {
65 int i, n;
66 char *mnts[MAX_MOUNTS];
67 static char debugfs_path[1024];
68
69 /* Cached copy */
70 if (*debugfs_path)
71 return debugfs_path;
72
73 *debugfs_path = '\0';
74 n = stress_mount_get(mnts, MAX_MOUNTS);
75 for (i = 0; i < n; i++) {
76 struct statfs buf;
77
78 (void)memset(&buf, 0, sizeof(buf));
79 if (statfs(mnts[i], &buf) < 0)
80 continue;
81 if (buf.f_type == DEBUGFS_MAGIC) {
82 shim_strlcpy(debugfs_path, mnts[i], sizeof(debugfs_path));
83 stress_mount_free(mnts, n);
84 return debugfs_path;
85 }
86 }
87 stress_mount_free(mnts, n);
88
89 return NULL;
90 }
91
92 /*
93 * stress_ftrace_free()
94 * free up rb tree
95 */
stress_ftrace_free(void)96 void stress_ftrace_free(void)
97 {
98 struct rb_node *tn, *next;
99
100 if (!(g_opt_flags & OPT_FLAGS_FTRACE))
101 return;
102
103 for (tn = RB_MIN(rb_tree, &rb_root); tn; tn = next) {
104 free(tn->func_name);
105 next = RB_NEXT(rb_tree, &rb_root, tn);
106 RB_REMOVE(rb_tree, &rb_root, tn);
107 free(tn);
108 }
109 RB_INIT(&rb_root);
110 }
111
112 /*
113 * stress_ftrace_parse_trace_stat_file()
114 * parse the ftrace files for function timing stats
115 */
stress_ftrace_parse_trace_stat_file(const char * path,const bool start)116 static int stress_ftrace_parse_trace_stat_file(const char *path, const bool start)
117 {
118 FILE *fp;
119 char buffer[4096];
120
121 fp = fopen(path, "r");
122 if (!fp)
123 return 0;
124
125 while (fgets(buffer, sizeof(buffer), fp) != NULL) {
126 struct rb_node *tn, node;
127 char *ptr, *func_name, *num = "0";
128 int64_t count;
129 double time_us;
130
131 if (strstr(buffer, "Function"))
132 continue;
133 if (strstr(buffer, "----"))
134 continue;
135
136 /*
137 * Skip over leading spaces and find function name
138 */
139 for (ptr = buffer; *ptr && isspace(*ptr); ptr++)
140 ;
141 if (!*ptr)
142 continue;
143 func_name = ptr;
144
145 /*
146 * Skip over leading spaces and find hit count
147 */
148 for (; *ptr && !isspace(*ptr); ptr++)
149 ;
150 if (!*ptr)
151 continue;
152 *ptr++ = '\0';
153 for (; *ptr && isspace(*ptr); ptr++)
154 ;
155 num = ptr;
156 for (; *ptr && !isspace(*ptr); ptr++)
157 ;
158 if (!*ptr)
159 continue;
160 *ptr++ = '\0';
161 count = (int64_t)atoll(num);
162
163 /*
164 * Skip over leading spaces and find time consumed
165 */
166 for (; *ptr && isspace(*ptr); ptr++)
167 ;
168 if (!*ptr)
169 continue;
170 if (sscanf(ptr, "%lf", &time_us) != 1)
171 time_us = 0.0;
172
173 node.func_name = func_name;
174
175 tn = RB_FIND(rb_tree, &rb_root, &node);
176 if (tn) {
177 if (start) {
178 tn->start_count += count;
179 tn->start_time_us += time_us;
180 } else {
181 tn->end_count += count;
182 tn->end_time_us += time_us;
183 }
184 } else {
185 tn = malloc(sizeof(*tn));
186 if (!tn)
187 goto memory_fail;
188 tn->func_name = strdup(func_name);
189 if (!tn->func_name) {
190 free(tn);
191 goto memory_fail;
192 }
193 tn->start_count = 0;
194 tn->end_count = 0;
195 tn->start_time_us = 0.0;
196 tn->end_time_us = 0.0;
197
198 if (start) {
199 tn->start_count = count;
200 tn->start_time_us = time_us;
201 } else {
202 tn->end_count = count;
203 tn->end_time_us = time_us;
204 }
205 RB_INSERT(rb_tree, &rb_root, tn);
206 }
207 }
208 (void)fclose(fp);
209 return 0;
210
211 memory_fail:
212 (void)fclose(fp);
213 pr_inf("ftrace: disabled, out of memory collecting function information\n");
214 stress_ftrace_free();
215 return -1;
216 }
217
218 /*
219 * stress_ftrace_parse_stat_files()
220 * read trace stat files and parse the data into the rb tree
221 */
stress_ftrace_parse_stat_files(const char * path,const bool start)222 static int stress_ftrace_parse_stat_files(const char *path, const bool start)
223 {
224 DIR *dp;
225 struct dirent *de;
226 char filename[PATH_MAX];
227
228 (void)snprintf(filename, sizeof(filename), "%s/tracing/trace_stat", path);
229 dp = opendir(filename);
230 if (!dp)
231 return -1;
232 while ((de = readdir(dp)) != NULL) {
233 if (strncmp(de->d_name, "function", 8) == 0) {
234 char funcfile[PATH_MAX];
235
236 (void)snprintf(funcfile, sizeof(funcfile),
237 "%s/tracing/trace_stat/%s", path, de->d_name);
238 stress_ftrace_parse_trace_stat_file(funcfile, start);
239 }
240 }
241 (void)closedir(dp);
242
243 return 0;
244 }
245
246 /*
247 * stress_ftrace_add_pid()
248 * enable/append/stop tracing on specific events.
249 * if pid < 0 then tracing pids are all removed otherwise
250 * the pid is added to the tracing events
251 */
stress_ftrace_add_pid(const pid_t pid)252 void stress_ftrace_add_pid(const pid_t pid)
253 {
254 char filename[PATH_MAX];
255 char *path;
256 char buffer[32];
257 int fd;
258 ssize_t ret;
259
260 if (!(g_opt_flags & OPT_FLAGS_FTRACE))
261 return;
262
263 path = stress_ftrace_get_debugfs_path();
264 if (!path)
265 return;
266
267 (void)snprintf(filename, sizeof(filename), "%s/tracing/set_ftrace_pid", path);
268 fd = open(filename, O_WRONLY | (pid < 0 ? O_TRUNC : O_APPEND));
269 if (fd < 0)
270 return;
271 if (pid == -1) {
272 strcpy(buffer, " ");
273 } else {
274 snprintf(buffer, sizeof(buffer), "%d", (int)pid);
275 }
276 ret = write(fd, buffer, strlen(buffer));
277 (void)ret;
278 (void)close(fd);
279 }
280
281 /*
282 * stress_ftrace_start()
283 * start ftracing function calls
284 */
stress_ftrace_start(void)285 int stress_ftrace_start(void)
286 {
287 char *path, filename[PATH_MAX];
288
289 if (!(g_opt_flags & OPT_FLAGS_FTRACE))
290 return 0;
291
292 RB_INIT(&rb_root);
293
294 if (!stress_check_capability(SHIM_CAP_SYS_ADMIN)) {
295 pr_inf("ftrace: requires CAP_SYS_ADMIN capability for tracing\n");
296 return -1;
297 }
298
299 path = stress_ftrace_get_debugfs_path();
300 if (!path) {
301 pr_inf("ftrace: cannot find a mounted debugfs\n");
302 return -1;
303 }
304
305 (void)snprintf(filename, sizeof(filename), "%s/tracing/function_profile_enabled", path);
306 if (system_write(filename, "0", 1) < 0) {
307 pr_inf("ftrace: cannot enable function profiling, errno=%d (%s)\n",
308 errno, strerror(errno));
309 return -1;
310 }
311 stress_ftrace_add_pid(-1);
312 stress_ftrace_add_pid(getpid());
313 (void)snprintf(filename, sizeof(filename), "%s/tracing/function_profile_enabled", path);
314 if (system_write(filename, "1", 1) < 0) {
315 pr_inf("ftrace: cannot enable function profiling, errno=%d (%s)\n",
316 errno, strerror(errno));
317 return -1;
318 }
319 if (stress_ftrace_parse_stat_files(path, true) < 0)
320 return -1;
321
322 tracing_enabled = true;
323
324 return 0;
325 }
326
327 /*
328 * strace_ftrace_is_syscall()
329 * return true if function name looks like a system call
330 */
strace_ftrace_is_syscall(const char * func_name)331 static inline bool strace_ftrace_is_syscall(const char *func_name)
332 {
333 if (*func_name == '_' &&
334 strstr(func_name, "_sys_") &&
335 !strstr(func_name, "do_sys") &&
336 strncmp(func_name, "___", 3))
337 return true;
338
339 return false;
340 }
341
342 /*
343 * stress_ftrace_start()
344 * start ftracing function calls
345 */
stress_ftrace_analyze(void)346 static void stress_ftrace_analyze(void)
347 {
348 struct rb_node *tn, *next;
349 uint64_t sys_calls = 0, func_calls = 0;
350
351 pr_inf("ftrace: %-30.30s %15.15s %20.20s\n", "System Call", "Number of Calls", "Total Time (us)");
352
353 for (tn = RB_MIN(rb_tree, &rb_root); tn; tn = next) {
354 int64_t count = tn->end_count - tn->start_count;
355 if (count > 0) {
356 func_calls++;
357 if (strace_ftrace_is_syscall(tn->func_name)) {
358 double time_us = tn->end_time_us -
359 tn->start_time_us;
360
361 pr_inf("ftrace: %-30.30s %15" PRIu64 " %20.2f\n", tn->func_name, count, time_us);
362 sys_calls++;
363 }
364 }
365
366 next = RB_NEXT(rb_tree, &rb_root, tn);
367 }
368 pr_inf("ftrace: %" PRIu64 " kernel functions called, %" PRIu64 " were system calls\n",
369 func_calls, sys_calls);
370 }
371
372 /*
373 * stress_ftrace_start()
374 * stop ftracing function calls and analyze the collected
375 * stats
376 */
stress_ftrace_stop(void)377 void stress_ftrace_stop(void)
378 {
379 char *path, filename[PATH_MAX];
380
381 if (!(g_opt_flags & OPT_FLAGS_FTRACE))
382 return;
383
384 if (!tracing_enabled)
385 return;
386
387 path = stress_ftrace_get_debugfs_path();
388 if (!path)
389 return;
390
391 stress_ftrace_add_pid(-1);
392 (void)snprintf(filename, sizeof(filename), "%s/tracing/function_profile_enabled", path);
393 if (system_write(filename, "0", 1) < 0) {
394 pr_inf("ftrace: cannot disable function profiling, errno=%d (%s)\n",
395 errno, strerror(errno));
396 return;
397 }
398
399 (void)snprintf(filename, sizeof(filename), "%s/tracing/trace_stat", path);
400 if (stress_ftrace_parse_stat_files(path, false) < 0)
401 return;
402 stress_ftrace_analyze();
403 }
404
405 #else
stress_ftrace_add_pid(const pid_t pid)406 void stress_ftrace_add_pid(const pid_t pid)
407 {
408 (void)pid;
409 }
410
stress_ftrace_free(void)411 void stress_ftrace_free(void)
412 {
413 }
414
stress_ftrace_start(void)415 int stress_ftrace_start(void)
416 {
417 if (!(g_opt_flags & OPT_FLAGS_FTRACE))
418 return 0;
419 pr_inf("ftrace: this option is not implemented on this system: %s %s\n",
420 stress_get_uname_info(), stress_get_compiler());
421
422 return 0;
423 }
424
stress_ftrace_stop(void)425 void stress_ftrace_stop(void)
426 {
427 }
428 #endif
429