1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
4 */
5
6 #define _GNU_SOURCE
7 #include <dirent.h>
8 #include <stdarg.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <unistd.h>
12 #include <ctype.h>
13 #include <errno.h>
14 #include <fcntl.h>
15 #include <sched.h>
16 #include <stdio.h>
17
18 #include "utils.h"
19
20 #define MAX_MSG_LENGTH 1024
21 int config_debug;
22
23 /*
24 * err_msg - print an error message to the stderr
25 */
err_msg(const char * fmt,...)26 void err_msg(const char *fmt, ...)
27 {
28 char message[MAX_MSG_LENGTH];
29 va_list ap;
30
31 va_start(ap, fmt);
32 vsnprintf(message, sizeof(message), fmt, ap);
33 va_end(ap);
34
35 fprintf(stderr, "%s", message);
36 }
37
38 /*
39 * debug_msg - print a debug message to stderr if debug is set
40 */
debug_msg(const char * fmt,...)41 void debug_msg(const char *fmt, ...)
42 {
43 char message[MAX_MSG_LENGTH];
44 va_list ap;
45
46 if (!config_debug)
47 return;
48
49 va_start(ap, fmt);
50 vsnprintf(message, sizeof(message), fmt, ap);
51 va_end(ap);
52
53 fprintf(stderr, "%s", message);
54 }
55
56 /*
57 * get_llong_from_str - get a long long int from a string
58 */
get_llong_from_str(char * start)59 long long get_llong_from_str(char *start)
60 {
61 long long value;
62 char *end;
63
64 errno = 0;
65 value = strtoll(start, &end, 10);
66 if (errno || start == end)
67 return -1;
68
69 return value;
70 }
71
72 /*
73 * get_duration - fill output with a human readable duration since start_time
74 */
get_duration(time_t start_time,char * output,int output_size)75 void get_duration(time_t start_time, char *output, int output_size)
76 {
77 time_t now = time(NULL);
78 struct tm *tm_info;
79 time_t duration;
80
81 duration = difftime(now, start_time);
82 tm_info = gmtime(&duration);
83
84 snprintf(output, output_size, "%3d %02d:%02d:%02d",
85 tm_info->tm_yday,
86 tm_info->tm_hour,
87 tm_info->tm_min,
88 tm_info->tm_sec);
89 }
90
91 /*
92 * parse_cpu_set - parse a cpu_list filling cpu_set_t argument
93 *
94 * Receives a cpu list, like 1-3,5 (cpus 1, 2, 3, 5), and then set
95 * filling cpu_set_t argument.
96 *
97 * Returns 1 on success, 0 otherwise.
98 */
parse_cpu_set(char * cpu_list,cpu_set_t * set)99 int parse_cpu_set(char *cpu_list, cpu_set_t *set)
100 {
101 const char *p;
102 int end_cpu;
103 int nr_cpus;
104 int cpu;
105 int i;
106
107 CPU_ZERO(set);
108
109 nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
110
111 for (p = cpu_list; *p; ) {
112 cpu = atoi(p);
113 if (cpu < 0 || (!cpu && *p != '0') || cpu >= nr_cpus)
114 goto err;
115
116 while (isdigit(*p))
117 p++;
118 if (*p == '-') {
119 p++;
120 end_cpu = atoi(p);
121 if (end_cpu < cpu || (!end_cpu && *p != '0') || end_cpu >= nr_cpus)
122 goto err;
123 while (isdigit(*p))
124 p++;
125 } else
126 end_cpu = cpu;
127
128 if (cpu == end_cpu) {
129 debug_msg("cpu_set: adding cpu %d\n", cpu);
130 CPU_SET(cpu, set);
131 } else {
132 for (i = cpu; i <= end_cpu; i++) {
133 debug_msg("cpu_set: adding cpu %d\n", i);
134 CPU_SET(i, set);
135 }
136 }
137
138 if (*p == ',')
139 p++;
140 }
141
142 return 0;
143 err:
144 debug_msg("Error parsing the cpu set %s\n", cpu_list);
145 return 1;
146 }
147
148 /*
149 * parse_duration - parse duration with s/m/h/d suffix converting it to seconds
150 */
parse_seconds_duration(char * val)151 long parse_seconds_duration(char *val)
152 {
153 char *end;
154 long t;
155
156 t = strtol(val, &end, 10);
157
158 if (end) {
159 switch (*end) {
160 case 's':
161 case 'S':
162 break;
163 case 'm':
164 case 'M':
165 t *= 60;
166 break;
167 case 'h':
168 case 'H':
169 t *= 60 * 60;
170 break;
171
172 case 'd':
173 case 'D':
174 t *= 24 * 60 * 60;
175 break;
176 }
177 }
178
179 return t;
180 }
181
182 /*
183 * parse_ns_duration - parse duration with ns/us/ms/s converting it to nanoseconds
184 */
parse_ns_duration(char * val)185 long parse_ns_duration(char *val)
186 {
187 char *end;
188 long t;
189
190 t = strtol(val, &end, 10);
191
192 if (end) {
193 if (!strncmp(end, "ns", 2)) {
194 return t;
195 } else if (!strncmp(end, "us", 2)) {
196 t *= 1000;
197 return t;
198 } else if (!strncmp(end, "ms", 2)) {
199 t *= 1000 * 1000;
200 return t;
201 } else if (!strncmp(end, "s", 1)) {
202 t *= 1000 * 1000 * 1000;
203 return t;
204 }
205 return -1;
206 }
207
208 return t;
209 }
210
211 /*
212 * This is a set of helper functions to use SCHED_DEADLINE.
213 */
214 #ifdef __x86_64__
215 # define __NR_sched_setattr 314
216 # define __NR_sched_getattr 315
217 #elif __i386__
218 # define __NR_sched_setattr 351
219 # define __NR_sched_getattr 352
220 #elif __arm__
221 # define __NR_sched_setattr 380
222 # define __NR_sched_getattr 381
223 #elif __aarch64__ || __riscv
224 # define __NR_sched_setattr 274
225 # define __NR_sched_getattr 275
226 #elif __powerpc__
227 # define __NR_sched_setattr 355
228 # define __NR_sched_getattr 356
229 #elif __s390x__
230 # define __NR_sched_setattr 345
231 # define __NR_sched_getattr 346
232 #endif
233
234 #define SCHED_DEADLINE 6
235
sched_setattr(pid_t pid,const struct sched_attr * attr,unsigned int flags)236 static inline int sched_setattr(pid_t pid, const struct sched_attr *attr,
237 unsigned int flags) {
238 return syscall(__NR_sched_setattr, pid, attr, flags);
239 }
240
sched_getattr(pid_t pid,struct sched_attr * attr,unsigned int size,unsigned int flags)241 static inline int sched_getattr(pid_t pid, struct sched_attr *attr,
242 unsigned int size, unsigned int flags)
243 {
244 return syscall(__NR_sched_getattr, pid, attr, size, flags);
245 }
246
__set_sched_attr(int pid,struct sched_attr * attr)247 int __set_sched_attr(int pid, struct sched_attr *attr)
248 {
249 int flags = 0;
250 int retval;
251
252 retval = sched_setattr(pid, attr, flags);
253 if (retval < 0) {
254 err_msg("Failed to set sched attributes to the pid %d: %s\n",
255 pid, strerror(errno));
256 return 1;
257 }
258
259 return 0;
260 }
261
262 /*
263 * procfs_is_workload_pid - check if a procfs entry contains a comm_prefix* comm
264 *
265 * Check if the procfs entry is a directory of a process, and then check if the
266 * process has a comm with the prefix set in char *comm_prefix. As the
267 * current users of this function only check for kernel threads, there is no
268 * need to check for the threads for the process.
269 *
270 * Return: True if the proc_entry contains a comm file with comm_prefix*.
271 * Otherwise returns false.
272 */
procfs_is_workload_pid(const char * comm_prefix,struct dirent * proc_entry)273 static int procfs_is_workload_pid(const char *comm_prefix, struct dirent *proc_entry)
274 {
275 char buffer[MAX_PATH];
276 int comm_fd, retval;
277 char *t_name;
278
279 if (proc_entry->d_type != DT_DIR)
280 return 0;
281
282 if (*proc_entry->d_name == '.')
283 return 0;
284
285 /* check if the string is a pid */
286 for (t_name = proc_entry->d_name; t_name; t_name++) {
287 if (!isdigit(*t_name))
288 break;
289 }
290
291 if (*t_name != '\0')
292 return 0;
293
294 snprintf(buffer, MAX_PATH, "/proc/%s/comm", proc_entry->d_name);
295 comm_fd = open(buffer, O_RDONLY);
296 if (comm_fd < 0)
297 return 0;
298
299 memset(buffer, 0, MAX_PATH);
300 retval = read(comm_fd, buffer, MAX_PATH);
301
302 close(comm_fd);
303
304 if (retval <= 0)
305 return 0;
306
307 retval = strncmp(comm_prefix, buffer, strlen(comm_prefix));
308 if (retval)
309 return 0;
310
311 /* comm already have \n */
312 debug_msg("Found workload pid:%s comm:%s", proc_entry->d_name, buffer);
313
314 return 1;
315 }
316
317 /*
318 * set_comm_sched_attr - set sched params to threads starting with char *comm_prefix
319 *
320 * This function uses procfs to list the currently running threads and then set the
321 * sched_attr *attr to the threads that start with char *comm_prefix. It is
322 * mainly used to set the priority to the kernel threads created by the
323 * tracers.
324 */
set_comm_sched_attr(const char * comm_prefix,struct sched_attr * attr)325 int set_comm_sched_attr(const char *comm_prefix, struct sched_attr *attr)
326 {
327 struct dirent *proc_entry;
328 DIR *procfs;
329 int retval;
330
331 if (strlen(comm_prefix) >= MAX_PATH) {
332 err_msg("Command prefix is too long: %d < strlen(%s)\n",
333 MAX_PATH, comm_prefix);
334 return 1;
335 }
336
337 procfs = opendir("/proc");
338 if (!procfs) {
339 err_msg("Could not open procfs\n");
340 return 1;
341 }
342
343 while ((proc_entry = readdir(procfs))) {
344
345 retval = procfs_is_workload_pid(comm_prefix, proc_entry);
346 if (!retval)
347 continue;
348
349 /* procfs_is_workload_pid confirmed it is a pid */
350 retval = __set_sched_attr(atoi(proc_entry->d_name), attr);
351 if (retval) {
352 err_msg("Error setting sched attributes for pid:%s\n", proc_entry->d_name);
353 goto out_err;
354 }
355
356 debug_msg("Set sched attributes for pid:%s\n", proc_entry->d_name);
357 }
358 return 0;
359
360 out_err:
361 closedir(procfs);
362 return 1;
363 }
364
365 #define INVALID_VAL (~0L)
get_long_ns_after_colon(char * start)366 static long get_long_ns_after_colon(char *start)
367 {
368 long val = INVALID_VAL;
369
370 /* find the ":" */
371 start = strstr(start, ":");
372 if (!start)
373 return -1;
374
375 /* skip ":" */
376 start++;
377 val = parse_ns_duration(start);
378
379 return val;
380 }
381
get_long_after_colon(char * start)382 static long get_long_after_colon(char *start)
383 {
384 long val = INVALID_VAL;
385
386 /* find the ":" */
387 start = strstr(start, ":");
388 if (!start)
389 return -1;
390
391 /* skip ":" */
392 start++;
393 val = get_llong_from_str(start);
394
395 return val;
396 }
397
398 /*
399 * parse priority in the format:
400 * SCHED_OTHER:
401 * o:<prio>
402 * O:<prio>
403 * SCHED_RR:
404 * r:<prio>
405 * R:<prio>
406 * SCHED_FIFO:
407 * f:<prio>
408 * F:<prio>
409 * SCHED_DEADLINE:
410 * d:runtime:period
411 * D:runtime:period
412 */
parse_prio(char * arg,struct sched_attr * sched_param)413 int parse_prio(char *arg, struct sched_attr *sched_param)
414 {
415 long prio;
416 long runtime;
417 long period;
418
419 memset(sched_param, 0, sizeof(*sched_param));
420 sched_param->size = sizeof(*sched_param);
421
422 switch (arg[0]) {
423 case 'd':
424 case 'D':
425 /* d:runtime:period */
426 if (strlen(arg) < 4)
427 return -1;
428
429 runtime = get_long_ns_after_colon(arg);
430 if (runtime == INVALID_VAL)
431 return -1;
432
433 period = get_long_ns_after_colon(&arg[2]);
434 if (period == INVALID_VAL)
435 return -1;
436
437 if (runtime > period)
438 return -1;
439
440 sched_param->sched_policy = SCHED_DEADLINE;
441 sched_param->sched_runtime = runtime;
442 sched_param->sched_deadline = period;
443 sched_param->sched_period = period;
444 break;
445 case 'f':
446 case 'F':
447 /* f:prio */
448 prio = get_long_after_colon(arg);
449 if (prio == INVALID_VAL)
450 return -1;
451
452 if (prio < sched_get_priority_min(SCHED_FIFO))
453 return -1;
454 if (prio > sched_get_priority_max(SCHED_FIFO))
455 return -1;
456
457 sched_param->sched_policy = SCHED_FIFO;
458 sched_param->sched_priority = prio;
459 break;
460 case 'r':
461 case 'R':
462 /* r:prio */
463 prio = get_long_after_colon(arg);
464 if (prio == INVALID_VAL)
465 return -1;
466
467 if (prio < sched_get_priority_min(SCHED_RR))
468 return -1;
469 if (prio > sched_get_priority_max(SCHED_RR))
470 return -1;
471
472 sched_param->sched_policy = SCHED_RR;
473 sched_param->sched_priority = prio;
474 break;
475 case 'o':
476 case 'O':
477 /* o:prio */
478 prio = get_long_after_colon(arg);
479 if (prio == INVALID_VAL)
480 return -1;
481
482 if (prio < sched_get_priority_min(SCHED_OTHER))
483 return -1;
484 if (prio > sched_get_priority_max(SCHED_OTHER))
485 return -1;
486
487 sched_param->sched_policy = SCHED_OTHER;
488 sched_param->sched_priority = prio;
489 break;
490 default:
491 return -1;
492 }
493 return 0;
494 }
495
496 /*
497 * set_cpu_dma_latency - set the /dev/cpu_dma_latecy
498 *
499 * This is used to reduce the exit from idle latency. The value
500 * will be reset once the file descriptor of /dev/cpu_dma_latecy
501 * is closed.
502 *
503 * Return: the /dev/cpu_dma_latecy file descriptor
504 */
set_cpu_dma_latency(int32_t latency)505 int set_cpu_dma_latency(int32_t latency)
506 {
507 int retval;
508 int fd;
509
510 fd = open("/dev/cpu_dma_latency", O_RDWR);
511 if (fd < 0) {
512 err_msg("Error opening /dev/cpu_dma_latency\n");
513 return -1;
514 }
515
516 retval = write(fd, &latency, 4);
517 if (retval < 1) {
518 err_msg("Error setting /dev/cpu_dma_latency\n");
519 close(fd);
520 return -1;
521 }
522
523 debug_msg("Set /dev/cpu_dma_latency to %d\n", latency);
524
525 return fd;
526 }
527
528 #define _STR(x) #x
529 #define STR(x) _STR(x)
530
531 /*
532 * find_mount - find a the mount point of a given fs
533 *
534 * Returns 0 if mount is not found, otherwise return 1 and fill mp
535 * with the mount point.
536 */
find_mount(const char * fs,char * mp,int sizeof_mp)537 static const int find_mount(const char *fs, char *mp, int sizeof_mp)
538 {
539 char mount_point[MAX_PATH];
540 char type[100];
541 int found;
542 FILE *fp;
543
544 fp = fopen("/proc/mounts", "r");
545 if (!fp)
546 return 0;
547
548 while (fscanf(fp, "%*s %" STR(MAX_PATH) "s %99s %*s %*d %*d\n", mount_point, type) == 2) {
549 if (strcmp(type, fs) == 0) {
550 found = 1;
551 break;
552 }
553 }
554 fclose(fp);
555
556 if (!found)
557 return 0;
558
559 memset(mp, 0, sizeof_mp);
560 strncpy(mp, mount_point, sizeof_mp - 1);
561
562 debug_msg("Fs %s found at %s\n", fs, mp);
563 return 1;
564 }
565
566 /*
567 * get_self_cgroup - get the current thread cgroup path
568 *
569 * Parse /proc/$$/cgroup file to get the thread's cgroup. As an example of line to parse:
570 *
571 * 0::/user.slice/user-0.slice/session-3.scope'\n'
572 *
573 * This function is interested in the content after the second : and before the '\n'.
574 *
575 * Returns 1 if a string was found, 0 otherwise.
576 */
get_self_cgroup(char * self_cg,int sizeof_self_cg)577 static int get_self_cgroup(char *self_cg, int sizeof_self_cg)
578 {
579 char path[MAX_PATH], *start;
580 int fd, retval;
581
582 snprintf(path, MAX_PATH, "/proc/%d/cgroup", getpid());
583
584 fd = open(path, O_RDONLY);
585 if (fd < 0)
586 return 0;
587
588 retval = read(fd, path, MAX_PATH);
589
590 close(fd);
591
592 if (retval <= 0)
593 return 0;
594
595 start = path;
596
597 start = strstr(start, ":");
598 if (!start)
599 return 0;
600
601 /* skip ":" */
602 start++;
603
604 start = strstr(start, ":");
605 if (!start)
606 return 0;
607
608 /* skip ":" */
609 start++;
610
611 if (strlen(start) >= sizeof_self_cg)
612 return 0;
613
614 snprintf(self_cg, sizeof_self_cg, "%s", start);
615
616 /* Swap '\n' with '\0' */
617 start = strstr(self_cg, "\n");
618
619 /* there must be '\n' */
620 if (!start)
621 return 0;
622
623 /* ok, it found a string after the second : and before the \n */
624 *start = '\0';
625
626 return 1;
627 }
628
629 /*
630 * set_comm_cgroup - Set cgroup to pid_t pid
631 *
632 * If cgroup argument is not NULL, the threads will move to the given cgroup.
633 * Otherwise, the cgroup of the calling, i.e., rtla, thread will be used.
634 *
635 * Supports cgroup v2.
636 *
637 * Returns 1 on success, 0 otherwise.
638 */
set_pid_cgroup(pid_t pid,const char * cgroup)639 int set_pid_cgroup(pid_t pid, const char *cgroup)
640 {
641 char cgroup_path[MAX_PATH - strlen("/cgroup.procs")];
642 char cgroup_procs[MAX_PATH];
643 char pid_str[24];
644 int retval;
645 int cg_fd;
646
647 retval = find_mount("cgroup2", cgroup_path, sizeof(cgroup_path));
648 if (!retval) {
649 err_msg("Did not find cgroupv2 mount point\n");
650 return 0;
651 }
652
653 if (!cgroup) {
654 retval = get_self_cgroup(&cgroup_path[strlen(cgroup_path)],
655 sizeof(cgroup_path) - strlen(cgroup_path));
656 if (!retval) {
657 err_msg("Did not find self cgroup\n");
658 return 0;
659 }
660 } else {
661 snprintf(&cgroup_path[strlen(cgroup_path)],
662 sizeof(cgroup_path) - strlen(cgroup_path), "%s/", cgroup);
663 }
664
665 snprintf(cgroup_procs, MAX_PATH, "%s/cgroup.procs", cgroup_path);
666
667 debug_msg("Using cgroup path at: %s\n", cgroup_procs);
668
669 cg_fd = open(cgroup_procs, O_RDWR);
670 if (cg_fd < 0)
671 return 0;
672
673 snprintf(pid_str, sizeof(pid_str), "%d\n", pid);
674
675 retval = write(cg_fd, pid_str, strlen(pid_str));
676 if (retval < 0)
677 err_msg("Error setting cgroup attributes for pid:%s - %s\n",
678 pid_str, strerror(errno));
679 else
680 debug_msg("Set cgroup attributes for pid:%s\n", pid_str);
681
682 close(cg_fd);
683
684 return (retval >= 0);
685 }
686
687 /**
688 * set_comm_cgroup - Set cgroup to threads starting with char *comm_prefix
689 *
690 * If cgroup argument is not NULL, the threads will move to the given cgroup.
691 * Otherwise, the cgroup of the calling, i.e., rtla, thread will be used.
692 *
693 * Supports cgroup v2.
694 *
695 * Returns 1 on success, 0 otherwise.
696 */
set_comm_cgroup(const char * comm_prefix,const char * cgroup)697 int set_comm_cgroup(const char *comm_prefix, const char *cgroup)
698 {
699 char cgroup_path[MAX_PATH - strlen("/cgroup.procs")];
700 char cgroup_procs[MAX_PATH];
701 struct dirent *proc_entry;
702 DIR *procfs;
703 int retval;
704 int cg_fd;
705
706 if (strlen(comm_prefix) >= MAX_PATH) {
707 err_msg("Command prefix is too long: %d < strlen(%s)\n",
708 MAX_PATH, comm_prefix);
709 return 0;
710 }
711
712 retval = find_mount("cgroup2", cgroup_path, sizeof(cgroup_path));
713 if (!retval) {
714 err_msg("Did not find cgroupv2 mount point\n");
715 return 0;
716 }
717
718 if (!cgroup) {
719 retval = get_self_cgroup(&cgroup_path[strlen(cgroup_path)],
720 sizeof(cgroup_path) - strlen(cgroup_path));
721 if (!retval) {
722 err_msg("Did not find self cgroup\n");
723 return 0;
724 }
725 } else {
726 snprintf(&cgroup_path[strlen(cgroup_path)],
727 sizeof(cgroup_path) - strlen(cgroup_path), "%s/", cgroup);
728 }
729
730 snprintf(cgroup_procs, MAX_PATH, "%s/cgroup.procs", cgroup_path);
731
732 debug_msg("Using cgroup path at: %s\n", cgroup_procs);
733
734 cg_fd = open(cgroup_procs, O_RDWR);
735 if (cg_fd < 0)
736 return 0;
737
738 procfs = opendir("/proc");
739 if (!procfs) {
740 err_msg("Could not open procfs\n");
741 goto out_cg;
742 }
743
744 while ((proc_entry = readdir(procfs))) {
745
746 retval = procfs_is_workload_pid(comm_prefix, proc_entry);
747 if (!retval)
748 continue;
749
750 retval = write(cg_fd, proc_entry->d_name, strlen(proc_entry->d_name));
751 if (retval < 0) {
752 err_msg("Error setting cgroup attributes for pid:%s - %s\n",
753 proc_entry->d_name, strerror(errno));
754 goto out_procfs;
755 }
756
757 debug_msg("Set cgroup attributes for pid:%s\n", proc_entry->d_name);
758 }
759
760 closedir(procfs);
761 close(cg_fd);
762 return 1;
763
764 out_procfs:
765 closedir(procfs);
766 out_cg:
767 close(cg_fd);
768 return 0;
769 }
770
771 /**
772 * auto_house_keeping - Automatically move rtla out of measurement threads
773 *
774 * Try to move rtla away from the tracer, if possible.
775 *
776 * Returns 1 on success, 0 otherwise.
777 */
auto_house_keeping(cpu_set_t * monitored_cpus)778 int auto_house_keeping(cpu_set_t *monitored_cpus)
779 {
780 cpu_set_t rtla_cpus, house_keeping_cpus;
781 int retval;
782
783 /* first get the CPUs in which rtla can actually run. */
784 retval = sched_getaffinity(getpid(), sizeof(rtla_cpus), &rtla_cpus);
785 if (retval == -1) {
786 debug_msg("Could not get rtla affinity, rtla might run with the threads!\n");
787 return 0;
788 }
789
790 /* then check if the existing setup is already good. */
791 CPU_AND(&house_keeping_cpus, &rtla_cpus, monitored_cpus);
792 if (!CPU_COUNT(&house_keeping_cpus)) {
793 debug_msg("rtla and the monitored CPUs do not share CPUs.");
794 debug_msg("Skipping auto house-keeping\n");
795 return 1;
796 }
797
798 /* remove the intersection */
799 CPU_XOR(&house_keeping_cpus, &rtla_cpus, monitored_cpus);
800
801 /* get only those that rtla can run */
802 CPU_AND(&house_keeping_cpus, &house_keeping_cpus, &rtla_cpus);
803
804 /* is there any cpu left? */
805 if (!CPU_COUNT(&house_keeping_cpus)) {
806 debug_msg("Could not find any CPU for auto house-keeping\n");
807 return 0;
808 }
809
810 retval = sched_setaffinity(getpid(), sizeof(house_keeping_cpus), &house_keeping_cpus);
811 if (retval == -1) {
812 debug_msg("Could not set affinity for auto house-keeping\n");
813 return 0;
814 }
815
816 debug_msg("rtla automatically moved to an auto house-keeping cpu set\n");
817
818 return 1;
819 }
820