1756 lines · 40.6 KB · C
RawDownload
/* * trace_events_hist - trace event hist triggers * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the * GNU General Public License for more details. * * Copyright (C) 2015 Tom Zanussi <tom.zanussi@linux.intel.com> */ #include <linux/module.h>#include <linux/kallsyms.h>#include <linux/mutex.h>#include <linux/slab.h>#include <linux/stacktrace.h>#include <linux/rculist.h> #include "tracing_map.h"#include "trace.h" struct hist_field; typedef u64 (*hist_field_fn_t) (struct hist_field *field, void *event); struct hist_field {	struct ftrace_event_field	*field;	unsigned long			flags;	hist_field_fn_t			fn;	unsigned int			size;	unsigned int			offset;}; static u64 hist_field_none(struct hist_field *field, void *event){	return 0;} static u64 hist_field_counter(struct hist_field *field, void *event){	return 1;} static u64 hist_field_string(struct hist_field *hist_field, void *event){	char *addr = (char *)(event + hist_field->field->offset); 	return (u64)(unsigned long)addr;} static u64 hist_field_dynstring(struct hist_field *hist_field, void *event){	u32 str_item = *(u32 *)(event + hist_field->field->offset);	int str_loc = str_item & 0xffff;	char *addr = (char *)(event + str_loc); 	return (u64)(unsigned long)addr;} static u64 hist_field_pstring(struct hist_field *hist_field, void *event){	char **addr = (char **)(event + hist_field->field->offset); 	return (u64)(unsigned long)*addr;} static u64 hist_field_log2(struct hist_field *hist_field, void *event){	u64 val = *(u64 *)(event + hist_field->field->offset); 	return (u64) ilog2(roundup_pow_of_two(val));} #define DEFINE_HIST_FIELD_FN(type)					\static u64 hist_field_##type(struct hist_field *hist_field, void *event)\{									\	type *addr = (type *)(event + hist_field->field->offset);	\									\	return (u64)(unsigned long)*addr;				\} DEFINE_HIST_FIELD_FN(s64);DEFINE_HIST_FIELD_FN(u64);DEFINE_HIST_FIELD_FN(s32);DEFINE_HIST_FIELD_FN(u32);DEFINE_HIST_FIELD_FN(s16);DEFINE_HIST_FIELD_FN(u16);DEFINE_HIST_FIELD_FN(s8);DEFINE_HIST_FIELD_FN(u8); #define for_each_hist_field(i, hist_data)	\	for ((i) = 0; (i) < (hist_data)->n_fields; (i)++) #define for_each_hist_val_field(i, hist_data)	\	for ((i) = 0; (i) < (hist_data)->n_vals; (i)++) #define for_each_hist_key_field(i, hist_data)	\	for ((i) = (hist_data)->n_vals; (i) < (hist_data)->n_fields; (i)++) #define HIST_STACKTRACE_DEPTH	16#define HIST_STACKTRACE_SIZE	(HIST_STACKTRACE_DEPTH * sizeof(unsigned long))#define HIST_STACKTRACE_SKIP	5 #define HITCOUNT_IDX		0#define HIST_KEY_SIZE_MAX	(MAX_FILTER_STR_VAL + HIST_STACKTRACE_SIZE) enum hist_field_flags {	HIST_FIELD_FL_HITCOUNT		= 1,	HIST_FIELD_FL_KEY		= 2,	HIST_FIELD_FL_STRING		= 4,	HIST_FIELD_FL_HEX		= 8,	HIST_FIELD_FL_SYM		= 16,	HIST_FIELD_FL_SYM_OFFSET	= 32,	HIST_FIELD_FL_EXECNAME		= 64,	HIST_FIELD_FL_SYSCALL		= 128,	HIST_FIELD_FL_STACKTRACE	= 256,	HIST_FIELD_FL_LOG2		= 512,}; struct hist_trigger_attrs {	char		*keys_str;	char		*vals_str;	char		*sort_key_str;	char		*name;	bool		pause;	bool		cont;	bool		clear;	unsigned int	map_bits;}; struct hist_trigger_data {	struct hist_field               *fields[TRACING_MAP_FIELDS_MAX];	unsigned int			n_vals;	unsigned int			n_keys;	unsigned int			n_fields;	unsigned int			key_size;	struct tracing_map_sort_key	sort_keys[TRACING_MAP_SORT_KEYS_MAX];	unsigned int			n_sort_keys;	struct trace_event_file		*event_file;	struct hist_trigger_attrs	*attrs;	struct tracing_map		*map;}; static hist_field_fn_t select_value_fn(int field_size, int field_is_signed){	hist_field_fn_t fn = NULL; 	switch (field_size) {	case 8:		if (field_is_signed)			fn = hist_field_s64;		else			fn = hist_field_u64;		break;	case 4:		if (field_is_signed)			fn = hist_field_s32;		else			fn = hist_field_u32;		break;	case 2:		if (field_is_signed)			fn = hist_field_s16;		else			fn = hist_field_u16;		break;	case 1:		if (field_is_signed)			fn = hist_field_s8;		else			fn = hist_field_u8;		break;	} 	return fn;} static int parse_map_size(char *str){	unsigned long size, map_bits;	int ret; 	strsep(&str, "=");	if (!str) {		ret = -EINVAL;		goto out;	} 	ret = kstrtoul(str, 0, &size);	if (ret)		goto out; 	map_bits = ilog2(roundup_pow_of_two(size));	if (map_bits < TRACING_MAP_BITS_MIN ||	    map_bits > TRACING_MAP_BITS_MAX)		ret = -EINVAL;	else		ret = map_bits; out:	return ret;} static void destroy_hist_trigger_attrs(struct hist_trigger_attrs *attrs){	if (!attrs)		return; 	kfree(attrs->name);	kfree(attrs->sort_key_str);	kfree(attrs->keys_str);	kfree(attrs->vals_str);	kfree(attrs);} static struct hist_trigger_attrs *parse_hist_trigger_attrs(char *trigger_str){	struct hist_trigger_attrs *attrs;	int ret = 0; 	attrs = kzalloc(sizeof(*attrs), GFP_KERNEL);	if (!attrs)		return ERR_PTR(-ENOMEM); 	while (trigger_str) {		char *str = strsep(&trigger_str, ":"); 		if ((strncmp(str, "key=", strlen("key=")) == 0) ||		    (strncmp(str, "keys=", strlen("keys=")) == 0))			attrs->keys_str = kstrdup(str, GFP_KERNEL);		else if ((strncmp(str, "val=", strlen("val=")) == 0) ||			 (strncmp(str, "vals=", strlen("vals=")) == 0) ||			 (strncmp(str, "values=", strlen("values=")) == 0))			attrs->vals_str = kstrdup(str, GFP_KERNEL);		else if (strncmp(str, "sort=", strlen("sort=")) == 0)			attrs->sort_key_str = kstrdup(str, GFP_KERNEL);		else if (strncmp(str, "name=", strlen("name=")) == 0)			attrs->name = kstrdup(str, GFP_KERNEL);		else if (strcmp(str, "pause") == 0)			attrs->pause = true;		else if ((strcmp(str, "cont") == 0) ||			 (strcmp(str, "continue") == 0))			attrs->cont = true;		else if (strcmp(str, "clear") == 0)			attrs->clear = true;		else if (strncmp(str, "size=", strlen("size=")) == 0) {			int map_bits = parse_map_size(str); 			if (map_bits < 0) {				ret = map_bits;				goto free;			}			attrs->map_bits = map_bits;		} else {			ret = -EINVAL;			goto free;		}	} 	if (!attrs->keys_str) {		ret = -EINVAL;		goto free;	} 	return attrs; free:	destroy_hist_trigger_attrs(attrs); 	return ERR_PTR(ret);} static inline void save_comm(char *comm, struct task_struct *task){	if (!task->pid) {		strcpy(comm, "<idle>");		return;	} 	if (WARN_ON_ONCE(task->pid < 0)) {		strcpy(comm, "<XXX>");		return;	} 	memcpy(comm, task->comm, TASK_COMM_LEN);} static void hist_trigger_elt_comm_free(struct tracing_map_elt *elt){	kfree((char *)elt->private_data);} static int hist_trigger_elt_comm_alloc(struct tracing_map_elt *elt){	struct hist_trigger_data *hist_data = elt->map->private_data;	struct hist_field *key_field;	unsigned int i; 	for_each_hist_key_field(i, hist_data) {		key_field = hist_data->fields[i]; 		if (key_field->flags & HIST_FIELD_FL_EXECNAME) {			unsigned int size = TASK_COMM_LEN + 1; 			elt->private_data = kzalloc(size, GFP_KERNEL);			if (!elt->private_data)				return -ENOMEM;			break;		}	} 	return 0;} static void hist_trigger_elt_comm_copy(struct tracing_map_elt *to,				       struct tracing_map_elt *from){	char *comm_from = from->private_data;	char *comm_to = to->private_data; 	if (comm_from)		memcpy(comm_to, comm_from, TASK_COMM_LEN + 1);} static void hist_trigger_elt_comm_init(struct tracing_map_elt *elt){	char *comm = elt->private_data; 	if (comm)		save_comm(comm, current);} static const struct tracing_map_ops hist_trigger_elt_comm_ops = {	.elt_alloc	= hist_trigger_elt_comm_alloc,	.elt_copy	= hist_trigger_elt_comm_copy,	.elt_free	= hist_trigger_elt_comm_free,	.elt_init	= hist_trigger_elt_comm_init,}; static void destroy_hist_field(struct hist_field *hist_field){	kfree(hist_field);} static struct hist_field *create_hist_field(struct ftrace_event_field *field,					    unsigned long flags){	struct hist_field *hist_field; 	if (field && is_function_field(field))		return NULL; 	hist_field = kzalloc(sizeof(struct hist_field), GFP_KERNEL);	if (!hist_field)		return NULL; 	if (flags & HIST_FIELD_FL_HITCOUNT) {		hist_field->fn = hist_field_counter;		goto out;	} 	if (flags & HIST_FIELD_FL_STACKTRACE) {		hist_field->fn = hist_field_none;		goto out;	} 	if (flags & HIST_FIELD_FL_LOG2) {		hist_field->fn = hist_field_log2;		goto out;	} 	if (WARN_ON_ONCE(!field))		goto out; 	if (is_string_field(field)) {		flags |= HIST_FIELD_FL_STRING; 		if (field->filter_type == FILTER_STATIC_STRING)			hist_field->fn = hist_field_string;		else if (field->filter_type == FILTER_DYN_STRING)			hist_field->fn = hist_field_dynstring;		else			hist_field->fn = hist_field_pstring;	} else {		hist_field->fn = select_value_fn(field->size,						 field->is_signed);		if (!hist_field->fn) {			destroy_hist_field(hist_field);			return NULL;		}	} out:	hist_field->field = field;	hist_field->flags = flags; 	return hist_field;} static void destroy_hist_fields(struct hist_trigger_data *hist_data){	unsigned int i; 	for (i = 0; i < TRACING_MAP_FIELDS_MAX; i++) {		if (hist_data->fields[i]) {			destroy_hist_field(hist_data->fields[i]);			hist_data->fields[i] = NULL;		}	}} static int create_hitcount_val(struct hist_trigger_data *hist_data){	hist_data->fields[HITCOUNT_IDX] =		create_hist_field(NULL, HIST_FIELD_FL_HITCOUNT);	if (!hist_data->fields[HITCOUNT_IDX])		return -ENOMEM; 	hist_data->n_vals++; 	if (WARN_ON(hist_data->n_vals > TRACING_MAP_VALS_MAX))		return -EINVAL; 	return 0;} static int create_val_field(struct hist_trigger_data *hist_data,			    unsigned int val_idx,			    struct trace_event_file *file,			    char *field_str){	struct ftrace_event_field *field = NULL;	unsigned long flags = 0;	char *field_name;	int ret = 0; 	if (WARN_ON(val_idx >= TRACING_MAP_VALS_MAX))		return -EINVAL; 	field_name = strsep(&field_str, ".");	if (field_str) {		if (strcmp(field_str, "hex") == 0)			flags |= HIST_FIELD_FL_HEX;		else {			ret = -EINVAL;			goto out;		}	} 	field = trace_find_event_field(file->event_call, field_name);	if (!field) {		ret = -EINVAL;		goto out;	} 	hist_data->fields[val_idx] = create_hist_field(field, flags);	if (!hist_data->fields[val_idx]) {		ret = -ENOMEM;		goto out;	} 	++hist_data->n_vals; 	if (WARN_ON(hist_data->n_vals > TRACING_MAP_VALS_MAX))		ret = -EINVAL; out:	return ret;} static int create_val_fields(struct hist_trigger_data *hist_data,			     struct trace_event_file *file){	char *fields_str, *field_str;	unsigned int i, j;	int ret; 	ret = create_hitcount_val(hist_data);	if (ret)		goto out; 	fields_str = hist_data->attrs->vals_str;	if (!fields_str)		goto out; 	strsep(&fields_str, "=");	if (!fields_str)		goto out; 	for (i = 0, j = 1; i < TRACING_MAP_VALS_MAX &&		     j < TRACING_MAP_VALS_MAX; i++) {		field_str = strsep(&fields_str, ",");		if (!field_str)			break;		if (strcmp(field_str, "hitcount") == 0)			continue;		ret = create_val_field(hist_data, j++, file, field_str);		if (ret)			goto out;

Lines 1–500 of 1756.