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/trace | |
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/trace')
-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 | } |