diff options
Diffstat (limited to 'src/tracefs-instance.c')
-rw-r--r-- | src/tracefs-instance.c | 1241 |
1 files changed, 1241 insertions, 0 deletions
diff --git a/src/tracefs-instance.c b/src/tracefs-instance.c new file mode 100644 index 0000000..57f5c7f --- /dev/null +++ b/src/tracefs-instance.c @@ -0,0 +1,1241 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2008, 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + * Updates: + * Copyright (C) 2019, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <ctype.h> +#include <errno.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <dirent.h> +#include <regex.h> +#include <limits.h> +#include <pthread.h> +#include "tracefs.h" +#include "tracefs-local.h" + +enum { + FLAG_INSTANCE_NEWLY_CREATED = (1 << 0), + FLAG_INSTANCE_DELETED = (1 << 1), +}; + + +struct tracefs_options_mask toplevel_supported_opts; +struct tracefs_options_mask toplevel_enabled_opts; + +__hidden inline struct tracefs_options_mask * +supported_opts_mask(struct tracefs_instance *instance) +{ + return instance ? &instance->supported_opts : &toplevel_supported_opts; +} + +__hidden inline struct tracefs_options_mask * +enabled_opts_mask(struct tracefs_instance *instance) +{ + return instance ? &instance->enabled_opts : &toplevel_enabled_opts; +} + +/** + * instance_alloc - allocate a new ftrace instance + * @trace_dir - Full path to the tracing directory, where the instance is + * @name: The name of the instance (instance will point to this) + * + * Returns a newly allocated instance, or NULL in case of an error. + */ +static struct tracefs_instance *instance_alloc(const char *trace_dir, const char *name) +{ + struct tracefs_instance *instance; + + instance = calloc(1, sizeof(*instance)); + if (!instance) + goto error; + instance->trace_dir = strdup(trace_dir); + if (!instance->trace_dir) + goto error; + if (name) { + instance->name = strdup(name); + if (!instance->name) + goto error; + } + + if (pthread_mutex_init(&instance->lock, NULL) < 0) + goto error; + + instance->ftrace_filter_fd = -1; + instance->ftrace_notrace_fd = -1; + instance->ftrace_marker_fd = -1; + instance->ftrace_marker_raw_fd = -1; + + return instance; + +error: + if (instance) { + free(instance->name); + free(instance->trace_dir); + free(instance); + } + return NULL; +} + + +__hidden int trace_get_instance(struct tracefs_instance *instance) +{ + int ret; + + pthread_mutex_lock(&instance->lock); + if (instance->flags & FLAG_INSTANCE_DELETED) { + ret = -1; + } else { + instance->ref++; + ret = 0; + } + pthread_mutex_unlock(&instance->lock); + return ret; +} + +__hidden void trace_put_instance(struct tracefs_instance *instance) +{ + pthread_mutex_lock(&instance->lock); + if (--instance->ref < 0) + instance->flags |= FLAG_INSTANCE_DELETED; + pthread_mutex_unlock(&instance->lock); + + if (!(instance->flags & FLAG_INSTANCE_DELETED)) + return; + + if (instance->ftrace_filter_fd >= 0) + close(instance->ftrace_filter_fd); + + if (instance->ftrace_notrace_fd >= 0) + close(instance->ftrace_notrace_fd); + + if (instance->ftrace_marker_fd >= 0) + close(instance->ftrace_marker_fd); + + if (instance->ftrace_marker_raw_fd >= 0) + close(instance->ftrace_marker_raw_fd); + + free(instance->trace_dir); + free(instance->name); + pthread_mutex_destroy(&instance->lock); + free(instance); +} + +/** + * tracefs_instance_free - Free an instance, previously allocated by + tracefs_instance_create() + * @instance: Pointer to the instance to be freed + * + */ +void tracefs_instance_free(struct tracefs_instance *instance) +{ + if (!instance) + return; + + trace_put_instance(instance); +} + +static mode_t get_trace_file_permissions(char *name) +{ + mode_t rmode = 0; + struct stat st; + char *path; + int ret; + + path = tracefs_get_tracing_file(name); + if (!path) + return 0; + ret = stat(path, &st); + if (ret) + goto out; + rmode = st.st_mode & ACCESSPERMS; +out: + tracefs_put_tracing_file(path); + return rmode; +} + +/** + * tracefs_instance_is_new - Check if the instance is newly created by the library + * @instance: Pointer to an ftrace instance + * + * Returns true, if the ftrace instance is newly created by the library or + * false otherwise. + */ +bool tracefs_instance_is_new(struct tracefs_instance *instance) +{ + if (instance && (instance->flags & FLAG_INSTANCE_NEWLY_CREATED)) + return true; + return false; +} + +/** + * tracefs_instance_create - Create a new ftrace instance + * @name: Name of the instance to be created + * + * Allocates and initializes a new instance structure. If the instance does not + * exist in the system, create it. + * Returns a pointer to a newly allocated instance, or NULL in case of an error. + * The returned instance must be freed by tracefs_instance_free(). + */ +struct tracefs_instance *tracefs_instance_create(const char *name) +{ + struct tracefs_instance *inst = NULL; + char *path = NULL; + const char *tdir; + struct stat st; + mode_t mode; + int ret; + + tdir = tracefs_tracing_dir(); + if (!tdir) + return NULL; + inst = instance_alloc(tdir, name); + if (!inst) + return NULL; + + path = tracefs_instance_get_dir(inst); + ret = stat(path, &st); + if (ret < 0) { + /* Cannot create the top instance, if it does not exist! */ + if (!name) + goto error; + mode = get_trace_file_permissions("instances"); + if (mkdir(path, mode)) + goto error; + inst->flags |= FLAG_INSTANCE_NEWLY_CREATED; + } + tracefs_put_tracing_file(path); + return inst; + +error: + tracefs_instance_free(inst); + return NULL; +} + +/** + * tracefs_instance_alloc - Allocate an instance structure for existing trace instance + * @tracing_dir: full path to the system trace directory, where the new instance is + * if NULL, the default top tracing directory is used. + * @name: Name of the instance. + * + * Allocates and initializes a new instance structure. If the instance does not + * exist, do not create it and exit with error. + * Returns a pointer to a newly allocated instance, or NULL in case of an error + * or the requested instance does not exists. + * The returned instance must be freed by tracefs_instance_free(). + */ +struct tracefs_instance *tracefs_instance_alloc(const char *tracing_dir, + const char *name) +{ + struct tracefs_instance *inst = NULL; + char file[PATH_MAX]; + const char *tdir; + struct stat st; + int ret; + + if (tracing_dir) { + ret = stat(tracing_dir, &st); + if (ret < 0 || !S_ISDIR(st.st_mode)) + return NULL; + tdir = tracing_dir; + + } else + tdir = tracefs_tracing_dir(); + if (!tdir) + return NULL; + + if (name) { + sprintf(file, "%s/instances/%s", tdir, name); + ret = stat(file, &st); + if (ret < 0 || !S_ISDIR(st.st_mode)) + return NULL; + } + inst = instance_alloc(tdir, name); + + return inst; +} + +/** + * tracefs_instance_destroy - Remove a ftrace instance + * @instance: Pointer to the instance to be removed + * + * Returns -1 in case of an error, or 0 otherwise. + */ +int tracefs_instance_destroy(struct tracefs_instance *instance) +{ + char *path; + int ret = -1; + + if (!instance || !instance->name) { + tracefs_warning("Cannot remove top instance"); + return -1; + } + + path = tracefs_instance_get_dir(instance); + if (path) + ret = rmdir(path); + tracefs_put_tracing_file(path); + if (ret) { + pthread_mutex_lock(&instance->lock); + instance->flags |= FLAG_INSTANCE_DELETED; + pthread_mutex_unlock(&instance->lock); + } + + return ret; +} + +/** + * tracefs_instance_get_file - return the path to an instance file. + * @instance: ftrace instance, can be NULL for the top instance + * @file: name of file to return + * + * Returns the path of the @file for the given @instance, or NULL in + * case of an error. + * + * Must use tracefs_put_tracing_file() to free the returned string. + */ +char * +tracefs_instance_get_file(struct tracefs_instance *instance, const char *file) +{ + char *path = NULL; + int ret; + + if (!instance) + return tracefs_get_tracing_file(file); + if (!instance->name) + ret = asprintf(&path, "%s/%s", instance->trace_dir, file); + else + ret = asprintf(&path, "%s/instances/%s/%s", + instance->trace_dir, instance->name, file); + if (ret < 0) + return NULL; + + return path; +} + +/** + * tracefs_instance_get_dir - return the path to the instance directory. + * @instance: ftrace instance, can be NULL for the top instance + * + * Returns the full path to the instance directory + * + * Must use tracefs_put_tracing_file() to free the returned string. + */ +char *tracefs_instance_get_dir(struct tracefs_instance *instance) +{ + char *path = NULL; + int ret; + + if (!instance) /* Top instance of default system trace directory */ + return trace_find_tracing_dir(false); + + if (!instance->name) + return strdup(instance->trace_dir); + + ret = asprintf(&path, "%s/instances/%s", instance->trace_dir, instance->name); + if (ret < 0) { + tracefs_warning("Failed to allocate path for instance %s", + instance->name); + return NULL; + } + + return path; +} + +/** + * tracefs_instance_get_name - return the name of an instance + * @instance: ftrace instance + * + * Returns the name of the given @instance. + * The returned string must *not* be freed. + */ +const char *tracefs_instance_get_name(struct tracefs_instance *instance) +{ + if (instance) + return instance->name; + return NULL; +} + +/** + * tracefs_instance_get_buffer_size - return the buffer size of the ring buffer + * @instance: The instance to get the buffer size from + * @cpu: if less that zero, will return the total size, otherwise the cpu size + * + * Returns the buffer size. If @cpu is less than zero, it returns the total size + * of the ring buffer otherwise it returs the size of the buffer for the given + * CPU. + * + * Returns -1 on error. + */ +ssize_t tracefs_instance_get_buffer_size(struct tracefs_instance *instance, int cpu) +{ + unsigned long long size; + char *path; + char *val; + int ret; + + if (cpu < 0) { + val = tracefs_instance_file_read(instance, "buffer_total_size_kb", NULL); + } else { + ret = asprintf(&path, "per_cpu/cpu%d/buffer_size_kb", cpu); + if (ret < 0) + return ret; + + val = tracefs_instance_file_read(instance, path, NULL); + free(path); + } + + if (!val) + return -1; + + size = strtoull(val, NULL, 0); + free(val); + return size; +} + +int tracefs_instance_set_buffer_size(struct tracefs_instance *instance, size_t size, int cpu) +{ + char *path; + char *val; + int ret; + + ret = asprintf(&val, "%zd", size); + if (ret < 0) + return ret; + + if (cpu < 0) { + ret = tracefs_instance_file_write(instance, "buffer_size_kb", val); + } else { + ret = asprintf(&path, "per_cpu/cpu%d/buffer_size_kb", cpu); + if (ret < 0) { + free(val); + return ret; + } + + ret = tracefs_instance_file_write(instance, path, val); + free(path); + } + free(val); + + return ret < 0 ? -1 : 0; +} + +/** + * tracefs_instance_get_trace_dir - return the top trace directory, where the instance is confuigred + * @instance: ftrace instance + * + * Returns the top trace directory where the given @instance is configured. + * The returned string must *not* be freed. + */ +const char *tracefs_instance_get_trace_dir(struct tracefs_instance *instance) +{ + if (instance) + return instance->trace_dir; + return NULL; +} + +static int write_file(const char *file, const char *str, int flags) +{ + int ret = 0; + int fd; + + fd = open(file, flags); + if (fd < 0) { + tracefs_warning("Failed to open '%s'", file); + return -1; + } + + if (str) + ret = write(fd, str, strlen(str)); + + close(fd); + return ret; +} + +static int instance_file_write(struct tracefs_instance *instance, + const char *file, const char *str, int flags) +{ + struct stat st; + char *path; + int ret; + + path = tracefs_instance_get_file(instance, file); + if (!path) + return -1; + ret = stat(path, &st); + if (ret == 0) + ret = write_file(path, str, flags); + tracefs_put_tracing_file(path); + + return ret; +} + +/** + * tracefs_instance_file_write - Write in trace file of specific instance. + * @instance: ftrace instance, can be NULL for the top instance + * @file: name of the file + * @str: nul terminated string, that will be written in the file. + * + * Returns the number of written bytes, or -1 in case of an error + */ +int tracefs_instance_file_write(struct tracefs_instance *instance, + const char *file, const char *str) +{ + return instance_file_write(instance, file, str, O_WRONLY | O_TRUNC); +} + +/** + * tracefs_instance_file_append - Append to a trace file of specific instance. + * @instance: ftrace instance, can be NULL for the top instance. + * @file: name of the file. + * @str: nul terminated string, that will be appended to the file. + * + * Returns the number of appended bytes, or -1 in case of an error. + */ +int tracefs_instance_file_append(struct tracefs_instance *instance, + const char *file, const char *str) +{ + return instance_file_write(instance, file, str, O_WRONLY); +} + +/** + * tracefs_instance_file_clear - Clear a trace file of specific instance. + * Note, it only opens with O_TRUNC and closes the file. If the file has + * content that does not get cleared in this way, this will not have any + * effect. For example, set_ftrace_filter can have probes that are not + * cleared by O_TRUNC: + * + * echo "schedule:stacktrace" > set_ftrace_filter + * + * This function will not clear the above "set_ftrace_filter" after that + * command. + * @instance: ftrace instance, can be NULL for the top instance. + * @file: name of the file to clear. + * + * Returns 0 on success, or -1 in case of an error. + */ +int tracefs_instance_file_clear(struct tracefs_instance *instance, + const char *file) +{ + return instance_file_write(instance, file, NULL, O_WRONLY | O_TRUNC); +} + +/** + * tracefs_instance_file_read - Read from a trace file of specific instance. + * @instance: ftrace instance, can be NULL for the top instance + * @file: name of the file + * @psize: returns the number of bytes read + * + * Returns a pointer to a nul terminated string, read from the file, or NULL in + * case of an error. + * The return string must be freed by free() + */ +char *tracefs_instance_file_read(struct tracefs_instance *instance, + const char *file, int *psize) +{ + char *buf = NULL; + int size = 0; + char *path; + + path = tracefs_instance_get_file(instance, file); + if (!path) + return NULL; + + size = str_read_file(path, &buf, true); + + tracefs_put_tracing_file(path); + if (buf && psize) + *psize = size; + + return buf; +} + +/** + * tracefs_instance_file_read_number - Read long long integer from a trace file. + * @instance: ftrace instance, can be NULL for the top instance + * @file: name of the file + * @res: The integer from the file. + * + * Returns 0 if the reading is successful and the result is stored in res, -1 + * in case of an error. + */ +int tracefs_instance_file_read_number(struct tracefs_instance *instance, + const char *file, long long *res) +{ + long long num; + int ret = -1; + int size = 0; + char *endptr; + char *str; + + str = tracefs_instance_file_read(instance, file, &size); + if (size && str) { + errno = 0; + num = strtoll(str, &endptr, 0); + if (errno == 0 && str != endptr) { + *res = num; + ret = 0; + } + } + free(str); + return ret; +} + +/** + * tracefs_instance_file_open - Open a trace file for reading and writing + * @instance: ftrace instance, can be NULL for the top instance + * @file: name of the file + * @mode: file open flags, -1 for default O_RDWR + * + * Returns -1 in case of an error, or a valid file descriptor otherwise. + * The returned FD must be closed with close() + */ +int tracefs_instance_file_open(struct tracefs_instance *instance, + const char *file, int mode) +{ + int flags = O_RDWR; + int fd = -1; + char *path; + + path = tracefs_instance_get_file(instance, file); + if (!path) + return -1; + + if (mode >= 0) + flags = mode; + fd = open(path, flags); + tracefs_put_tracing_file(path); + + return fd; +} + +static bool check_file_exists(struct tracefs_instance *instance, + const char *name, bool dir) +{ + char file[PATH_MAX]; + struct stat st; + char *path; + int ret; + + path = tracefs_instance_get_dir(instance); + if (name) + snprintf(file, PATH_MAX, "%s/%s", path, name); + else + snprintf(file, PATH_MAX, "%s", path); + tracefs_put_tracing_file(path); + ret = stat(file, &st); + if (ret < 0) + return false; + + return !dir == !S_ISDIR(st.st_mode); +} + +/** + * tracefs_instance_exists - Check an instance with given name exists + * @name: name of the instance + * + * Returns true if the instance exists, false otherwise + * + */ +bool tracefs_instance_exists(const char *name) +{ + char file[PATH_MAX]; + + if (!name) + return false; + snprintf(file, PATH_MAX, "instances/%s", name); + return check_file_exists(NULL, file, true); +} + +/** + * tracefs_file_exists - Check if a file with given name exists in given instance + * @instance: ftrace instance, can be NULL for the top instance + * @name: name of the file + * + * Returns true if the file exists, false otherwise + * + * If a directory with the given name exists, false is returned. + */ +bool tracefs_file_exists(struct tracefs_instance *instance, const char *name) +{ + return check_file_exists(instance, name, false); +} + +/** + * tracefs_dir_exists - Check if a directory with given name exists in given instance + * @instance: ftrace instance, can be NULL for the top instance + * @name: name of the directory + * + * Returns true if the directory exists, false otherwise + */ +bool tracefs_dir_exists(struct tracefs_instance *instance, const char *name) +{ + return check_file_exists(instance, name, true); +} + +/** + * tracefs_instances_walk - Iterate through all ftrace instances in the system + * @callback: user callback, called for each instance. Instance name is passed + * as input parameter. If the @callback returns non-zero, + * the iteration stops. + * @context: user context, passed to the @callback. + * + * Returns -1 in case of an error, 1 if the iteration was stopped because of the + * callback return value or 0 otherwise. + */ +int tracefs_instances_walk(int (*callback)(const char *, void *), void *context) +{ + struct dirent *dent; + char *path = NULL; + DIR *dir = NULL; + struct stat st; + int fret = -1; + int ret; + + path = tracefs_get_tracing_file("instances"); + if (!path) + return -1; + ret = stat(path, &st); + if (ret < 0 || !S_ISDIR(st.st_mode)) + goto out; + + dir = opendir(path); + if (!dir) + goto out; + fret = 0; + while ((dent = readdir(dir))) { + char *instance; + + if (strcmp(dent->d_name, ".") == 0 || + strcmp(dent->d_name, "..") == 0) + continue; + instance = trace_append_file(path, dent->d_name); + ret = stat(instance, &st); + free(instance); + if (ret < 0 || !S_ISDIR(st.st_mode)) + continue; + if (callback(dent->d_name, context)) { + fret = 1; + break; + } + } + +out: + if (dir) + closedir(dir); + tracefs_put_tracing_file(path); + return fret; +} + +static inline bool match(const char *str, regex_t *re) +{ + if (!re) + return true; + return regexec(re, str, 0, NULL, 0) == 0; +} + +struct instance_list { + regex_t *re; + char **list; + int failed; +}; + +static int build_list(const char *name, void *data) +{ + struct instance_list *list = data; + char **instances; + int ret = -1; + + if (!match(name, list->re)) + return 0; + + instances = tracefs_list_add(list->list, name); + if (!instances) + goto out; + + list->list = instances; + ret = 0; + + out: + list->failed = ret; + return ret; +} + +/** + * tracefs_instances - return a list of instance names + * @regex: A regex of instances to filter on (NULL to match all) + * + * Returns a list of names of existing instances, that must be + * freed with tracefs_list_free(). Note, if there are no matches + * then an empty list will be returned (not NULL). + * NULL on error. + */ +char **tracefs_instances(const char *regex) +{ + struct instance_list list = { .re = NULL, .list = NULL }; + regex_t re; + int ret; + + if (regex) { + ret = regcomp(&re, regex, REG_ICASE|REG_NOSUB); + if (ret < 0) + return NULL; + list.re = &re; + } + + ret = tracefs_instances_walk(build_list, &list); + if (ret < 0 || list.failed) { + tracefs_list_free(list.list); + list.list = NULL; + } else { + /* No matches should produce an empty list */ + if (!list.list) + list.list = trace_list_create_empty(); + } + return list.list; +} + +/** + * tracefs_get_clock - Get the current trace clock + * @instance: ftrace instance, can be NULL for the top instance + * + * Returns the current trace clock of the given instance, or NULL in + * case of an error. + * The return string must be freed by free() + */ +char *tracefs_get_clock(struct tracefs_instance *instance) +{ + char *all_clocks = NULL; + char *ret = NULL; + int bytes = 0; + char *clock; + char *cont; + + all_clocks = tracefs_instance_file_read(instance, "trace_clock", &bytes); + if (!all_clocks || !bytes) + goto out; + + clock = strstr(all_clocks, "["); + if (!clock) + goto out; + clock++; + cont = strstr(clock, "]"); + if (!cont) + goto out; + *cont = '\0'; + + ret = strdup(clock); +out: + free(all_clocks); + return ret; +} + +/** + * tracefs_instance_set_affinity_raw - write a hex bitmask into the affinity + * @instance: The instance to set affinity to (NULL for top level) + * @mask: String containing the hex value to set the tracing affinity to. + * + * Sets the tracing affinity CPU mask for @instance. The @mask is the raw + * value that is used to write into the tracing system. + * + * Return 0 on success and -1 on error. + */ +int tracefs_instance_set_affinity_raw(struct tracefs_instance *instance, + const char *mask) +{ + return tracefs_instance_file_write(instance, "tracing_cpumask", mask); +} + +/** + * tracefs_instance_set_affinity_set - use a cpu_set to define tracing affinity + * @instance: The instance to set affinity to (NULL for top level) + * @set: A CPU set that describes the CPU affinity to set tracing to. + * @set_size: The size in bytes of @set (use CPU_ALLOC_SIZE() to get this value) + * + * Sets the tracing affinity CPU mask for @instance. The bits in @set will be + * used to set the CPUs to have tracing on. + * + * If @set is NULL, then all CPUs defined by sysconf(_SC_NPROCESSORS_CONF) + * will be set, and @set_size is ignored. + * + * Return 0 on success and -1 on error. + */ +int tracefs_instance_set_affinity_set(struct tracefs_instance *instance, + cpu_set_t *set, size_t set_size) +{ + struct trace_seq seq; + bool free_set = false; + bool hit = false; + int nr_cpus; + int cpu; + int ret = -1; + int w, n, i; + + trace_seq_init(&seq); + + /* NULL set means all CPUs to be set */ + if (!set) { + nr_cpus = sysconf(_SC_NPROCESSORS_CONF); + set = CPU_ALLOC(nr_cpus); + if (!set) + goto out; + set_size = CPU_ALLOC_SIZE(nr_cpus); + CPU_ZERO_S(set_size, set); + /* Set all CPUS */ + for (cpu = 0; cpu < nr_cpus; cpu++) + CPU_SET_S(cpu, set_size, set); + free_set = true; + } + /* Convert to a bitmask hex string */ + nr_cpus = (set_size + 1) * 8; + if (nr_cpus < 1) { + /* Must have at least one bit set */ + errno = EINVAL; + goto out; + } + /* Start backwards from 32 bits */ + for (w = ((nr_cpus + 31) / 32) - 1; w >= 0; w--) { + /* Now move one nibble at a time */ + for (n = 7; n >= 0; n--) { + int nibble = 0; + + if ((n * 4) + (w * 32) >= nr_cpus) + continue; + + /* One bit at a time */ + for (i = 3; i >= 0; i--) { + cpu = (w * 32) + (n * 4) + i; + if (cpu >= nr_cpus) + continue; + if (CPU_ISSET_S(cpu, set_size, set)) { + nibble |= 1 << i; + hit = true; + } + } + if (hit && trace_seq_printf(&seq, "%x", nibble) < 0) + goto out; + } + if (hit && w) + if (trace_seq_putc(&seq, ',') < 0) + goto out; + } + if (!hit) { + errno = EINVAL; + goto out; + } + trace_seq_terminate(&seq); + ret = tracefs_instance_set_affinity_raw(instance, seq.buffer); + out: + trace_seq_destroy(&seq); + if (free_set) + CPU_FREE(set); + return ret; +} + +/** + * tracefs_instance_set_affinity - Set the affinity defined by CPU values. + * @instance: The instance to set affinity to (NULL for top level) + * @cpu_str: A string of values that define what CPUs to set. + * + * Sets the tracing affinity CPU mask for @instance. The @cpu_str is a set + * of decimal numbers used to state which CPU should be part of the affinity + * mask. A range may also be specified via a hyphen. + * + * For example, "1,4,6-8" + * + * The numbers do not need to be in order. + * + * If @cpu_str is NULL, then all CPUs defined by sysconf(_SC_NPROCESSORS_CONF) + * will be set. + * + * Return 0 on success and -1 on error. + */ +int tracefs_instance_set_affinity(struct tracefs_instance *instance, + const char *cpu_str) +{ + cpu_set_t *set = NULL; + size_t set_size; + char *word; + char *cpus; + char *del; + char *c; + int max_cpu = 0; + int cpu1, cpu2; + int len; + int ret = -1; + + /* NULL cpu_str means to set all CPUs in the mask */ + if (!cpu_str) + return tracefs_instance_set_affinity_set(instance, NULL, 0); + + /* First, find out how many CPUs are needed */ + cpus = strdup(cpu_str); + if (!cpus) + return -1; + len = strlen(cpus) + 1; + for (word = strtok_r(cpus, ",", &del); word; word = strtok_r(NULL, ",", &del)) { + cpu1 = atoi(word); + if (cpu1 < 0) { + errno = EINVAL; + goto out; + } + if (cpu1 > max_cpu) + max_cpu = cpu1; + cpu2 = -1; + if ((c = strchr(word, '-'))) { + c++; + cpu2 = atoi(c); + if (cpu2 < cpu1) { + errno = EINVAL; + goto out; + } + if (cpu2 > max_cpu) + max_cpu = cpu2; + } + } + /* + * Now ideally, cpus should fit cpu_str as it was orginally allocated + * by strdup(). But I'm paranoid, and can imagine someone playing tricks + * with threads, and changes cpu_str from another thread and messes + * with this. At least only copy what we know is allocated. + */ + strncpy(cpus, cpu_str, len); + + set = CPU_ALLOC(max_cpu + 1); + if (!set) + goto out; + set_size = CPU_ALLOC_SIZE(max_cpu + 1); + CPU_ZERO_S(set_size, set); + + for (word = strtok_r(cpus, ",", &del); word; word = strtok_r(NULL, ",", &del)) { + cpu1 = atoi(word); + if (cpu1 < 0 || cpu1 > max_cpu) { + /* Someone playing games? */ + errno = EACCES; + goto out; + } + cpu2 = cpu1; + if ((c = strchr(word, '-'))) { + c++; + cpu2 = atoi(c); + if (cpu2 < cpu1 || cpu2 > max_cpu) { + errno = EACCES; + goto out; + } + } + for ( ; cpu1 <= cpu2; cpu1++) + CPU_SET(cpu1, set); + } + ret = tracefs_instance_set_affinity_set(instance, set, set_size); + out: + free(cpus); + CPU_FREE(set); + return ret; +} + +/** + * tracefs_instance_get_affinity_raw - read the affinity instance file + * @instance: The instance to get affinity of (NULL for top level) + * + * Reads the affinity file for @instance (or the top level if @instance + * is NULL) and returns it. The returned string must be freed with free(). + * + * Returns the affinity mask on success, and must be freed with free() + * or NULL on error. + */ +char *tracefs_instance_get_affinity_raw(struct tracefs_instance *instance) +{ + return tracefs_instance_file_read(instance, "tracing_cpumask", NULL); +} + +static inline int update_cpu_set(int cpus, int cpu_set, int cpu, + cpu_set_t *set, size_t set_size) +{ + int bit = 1 << cpu; + + if (!(cpus & bit)) + return 0; + + CPU_SET_S(cpu_set + cpu, set_size, set); + + /* + * It is possible that the passed in set_size is not big enough + * to hold the cpu we just set. If that's the case, do not report + * it as being set. + * + * The CPU_ISSET_S() should return false if the CPU given to it + * is bigger than the set itself. + */ + return CPU_ISSET_S(cpu_set + cpu, set_size, set) ? 1 : 0; +} + +/** + * tracefs_instance_get_affinity_set - Retrieve the cpuset of an instance affinity + * @instance: The instance to get affinity of (NULL for top level) + * @set: A CPU set to put the affinity into. + * @set_size: The size in bytes of @set (use CPU_ALLOC_SIZE() to get this value) + * + * Reads the affinity of a given instance and updates the CPU set by the + * instance. + * + * Returns the number of CPUS that are set, or -1 on error. + */ +int tracefs_instance_get_affinity_set(struct tracefs_instance *instance, + cpu_set_t *set, size_t set_size) +{ + char *affinity; + int cpu_set; + int cpus; + int cnt = 0; + int ch; + int i; + + if (!set || !set_size) { + errno = -EINVAL; + return -1; + } + + affinity = tracefs_instance_get_affinity_raw(instance); + if (!affinity) + return -1; + + /* + * The returned affinity should be a comma delimited + * hex string. Work backwards setting the values. + */ + cpu_set = 0; + i = strlen(affinity); + for (i--; i >= 0; i--) { + ch = affinity[i]; + if (isalnum(ch)) { + ch = tolower(ch); + if (isdigit(ch)) + cpus = ch - '0'; + else + cpus = ch - 'a' + 10; + + cnt += update_cpu_set(cpus, cpu_set, 0, set, set_size); + cnt += update_cpu_set(cpus, cpu_set, 1, set, set_size); + cnt += update_cpu_set(cpus, cpu_set, 2, set, set_size); + cnt += update_cpu_set(cpus, cpu_set, 3, set, set_size); + /* Next nibble */ + cpu_set += 4; + } + } + + free(affinity); + + return cnt; +} + +static inline int update_cpu(int cpus, int cpu_set, int cpu, int s, char **set) +{ + char *list; + int bit = 1 << cpu; + int ret; + + if (*set == (char *)-1) + return s; + + if (cpus & bit) { + /* If the previous CPU is set just return s */ + if (s >= 0) + return s; + /* Otherwise, return this cpu */ + return cpu_set + cpu; + } + + /* If the last CPU wasn't set, just return s */ + if (s < 0) + return s; + + /* Update the string */ + if (s == cpu_set + cpu - 1) { + ret = asprintf(&list, "%s%s%d", + *set ? *set : "", *set ? "," : "", s); + } else { + ret = asprintf(&list, "%s%s%d-%d", + *set ? *set : "", *set ? "," : "", + s, cpu_set + cpu - 1); + } + free(*set); + /* Force *set to be a failure */ + if (ret < 0) + *set = (char *)-1; + else + *set = list; + return -1; +} + +/** + * tracefs_instance_get_affinity - Retrieve a string of CPUs for instance affinity + * @instance: The instance to get affinity of (NULL for top level) + * + * Reads the affinity of a given instance and returns a CPU count of the + * instance. For example, if it reads "eb" it will return: + * "0-1,3,5-7" + * + * If no CPUs are set, an empty string is returned "\0", and it too needs + * to be freed. + * + * Returns an allocated string containing the CPU affinity in "human readable" + * format which needs to be freed with free(), or NULL on error. + */ +char *tracefs_instance_get_affinity(struct tracefs_instance *instance) +{ + char *affinity; + char *set = NULL; + int cpu_set; + int cpus; + int ch; + int s = -1; + int i; + + affinity = tracefs_instance_get_affinity_raw(instance); + if (!affinity) + return NULL; + + /* + * The returned affinity should be a comma delimited + * hex string. Work backwards setting the values. + */ + cpu_set = 0; + i = strlen(affinity); + for (i--; i >= 0; i--) { + ch = affinity[i]; + if (isalnum(ch)) { + ch = tolower(ch); + if (isdigit(ch)) + cpus = ch - '0'; + else + cpus = ch - 'a' + 10; + s = update_cpu(cpus, cpu_set, 0, s, &set); + s = update_cpu(cpus, cpu_set, 1, s, &set); + s = update_cpu(cpus, cpu_set, 2, s, &set); + s = update_cpu(cpus, cpu_set, 3, s, &set); + + if (set == (char *)-1) { + set = NULL; + goto out; + } + /* Next nibble */ + cpu_set += 4; + } + } + /* Clean up in case the last CPU is set */ + s = update_cpu(0, cpu_set, 0, s, &set); + + if (!set) + set = strdup(""); + out: + free(affinity); + + return set; +} |