diff options
| author | Tom Zanussi <tom.zanussi@linux.intel.com> | 2013-10-24 09:59:28 -0400 |
|---|---|---|
| committer | Steven Rostedt <rostedt@goodmis.org> | 2013-12-21 22:02:16 -0500 |
| commit | 7862ad1846e994574cb47dc503cc2b1646ea6593 (patch) | |
| tree | cec348fd60d6f85d1ae772996d0b8241a31d5b35 /kernel | |
| parent | f21ecbb35f865a508073c0e73854da469a07f278 (diff) | |
tracing: Add 'enable_event' and 'disable_event' event trigger commands
Add 'enable_event' and 'disable_event' event_command commands.
enable_event and disable_event event triggers are added by the user
via these commands in a similar way and using practically the same
syntax as the analagous 'enable_event' and 'disable_event' ftrace
function commands, but instead of writing to the set_ftrace_filter
file, the enable_event and disable_event triggers are written to the
per-event 'trigger' files:
echo 'enable_event:system:event' > .../othersys/otherevent/trigger
echo 'disable_event:system:event' > .../othersys/otherevent/trigger
The above commands will enable or disable the 'system:event' trace
events whenever the othersys:otherevent events are hit.
This also adds a 'count' version that limits the number of times the
command will be invoked:
echo 'enable_event:system:event:N' > .../othersys/otherevent/trigger
echo 'disable_event:system:event:N' > .../othersys/otherevent/trigger
Where N is the number of times the command will be invoked.
The above commands will will enable or disable the 'system:event'
trace events whenever the othersys:otherevent events are hit, but only
N times.
This also makes the find_event_file() helper function extern, since
it's useful to use from other places, such as the event triggers code,
so make it accessible.
Link: http://lkml.kernel.org/r/f825f3048c3f6b026ee37ae5825f9fc373451828.1382622043.git.tom.zanussi@linux.intel.com
Signed-off-by: Tom Zanussi <tom.zanussi@linux.intel.com>
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Diffstat (limited to 'kernel')
| -rw-r--r-- | kernel/trace/trace.h | 4 | ||||
| -rw-r--r-- | kernel/trace/trace_events.c | 2 | ||||
| -rw-r--r-- | kernel/trace/trace_events_trigger.c | 359 |
3 files changed, 364 insertions, 1 deletions
diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index 50723e5e2b3c..ccbd8104cf99 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h | |||
| @@ -1028,6 +1028,10 @@ extern void trace_event_enable_cmd_record(bool enable); | |||
| 1028 | extern int event_trace_add_tracer(struct dentry *parent, struct trace_array *tr); | 1028 | extern int event_trace_add_tracer(struct dentry *parent, struct trace_array *tr); |
| 1029 | extern int event_trace_del_tracer(struct trace_array *tr); | 1029 | extern int event_trace_del_tracer(struct trace_array *tr); |
| 1030 | 1030 | ||
| 1031 | extern struct ftrace_event_file *find_event_file(struct trace_array *tr, | ||
| 1032 | const char *system, | ||
| 1033 | const char *event); | ||
| 1034 | |||
| 1031 | static inline void *event_file_data(struct file *filp) | 1035 | static inline void *event_file_data(struct file *filp) |
| 1032 | { | 1036 | { |
| 1033 | return ACCESS_ONCE(file_inode(filp)->i_private); | 1037 | return ACCESS_ONCE(file_inode(filp)->i_private); |
diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index 442775c9dbf3..9a974bd843d4 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c | |||
| @@ -1868,7 +1868,7 @@ struct event_probe_data { | |||
| 1868 | bool enable; | 1868 | bool enable; |
| 1869 | }; | 1869 | }; |
| 1870 | 1870 | ||
| 1871 | static struct ftrace_event_file * | 1871 | struct ftrace_event_file * |
| 1872 | find_event_file(struct trace_array *tr, const char *system, const char *event) | 1872 | find_event_file(struct trace_array *tr, const char *system, const char *event) |
| 1873 | { | 1873 | { |
| 1874 | struct ftrace_event_file *file; | 1874 | struct ftrace_event_file *file; |
diff --git a/kernel/trace/trace_events_trigger.c b/kernel/trace/trace_events_trigger.c index a3bd1da90c56..45e48b109d51 100644 --- a/kernel/trace/trace_events_trigger.c +++ b/kernel/trace/trace_events_trigger.c | |||
| @@ -864,6 +864,364 @@ static __init void unregister_trigger_traceon_traceoff_cmds(void) | |||
| 864 | unregister_event_command(&trigger_traceoff_cmd); | 864 | unregister_event_command(&trigger_traceoff_cmd); |
| 865 | } | 865 | } |
| 866 | 866 | ||
| 867 | /* Avoid typos */ | ||
| 868 | #define ENABLE_EVENT_STR "enable_event" | ||
| 869 | #define DISABLE_EVENT_STR "disable_event" | ||
| 870 | |||
| 871 | struct enable_trigger_data { | ||
| 872 | struct ftrace_event_file *file; | ||
| 873 | bool enable; | ||
| 874 | }; | ||
| 875 | |||
| 876 | static void | ||
| 877 | event_enable_trigger(struct event_trigger_data *data) | ||
| 878 | { | ||
| 879 | struct enable_trigger_data *enable_data = data->private_data; | ||
| 880 | |||
| 881 | if (enable_data->enable) | ||
| 882 | clear_bit(FTRACE_EVENT_FL_SOFT_DISABLED_BIT, &enable_data->file->flags); | ||
| 883 | else | ||
| 884 | set_bit(FTRACE_EVENT_FL_SOFT_DISABLED_BIT, &enable_data->file->flags); | ||
| 885 | } | ||
| 886 | |||
| 887 | static void | ||
| 888 | event_enable_count_trigger(struct event_trigger_data *data) | ||
| 889 | { | ||
| 890 | struct enable_trigger_data *enable_data = data->private_data; | ||
| 891 | |||
| 892 | if (!data->count) | ||
| 893 | return; | ||
| 894 | |||
| 895 | /* Skip if the event is in a state we want to switch to */ | ||
| 896 | if (enable_data->enable == !(enable_data->file->flags & FTRACE_EVENT_FL_SOFT_DISABLED)) | ||
| 897 | return; | ||
| 898 | |||
| 899 | if (data->count != -1) | ||
| 900 | (data->count)--; | ||
| 901 | |||
| 902 | event_enable_trigger(data); | ||
| 903 | } | ||
| 904 | |||
| 905 | static int | ||
| 906 | event_enable_trigger_print(struct seq_file *m, struct event_trigger_ops *ops, | ||
| 907 | struct event_trigger_data *data) | ||
| 908 | { | ||
| 909 | struct enable_trigger_data *enable_data = data->private_data; | ||
| 910 | |||
| 911 | seq_printf(m, "%s:%s:%s", | ||
| 912 | enable_data->enable ? ENABLE_EVENT_STR : DISABLE_EVENT_STR, | ||
| 913 | enable_data->file->event_call->class->system, | ||
| 914 | enable_data->file->event_call->name); | ||
| 915 | |||
| 916 | if (data->count == -1) | ||
| 917 | seq_puts(m, ":unlimited"); | ||
| 918 | else | ||
| 919 | seq_printf(m, ":count=%ld", data->count); | ||
| 920 | |||
| 921 | if (data->filter_str) | ||
| 922 | seq_printf(m, " if %s\n", data->filter_str); | ||
| 923 | else | ||
| 924 | seq_puts(m, "\n"); | ||
| 925 | |||
| 926 | return 0; | ||
| 927 | } | ||
| 928 | |||
| 929 | static void | ||
| 930 | event_enable_trigger_free(struct event_trigger_ops *ops, | ||
| 931 | struct event_trigger_data *data) | ||
| 932 | { | ||
| 933 | struct enable_trigger_data *enable_data = data->private_data; | ||
| 934 | |||
| 935 | if (WARN_ON_ONCE(data->ref <= 0)) | ||
| 936 | return; | ||
| 937 | |||
| 938 | data->ref--; | ||
| 939 | if (!data->ref) { | ||
| 940 | /* Remove the SOFT_MODE flag */ | ||
| 941 | trace_event_enable_disable(enable_data->file, 0, 1); | ||
| 942 | module_put(enable_data->file->event_call->mod); | ||
| 943 | trigger_data_free(data); | ||
| 944 | kfree(enable_data); | ||
| 945 | } | ||
| 946 | } | ||
| 947 | |||
| 948 | static struct event_trigger_ops event_enable_trigger_ops = { | ||
| 949 | .func = event_enable_trigger, | ||
| 950 | .print = event_enable_trigger_print, | ||
| 951 | .init = event_trigger_init, | ||
| 952 | .free = event_enable_trigger_free, | ||
| 953 | }; | ||
| 954 | |||
| 955 | static struct event_trigger_ops event_enable_count_trigger_ops = { | ||
| 956 | .func = event_enable_count_trigger, | ||
| 957 | .print = event_enable_trigger_print, | ||
| 958 | .init = event_trigger_init, | ||
| 959 | .free = event_enable_trigger_free, | ||
| 960 | }; | ||
| 961 | |||
| 962 | static struct event_trigger_ops event_disable_trigger_ops = { | ||
| 963 | .func = event_enable_trigger, | ||
| 964 | .print = event_enable_trigger_print, | ||
| 965 | .init = event_trigger_init, | ||
| 966 | .free = event_enable_trigger_free, | ||
| 967 | }; | ||
| 968 | |||
| 969 | static struct event_trigger_ops event_disable_count_trigger_ops = { | ||
| 970 | .func = event_enable_count_trigger, | ||
| 971 | .print = event_enable_trigger_print, | ||
| 972 | .init = event_trigger_init, | ||
| 973 | .free = event_enable_trigger_free, | ||
| 974 | }; | ||
| 975 | |||
| 976 | static int | ||
| 977 | event_enable_trigger_func(struct event_command *cmd_ops, | ||
| 978 | struct ftrace_event_file *file, | ||
| 979 | char *glob, char *cmd, char *param) | ||
| 980 | { | ||
| 981 | struct ftrace_event_file *event_enable_file; | ||
| 982 | struct enable_trigger_data *enable_data; | ||
| 983 | struct event_trigger_data *trigger_data; | ||
| 984 | struct event_trigger_ops *trigger_ops; | ||
| 985 | struct trace_array *tr = file->tr; | ||
| 986 | const char *system; | ||
| 987 | const char *event; | ||
| 988 | char *trigger; | ||
| 989 | char *number; | ||
| 990 | bool enable; | ||
| 991 | int ret; | ||
| 992 | |||
| 993 | if (!param) | ||
| 994 | return -EINVAL; | ||
| 995 | |||
| 996 | /* separate the trigger from the filter (s:e:n [if filter]) */ | ||
| 997 | trigger = strsep(¶m, " \t"); | ||
| 998 | if (!trigger) | ||
| 999 | return -EINVAL; | ||
| 1000 | |||
| 1001 | system = strsep(&trigger, ":"); | ||
| 1002 | if (!trigger) | ||
| 1003 | return -EINVAL; | ||
| 1004 | |||
| 1005 | event = strsep(&trigger, ":"); | ||
| 1006 | |||
| 1007 | ret = -EINVAL; | ||
| 1008 | event_enable_file = find_event_file(tr, system, event); | ||
| 1009 | if (!event_enable_file) | ||
| 1010 | goto out; | ||
| 1011 | |||
| 1012 | enable = strcmp(cmd, ENABLE_EVENT_STR) == 0; | ||
| 1013 | |||
| 1014 | trigger_ops = cmd_ops->get_trigger_ops(cmd, trigger); | ||
| 1015 | |||
| 1016 | ret = -ENOMEM; | ||
| 1017 | trigger_data = kzalloc(sizeof(*trigger_data), GFP_KERNEL); | ||
| 1018 | if (!trigger_data) | ||
| 1019 | goto out; | ||
| 1020 | |||
| 1021 | enable_data = kzalloc(sizeof(*enable_data), GFP_KERNEL); | ||
| 1022 | if (!enable_data) { | ||
| 1023 | kfree(trigger_data); | ||
| 1024 | goto out; | ||
| 1025 | } | ||
| 1026 | |||
| 1027 | trigger_data->count = -1; | ||
| 1028 | trigger_data->ops = trigger_ops; | ||
| 1029 | trigger_data->cmd_ops = cmd_ops; | ||
| 1030 | INIT_LIST_HEAD(&trigger_data->list); | ||
| 1031 | RCU_INIT_POINTER(trigger_data->filter, NULL); | ||
| 1032 | |||
| 1033 | enable_data->enable = enable; | ||
| 1034 | enable_data->file = event_enable_file; | ||
| 1035 | trigger_data->private_data = enable_data; | ||
| 1036 | |||
| 1037 | if (glob[0] == '!') { | ||
| 1038 | cmd_ops->unreg(glob+1, trigger_ops, trigger_data, file); | ||
| 1039 | kfree(trigger_data); | ||
| 1040 | kfree(enable_data); | ||
| 1041 | ret = 0; | ||
| 1042 | goto out; | ||
| 1043 | } | ||
| 1044 | |||
| 1045 | if (trigger) { | ||
| 1046 | number = strsep(&trigger, ":"); | ||
| 1047 | |||
| 1048 | ret = -EINVAL; | ||
| 1049 | if (!strlen(number)) | ||
| 1050 | goto out_free; | ||
| 1051 | |||
| 1052 | /* | ||
| 1053 | * We use the callback data field (which is a pointer) | ||
| 1054 | * as our counter. | ||
| 1055 | */ | ||
| 1056 | ret = kstrtoul(number, 0, &trigger_data->count); | ||
| 1057 | if (ret) | ||
| 1058 | goto out_free; | ||
| 1059 | } | ||
| 1060 | |||
| 1061 | if (!param) /* if param is non-empty, it's supposed to be a filter */ | ||
| 1062 | goto out_reg; | ||
| 1063 | |||
| 1064 | if (!cmd_ops->set_filter) | ||
| 1065 | goto out_reg; | ||
| 1066 | |||
| 1067 | ret = cmd_ops->set_filter(param, trigger_data, file); | ||
| 1068 | if (ret < 0) | ||
| 1069 | goto out_free; | ||
| 1070 | |||
| 1071 | out_reg: | ||
| 1072 | /* Don't let event modules unload while probe registered */ | ||
| 1073 | ret = try_module_get(event_enable_file->event_call->mod); | ||
| 1074 | if (!ret) { | ||
| 1075 | ret = -EBUSY; | ||
| 1076 | goto out_free; | ||
| 1077 | } | ||
| 1078 | |||
| 1079 | ret = trace_event_enable_disable(event_enable_file, 1, 1); | ||
| 1080 | if (ret < 0) | ||
| 1081 | goto out_put; | ||
| 1082 | ret = cmd_ops->reg(glob, trigger_ops, trigger_data, file); | ||
| 1083 | /* | ||
| 1084 | * The above returns on success the # of functions enabled, | ||
| 1085 | * but if it didn't find any functions it returns zero. | ||
| 1086 | * Consider no functions a failure too. | ||
| 1087 | */ | ||
| 1088 | if (!ret) { | ||
| 1089 | ret = -ENOENT; | ||
| 1090 | goto out_disable; | ||
| 1091 | } else if (ret < 0) | ||
| 1092 | goto out_disable; | ||
| 1093 | /* Just return zero, not the number of enabled functions */ | ||
| 1094 | ret = 0; | ||
| 1095 | out: | ||
| 1096 | return ret; | ||
| 1097 | |||
| 1098 | out_disable: | ||
| 1099 | trace_event_enable_disable(event_enable_file, 0, 1); | ||
| 1100 | out_put: | ||
| 1101 | module_put(event_enable_file->event_call->mod); | ||
| 1102 | out_free: | ||
| 1103 | kfree(trigger_data); | ||
| 1104 | kfree(enable_data); | ||
| 1105 | goto out; | ||
| 1106 | } | ||
| 1107 | |||
| 1108 | static int event_enable_register_trigger(char *glob, | ||
| 1109 | struct event_trigger_ops *ops, | ||
| 1110 | struct event_trigger_data *data, | ||
| 1111 | struct ftrace_event_file *file) | ||
| 1112 | { | ||
| 1113 | struct enable_trigger_data *enable_data = data->private_data; | ||
| 1114 | struct enable_trigger_data *test_enable_data; | ||
| 1115 | struct event_trigger_data *test; | ||
| 1116 | int ret = 0; | ||
| 1117 | |||
| 1118 | list_for_each_entry_rcu(test, &file->triggers, list) { | ||
| 1119 | test_enable_data = test->private_data; | ||
| 1120 | if (test_enable_data && | ||
| 1121 | (test_enable_data->file == enable_data->file)) { | ||
| 1122 | ret = -EEXIST; | ||
| 1123 | goto out; | ||
| 1124 | } | ||
| 1125 | } | ||
| 1126 | |||
| 1127 | if (data->ops->init) { | ||
| 1128 | ret = data->ops->init(data->ops, data); | ||
| 1129 | if (ret < 0) | ||
| 1130 | goto out; | ||
| 1131 | } | ||
| 1132 | |||
| 1133 | list_add_rcu(&data->list, &file->triggers); | ||
| 1134 | ret++; | ||
| 1135 | |||
| 1136 | if (trace_event_trigger_enable_disable(file, 1) < 0) { | ||
| 1137 | list_del_rcu(&data->list); | ||
| 1138 | ret--; | ||
| 1139 | } | ||
| 1140 | out: | ||
| 1141 | return ret; | ||
| 1142 | } | ||
| 1143 | |||
| 1144 | static void event_enable_unregister_trigger(char *glob, | ||
| 1145 | struct event_trigger_ops *ops, | ||
| 1146 | struct event_trigger_data *test, | ||
| 1147 | struct ftrace_event_file *file) | ||
| 1148 | { | ||
| 1149 | struct enable_trigger_data *test_enable_data = test->private_data; | ||
| 1150 | struct enable_trigger_data *enable_data; | ||
| 1151 | struct event_trigger_data *data; | ||
| 1152 | bool unregistered = false; | ||
| 1153 | |||
| 1154 | list_for_each_entry_rcu(data, &file->triggers, list) { | ||
| 1155 | enable_data = data->private_data; | ||
| 1156 | if (enable_data && | ||
| 1157 | (enable_data->file == test_enable_data->file)) { | ||
| 1158 | unregistered = true; | ||
| 1159 | list_del_rcu(&data->list); | ||
| 1160 | trace_event_trigger_enable_disable(file, 0); | ||
| 1161 | break; | ||
| 1162 | } | ||
| 1163 | } | ||
| 1164 | |||
| 1165 | if (unregistered && data->ops->free) | ||
| 1166 | data->ops->free(data->ops, data); | ||
| 1167 | } | ||
| 1168 | |||
| 1169 | static struct event_trigger_ops * | ||
| 1170 | event_enable_get_trigger_ops(char *cmd, char *param) | ||
| 1171 | { | ||
| 1172 | struct event_trigger_ops *ops; | ||
| 1173 | bool enable; | ||
| 1174 | |||
| 1175 | enable = strcmp(cmd, ENABLE_EVENT_STR) == 0; | ||
| 1176 | |||
| 1177 | if (enable) | ||
| 1178 | ops = param ? &event_enable_count_trigger_ops : | ||
| 1179 | &event_enable_trigger_ops; | ||
| 1180 | else | ||
| 1181 | ops = param ? &event_disable_count_trigger_ops : | ||
| 1182 | &event_disable_trigger_ops; | ||
| 1183 | |||
| 1184 | return ops; | ||
| 1185 | } | ||
| 1186 | |||
| 1187 | static struct event_command trigger_enable_cmd = { | ||
| 1188 | .name = ENABLE_EVENT_STR, | ||
| 1189 | .trigger_type = ETT_EVENT_ENABLE, | ||
| 1190 | .func = event_enable_trigger_func, | ||
| 1191 | .reg = event_enable_register_trigger, | ||
| 1192 | .unreg = event_enable_unregister_trigger, | ||
| 1193 | .get_trigger_ops = event_enable_get_trigger_ops, | ||
| 1194 | }; | ||
| 1195 | |||
| 1196 | static struct event_command trigger_disable_cmd = { | ||
| 1197 | .name = DISABLE_EVENT_STR, | ||
| 1198 | .trigger_type = ETT_EVENT_ENABLE, | ||
| 1199 | .func = event_enable_trigger_func, | ||
| 1200 | .reg = event_enable_register_trigger, | ||
| 1201 | .unreg = event_enable_unregister_trigger, | ||
| 1202 | .get_trigger_ops = event_enable_get_trigger_ops, | ||
| 1203 | }; | ||
| 1204 | |||
| 1205 | static __init void unregister_trigger_enable_disable_cmds(void) | ||
| 1206 | { | ||
| 1207 | unregister_event_command(&trigger_enable_cmd); | ||
| 1208 | unregister_event_command(&trigger_disable_cmd); | ||
| 1209 | } | ||
| 1210 | |||
| 1211 | static __init int register_trigger_enable_disable_cmds(void) | ||
| 1212 | { | ||
| 1213 | int ret; | ||
| 1214 | |||
| 1215 | ret = register_event_command(&trigger_enable_cmd); | ||
| 1216 | if (WARN_ON(ret < 0)) | ||
| 1217 | return ret; | ||
| 1218 | ret = register_event_command(&trigger_disable_cmd); | ||
| 1219 | if (WARN_ON(ret < 0)) | ||
| 1220 | unregister_trigger_enable_disable_cmds(); | ||
| 1221 | |||
| 1222 | return ret; | ||
| 1223 | } | ||
| 1224 | |||
| 867 | static __init int register_trigger_traceon_traceoff_cmds(void) | 1225 | static __init int register_trigger_traceon_traceoff_cmds(void) |
| 868 | { | 1226 | { |
| 869 | int ret; | 1227 | int ret; |
| @@ -883,6 +1241,7 @@ __init int register_trigger_cmds(void) | |||
| 883 | register_trigger_traceon_traceoff_cmds(); | 1241 | register_trigger_traceon_traceoff_cmds(); |
| 884 | register_trigger_snapshot_cmd(); | 1242 | register_trigger_snapshot_cmd(); |
| 885 | register_trigger_stacktrace_cmd(); | 1243 | register_trigger_stacktrace_cmd(); |
| 1244 | register_trigger_enable_disable_cmds(); | ||
| 886 | 1245 | ||
| 887 | return 0; | 1246 | return 0; |
| 888 | } | 1247 | } |
