tracing: Inter-event (e.g. latency) support
Subsystems (3)
v3 cover tracing: Inter-event (e.g. latency) support
Hi,
This is V3 of the inter-event tracing patchset, addressing input from V2:
- split out standalone bugfixes and cleanups to a separate patchset
- moved hunks around and merged patches as suggested
- added inter-event note in config option
- updates to duplicates patch [Vedang Patel]
- various memory allocation checks and use-after-free bugfix [Rajvi Jingar]
- various code improvements as suggested by Steve
- switched hist_field_flags over to using shifts
- added comments about synth_event mutex usage
- added example and explanation of field variables
- fixed field variable synth_event_mutex bug
- commented the whitespace handling in hist_trigger_func()
(the extra complexity is needed to allow spaces in actions)
- use smp_processor_id() instead of raw_ for cpu field
- added better description of aliases
- use static arrays instead of kmalloc for hist_err code, along with various other cleanups
- removed 'tracing: Add support for dynamic tracepoints' since I was mistaken about the need for this
Changes from V1:
There are too many changes to list in detail, most of them directly addressing input from V1, but here are the major changes from V1 (thanks to everyone who reviewed V1 and thanks to both Vedang Patel and Baohong Liu for their contributions and included patches):
- cleaned up the event format
- add types to synthetic event fields
- change to mutex ordering to avoid splat
- suppress output of the contributing trace events into the trace buffer, unless explicitly enabled
- changed from sched_wakeup to sched_waking in the examples
- extended timestamp updates
- use a flag to signify dynamic tracepoints, rather than via api changes
- recursion level fixes
- removal of the possibility of tracing_map duplicates (Vedang)
- removal of duplicate-merging code (Vedang)
- max buffer absolute timestamp fix (Baohong)
- separate variable definition and assignment (Baohong)
- make variables instance-safe
- split a couple larger patches into smaller, various refactoring
- string handling fixes
- use function pointer for synthetic tracepoint func
- use union for actions
- various clean-ups as mentioned in review
- Documentation updates
- special-case recursion allowance for synthetic event generation
NOTE: The first patch in the series, 'tracing: Exclude 'generic fields' from histograms' should go in regardless of the rest, since it fixes a bug in existing code.
Thanks,
Tom
This patchset adds support for 'inter-event' quantities to the trace event subsystem. The most important example of inter-event quantities are latencies, or the time differences between two events.
One of the main motivations for adding this capability is to provide a general-purpose base that existing existing tools such as the -RT latency_hist patchset can be built upon, while at the same time providing a simple way for users to track latencies (or any inter-event quantity) generically between any two events.
Previous -RT latency_hist patchsets that take advantage of the trace event subsystem have been submitted, but they essentially hard-code special-case tracepoints and application logic in ways that can't be reused. It seemed to me that rather than providing a one-off patchset devoted specifically to generating the specific histograms in the latency_hist patchset, it should be possible to build the same functionality on top of a generic layer allowing users to do similar things for other non-latency_hist applications.
In addition to preliminary patches that add some basic missing functionality such as a common ringbuffer-derived timestamp and dynamically-creatable tracepoints, the overall patchset is divided up into a few different areas that combine to produce the overall goal (The Documentation patch explains all the details):
- variables and simple expressions required to calculate a latency
In order to calculate a latency or any inter-event value, something from one event needs to be saved and later retrieved, and some operation such as subtraction or addition is performed on it. This means some minimal form of variables and expressions, which the first set of patches implements. Saving and retrieving events to use in a latency calculation is normally done using a hash table, and that's exactly what we have with trace event hist triggers, so that's where variables are instantiated, set, and retrieved. Basically, variables are set on one entry and retrieved and used by a 'matching' event.
- 'synthetic' events, combining variables from other events
The trace event interface is based on pseudo-files associated with individual events, so it wouldn't really make sense to have quantities derived from multiple events attached to any one of those events. For that reason, the patchset implements a means of combining variables from other events into a separate 'synthetic' event, which can be treated as if it were just like any other trace event in the system.
- 'actions' generating synthetic events, among other things
Variables and synthetic events provide the data and data structure for new events, but something still needs to actually generate an event using that data. 'Actions' are expanded to provide that capability. Though it hasn't been explicitly called as much before, the default 'action' currently for a hist trigger is to update the matching histogram entry's sum values. This patchset essentially expands that to provide a new 'onmatch.trace(event)' action that can be used to have one event generate another. The mechanism is extensible to other actions, and in fact the patchset also includes another, 'onmax(var).save(field,...)' that can be used to save context whenever a value exceeds the previous maximum (something also needed by latency_hist).
I'm submitting the patchset (based on tracing/for-next) as an RFC not only to get comments, but because there are still some problems I haven't fixed yet...
Here are some examples that should make things less abstract.
====
Example - wakeup latency
====
This basically implements the -RT latency_hist 'wakeup_latency' histogram using the synthetic events, variables, and actions described. The output below is from a run of cyclictest using the following command:
# rt-tests/cyclictest -p 80 -n -s -t 2
What we're measuring the latency of is the time between when a thread (of cyclictest) is awakened and when it's scheduled in. To do that we add triggers to sched_waking and sched_switch with the appropriate variables, and on a matching sched_switch event, generate a synthetic 'wakeup_latency' event. Since it's just another trace event like any other, we can also define a histogram on that event, the output of which is what we see displayed when reading the wakeup_latency 'hist' file.
First, we create a synthetic event called wakeup_latency, that creates 3 fields which will reference variables from other events:
# echo 'wakeup_latency u64 lat; pid_t pid; int prio' >> \
/sys/kernel/debug/tracing/synthetic_events Next we add a trigger to sched_waking, which saves the value of the '$common_timestamp' when that event is hit in a variable, ts0. Note that this happens only when 'comm==cyclictest'.
Also, '$common_timestamp' is a new field defined on every event (if needed - if there are no users of timestamps in a trace, timestamps won't be saved and there's no additional overhead from that).
# echo 'hist:keys=pid:ts0=$common_timestamp.usecs if comm=="cyclictest"' >> \
/sys/kernel/debug/tracing/events/sched/sched_waking/trigger Next, we add a trigger to sched_switch. When the pid being switched to matches the pid woken up by a previous sched_waking event, this event grabs the ts0 variable saved on that event, takes the difference between it and the current sched_switch's $common_timestamp, and assigns it to a new 'wakeup_lat' variable. It then generates the wakeup_latency synthetic event defined earlier by 'invoking' it as a function using as parameters the wakeup_lat variable and two sched_switch event fields directly:
# echo 'hist:keys=next_pid:wakeup_lat=$common_timestamp.usecs-$ts0: \
onmatch(sched.sched_waking).wakeup_latency($wakeup_lat,next_pid,next_prio) \
if next_comm=="cyclictest"' >> \
/sys/kernel/debug/tracing/events/sched/sched_switch/trigger Finally, all we have left to do is create a standard histogram simply naming the fields of the wakeup_latency synthetic event:
# echo 'hist:keys=pid,prio,lat:sort=pid,lat' >> \
/sys/kernel/debug/tracing/events/synthetic/wakeup_latency/triggerAt any time, we can see the histogram output by simply reading the synthetic/wakeup_latency/hist file:
# cat /sys/kernel/debug/tracing/events/synthetic/wakeup_latency/hist
# event histogram
#
# trigger info: hist:keys=pid,prio,lat:vals=hitcount:sort=pid,lat:size=2048 [active]
# { pid: 2418, prio: 120, lat: 6 } hitcount: 21
{ pid: 2418, prio: 120, lat: 7 } hitcount: 62
{ pid: 2418, prio: 120, lat: 8 } hitcount: 44
{ pid: 2418, prio: 120, lat: 9 } hitcount: 34
{ pid: 2418, prio: 120, lat: 10 } hitcount: 16
{ pid: 2418, prio: 120, lat: 11 } hitcount: 5
{ pid: 2418, prio: 120, lat: 12 } hitcount: 2
{ pid: 2418, prio: 120, lat: 13 } hitcount: 2
{ pid: 2418, prio: 120, lat: 14 } hitcount: 1
{ pid: 2418, prio: 120, lat: 15 } hitcount: 1
{ pid: 2418, prio: 120, lat: 16 } hitcount: 1
{ pid: 2418, prio: 120, lat: 18 } hitcount: 2
{ pid: 2418, prio: 120, lat: 19 } hitcount: 1
{ pid: 2418, prio: 120, lat: 20 } hitcount: 1
{ pid: 2418, prio: 120, lat: 21 } hitcount: 1
{ pid: 2418, prio: 120, lat: 22 } hitcount: 1
{ pid: 2418, prio: 120, lat: 23 } hitcount: 1
{ pid: 2418, prio: 120, lat: 56 } hitcount: 1
{ pid: 2418, prio: 120, lat: 60 } hitcount: 1
{ pid: 2418, prio: 120, lat: 123 } hitcount: 1
{ pid: 2419, prio: 19, lat: 4 } hitcount: 12
{ pid: 2419, prio: 19, lat: 5 } hitcount: 230
{ pid: 2419, prio: 19, lat: 6 } hitcount: 343
{ pid: 2419, prio: 19, lat: 7 } hitcount: 485
{ pid: 2419, prio: 19, lat: 8 } hitcount: 574
{ pid: 2419, prio: 19, lat: 9 } hitcount: 164
{ pid: 2419, prio: 19, lat: 10 } hitcount: 51
{ pid: 2419, prio: 19, lat: 11 } hitcount: 36
{ pid: 2419, prio: 19, lat: 12 } hitcount: 23
{ pid: 2419, prio: 19, lat: 13 } hitcount: 16
{ pid: 2419, prio: 19, lat: 14 } hitcount: 13
{ pid: 2419, prio: 19, lat: 15 } hitcount: 5
{ pid: 2419, prio: 19, lat: 16 } hitcount: 6
{ pid: 2419, prio: 19, lat: 17 } hitcount: 5
{ pid: 2419, prio: 19, lat: 18 } hitcount: 1
{ pid: 2419, prio: 19, lat: 19 } hitcount: 2
{ pid: 2419, prio: 19, lat: 26 } hitcount: 1
{ pid: 2419, prio: 19, lat: 29 } hitcount: 1
{ pid: 2419, prio: 19, lat: 37 } hitcount: 1
{ pid: 2419, prio: 19, lat: 38 } hitcount: 1
{ pid: 2420, prio: 19, lat: 4 } hitcount: 1
{ pid: 2420, prio: 19, lat: 5 } hitcount: 45
{ pid: 2420, prio: 19, lat: 6 } hitcount: 96
{ pid: 2420, prio: 19, lat: 7 } hitcount: 227
{ pid: 2420, prio: 19, lat: 8 } hitcount: 558
{ pid: 2420, prio: 19, lat: 9 } hitcount: 236
{ pid: 2420, prio: 19, lat: 10 } hitcount: 67
{ pid: 2420, prio: 19, lat: 11 } hitcount: 27
{ pid: 2420, prio: 19, lat: 12 } hitcount: 17
{ pid: 2420, prio: 19, lat: 13 } hitcount: 12
{ pid: 2420, prio: 19, lat: 14 } hitcount: 11
{ pid: 2420, prio: 19, lat: 15 } hitcount: 8
{ pid: 2420, prio: 19, lat: 16 } hitcount: 6
{ pid: 2420, prio: 19, lat: 17 } hitcount: 3
{ pid: 2420, prio: 19, lat: 18 } hitcount: 1
{ pid: 2420, prio: 19, lat: 23 } hitcount: 1
{ pid: 2420, prio: 19, lat: 25 } hitcount: 1
{ pid: 2420, prio: 19, lat: 26 } hitcount: 1
{ pid: 2420, prio: 19, lat: 434 } hitcount: 1 Totals:
Hits: 3488
Entries: 59
Dropped: 0 The above output uses the .usecs modifier to common_timestamp, so the latencies are reported in microseconds. The default, without the modifier, is nanoseconds, but that's too fine-grained to put directly into a histogram - for that however we can use the .log2 modifier on the 'lat' key. Otherwise the rest is the same:
# echo 'wakeup_latency u64 lat; pid_t pid; int prio' >> \
/sys/kernel/debug/tracing/synthetic_events # echo 'hist:keys=pid:ts0=$common_timestamp if comm=="cyclictest"' >> \
/sys/kernel/debug/tracing/events/sched/sched_waking/trigger # echo 'hist:keys=next_pid:wakeup_lat=$common_timestamp-$ts0: \
onmatch(sched.sched_waking).wakeup_latency($wakeup_lat,next_pid,next_prio) \
if next_comm=="cyclictest"' >> \
/sys/kernel/debug/tracing/events/sched/sched_switch/trigger # echo 'hist:keys=pid,prio,lat.log2:sort=pid,lat' >> \
/sys/kernel/debug/tracing/events/synthetic/wakeup_latency/trigger# cat /sys/kernel/debug/tracing/events/synthetic/wakeup_latency/hist
# event histogram
#
# trigger info: hist:keys=pid,prio,lat.log2:vals=hitcount:sort=pid,lat.log2:size=2048 [active]
# { pid: 2457, prio: 120, lat: ~ 2^13 } hitcount: 99
{ pid: 2457, prio: 120, lat: ~ 2^14 } hitcount: 91
{ pid: 2457, prio: 120, lat: ~ 2^15 } hitcount: 8
{ pid: 2458, prio: 19, lat: ~ 2^13 } hitcount: 1437
{ pid: 2458, prio: 19, lat: ~ 2^14 } hitcount: 519
{ pid: 2458, prio: 19, lat: ~ 2^15 } hitcount: 11
{ pid: 2458, prio: 19, lat: ~ 2^16 } hitcount: 2
{ pid: 2458, prio: 19, lat: ~ 2^18 } hitcount: 1
{ pid: 2459, prio: 19, lat: ~ 2^13 } hitcount: 874
{ pid: 2459, prio: 19, lat: ~ 2^14 } hitcount: 442
{ pid: 2459, prio: 19, lat: ~ 2^15 } hitcount: 4 Totals:
Hits: 3488
Entries: 11
Dropped: 0 ====
Example - wakeup latency with onmax()
====
This example is the same as the previous ones, but here we're additionally using the onmax() action to save some context (several fields of the sched_switch event) whenever the latency (wakeup_lat) exceeds the previous maximum.
As with the similar functionality of the -RT latency_hist histograms, it's useful to be able to capture information about the previous process, which potentially could have contributed to the maximum latency that was saved.
# echo 'wakeup_latency u64 lat; pid_t pid; int prio' >> \
/sys/kernel/debug/tracing/synthetic_events # echo 'hist:keys=pid:ts0=$common_timestamp.usecs if comm=="cyclictest"' >> \
/sys/kernel/debug/tracing/events/sched/sched_waking/trigger Here we add an onmax() action that saves some important fields of the sched_switch event along with the maximum, in addition to sending some of the same data to the synthetic event:
# echo 'hist:keys=next_pid:wakeup_lat=$common_timestamp.usecs-$ts0: \
onmax($wakeup_lat).save(next_comm,prev_pid,prev_prio,prev_comm): \
onmatch(sched.sched_waking).wakeup_latency($wakeup_lat,next_pid,next_prio) \
if next_comm=="cyclictest"' >> \
/sys/kernel/debug/tracing/events/sched/sched_switch/trigger # echo 'hist:keys=pid,prio,lat:sort=pid,lat' >> \
/sys/kernel/debug/tracing/events/synthetic/wakeup_latency/trigger To see the maximums and associated data for each pid, cat the sched_switch event, as that's the event the onmax() action is associated with:
# cat /sys/kernel/debug/tracing/events/sched/sched_switch/hist
# event histogram
#
# trigger info: hist:keys=next_pid:vals=hitcount:wakeup_lat=$common_timestamp.usecs-$ts0:sort=hitcount:size=2048:clock=global:onmax($wakeup_lat).save(next_comm,prev_pid,prev_prio,prev_comm):onmatch(sched.sched_waking).wakeup_latency($wakeup_lat,next_pid,next_prio) if next_comm=="cyclictest" [active]
# { next_pid: 2803 } hitcount: 198
max: 55 next_comm: cyclictest prev_pid: 0
prev_prio: 120 prev_comm: swapper/2
{ next_pid: 2805 } hitcount: 1319
max: 53 next_comm: cyclictest prev_pid: 0
prev_prio: 120 prev_comm: swapper/1
{ next_pid: 2804 } hitcount: 1970
max: 79 next_comm: cyclictest prev_pid: 0
prev_prio: 120 prev_comm: swapper/0
Totals:
Hits: 3487
Entries: 3
Dropped: 0
And, verifying, we can see that the max latencies captured above match the highest latencies for each thread in the wakeup_latency histogram:
# cat /sys/kernel/debug/tracing/events/synthetic/wakeup_latency/hist
# event histogram
#
# trigger info: hist:keys=pid,prio,lat:vals=hitcount:sort=pid,lat:size=2048 [active]
# { pid: 2803, prio: 120, lat: 6 } hitcount: 12
{ pid: 2803, prio: 120, lat: 7 } hitcount: 42 ...
{ pid: 2803, prio: 120, lat: 22 } hitcount: 2
{ pid: 2803, prio: 120, lat: 55 } hitcount: 1
{ pid: 2804, prio: 19, lat: 4 } hitcount: 5
{ pid: 2804, prio: 19, lat: 5 } hitcount: 188
...
{ pid: 2804, prio: 19, lat: 30 } hitcount: 1
{ pid: 2804, prio: 19, lat: 79 } hitcount: 1
{ pid: 2805, prio: 19, lat: 5 } hitcount: 19
{ pid: 2805, prio: 19, lat: 6 } hitcount: 73
...
{ pid: 2805, prio: 19, lat: 44 } hitcount: 1
{ pid: 2805, prio: 19, lat: 53 } hitcount: 1
Totals:
Hits: 3487
Entries: 57
Dropped: 0
====
Example - combined wakeup and switchtime (wakeupswitch) latency
====
Finally, this example is quite a bit more involved, but that's because it implements 3 latencies, one which is a combination of the other two. This also, is something that the -RT latency_hist patchset does and which this patchset adds generic support for.
The latency_hist patchset creates a few individual latency histograms but also combines them into larger overall combined histograms. For example, the time between when a thread is awakened and when it actually continues executing in userspace is something covered by a histogram, but it's also broken down into two sub-histograms, one covering the time between sched_waking and the time the thread is scheduled in (wakeup_latency as above), and the time between when the thread is scheduled in and the time it actually begins executing again (return from sys_nanosleep), covered by a separate switchtime_latency histogram.
The below combines the wakeup_latency histogram from before, adds a new switchtime_latency histogram, and another, wakeupswitch_latency, that's a combination of the other two.
There isn't anything really new here, other than that the use of the addition operator to add two latencies to produce the wakeupswitch_latency.
First, we create the familiar wakeup_latency histogram:
# echo 'wakeup_latency u64 lat; pid_t pid' >> \
/sys/kernel/debug/tracing/synthetic_events # echo 'hist:keys=pid:ts0=$common_timestamp.usecs \
if comm=="cyclictest"' >> \
/sys/kernel/debug/tracing/events/sched/sched_waking/trigger # echo 'hist:keys=next_pid:wakeup_lat=$common_timestamp.usecs-$ts0:\
onmatch(sched.sched_waking).wakeup_latency($wakeup_lat,next_pid) \
if next_comm=="cyclictest"' >> \
/sys/kernel/debug/tracing/events/sched/sched_switch/trigger Here we save the wakeup_latency lat value as wakeup_lat for use later in the combined latency:
# echo 'hist:keys=pid,lat:wakeup_lat=lat:sort=pid,lat' \
>> /sys/kernel/debug/tracing/events/synthetic/wakeup_latency/trigger Next, we create the switchtime_latency histogram:
# echo 'switchtime_latency u64 lat; pid_t pid' >> \
/sys/kernel/debug/tracing/synthetic_events Here we save the sched_switch next_pid field as 'pid'. This is so we can access the next_pid in the matching sys_exit_nanosleep event.
# echo 'hist:keys=next_pid:pid=next_pid:ts0=$common_timestamp.usecs \
if next_comm=="cyclictest"' >> \
/sys/kernel/debug/tracing/events/sched/sched_switch/trigger # echo 'hist:keys=common_pid:switchtime_lat=$common_timestamp.usecs-$ts0: \
onmatch(sched.sched_switch).switchtime_latency($switchtime_lat,$pid)' \
>> /sys/kernel/debug/tracing/events/syscalls/sys_exit_nanosleep/trigger # echo 'hist:keys=pid,lat:sort=pid,lat' \
>> /sys/kernel/debug/tracing/events/synthetic/switchtime_latency/trigger Finally, we create the combined wakeupswitch_latency:
# echo 'wakeupswitch_latency u64 lat; pid_t pid' >> \
/sys/kernel/debug/tracing/synthetic_events Here we calculate the combined latency using the saved $wakeup_lat variable from the wakeup_latency histogram and the lat value of the switchtime_latency, save it as ws_lat and then use it to generate the combined wakeupswitch latency:
# echo 'hist:keys=pid,lat:sort=pid,lat:ws_lat=$wakeup_lat+lat: \
onmatch(synthetic.wakeup_latency).wakeupswitch_latency($ws_lat,pid)' \
>> /sys/kernel/debug/tracing/events/synthetic/switchtime_latency/trigger # echo 'hist:keys=pid,lat:sort=pid,lat' >> \
/sys/kernel/debug/tracing/events/synthetic/wakeupswitch_latency/trigger After running our cyclictest workload, we can now look at each histogram, starting with wakeup_latency:
# cat /sys/kernel/debug/tracing/events/synthetic/wakeup_latency/hist
# event histogram
#
# trigger info: hist:keys=pid,lat:vals=hitcount:wakeup_lat=lat:sort=pid,lat:size=2048 [active]
# { pid: 4146, lat: 6 } hitcount: 8
{ pid: 4146, lat: 7 } hitcount: 50
{ pid: 4146, lat: 8 } hitcount: 53
{ pid: 4146, lat: 9 } hitcount: 34
{ pid: 4146, lat: 10 } hitcount: 22
{ pid: 4146, lat: 11 } hitcount: 6
{ pid: 4146, lat: 12 } hitcount: 4
{ pid: 4146, lat: 13 } hitcount: 1
{ pid: 4146, lat: 14 } hitcount: 4
{ pid: 4146, lat: 15 } hitcount: 2
{ pid: 4146, lat: 16 } hitcount: 2
{ pid: 4146, lat: 17 } hitcount: 3
{ pid: 4146, lat: 19 } hitcount: 1
{ pid: 4146, lat: 20 } hitcount: 2
{ pid: 4146, lat: 21 } hitcount: 4
{ pid: 4146, lat: 24 } hitcount: 1
{ pid: 4146, lat: 53 } hitcount: 1
{ pid: 4147, lat: 4 } hitcount: 1
{ pid: 4147, lat: 5 } hitcount: 156
{ pid: 4147, lat: 6 } hitcount: 344
{ pid: 4147, lat: 7 } hitcount: 560
{ pid: 4147, lat: 8 } hitcount: 540
{ pid: 4147, lat: 9 } hitcount: 195
{ pid: 4147, lat: 10 } hitcount: 50
{ pid: 4147, lat: 11 } hitcount: 38
{ pid: 4147, lat: 12 } hitcount: 26
{ pid: 4147, lat: 13 } hitcount: 13
{ pid: 4147, lat: 14 } hitcount: 12
{ pid: 4147, lat: 15 } hitcount: 10
{ pid: 4147, lat: 16 } hitcount: 3
{ pid: 4147, lat: 17 } hitcount: 2
{ pid: 4147, lat: 18 } hitcount: 4
{ pid: 4147, lat: 19 } hitcount: 2
{ pid: 4147, lat: 20 } hitcount: 1
{ pid: 4147, lat: 21 } hitcount: 1
{ pid: 4147, lat: 26 } hitcount: 1
{ pid: 4147, lat: 35 } hitcount: 1
{ pid: 4147, lat: 59 } hitcount: 1
{ pid: 4148, lat: 5 } hitcount: 38
{ pid: 4148, lat: 6 } hitcount: 229
{ pid: 4148, lat: 7 } hitcount: 219
{ pid: 4148, lat: 8 } hitcount: 486
{ pid: 4148, lat: 9 } hitcount: 181
{ pid: 4148, lat: 10 } hitcount: 59
{ pid: 4148, lat: 11 } hitcount: 27
{ pid: 4148, lat: 12 } hitcount: 23
{ pid: 4148, lat: 13 } hitcount: 16
{ pid: 4148, lat: 14 } hitcount: 7
{ pid: 4148, lat: 15 } hitcount: 7
{ pid: 4148, lat: 16 } hitcount: 6
{ pid: 4148, lat: 17 } hitcount: 2
{ pid: 4148, lat: 18 } hitcount: 2
{ pid: 4148, lat: 19 } hitcount: 4
{ pid: 4148, lat: 20 } hitcount: 3
{ pid: 4148, lat: 25 } hitcount: 1
{ pid: 4148, lat: 26 } hitcount: 1
{ pid: 4148, lat: 27 } hitcount: 1
{ pid: 4148, lat: 29 } hitcount: 2
{ pid: 4148, lat: 47 } hitcount: 1 Totals:
Hits: 3474
Entries: 59
Dropped: 0Here's the switchtime histogram:
# cat /sys/kernel/debug/tracing/events/synthetic/switchtime_latency/hist
# event histogram
#
# trigger info: hist:keys=pid,lat:vals=hitcount:sort=pid,lat:size=2048 [active]
# { pid: 4146, lat: 3 } hitcount: 11
{ pid: 4146, lat: 4 } hitcount: 32
{ pid: 4146, lat: 5 } hitcount: 65
{ pid: 4146, lat: 6 } hitcount: 37
{ pid: 4146, lat: 7 } hitcount: 35
{ pid: 4146, lat: 8 } hitcount: 5
{ pid: 4146, lat: 10 } hitcount: 1
{ pid: 4146, lat: 11 } hitcount: 1
{ pid: 4146, lat: 12 } hitcount: 1
{ pid: 4146, lat: 13 } hitcount: 1
{ pid: 4146, lat: 14 } hitcount: 1
{ pid: 4146, lat: 15 } hitcount: 1
{ pid: 4146, lat: 16 } hitcount: 2
{ pid: 4146, lat: 17 } hitcount: 1
{ pid: 4146, lat: 18 } hitcount: 1
{ pid: 4146, lat: 20 } hitcount: 1
{ pid: 4146, lat: 22 } hitcount: 1
{ pid: 4146, lat: 55 } hitcount: 1
{ pid: 4147, lat: 3 } hitcount: 554
{ pid: 4147, lat: 4 } hitcount: 999
{ pid: 4147, lat: 5 } hitcount: 193
{ pid: 4147, lat: 6 } hitcount: 102
{ pid: 4147, lat: 7 } hitcount: 38
{ pid: 4147, lat: 8 } hitcount: 21
{ pid: 4147, lat: 9 } hitcount: 8
{ pid: 4147, lat: 10 } hitcount: 10
{ pid: 4147, lat: 11 } hitcount: 11
{ pid: 4147, lat: 12 } hitcount: 7
{ pid: 4147, lat: 13 } hitcount: 2
{ pid: 4147, lat: 14 } hitcount: 2
{ pid: 4147, lat: 15 } hitcount: 5
{ pid: 4147, lat: 16 } hitcount: 2
{ pid: 4147, lat: 17 } hitcount: 3
{ pid: 4147, lat: 18 } hitcount: 2
{ pid: 4147, lat: 23 } hitcount: 2
{ pid: 4148, lat: 3 } hitcount: 245
{ pid: 4148, lat: 4 } hitcount: 761
{ pid: 4148, lat: 5 } hitcount: 152
{ pid: 4148, lat: 6 } hitcount: 64
{ pid: 4148, lat: 7 } hitcount: 25
{ pid: 4148, lat: 8 } hitcount: 7
{ pid: 4148, lat: 9 } hitcount: 14
{ pid: 4148, lat: 10 } hitcount: 11
{ pid: 4148, lat: 11 } hitcount: 12
{ pid: 4148, lat: 12 } hitcount: 6
{ pid: 4148, lat: 13 } hitcount: 2
{ pid: 4148, lat: 14 } hitcount: 7
{ pid: 4148, lat: 15 } hitcount: 2
{ pid: 4148, lat: 17 } hitcount: 2
{ pid: 4148, lat: 18 } hitcount: 1
{ pid: 4148, lat: 19 } hitcount: 1
{ pid: 4148, lat: 24 } hitcount: 1
{ pid: 4148, lat: 25 } hitcount: 1
{ pid: 4148, lat: 42 } hitcount: 1 Totals:
Hits: 3474
Entries: 54
Dropped: 0And here's the combined wakeupswitch latency histogram:
# cat /sys/kernel/debug/tracing/events/synthetic/wakeupswitch_latency/hist
# event histogram
#
# trigger info: hist:keys=pid,lat:vals=hitcount:sort=pid,lat:size=2048 [active]
# { pid: 4146, lat: 10 } hitcount: 16
{ pid: 4146, lat: 11 } hitcount: 18
{ pid: 4146, lat: 12 } hitcount: 25
{ pid: 4146, lat: 13 } hitcount: 38
{ pid: 4146, lat: 14 } hitcount: 25
{ pid: 4146, lat: 15 } hitcount: 14
{ pid: 4146, lat: 16 } hitcount: 17
{ pid: 4146, lat: 17 } hitcount: 14
{ pid: 4146, lat: 18 } hitcount: 7
{ pid: 4146, lat: 19 } hitcount: 1
{ pid: 4146, lat: 20 } hitcount: 3
{ pid: 4146, lat: 21 } hitcount: 2
{ pid: 4146, lat: 22 } hitcount: 1
{ pid: 4146, lat: 23 } hitcount: 1
{ pid: 4146, lat: 24 } hitcount: 1
{ pid: 4146, lat: 25 } hitcount: 2
{ pid: 4146, lat: 26 } hitcount: 2
{ pid: 4146, lat: 29 } hitcount: 1
{ pid: 4146, lat: 30 } hitcount: 1
{ pid: 4146, lat: 32 } hitcount: 1
{ pid: 4146, lat: 33 } hitcount: 2
{ pid: 4146, lat: 36 } hitcount: 1
{ pid: 4146, lat: 37 } hitcount: 1
{ pid: 4146, lat: 38 } hitcount: 1
{ pid: 4146, lat: 39 } hitcount: 1
{ pid: 4146, lat: 73 } hitcount: 1
{ pid: 4146, lat: 76 } hitcount: 1
{ pid: 4147, lat: 8 } hitcount: 54
{ pid: 4147, lat: 9 } hitcount: 205
{ pid: 4147, lat: 10 } hitcount: 391
{ pid: 4147, lat: 11 } hitcount: 544
{ pid: 4147, lat: 12 } hitcount: 342
{ pid: 4147, lat: 13 } hitcount: 141
{ pid: 4147, lat: 14 } hitcount: 68
{ pid: 4147, lat: 15 } hitcount: 46
{ pid: 4147, lat: 16 } hitcount: 42
{ pid: 4147, lat: 17 } hitcount: 23
{ pid: 4147, lat: 18 } hitcount: 23
{ pid: 4147, lat: 19 } hitcount: 17
{ pid: 4147, lat: 20 } hitcount: 8
{ pid: 4147, lat: 21 } hitcount: 7
{ pid: 4147, lat: 22 } hitcount: 7
{ pid: 4147, lat: 23 } hitcount: 8
{ pid: 4147, lat: 24 } hitcount: 4
{ pid: 4147, lat: 25 } hitcount: 6
{ pid: 4147, lat: 26 } hitcount: 4
{ pid: 4147, lat: 27 } hitcount: 1
{ pid: 4147, lat: 28 } hitcount: 4
{ pid: 4147, lat: 29 } hitcount: 2
{ pid: 4147, lat: 30 } hitcount: 3
{ pid: 4147, lat: 31 } hitcount: 3
{ pid: 4147, lat: 32 } hitcount: 1
{ pid: 4147, lat: 34 } hitcount: 1
{ pid: 4147, lat: 35 } hitcount: 3
{ pid: 4147, lat: 36 } hitcount: 1
{ pid: 4147, lat: 50 } hitcount: 1
{ pid: 4147, lat: 71 } hitcount: 1
{ pid: 4148, lat: 8 } hitcount: 2
{ pid: 4148, lat: 9 } hitcount: 100
{ pid: 4148, lat: 10 } hitcount: 156
{ pid: 4148, lat: 11 } hitcount: 340
{ pid: 4148, lat: 12 } hitcount: 341
{ pid: 4148, lat: 13 } hitcount: 139
{ pid: 4148, lat: 14 } hitcount: 55
{ pid: 4148, lat: 15 } hitcount: 42
{ pid: 4148, lat: 16 } hitcount: 24
{ pid: 4148, lat: 17 } hitcount: 17
{ pid: 4148, lat: 18 } hitcount: 18
{ pid: 4148, lat: 19 } hitcount: 17
{ pid: 4148, lat: 20 } hitcount: 7
{ pid: 4148, lat: 21 } hitcount: 4
{ pid: 4148, lat: 22 } hitcount: 7
{ pid: 4148, lat: 23 } hitcount: 6
{ pid: 4148, lat: 24 } hitcount: 3
{ pid: 4148, lat: 25 } hitcount: 7
{ pid: 4148, lat: 26 } hitcount: 5
{ pid: 4148, lat: 27 } hitcount: 3
{ pid: 4148, lat: 28 } hitcount: 3
{ pid: 4148, lat: 29 } hitcount: 1
{ pid: 4148, lat: 30 } hitcount: 4
{ pid: 4148, lat: 31 } hitcount: 1
{ pid: 4148, lat: 32 } hitcount: 2
{ pid: 4148, lat: 33 } hitcount: 3
{ pid: 4148, lat: 34 } hitcount: 1
{ pid: 4148, lat: 36 } hitcount: 1
{ pid: 4148, lat: 37 } hitcount: 2
{ pid: 4148, lat: 38 } hitcount: 1
{ pid: 4148, lat: 41 } hitcount: 1
{ pid: 4148, lat: 53 } hitcount: 1
{ pid: 4148, lat: 71 } hitcount: 1 Totals:
Hits: 3474
Entries: 90
Dropped: 0Finally, just to show that synthetic events are indeed just like any other event as far as the event subsystem is concerned, we can enable the synthetic events and see the events appear in the trace buffer:
# echo 1 > /sys/kernel/debug/tracing/events/synthetic/wakeup_latency/enable
# echo 1 > /sys/kernel/debug/tracing/events/synthetic/switchtime_latency/enable
# echo 1 > /sys/kernel/debug/tracing/events/synthetic/wakeupswitch_latency/enableBelow is a snippet of the contents of the trace file produced when the above histograms were generated:
# cat /sys/kernel/debug/tracing/trace
# tracer: nop
#
# entries-in-buffer/entries-written: 10503/10503 #P:4
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
<idle>-0 [001] d..2 23532.240146: wakeup_latency: lat=4, pid=6853
cyclictest-6853 [001] .... 23532.240153: switchtime_latency: lat=7, pid=6853
cyclictest-6853 [001] .... 23532.240157: wakeupswitch_latency: lat=11, pid=6853
gnome-terminal--2500 [001] d..2 23532.240672: wakeup_latency: lat=5, pid=6854
cyclictest-6854 [001] .... 23532.240676: switchtime_latency: lat=4, pid=6854
cyclictest-6854 [001] .... 23532.240677: wakeupswitch_latency: lat=9, pid=6854
gnome-terminal--2500 [001] d..2 23532.241169: wakeup_latency: lat=4, pid=6853
cyclictest-6853 [001] .... 23532.241172: switchtime_latency: lat=3, pid=6853
cyclictest-6853 [001] .... 23532.241174: wakeupswitch_latency: lat=7, pid=6853
<idle>-0 [001] d..2 23532.242189: wakeup_latency: lat=6, pid=6853
cyclictest-6853 [001] .... 23532.242195: switchtime_latency: lat=8, pid=6853
<idle>-0 [000] d..2 23532.242196: wakeup_latency: lat=12, pid=6854
cyclictest-6853 [001] .... 23532.240146: wakeupswitch_latency: lat=14, pid=6853
cyclictest-6854 [000] .... 23532.242196: switchtime_latency: lat=4, pid=6854
<idle>-0 [001] d..2 23532.240146: wakeup_latency: lat=2, pid=6853
cyclictest-6854 [000] .... 23532.242196: wakeupswitch_latency: lat=16, pid=6854
cyclictest-6853 [001] .... 23532.240146: switchtime_latency: lat=3, pid=6853
...One quick note about usage - the introduction of variables and actions obviously makes it harder to determine the cause of a hist trigger command failure - 'Invalid argument' is no longer sufficient in many cases.
For that reason, a new 'extended error' mechanism has been added to hist triggers, initially focused on variable and action-related errors, but something that could possibly expanded to other error conditions later.
To make use of it, simply read the 'hist' file of the event that was the target of the command.
In this example, we've entered the same command twice, resulting in an attempt to define the same variable (ts0) twice. After seeing the 'Invalid argument' error for the command, we read the same event's hist file and see a message to that effect at the bottom of the file:
# echo 'hist:keys=pid:ts0=$common_timestamp.usecs if comm=="cyclictest"' >> \
/sys/kernel/debug/tracing/events/sched/sched_waking/trigger # echo 'hist:keys=pid:ts0=$common_timestamp.usecs if comm=="cyclictest"' >> \
/sys/kernel/debug/tracing/events/sched/sched_waking/trigger-su: echo: write error: Invalid argument
# cat /sys/kernel/debug/tracing/events/sched/sched_waking/hist # event histogram
#
#
Totals:
Hits: 0
Entries: 0
Dropped: 0
Duplicates: 0
ERROR: Variable already defined: ts0
Last command: keys=pid:ts0=$common_timestamp.usecs if comm=="cyclictest"
The following changes since commit b47f93fc0b87b36eccf716b0c018f3a71e1f1111:
tracing: Reimplement log2 (2017-09-22 11:15:48 -0500)
are available in the git repository at:
https://github.com/tzanussi/linux-trace-inter-event.git tzanussi/inter-event-v3
https://github.com/tzanussi/linux-trace-inter-event/tree/tzanussi/inter-event-v3
Tom Zanussi (31):
ring-buffer: Add interface for setting absolute time stamps
ring-buffer: Redefine the unimplemented RINGBUF_TIME_TIME_STAMP
tracing: Give event triggers access to ring_buffer_event
tracing: Add ring buffer event param to hist field functions
tracing: Break out hist trigger assignment parsing
tracing: Add hist trigger timestamp support
tracing: Add per-element variable support to tracing_map
tracing: Add hist_data member to hist_field
tracing: Add usecs modifier for hist trigger timestamps
tracing: Add variable support to hist triggers
tracing: Account for variables in named trigger compatibility
tracing: Move get_hist_field_flags()
tracing: Add simple expression support to hist triggers
tracing: Generalize per-element hist trigger data
tracing: Pass tracing_map_elt to hist_field accessor functions
tracing: Add hist_field 'type' field
tracing: Add variable reference handling to hist triggers
tracing: Add hist trigger action hook
tracing: Add support for 'synthetic' events
tracing: Add support for 'field variables'
tracing: Add 'onmatch' hist trigger action support
tracing: Add 'onmax' hist trigger action support
tracing: Allow whitespace to surround hist trigger filter
tracing: Add cpu field for hist triggers
tracing: Add hist trigger support for variable reference aliases
tracing: Add 'last error' error facility for hist triggers
tracing: Add inter-event hist trigger Documentation
tracing: Make tracing_set_clock() non-static
tracing: Add a clock attribute for hist triggers
tracing: Increase trace_recursive_lock() limit for synthetic events
tracing: Add inter-event blurb to HIST_TRIGGERS config option
Vedang Patel (2):
tracing: Add support to detect and avoid duplicates
tracing: Remove code which merges duplicates
Documentation/trace/events.txt | 431 ++++ include/linux/ring_buffer.h | 14 +- include/linux/trace_events.h | 14 +- kernel/trace/Kconfig | 3 + kernel/trace/ring_buffer.c | 126 +- kernel/trace/trace.c | 52 +- kernel/trace/trace.h | 18 +- kernel/trace/trace_events_hist.c | 4473 +++++++++++++++++++++++++++++++---- kernel/trace/trace_events_trigger.c | 53 +- kernel/trace/tracing_map.c | 232 +- kernel/trace/tracing_map.h | 18 +- 11 files changed, 4854 insertions(+), 580 deletions(-)
Signature · 1 line
1.9.3
PATCH v3 1/33 tracing: Add support to detect and avoid duplicates
From: Vedang Patel <[email protected]>
A duplicate in the tracing_map hash table is when 2 different entries have the same key and, as a result, the key_hash. This is possible due to a race condition in the algorithm. This race condition is inherent to the algorithm and not a bug. This was fine because, until now, we were only interested in the sum of all the values related to a particular key (the duplicates are dealt with in tracing_map_sort_entries()). But, with the inclusion of variables[1], we are interested in individual values. So, it will not be clear what value to choose when there are duplicates. So, the duplicates need to be removed.
The duplicates can occur in the code in the following scenarios:
- A thread is in the process of adding a new element. It has successfully executed cmpxchg() and inserted the key. But, it is still not done acquiring the trace_map_elt struct, populating it and storing the pointer to the struct in the value field of tracing_map hash table. If another thread comes in at this time and wants to add an element with the same key, it will not see the current element and add a new one.
- There are multiple threads trying to execute cmpxchg at the same time, one of the threads will succeed and the others will fail. The ones which fail will go ahead increment 'idx' and add a new element there creating a duplicate.
This patch detects and avoids the first condition by asking the thread which detects the duplicate to loop one more time. There is also a possibility of infinite loop if the thread which is trying to insert goes to sleep indefinitely and the one which is trying to insert a new element detects a duplicate. Which is why, the thread loops for map_size iterations before returning NULL.
The second scenario is avoided by preventing the threads which failed cmpxchg() from incrementing idx. This way, they will loop around and check if the thread which succeeded in executing cmpxchg() had the same key.
[1] http://lkml.kernel.org/r/[email protected]
--- kernel/trace/tracing_map.c | 41 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-)
kernel/trace/tracing_map.c +36 -5
@@ -414,7 +414,9 @@ static inline bool keys_match(void *key, void *test_key, unsigned key_size) __tracing_map_insert(struct tracing_map *map, void *key, bool lookup_only) { u32 idx, key_hash, test_key; +int dup_try =0 ;struct tracing_map_entry *entry; +struct tracing_map_elt *val; key_hash =jhash (key, map->key_size,0 );if (key_hash ==0 )
@@ -426,11 +428,33 @@ static inline bool keys_match(void *key, void *test_key, unsigned key_size) entry =TRACING_MAP_ENTRY (map->map, idx); test_key = entry->key; -if (test_key && test_key == key_hash && entry->val && -keys_match (key, entry->val->key, map->key_size)) { -if (!lookup_only) -atomic64_inc (&map->hits); -return entry->val; +if (test_key && test_key == key_hash) { + val =READ_ONCE (entry->val); +if (val && +keys_match (key, val->key, map->key_size)) { +if (!lookup_only) +atomic64_inc (&map->hits); +return val; + }else if (unlikely (!val)) { +/* +* The key is present. But, val (pointer to elt +* struct) is still NULL. which means some other +* thread is in the process of inserting an +* element. +* +* On top of that, it's key_hash is same as the +* one being inserted right now. So, it's +* possible that the element has the same +* key as well. +*/ + + dup_try++; +if (dup_try > map->map_size) { +atomic64_inc (&map->drops); +break ; + } +continue ; + } }if (!test_key) {
@@ -452,6 +476,13 @@ static inline bool keys_match(void *key, void *test_key, unsigned key_size)atomic64_inc (&map->hits);return entry->val; + }else { +/* +* cmpxchg() failed. Loop around once +* more to check what key was inserted. +*/ + dup_try++; +continue ; } }
Signature · 1 line
1.9.3
PATCH v3 2/33 tracing: Remove code which merges duplicates
In reply to Tom ZanussiFrom: Vedang Patel <[email protected]>
We now have the logic to detect and remove duplicates in the tracing_map hash table. The code which merges duplicates in the histogram is redundant now. So, modify this code just to detect duplicates. The duplication detection code is still kept to ensure that any rare race condition which might cause duplicates does not go unnoticed.
--- kernel/trace/trace_events_hist.c | 11 ------ kernel/trace/tracing_map.c | 83 +++------------------------------------- kernel/trace/tracing_map.h | 7 ---- 3 files changed, 6 insertions(+), 95 deletions(-)
kernel/trace/trace_events_hist.c +0 -11
@@ -340,16 +340,6 @@ static int hist_trigger_elt_comm_alloc(struct tracing_map_elt *elt)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;
@@ -360,7 +350,6 @@ static void hist_trigger_elt_comm_init(struct tracing_map_elt *elt)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, };
kernel/trace/tracing_map.c +6 -77
113 lines not on this page · raw message
kernel/trace/tracing_map.h +0 -7
@@ -214,11 +214,6 @@ struct tracing_map {* Element allocation occurs before tracing begins, when the * tracing_map_init() call is made by client code. * -* @elt_copy: At certain points in the lifetime of an element, it may -* need to be copied. The copy should include a copy of the -* client-allocated data, which can be copied into the 'to' -* element from the 'from' element. -* * @elt_free: When a tracing_map_elt is freed, this function is called * and allows client-allocated per-element data to be freed. *
@@ -232,8 +227,6 @@ struct tracing_map {*/ struct tracing_map_ops {int (*elt_alloc)(struct tracing_map_elt *elt); -void (*elt_copy)(struct tracing_map_elt *to, -struct tracing_map_elt *from);void (*elt_free)(struct tracing_map_elt *elt);void (*elt_clear)(struct tracing_map_elt *elt);void (*elt_init)(struct tracing_map_elt *elt);
Signature · 1 line
1.9.3
PATCH v3 3/33 ring-buffer: Add interface for setting absolute time stamps
In reply to Tom ZanussiDefine a new function, tracing_set_time_stamp_abs(), which can be used to enable or disable the use of absolute timestamps rather than time deltas for a trace array.
This resets the buffer to prevent a mix of time deltas and absolute timestamps.
Only the interface is added here; a subsequent patch will add the underlying implementation.
--- include/linux/ring_buffer.h | 2 ++ kernel/trace/ring_buffer.c | 11 +++++++++++ kernel/trace/trace.c | 40 +++++++++++++++++++++++++++++++++++++++- kernel/trace/trace.h | 3 +++ 4 files changed, 55 insertions(+), 1 deletion(-)
include/linux/ring_buffer.h +2 -0
@@ -180,6 +180,8 @@ void ring_buffer_normalize_time_stamp(struct ring_buffer *buffer, int cpu, u64 *ts);void ring_buffer_set_clock (struct ring_buffer *buffer,u64 (*clock)(void )); +void ring_buffer_set_time_stamp_abs (struct ring_buffer *buffer,bool abs); +bool ring_buffer_time_stamp_abs (struct ring_buffer *buffer);size_t ring_buffer_page_len (void *page);
kernel/trace/ring_buffer.c +11 -0
@@ -485,6 +485,7 @@ struct ring_buffer {u64 (*clock)(void);struct rb_irq_work irq_work; +bool time_stamp_abs; };struct ring_buffer_iter {
@@ -1379,6 +1380,16 @@ void ring_buffer_set_clock(struct ring_buffer *buffer, buffer->clock = clock; } +void ring_buffer_set_time_stamp_abs (struct ring_buffer *buffer,bool abs) +{ + buffer->time_stamp_abs = abs; +} + +bool ring_buffer_time_stamp_abs (struct ring_buffer *buffer) +{ +return buffer->time_stamp_abs; +} +static void rb_reset_cpu (struct ring_buffer_per_cpu *cpu_buffer);static inline unsigned long rb_page_entries (struct buffer_page *bpage)
kernel/trace/trace.c +39 -1
@@ -2269,7 +2269,7 @@ struct ring_buffer_event * *current_rb = trace_file->tr->trace_buffer.buffer; -if ((trace_file->flags & +if (!ring_buffer_time_stamp_abs (*current_rb) && (trace_file->flags & (EVENT_FILE_FL_SOFT_DISABLED |EVENT_FILE_FL_FILTERED )) && (entry =this_cpu_read (trace_buffered_event))) {/* Try to use the per cpu buffer first */
@@ -6285,6 +6285,44 @@ static int tracing_clock_open(struct inode *inode, struct file *file)return ret; } +int tracing_set_time_stamp_abs (struct trace_array *tr,bool abs) +{ +int ret =0 ; + +mutex_lock (&trace_types_lock); + +if (abs && tr->time_stamp_abs_ref++) + goto out; + +if (!abs) { +if (WARN_ON_ONCE (!tr->time_stamp_abs_ref)) { + ret = -EINVAL ; + goto out; + } + +if (--tr->time_stamp_abs_ref) + goto out; + } + +ring_buffer_set_time_stamp_abs (tr->trace_buffer.buffer, abs); + +/* +* New timestamps may not be consistent with the previous setting. +* Reset the buffer so that it doesn't have incomparable timestamps. +*/ +tracing_reset_online_cpus (&tr->trace_buffer); + +#ifdef CONFIG_TRACER_MAX_TRACE +if (tr->max_buffer.buffer) +ring_buffer_set_time_stamp_abs (tr->max_buffer.buffer, abs); +tracing_reset_online_cpus (&tr->max_buffer); +#endif + out: +mutex_unlock (&trace_types_lock); + +return ret; +} +struct ftrace_buffer_info {struct trace_iterator iter;void *spare;
kernel/trace/trace.h +3 -0
@@ -272,6 +272,7 @@ struct trace_array {/* function tracing enabled */ int function_enabled;#endif +int time_stamp_abs_ref; };enum {
@@ -285,6 +286,8 @@ enum {extern int trace_array_get (struct trace_array *tr);extern void trace_array_put (struct trace_array *tr); +extern int tracing_set_time_stamp_abs (struct trace_array *tr,bool abs); +/* * The global tracer (top) should be the first trace array added, * but we check the flag anyway.
Signature · 1 line
1.9.3
Steven Rostedt
maintainerRe: [PATCH v3 03/33] ring-buffer: Add interface for setting absolute time stamps
kernel/trace/trace.c static int tracing_clock_open(struct inode *inode, struct file *file) · line 6311
Quoted 3 lines · On Fri, 22 Sep 2017 14:59:43 -0500 Tom Zanussi <[email protected]>…
kernel/trace/trace.c +3
-0+/* +* New timestamps may not be consistent with the previous setting. +* Reset the buffer so that it doesn't have incomparable timestamps.
I would like to make the ring buffer robust enough to handle both. Can this be done?
-- Steve
Quoted 3 lines
kernel/trace/trace.c +3
-0+*/ +tracing_reset_online_cpus (&tr->trace_buffer); +
Re: [PATCH v3 03/33] ring-buffer: Add interface for setting absolute time stamps
Hi Steve,
Quoted 10 lines · Steven Rostedt
kernel/trace/trace.c static int tracing_clock_open(struct inode *inode, struct file *file) · line 6311
Quoted 3 lines · On Fri, 22 Sep 2017 14:59:43 -0500 Tom Zanussi <[email protected]>…
kernel/trace/trace.c +3
-0+/* +* New timestamps may not be consistent with the previous setting. +* Reset the buffer so that it doesn't have incomparable timestamps. I would like to make the ring buffer robust enough to handle both. Can this be done?
Probably - I'll take a look at doing this for V4...
Tom
Quoted 5 lines
-- Steve
Quoted 3 lines
kernel/trace/trace.c +3
-0+*/ +tracing_reset_online_cpus (&tr->trace_buffer); +
PATCH v3 4/33 ring-buffer: Redefine the unimplemented RINGBUF_TIME_TIME_STAMP
In reply to Tom ZanussiRINGBUF_TYPE_TIME_STAMP is defined but not used, and from what I can gather was reserved for something like an absolute timestamp feature for the ring buffer, if not a complete replacement of the current time_delta scheme.
This code redefines RINGBUF_TYPE_TIME_STAMP to implement absolute time stamps. Another way to look at it is that it essentially forces extended time_deltas for all events.
The motivation for doing this is to enable time_deltas that aren't dependent on previous events in the ring buffer, making it feasible to use the ring_buffer_event timetamps in a more random-access way, for purposes other than serial event printing.
To set/reset this mode, use tracing_set_timestamp_abs() from the previous interface patch.
--- include/linux/ring_buffer.h | 12 ++--- kernel/trace/ring_buffer.c | 105 ++++++++++++++++++++++++++++++++------------ 2 files changed, 83 insertions(+), 34 deletions(-)
include/linux/ring_buffer.h +7 -5
@@ -36,10 +36,12 @@ struct ring_buffer_event {* array[0] = time delta (28 .. 59) * size = 8 bytes * -* @RINGBUF_TYPE_TIME_STAMP: Sync time stamp with external clock -* array[0] = tv_nsec -* array[1..2] = tv_sec -* size = 16 bytes +* @RINGBUF_TYPE_TIME_STAMP: Absolute timestamp +* Same format as TIME_EXTEND except that the +* value is an absolute timestamp, not a delta +* event.time_delta contains bottom 27 bits +* array[0] = top (28 .. 59) bits +* size = 8 bytes * * <= @RINGBUF_TYPE_DATA_TYPE_LEN_MAX: * Data record
@@ -56,12 +58,12 @@ enum ring_buffer_type {RINGBUF_TYPE_DATA_TYPE_LEN_MAX =28 ,RINGBUF_TYPE_PADDING ,RINGBUF_TYPE_TIME_EXTEND , -/* FIXME: RINGBUF_TYPE_TIME_STAMP not implemented */ RINGBUF_TYPE_TIME_STAMP, };unsigned ring_buffer_event_length (struct ring_buffer_event *event);void *ring_buffer_event_data (struct ring_buffer_event *event); +u64 ring_buffer_event_time_stamp (struct ring_buffer_event *event);/* * ring_buffer_discard_commit will remove an event that has not
kernel/trace/ring_buffer.c +76 -29
242 lines not on this page · raw message
Signature · 1 line
1.9.3
PATCH v3 5/33 tracing: Give event triggers access to ring_buffer_event
In reply to Tom ZanussiThe ring_buffer event can provide a timestamp that may be useful to various triggers - pass it into the handlers for that purpose.
--- include/linux/trace_events.h | 14 ++++++----- kernel/trace/trace.h | 9 +++---- kernel/trace/trace_events_hist.c | 11 +++++---- kernel/trace/trace_events_trigger.c | 47 +++++++++++++++++++++++-------------- 4 files changed, 49 insertions(+), 32 deletions(-)
include/linux/trace_events.h +8 -6
@@ -400,11 +400,13 @@ enum event_trigger_type {extern int filter_match_preds (struct event_filter *filter,void *rec); -extern enum event_trigger_type event_triggers_call (struct trace_event_file *file, -void *rec); -extern void event_triggers_post_call (struct trace_event_file *file, -enum event_trigger_type tt, -void *rec); +extern enum event_trigger_type +event_triggers_call (struct trace_event_file *file,void *rec, +struct ring_buffer_event *event); +extern void +event_triggers_post_call (struct trace_event_file *file, +enum event_trigger_type tt, +void *rec,struct ring_buffer_event *event);bool trace_event_ignore_this_pid (struct trace_event_file *trace_file);
@@ -424,7 +426,7 @@ extern void event_triggers_post_call(struct trace_event_file *file,if (!(eflags &EVENT_FILE_FL_TRIGGER_COND )) {if (eflags &EVENT_FILE_FL_TRIGGER_MODE ) -event_triggers_call (file,NULL ); +event_triggers_call (file,NULL ,NULL );if (eflags &EVENT_FILE_FL_SOFT_DISABLED )return true;if (eflags &EVENT_FILE_FL_PID_FILTER )
kernel/trace/trace.h +5 -4
@@ -1293,7 +1293,7 @@ static inline void trace_buffer_unlock_commit(struct trace_array *tr,unsigned long eflags = file->flags;if (eflags &EVENT_FILE_FL_TRIGGER_COND ) - *tt =event_triggers_call (file, entry); + *tt =event_triggers_call (file, entry, event); if (test_bit(EVENT_FILE_FL_SOFT_DISABLED_BIT, &file->flags) || (unlikely(file->flags & EVENT_FILE_FL_FILTERED) &&
@@ -1330,7 +1330,7 @@ static inline void trace_buffer_unlock_commit(struct trace_array *tr,trace_buffer_unlock_commit (file->tr, buffer, event, irq_flags, pc);if (tt) -event_triggers_post_call (file, tt, entry); +event_triggers_post_call (file, tt, entry, event); }/**
@@ -1363,7 +1363,7 @@ static inline void trace_buffer_unlock_commit(struct trace_array *tr, irq_flags, pc, regs);if (tt) -event_triggers_post_call (file, tt, entry); +event_triggers_post_call (file, tt, entry, event); }#define FILTER_PRED_INVALID ((unsigned short)-1)
@@ -1588,7 +1588,8 @@ extern void set_named_trigger_data(struct event_trigger_data *data,*/ struct event_trigger_ops {void (*func)(struct event_trigger_data *data, -void *rec); +void *rec, +struct ring_buffer_event *rbe);int (*init)(struct event_trigger_ops *ops,struct event_trigger_data *data); void (*free)(struct event_trigger_ops *ops,
kernel/trace/trace_events_hist.c +7 -4
@@ -909,7 +909,8 @@ static inline void add_to_key(char *compound_key, void *key,memcpy (compound_key + key_field->offset, key, size); } -static void event_hist_trigger (struct event_trigger_data *data,void *rec) +static void event_hist_trigger (struct event_trigger_data *data,void *rec, +struct ring_buffer_event *event) {struct hist_trigger_data *hist_data = data->private_data;bool use_compound_key = (hist_data->n_keys >1 );
@@ -1660,7 +1661,8 @@ __init int register_trigger_hist_cmd(void) }static void -hist_enable_trigger (struct event_trigger_data *data,void *rec) +hist_enable_trigger (struct event_trigger_data *data,void *rec, +struct ring_buffer_event *event) {struct enable_trigger_data *enable_data = data->private_data;struct event_trigger_data *test;
@@ -1676,7 +1678,8 @@ __init int register_trigger_hist_cmd(void) }static void -hist_enable_count_trigger (struct event_trigger_data *data,void *rec) +hist_enable_count_trigger (struct event_trigger_data *data,void *rec, +struct ring_buffer_event *event) {if (!data->count)return ;
@@ -1684,7 +1687,7 @@ __init int register_trigger_hist_cmd(void)if (data->count !=-1 ) (data->count)--; -hist_enable_trigger (data, rec); +hist_enable_trigger (data, rec, event); }static struct event_trigger_ops hist_enable_trigger_ops = {
kernel/trace/trace_events_trigger.c +29 -18
153 lines not on this page · raw message
Signature · 1 line
1.9.3
PATCH v3 6/33 tracing: Add ring buffer event param to hist field functions
In reply to Tom ZanussiSome events such as timestamps require access to a ring_buffer_event struct; add a param so that hist field functions can access that.
--- kernel/trace/trace_events_hist.c | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-)
kernel/trace/trace_events_hist.c +24 -15
@@ -26,7 +26,8 @@struct hist_field ; -typedef u64 (*hist_field_fn_t ) (struct hist_field *field,void *event); +typedef u64 (*hist_field_fn_t ) (struct hist_field *field,void *event, +struct ring_buffer_event *rbe);#define HIST_FIELD_OPERANDS_MAX 2
@@ -40,24 +41,28 @@ struct hist_field {struct hist_field *operands[HIST_FIELD_OPERANDS_MAX ]; }; -static u64 hist_field_none (struct hist_field *field,void *event) +static u64 hist_field_none (struct hist_field *field,void *event, +struct ring_buffer_event *rbe) {return 0 ; } -static u64 hist_field_counter (struct hist_field *field,void *event) +static u64 hist_field_counter (struct hist_field *field,void *event, +struct ring_buffer_event *rbe) {return 1 ; } -static u64 hist_field_string (struct hist_field *hist_field,void *event) +static u64 hist_field_string (struct hist_field *hist_field,void *event, +struct ring_buffer_event *rbe) {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) +static u64 hist_field_dynstring (struct hist_field *hist_field,void *event, +struct ring_buffer_event *rbe) {u32 str_item = *(u32 *)(event + hist_field->field->offset);int str_loc = str_item &0xffff ;
@@ -66,24 +71,28 @@ static u64 hist_field_dynstring(struct hist_field *hist_field, void *event)return (u64 )(unsigned long )addr; } -static u64 hist_field_pstring (struct hist_field *hist_field,void *event) +static u64 hist_field_pstring (struct hist_field *hist_field,void *event, +struct ring_buffer_event *rbe) {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) +static u64 hist_field_log2 (struct hist_field *hist_field,void *event, +struct ring_buffer_event *rbe) {struct hist_field *operand = hist_field->operands[0 ]; -u64 val = operand->fn (operand, event); +u64 val = operand->fn (operand, event, rbe);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)\ + static u64 hist_field_##type(struct hist_field *hist_field, \ + void *event, \ + struct ring_buffer_event *rbe) \ { \ type *addr = (type *)(event + hist_field->field->offset); \ \
@@ -871,8 +880,8 @@ static bool need_tracing_map_ops(struct hist_trigger_data *hist_data) }static void hist_trigger_elt_update (struct hist_trigger_data *hist_data, -struct tracing_map_elt *elt, -void *rec) +struct tracing_map_elt *elt,void *rec, +struct ring_buffer_event *rbe) {struct hist_field *hist_field;unsigned int i;
@@ -880,7 +889,7 @@ static void hist_trigger_elt_update(struct hist_trigger_data *hist_data,for_each_hist_val_field (i, hist_data) { hist_field = hist_data->fields[i]; - hist_val = hist_field->fn (hist_field, rec); + hist_val = hist_field->fn (hist_field, rec, rbe);tracing_map_update_sum (elt, i, hist_val); } }
@@ -910,7 +919,7 @@ static inline void add_to_key(char *compound_key, void *key, }static void event_hist_trigger (struct event_trigger_data *data,void *rec, -struct ring_buffer_event *event) +struct ring_buffer_event *rbe) {struct hist_trigger_data *hist_data = data->private_data;bool use_compound_key = (hist_data->n_keys >1 );
@@ -939,7 +948,7 @@ static void event_hist_trigger(struct event_trigger_data *data, void *rec, key = entries; }else { - field_contents = key_field->fn (key_field, rec); + field_contents = key_field->fn (key_field, rec, rbe);if (key_field->flags &HIST_FIELD_FL_STRING ) { key = (void *)(unsigned long )field_contents; use_compound_key = true;
@@ -956,7 +965,7 @@ static void event_hist_trigger(struct event_trigger_data *data, void *rec, elt =tracing_map_insert (hist_data->map, key);if (elt) -hist_trigger_elt_update (hist_data, elt, rec); +hist_trigger_elt_update (hist_data, elt, rec, rbe); } static void hist_trigger_stacktrace_print(struct seq_file *m,
Signature · 1 line
1.9.3
PATCH v3 7/33 tracing: Break out hist trigger assignment parsing
In reply to Tom ZanussiThis will make it easier to add variables, and makes the parsing code cleaner regardless.
--- kernel/trace/trace_events_hist.c | 72 ++++++++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 21 deletions(-)
kernel/trace/trace_events_hist.c +51 -21
@@ -251,6 +251,51 @@ static void destroy_hist_trigger_attrs(struct hist_trigger_attrs *attrs)kfree (attrs); } +static int parse_assignment (char *str,struct hist_trigger_attrs *attrs) +{ +int ret =0 ; + +if ((strncmp (str,"key=" ,strlen ("key=" )) ==0 ) || + (strncmp (str,"keys=" ,strlen ("keys=" )) ==0 )) { + attrs->keys_str =kstrdup (str,GFP_KERNEL ); +if (!attrs->keys_str) { + ret = -ENOMEM ; + goto out; + } + }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 ); +if (!attrs->vals_str) { + ret = -ENOMEM ; + goto out; + } + }else if (strncmp (str,"sort=" ,strlen ("sort=" )) ==0 ) { + attrs->sort_key_str =kstrdup (str,GFP_KERNEL ); +if (!attrs->sort_key_str) { + ret = -ENOMEM ; + goto out; + } + }else if (strncmp (str,"name=" ,strlen ("name=" )) ==0 ) { + attrs->name =kstrdup (str,GFP_KERNEL ); +if (!attrs->name) { + ret = -ENOMEM ; + goto out; + } + }else if (strncmp (str,"size=" ,strlen ("size=" )) ==0 ) { +int map_bits =parse_map_size (str); + +if (map_bits <0 ) { + ret = map_bits; + goto out; + } + attrs->map_bits = map_bits; + }else + ret = -EINVAL ; + out: +return ret; +} +static struct hist_trigger_attrs *parse_hist_trigger_attrs (char *trigger_str) {struct hist_trigger_attrs *attrs;
@@ -263,33 +308,18 @@ static struct hist_trigger_attrs *parse_hist_trigger_attrs(char *trigger_str)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 ) +if (strchr (str,'=' )) { + ret =parse_assignment (str, attrs); +if (ret) + goto free; + }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 { +else { ret = -EINVAL ; goto free; }
Signature · 1 line
1.9.3
PATCH v3 8/33 tracing: Add hist trigger timestamp support
In reply to Tom ZanussiAdd support for a timestamp event field. This is actually a 'pseudo-' event field in that it behaves like it's part of the event record, but is really part of the corresponding ring buffer event.
To make use of the timestamp field, users can specify "$common_timestamp" as a field name for any histogram. Note that this doesn't make much sense on its own either as either a key or value, but needs to be supported even so, since follow-on patches will add support for making use of this field in time deltas. The '$' is used as a prefix on the variable name to indicate that it's not an bonafide event field - so you won't find it in the event description - but rather it's a synthetic field that can be used like a real field).
Note that the use of this field requires the ring buffer be put into TIME_EXTEND_ABS mode, which saves the complete timestamp for each event rather than an offset. This mode will be enabled if and only if a histogram makes use of the "$common_timestamp" field.
--- kernel/trace/trace_events_hist.c | 90 ++++++++++++++++++++++++++++++---------- 1 file changed, 67 insertions(+), 23 deletions(-)
kernel/trace/trace_events_hist.c +67 -23
@@ -89,6 +89,12 @@ static u64 hist_field_log2(struct hist_field *hist_field, void *event,return (u64 )ilog2 (roundup_pow_of_two (val)); } +static u64 hist_field_timestamp (struct hist_field *hist_field,void *event, +struct ring_buffer_event *rbe) +{ +return ring_buffer_event_time_stamp (rbe); +} +#define DEFINE_HIST_FIELD_FN (type) \ static u64 hist_field_##type(struct hist_field *hist_field, \ void *event, \
@@ -135,6 +141,7 @@ enum hist_field_flags {HIST_FIELD_FL_SYSCALL =1 <<7 ,HIST_FIELD_FL_STACKTRACE =1 <<8 ,HIST_FIELD_FL_LOG2 =1 <<9 , + HIST_FIELD_FL_TIMESTAMP = 1 << 10, }; struct hist_trigger_attrs {
@@ -159,6 +166,7 @@ struct hist_trigger_data {struct trace_event_file *event_file;struct hist_trigger_attrs *attrs;struct tracing_map *map; +bool enable_timestamps; }; static const char *hist_field_name(struct hist_field *field,
@@ -173,6 +181,8 @@ static const char *hist_field_name(struct hist_field *field, field_name = field->field->name; else if (field->flags & HIST_FIELD_FL_LOG2) field_name =hist_field_name (field->operands[0 ], ++level); + else if (field->flags & HIST_FIELD_FL_TIMESTAMP) + field_name ="$common_timestamp" ;if (field_name ==NULL ) field_name ="" ;
@@ -440,6 +450,12 @@ static struct hist_field *create_hist_field(struct ftrace_event_field *field, goto out; } +if (flags &HIST_FIELD_FL_TIMESTAMP ) { + hist_field->fn = hist_field_timestamp; + hist_field->size =sizeof (u64); + goto out; + } +if (WARN_ON_ONCE (!field)) goto out;
@@ -517,10 +533,15 @@ static int create_val_field(struct hist_trigger_data *hist_data, } } - field =trace_find_event_field (file->event_call, field_name); -if (!field || !field->size) { - ret = -EINVAL ; - goto out; +if (strcmp (field_name,"$common_timestamp" ) ==0 ) { + flags |=HIST_FIELD_FL_TIMESTAMP ; + hist_data->enable_timestamps = true; + }else { + field =trace_find_event_field (file->event_call, field_name); +if (!field || !field->size) { + ret = -EINVAL ; + goto out; + } } hist_data->fields[val_idx] =create_hist_field (field, flags);
@@ -615,16 +636,22 @@ static int create_key_field(struct hist_trigger_data *hist_data, } } - field =trace_find_event_field (file->event_call, field_name); -if (!field || !field->size) { - ret = -EINVAL ; - goto out; - } +if (strcmp (field_name,"$common_timestamp" ) ==0 ) { + flags |=HIST_FIELD_FL_TIMESTAMP ; + hist_data->enable_timestamps = true; + key_size =sizeof (u64); + }else { + field =trace_find_event_field (file->event_call, field_name); +if (!field || !field->size) { + ret = -EINVAL ; + goto out; + } -if (is_string_field (field)) - key_size =MAX_FILTER_STR_VAL ; -else - key_size = field->size; +if (is_string_field (field)) + key_size =MAX_FILTER_STR_VAL ; +else + key_size = field->size; + } } hist_data->fields[key_idx] =create_hist_field (field, flags);
@@ -820,6 +847,9 @@ static int create_tracing_map_fields(struct hist_trigger_data *hist_data)if (hist_field->flags &HIST_FIELD_FL_STACKTRACE ) cmp_fn = tracing_map_cmp_none; +else if (!field) + cmp_fn =tracing_map_cmp_num (hist_field->size, + hist_field->is_signed);else if (is_string_field (field)) cmp_fn = tracing_map_cmp_string;else
@@ -1217,7 +1247,11 @@ static void hist_field_print(struct seq_file *m, struct hist_field *hist_field) {const char *field_name =hist_field_name (hist_field,0 ); -seq_printf (m,"%s" , field_name); +if (hist_field->flags &HIST_FIELD_FL_TIMESTAMP ) +seq_puts (m,"$common_timestamp" ); +else if (field_name) +seq_printf (m,"%s" , field_name); +if (hist_field->flags) {const char *flags_str =get_hist_field_flags (hist_field);
@@ -1268,27 +1302,25 @@ static int event_hist_trigger_print(struct seq_file *m,for (i =0 ; i < hist_data->n_sort_keys; i++) {struct tracing_map_sort_key *sort_key; +unsigned int idx; sort_key = &hist_data->sort_keys[i]; + idx = sort_key->field_idx; + +if (WARN_ON (idx >=TRACING_MAP_FIELDS_MAX )) +return -EINVAL ;if (i >0 )seq_puts (m,"," ); -if (sort_key->field_idx ==HITCOUNT_IDX ) +if (idx ==HITCOUNT_IDX )seq_puts (m,"hitcount" ); -else { -unsigned int idx = sort_key->field_idx; - -if (WARN_ON (idx >=TRACING_MAP_FIELDS_MAX )) -return -EINVAL ; - +else hist_field_print (m, hist_data->fields[idx]); - }if (sort_key->descending)seq_puts (m,".descending" ); } -seq_printf (m,":size=%u" , (1 << hist_data->map->map_bits));if (data->filter_str)
@@ -1456,6 +1488,10 @@ static bool hist_trigger_match(struct event_trigger_data *data,return false;if (key_field->offset != key_field_test->offset)return false; +if (key_field->size != key_field_test->size) +return false; +if (key_field->is_signed != key_field_test->is_signed) +return false; }for (i =0 ; i < hist_data->n_sort_keys; i++) {
@@ -1538,6 +1574,9 @@ static int hist_register_trigger(char *glob, struct event_trigger_ops *ops,update_cond_flag (file); +if (hist_data->enable_timestamps) +tracing_set_time_stamp_abs (file->tr, true); +if (trace_event_trigger_enable_disable (file,1 ) <0 ) {list_del_rcu (&data->list);update_cond_flag (file);
@@ -1572,6 +1611,11 @@ static void hist_unregister_trigger(char *glob, struct event_trigger_ops *ops,if (unregistered && test->ops->free) test->ops->free (test->ops, test); + +if (hist_data->enable_timestamps) { +if (unregistered) +tracing_set_time_stamp_abs (file->tr, false); + } }static void hist_unreg_all (struct trace_event_file *file)
Signature · 1 line
1.9.3
PATCH v3 9/33 tracing: Add per-element variable support to tracing_map
In reply to Tom ZanussiIn order to allow information to be passed between trace events, add support for per-element variables to tracing_map. This provides a means for histograms to associate a value or values with an entry when it's saved or updated, and retrieved by a subsequent event occurrences.
Variables can be set using tracing_map_set_var() and read using tracing_map_read_var(). tracing_map_var_set() returns true or false depending on whether or not the variable has been set or not, which is important for event-matching applications.
tracing_map_read_var_once() reads the variable and resets it to the 'unset' state, implementing read-once variables, which are also important for event-matching uses.
--- kernel/trace/tracing_map.c | 108 +++++++++++++++++++++++++++++++++++++++++++++ kernel/trace/tracing_map.h | 11 +++++ 2 files changed, 119 insertions(+)
kernel/trace/tracing_map.c +108 -0
@@ -66,6 +66,73 @@ u64 tracing_map_read_sum(struct tracing_map_elt *elt, unsigned int i)return (u64 )atomic64_read (&elt->fields[i].sum); } +/** +* tracing_map_set_var - Assign a tracing_map_elt's variable field +* @elt: The tracing_map_elt +* @i: The index of the given variable associated with the tracing_map_elt +* @n: The value to assign +* +* Assign n to variable i associated with the specified tracing_map_elt +* instance. The index i is the index returned by the call to +* tracing_map_add_var() when the tracing map was set up. +*/ +void tracing_map_set_var (struct tracing_map_elt *elt,unsigned int i,u64 n) +{ +atomic64_set (&elt->vars[i], n); + elt->var_set[i] = true; +} + +/** +* tracing_map_var_set - Return whether or not a variable has been set +* @elt: The tracing_map_elt +* @i: The index of the given variable associated with the tracing_map_elt +* +* Return true if the variable has been set, false otherwise. The +* index i is the index returned by the call to tracing_map_add_var() +* when the tracing map was set up. +*/ +bool tracing_map_var_set (struct tracing_map_elt *elt,unsigned int i) +{ +return elt->var_set[i]; +} + +/** +* tracing_map_read_var - Return the value of a tracing_map_elt's variable field +* @elt: The tracing_map_elt +* @i: The index of the given variable associated with the tracing_map_elt +* +* Retrieve the value of the variable i associated with the specified +* tracing_map_elt instance. The index i is the index returned by the +* call to tracing_map_add_var() when the tracing map was set +* up. +* +* Return: The variable value associated with field i for elt. +*/ +u64 tracing_map_read_var (struct tracing_map_elt *elt,unsigned int i) +{ +return (u64 )atomic64_read (&elt->vars[i]); +} + +/** +* tracing_map_read_var_once - Return and reset a tracing_map_elt's variable field +* @elt: The tracing_map_elt +* @i: The index of the given variable associated with the tracing_map_elt +* +* Retrieve the value of the variable i associated with the specified +* tracing_map_elt instance, and reset the variable to the 'not set' +* state. The index i is the index returned by the call to +* tracing_map_add_var() when the tracing map was set up. The reset +* essentially makes the variable a read-once variable if it's only +* accessed using this function. +* +* Return: The variable value associated with field i for elt. +*/ +u64 tracing_map_read_var_once (struct tracing_map_elt *elt,unsigned int i) +{ + elt->var_set[i] = false; +return (u64 )atomic64_read (&elt->vars[i]); +} +int tracing_map_cmp_string (void *val_a,void *val_b) {char *a = val_a;
@@ -171,6 +238,28 @@ int tracing_map_add_sum_field(struct tracing_map *map) }/** +* tracing_map_add_var - Add a field describing a tracing_map var +* @map: The tracing_map +* +* Add a var to the map and return the index identifying it in the map +* and associated tracing_map_elts. This is the index used for +* instance to update a var for a particular tracing_map_elt using +* tracing_map_update_var() or reading it via tracing_map_read_var(). +* +* Return: The index identifying the var in the map and associated +* tracing_map_elts, or -EINVAL on error. +*/ +int tracing_map_add_var (struct tracing_map *map) +{ +int ret = -EINVAL ; + +if (map->n_vars <TRACING_MAP_VARS_MAX ) + ret = map->n_vars++; + +return ret; +} + +/** * tracing_map_add_key_field - Add a field describing a tracing_map key * @map: The tracing_map * @offset: The offset within the key
@@ -280,6 +369,11 @@ static void tracing_map_elt_clear(struct tracing_map_elt *elt)if (elt->fields[i].cmp_fn == tracing_map_cmp_atomic64)atomic64_set (&elt->fields[i].sum,0 ); +for (i =0 ; i < elt->map->n_vars; i++) { +atomic64_set (&elt->vars[i],0 ); + elt->var_set[i] = false; + } +if (elt->map->ops && elt->map->ops->elt_clear) elt->map->ops->elt_clear (elt); }
@@ -306,6 +400,8 @@ static void tracing_map_elt_free(struct tracing_map_elt *elt)if (elt->map->ops && elt->map->ops->elt_free) elt->map->ops->elt_free (elt);kfree (elt->fields); +kfree (elt->vars); +kfree (elt->var_set);kfree (elt->key);kfree (elt); }
@@ -333,6 +429,18 @@ static struct tracing_map_elt *tracing_map_elt_alloc(struct tracing_map *map) goto free; } + elt->vars =kcalloc (map->n_vars,sizeof (*elt->vars),GFP_KERNEL ); +if (!elt->vars) { + err = -ENOMEM ; + goto free; + } + + elt->var_set =kcalloc (map->n_vars,sizeof (*elt->var_set),GFP_KERNEL ); +if (!elt->var_set) { + err = -ENOMEM ; + goto free; + } +tracing_map_elt_init_fields (elt);if (map->ops && map->ops->elt_alloc) {
kernel/trace/tracing_map.h +11 -0
@@ -9,6 +9,7 @@#define TRACING_MAP_VALS_MAX 3#define TRACING_MAP_FIELDS_MAX (TRACING_MAP_KEYS_MAX + \ TRACING_MAP_VALS_MAX) +#define TRACING_MAP_VARS_MAX 16#define TRACING_MAP_SORT_KEYS_MAX 2typedef int (*tracing_map_cmp_fn_t ) (void *val_a,void *val_b);
@@ -136,6 +137,8 @@ struct tracing_map_field {struct tracing_map_elt {struct tracing_map *map;struct tracing_map_field *fields; +atomic64_t *vars; +bool *var_set;void *key;void *private_data; };
@@ -191,6 +194,7 @@ struct tracing_map {int key_idx[TRACING_MAP_KEYS_MAX ];unsigned int n_keys;struct tracing_map_sort_key sort_key; +unsigned int n_vars;atomic64_t hits;atomic64_t drops; };
@@ -240,6 +244,7 @@ struct tracing_map_ops {extern int tracing_map_init (struct tracing_map *map);extern int tracing_map_add_sum_field (struct tracing_map *map); +extern int tracing_map_add_var (struct tracing_map *map);extern int tracing_map_add_key_field (struct tracing_map *map,unsigned int offset,tracing_map_cmp_fn_t cmp_fn);
@@ -259,7 +264,13 @@ extern tracing_map_cmp_fn_t tracing_map_cmp_num(int field_size,extern void tracing_map_update_sum (struct tracing_map_elt *elt,unsigned int i,u64 n); +extern void tracing_map_set_var (struct tracing_map_elt *elt, +unsigned int i,u64 n); +extern bool tracing_map_var_set (struct tracing_map_elt *elt,unsigned int i);extern u64 tracing_map_read_sum (struct tracing_map_elt *elt,unsigned int i); +extern u64 tracing_map_read_var (struct tracing_map_elt *elt,unsigned int i); +extern u64 tracing_map_read_var_once (struct tracing_map_elt *elt,unsigned int i); + extern void tracing_map_set_field_descr(struct tracing_map *map, unsigned int i, unsigned int key_offset,
Signature · 1 line
1.9.3
PATCH v3 10/33 tracing: Add hist_data member to hist_field
In reply to Tom ZanussiAllow hist_data access via hist_field. Some users of hist_fields require or will require more access to the associated hist_data.
--- kernel/trace/trace_events_hist.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-)
kernel/trace/trace_events_hist.c +9 -5
@@ -39,6 +39,7 @@ struct hist_field {unsigned int offset;unsigned int is_signed;struct hist_field *operands[HIST_FIELD_OPERANDS_MAX ]; +struct hist_trigger_data *hist_data; }; static u64 hist_field_none(struct hist_field *field, void *event,
@@ -420,7 +421,8 @@ 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, +static struct hist_field *create_hist_field (struct hist_trigger_data *hist_data, +struct ftrace_event_field *field,unsigned long flags) {struct hist_field *hist_field;
@@ -432,6 +434,8 @@ static struct hist_field *create_hist_field(struct ftrace_event_field *field,if (!hist_field)return NULL ; + hist_field->hist_data = hist_data; +if (flags &HIST_FIELD_FL_HITCOUNT ) { hist_field->fn = hist_field_counter; goto out;
@@ -445,7 +449,7 @@ static struct hist_field *create_hist_field(struct ftrace_event_field *field,if (flags &HIST_FIELD_FL_LOG2 ) {unsigned long fl = flags & ~HIST_FIELD_FL_LOG2 ; hist_field->fn = hist_field_log2; - hist_field->operands[0 ] =create_hist_field (field, fl); + hist_field->operands[0 ] =create_hist_field (hist_data, field, fl); hist_field->size = hist_field->operands[0 ]->size; goto out; }
@@ -498,7 +502,7 @@ static void destroy_hist_fields(struct hist_trigger_data *hist_data)static int create_hitcount_val (struct hist_trigger_data *hist_data) { hist_data->fields[HITCOUNT_IDX ] = -create_hist_field (NULL ,HIST_FIELD_FL_HITCOUNT ); +create_hist_field (hist_data,NULL ,HIST_FIELD_FL_HITCOUNT );if (!hist_data->fields[HITCOUNT_IDX ])return -ENOMEM ;
@@ -544,7 +548,7 @@ static int create_val_field(struct hist_trigger_data *hist_data, } } - hist_data->fields[val_idx] =create_hist_field (field, flags); + hist_data->fields[val_idx] =create_hist_field (hist_data, field, flags);if (!hist_data->fields[val_idx]) { ret = -ENOMEM ; goto out;
@@ -654,7 +658,7 @@ static int create_key_field(struct hist_trigger_data *hist_data, } } - hist_data->fields[key_idx] =create_hist_field (field, flags); + hist_data->fields[key_idx] =create_hist_field (hist_data, field, flags);if (!hist_data->fields[key_idx]) { ret = -ENOMEM ; goto out;
Signature · 1 line
1.9.3
PATCH v3 11/33 tracing: Add usecs modifier for hist trigger timestamps
In reply to Tom ZanussiAppending .usecs onto a common_timestamp field will cause the timestamp value to be in microseconds instead of the default nanoseconds. A typical latency histogram using usecs would look like this:
# echo 'hist:keys=pid,prio:ts0=$common_timestamp.usecs ... # echo 'hist:keys=next_pid:wakeup_lat=$common_timestamp.usecs-$ts0 ...
This also adds an external trace_clock_in_ns() to trace.c for the timestamp conversion.
--- kernel/trace/trace.c | 8 ++++++++ kernel/trace/trace.h | 2 ++ kernel/trace/trace_events_hist.c | 28 ++++++++++++++++++++++------ 3 files changed, 32 insertions(+), 6 deletions(-)
kernel/trace/trace.c +8 -0
@@ -1170,6 +1170,14 @@ unsigned long nsecs_to_usecs(unsigned long nsecs) ARCH_TRACE_CLOCKS }; +bool trace_clock_in_ns (struct trace_array *tr) +{ +if (trace_clocks[tr->clock_id].in_ns) +return true; + +return false; +} +/* * trace_parser_get_init - gets the buffer for trace parser */
kernel/trace/trace.h +2 -0
@@ -288,6 +288,8 @@ enum {extern int tracing_set_time_stamp_abs (struct trace_array *tr,bool abs); +extern bool trace_clock_in_ns (struct trace_array *tr); +/* * The global tracer (top) should be the first trace array added, * but we check the flag anyway.
kernel/trace/trace_events_hist.c +22 -6
@@ -90,12 +90,6 @@ static u64 hist_field_log2(struct hist_field *hist_field, void *event,return (u64 )ilog2 (roundup_pow_of_two (val)); } -static u64 hist_field_timestamp (struct hist_field *hist_field,void *event, -struct ring_buffer_event *rbe) -{ -return ring_buffer_event_time_stamp (rbe); -} -#define DEFINE_HIST_FIELD_FN (type) \ static u64 hist_field_##type(struct hist_field *hist_field, \ void *event, \
@@ -143,6 +137,7 @@ enum hist_field_flags {HIST_FIELD_FL_STACKTRACE =1 <<8 ,HIST_FIELD_FL_LOG2 =1 <<9 ,HIST_FIELD_FL_TIMESTAMP =1 <<10 , + HIST_FIELD_FL_TIMESTAMP_USECS = 1 << 11, }; struct hist_trigger_attrs {
@@ -153,6 +148,7 @@ struct hist_trigger_attrs {bool pause;bool cont;bool clear; +bool ts_in_usecs;unsigned int map_bits; };
@@ -170,6 +166,20 @@ struct hist_trigger_data {bool enable_timestamps; }; +static u64 hist_field_timestamp (struct hist_field *hist_field,void *event, +struct ring_buffer_event *rbe) +{ +struct hist_trigger_data *hist_data = hist_field->hist_data; +struct trace_array *tr = hist_data->event_file->tr; + +u64 ts =ring_buffer_event_time_stamp (rbe); + +if (hist_data->attrs->ts_in_usecs &&trace_clock_in_ns (tr)) + ts =ns2usecs (ts); + +return ts; +} +static const char *hist_field_name (struct hist_field *field,unsigned int level) {
@@ -634,6 +644,8 @@ static int create_key_field(struct hist_trigger_data *hist_data, flags |=HIST_FIELD_FL_SYSCALL ; else if (strcmp(field_str, "log2") == 0) flags |= HIST_FIELD_FL_LOG2; + else if (strcmp(field_str, "usecs") == 0) + flags |= HIST_FIELD_FL_TIMESTAMP_USECS; else { ret = -EINVAL; goto out;
@@ -643,6 +655,8 @@ static int create_key_field(struct hist_trigger_data *hist_data,if (strcmp (field_name,"$common_timestamp" ) ==0 ) { flags |=HIST_FIELD_FL_TIMESTAMP ; hist_data->enable_timestamps = true; +if (flags &HIST_FIELD_FL_TIMESTAMP_USECS ) + hist_data->attrs->ts_in_usecs = true; key_size =sizeof (u64); }else { field =trace_find_event_field (file->event_call, field_name);
@@ -1243,6 +1257,8 @@ static const char *get_hist_field_flags(struct hist_field *hist_field) flags_str ="syscall" ; else if (hist_field->flags & HIST_FIELD_FL_LOG2) flags_str ="log2" ; + else if (hist_field->flags & HIST_FIELD_FL_TIMESTAMP_USECS) + flags_str ="usecs" ;return flags_str; }
Signature · 1 line
1.9.3
PATCH v3 12/33 tracing: Add variable support to hist triggers
In reply to Tom ZanussiAdd support for saving the value of a current event's event field by assigning it to a variable that can be read by a subsequent event.
The basic syntax for saving a variable is to simply prefix a unique variable name not corresponding to any keyword along with an '=' sign to any event field.
Both keys and values can be saved and retrieved in this way:
# echo 'hist:keys=next_pid:vals=$ts0:ts0=common_timestamp ...
# echo 'hist:timer_pid=common_pid:key=$timer_pid ...'If a variable isn't a key variable or prefixed with 'vals=', the associated event field will be saved in a variable but won't be summed as a value:
# echo 'hist:keys=next_pid:ts1=common_timestamp:...
Multiple variables can be assigned at the same time:
# echo 'hist:keys=pid:vals=$ts0,$b,field2:ts0=common_timestamp,b=field1 ...
Multiple (or single) variables can also be assigned at the same time using separate assignments:
# echo 'hist:keys=pid:vals=$ts0:ts0=common_timestamp:b=field1:c=field2 ...
Variables set as above can be used by being referenced from another event, as described in a subsequent patch.
--- kernel/trace/trace_events_hist.c | 374 ++++++++++++++++++++++++++++++++++----- 1 file changed, 334 insertions(+), 40 deletions(-)
kernel/trace/trace_events_hist.c +334 -40
@@ -30,6 +30,13 @@ typedef u64 (*hist_field_fn_t) (struct hist_field *field, void *event, struct ring_buffer_event *rbe);#define HIST_FIELD_OPERANDS_MAX 2 +#define HIST_FIELDS_MAX (TRACING_MAP_FIELDS_MAX + TRACING_MAP_VARS_MAX) + +struct hist_var { +char *name; +struct hist_trigger_data *hist_data; +unsigned int idx; +};struct hist_field {struct ftrace_event_field *field;
@@ -40,6 +47,7 @@ struct hist_field {unsigned int is_signed;struct hist_field *operands[HIST_FIELD_OPERANDS_MAX ];struct hist_trigger_data *hist_data; +struct hist_var var; }; static u64 hist_field_none(struct hist_field *field, void *event,
@@ -138,6 +146,14 @@ enum hist_field_flags {HIST_FIELD_FL_LOG2 =1 <<9 ,HIST_FIELD_FL_TIMESTAMP =1 <<10 ,HIST_FIELD_FL_TIMESTAMP_USECS =1 <<11 , +HIST_FIELD_FL_VAR =1 <<12 , + HIST_FIELD_FL_VAR_ONLY = 1 << 13, +}; + +struct var_defs { +unsigned int n_vars; +char *name[TRACING_MAP_VARS_MAX ]; +char *expr[TRACING_MAP_VARS_MAX ]; }; struct hist_trigger_attrs {
@@ -150,13 +166,20 @@ struct hist_trigger_attrs {bool clear;bool ts_in_usecs;unsigned int map_bits; + +char *assignment_str[TRACING_MAP_VARS_MAX ]; +unsigned int n_assignments; + +struct var_defs var_defs; };struct hist_trigger_data { -struct hist_field *fields[TRACING_MAP_FIELDS_MAX ]; +struct hist_field *fields[HIST_FIELDS_MAX ];unsigned int n_vals;unsigned int n_keys;unsigned int n_fields; +unsigned int n_vars; +unsigned int n_var_only;unsigned int key_size;struct tracing_map_sort_key sort_keys[TRACING_MAP_SORT_KEYS_MAX ];unsigned int n_sort_keys;
@@ -164,6 +187,7 @@ struct hist_trigger_data {struct hist_trigger_attrs *attrs;struct tracing_map *map;bool enable_timestamps; +bool remove; }; static u64 hist_field_timestamp(struct hist_field *hist_field, void *event,
@@ -262,9 +286,14 @@ static int parse_map_size(char *str)static void destroy_hist_trigger_attrs (struct hist_trigger_attrs *attrs) { +unsigned int i; +if (!attrs)return ; +for (i =0 ; i < attrs->n_assignments; i++) +kfree (attrs->assignment_str[i]); +kfree (attrs->name);kfree (attrs->sort_key_str);kfree (attrs->keys_str);
@@ -311,8 +340,22 @@ static int parse_assignment(char *str, struct hist_trigger_attrs *attrs) goto out; } attrs->map_bits = map_bits; - }else - ret = -EINVAL ; + }else { +char *assignment; + +if (attrs->n_assignments ==TRACING_MAP_VARS_MAX ) { + ret = -EINVAL ; + goto out; + } + + assignment =kstrdup (str,GFP_KERNEL ); +if (!assignment) { + ret = -ENOMEM ; + goto out; + } + + attrs->assignment_str[attrs->n_assignments++] = assignment; + } out:return ret; }
@@ -428,12 +471,15 @@ static void destroy_hist_field(struct hist_field *hist_field,for (i =0 ; i <HIST_FIELD_OPERANDS_MAX ; i++)destroy_hist_field (hist_field->operands[i], level +1 ); +kfree (hist_field->var.name); +kfree (hist_field); }static struct hist_field *create_hist_field (struct hist_trigger_data *hist_data,struct ftrace_event_field *field, -unsigned long flags) +unsigned long flags, +char *var_name) {struct hist_field *hist_field;
@@ -459,7 +505,7 @@ static struct hist_field *create_hist_field(struct hist_trigger_data *hist_data,if (flags &HIST_FIELD_FL_LOG2 ) {unsigned long fl = flags & ~HIST_FIELD_FL_LOG2 ; hist_field->fn = hist_field_log2; - hist_field->operands[0 ] =create_hist_field (hist_data, field, fl); + hist_field->operands[0 ] =create_hist_field (hist_data, field, fl,NULL ); hist_field->size = hist_field->operands[0 ]->size; goto out; }
@@ -494,14 +540,23 @@ static struct hist_field *create_hist_field(struct hist_trigger_data *hist_data, hist_field->field = field; hist_field->flags = flags; +if (var_name) { + hist_field->var.name =kstrdup (var_name,GFP_KERNEL ); +if (!hist_field->var.name) + goto free; + } +return hist_field; + free: +destroy_hist_field (hist_field,0 ); +return NULL ; }static void destroy_hist_fields (struct hist_trigger_data *hist_data) {unsigned int i; -for (i =0 ; i <TRACING_MAP_FIELDS_MAX ; i++) { +for (i =0 ; i <HIST_FIELDS_MAX ; i++) {if (hist_data->fields[i]) {destroy_hist_field (hist_data->fields[i],0 ); hist_data->fields[i] =NULL ;
@@ -512,11 +567,12 @@ static void destroy_hist_fields(struct hist_trigger_data *hist_data)static int create_hitcount_val (struct hist_trigger_data *hist_data) { hist_data->fields[HITCOUNT_IDX ] = -create_hist_field (hist_data,NULL ,HIST_FIELD_FL_HITCOUNT ); +create_hist_field (hist_data,NULL ,HIST_FIELD_FL_HITCOUNT ,NULL );if (!hist_data->fields[HITCOUNT_IDX ])return -ENOMEM ; hist_data->n_vals++; + hist_data->n_fields++;if (WARN_ON (hist_data->n_vals >TRACING_MAP_VALS_MAX ))return -EINVAL ;
@@ -524,19 +580,53 @@ static int create_hitcount_val(struct hist_trigger_data *hist_data)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) +static struct hist_field *find_var_field (struct hist_trigger_data *hist_data, +const char *var_name) +{ +struct hist_field *hist_field, *found =NULL ; +int i; + +for_each_hist_field (i, hist_data) { + hist_field = hist_data->fields[i]; +if (hist_field && hist_field->flags &HIST_FIELD_FL_VAR && +strcmp (hist_field->var.name, var_name) ==0 ) { + found = hist_field; +break ; + } + } + +return found; +} + +static struct hist_field *find_var (struct trace_event_file *file, +const char *var_name) +{ +struct hist_trigger_data *hist_data; +struct event_trigger_data *test; +struct hist_field *hist_field; + +list_for_each_entry_rcu (test, &file->triggers, list) { +if (test->cmd_ops->trigger_type ==ETT_EVENT_HIST ) { + hist_data = test->private_data; + hist_field =find_var_field (hist_data, var_name); +if (hist_field) +return hist_field; + } + } + +return NULL ; +} + +static int __create_val_field (struct hist_trigger_data *hist_data, +unsigned int val_idx, +struct trace_event_file *file, +char *var_name,char *field_str, +unsigned long flags) {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 )
@@ -558,25 +648,65 @@ static int create_val_field(struct hist_trigger_data *hist_data, } } - hist_data->fields[val_idx] =create_hist_field (hist_data, field, flags); + hist_data->fields[val_idx] =create_hist_field (hist_data, field, flags, var_name);if (!hist_data->fields[val_idx]) { ret = -ENOMEM ; goto out; } ++hist_data->n_vals; + ++hist_data->n_fields; -if (WARN_ON (hist_data->n_vals >TRACING_MAP_VALS_MAX )) +if (hist_data->fields[val_idx]->flags &HIST_FIELD_FL_VAR_ONLY ) + hist_data->n_var_only++; + +if (WARN_ON (hist_data->n_vals >TRACING_MAP_VALS_MAX +TRACING_MAP_VARS_MAX )) ret = -EINVAL ; out:return ret; } +static int create_val_field (struct hist_trigger_data *hist_data, +unsigned int val_idx, +struct trace_event_file *file, +char *field_str) +{ +if (WARN_ON (val_idx >=TRACING_MAP_VALS_MAX )) +return -EINVAL ; + +return __create_val_field (hist_data, val_idx, file,NULL , field_str,0 ); +} + +static int create_var_field (struct hist_trigger_data *hist_data, +unsigned int val_idx, +struct trace_event_file *file, +char *var_name,char *expr_str) +{ +unsigned long flags =0 ; + +if (WARN_ON (val_idx >=TRACING_MAP_VALS_MAX +TRACING_MAP_VARS_MAX )) +return -EINVAL ; + +if (find_var (file, var_name) && !hist_data->remove) { +return -EINVAL ; + } + + flags |=HIST_FIELD_FL_VAR ; + hist_data->n_vars++; +if (hist_data->n_vars >TRACING_MAP_VARS_MAX ) { +return -EINVAL ; + } + + flags |=HIST_FIELD_FL_VAR_ONLY ; + +return __create_val_field (hist_data, val_idx, file, var_name, expr_str, flags); +} +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; +unsigned int i, j =1 ;int ret; ret =create_hitcount_val (hist_data);
@@ -596,12 +726,15 @@ static int create_val_fields(struct hist_trigger_data *hist_data, 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; } +if (fields_str && (strcmp (fields_str,"hitcount" ) !=0 )) ret = -EINVAL ; out:
@@ -615,11 +748,12 @@ static int create_key_field(struct hist_trigger_data *hist_data, char *field_str) {struct ftrace_event_field *field =NULL ; +struct hist_field *hist_field =NULL ;unsigned long flags =0 ;unsigned int key_size;int ret =0 ; -if (WARN_ON (key_idx >=TRACING_MAP_FIELDS_MAX )) +if (WARN_ON (key_idx >=HIST_FIELDS_MAX ))return -EINVAL ; flags |=HIST_FIELD_FL_KEY ;
@@ -627,6 +761,7 @@ static int create_key_field(struct hist_trigger_data *hist_data,if (strcmp (field_str,"stacktrace" ) ==0 ) { flags |=HIST_FIELD_FL_STACKTRACE ; key_size =sizeof (unsigned long ) *HIST_STACKTRACE_DEPTH ; + hist_field =create_hist_field (hist_data,NULL , flags,NULL ); }else {char *field_name =strsep (&field_str,"." );
@@ -672,7 +807,7 @@ static int create_key_field(struct hist_trigger_data *hist_data, } } - hist_data->fields[key_idx] =create_hist_field (hist_data, field, flags); + hist_data->fields[key_idx] =create_hist_field (hist_data, field, flags,NULL );if (!hist_data->fields[key_idx]) { ret = -ENOMEM ; goto out;
@@ -688,6 +823,7 @@ static int create_key_field(struct hist_trigger_data *hist_data, } hist_data->n_keys++; + hist_data->n_fields++;if (WARN_ON (hist_data->n_keys >TRACING_MAP_KEYS_MAX ))return -EINVAL ;
@@ -731,21 +867,108 @@ static int create_key_fields(struct hist_trigger_data *hist_data,return ret; } +static int create_var_fields (struct hist_trigger_data *hist_data, +struct trace_event_file *file) +{ +unsigned int i, j = hist_data->n_vals; +int ret =0 ; + +unsigned int n_vars = hist_data->attrs->var_defs.n_vars; + +for (i =0 ; i < n_vars; i++) { +char *var_name = hist_data->attrs->var_defs.name[i]; +char *expr = hist_data->attrs->var_defs.expr[i]; + + ret =create_var_field (hist_data, j++, file, var_name, expr); +if (ret) + goto out; + } + out: +return ret; +} + +static void free_var_defs (struct hist_trigger_data *hist_data) +{ +unsigned int i; + +for (i =0 ; i < hist_data->attrs->var_defs.n_vars; i++) { +kfree (hist_data->attrs->var_defs.name[i]); +kfree (hist_data->attrs->var_defs.expr[i]); + } + + hist_data->attrs->var_defs.n_vars =0 ; +} + +static int parse_var_defs (struct hist_trigger_data *hist_data) +{ +char *s, *str, *var_name, *field_str; +unsigned int i, j, n_vars =0 ; +int ret =0 ; + +for (i =0 ; i < hist_data->attrs->n_assignments; i++) { + str = hist_data->attrs->assignment_str[i]; +for (j =0 ; j <TRACING_MAP_VARS_MAX ; j++) { + field_str =strsep (&str,"," ); +if (!field_str) +break ; + + var_name =strsep (&field_str,"=" ); +if (!var_name || !field_str) { + ret = -EINVAL ; + goto free; + } + + s =kstrdup (var_name,GFP_KERNEL ); +if (!s) { + ret = -ENOMEM ; + goto free; + } + hist_data->attrs->var_defs.name[n_vars] = s; + + s =kstrdup (field_str,GFP_KERNEL ); +if (!s) { + ret = -ENOMEM ; + goto free; + } + hist_data->attrs->var_defs.expr[n_vars++] = s; + + hist_data->attrs->var_defs.n_vars = n_vars; + +if (n_vars ==TRACING_MAP_VARS_MAX ) + goto free; + } + } + +return ret; + free: +free_var_defs (hist_data); + +return ret; +} +static int create_hist_fields (struct hist_trigger_data *hist_data,struct trace_event_file *file) {int ret; + ret =parse_var_defs (hist_data); +if (ret) + goto out; + ret =create_val_fields (hist_data, file);if (ret) goto out; - ret =create_key_fields (hist_data, file); + ret =create_var_fields (hist_data, file);if (ret) goto out; - hist_data->n_fields = hist_data->n_vals + hist_data->n_keys; + ret =create_key_fields (hist_data, file); +if (ret) + goto out; out: +free_var_defs (hist_data); +return ret; }
@@ -768,7 +991,7 @@ static int create_sort_keys(struct hist_trigger_data *hist_data)char *fields_str = hist_data->attrs->sort_key_str;struct tracing_map_sort_key *sort_key;int descending, ret =0 ; -unsigned int i, j; +unsigned int i, j, k; hist_data->n_sort_keys =1 ;/* we always have at least one, hitcount */
@@ -816,12 +1039,19 @@ static int create_sort_keys(struct hist_trigger_data *hist_data)continue ; } -for (j =1 ; j < hist_data->n_fields; j++) { +for (j =1 , k =1 ; j < hist_data->n_fields; j++) { +unsigned int idx; + hist_field = hist_data->fields[j]; +if (hist_field->flags &HIST_FIELD_FL_VAR_ONLY ) +continue ; + + idx = k++; + test_name =hist_field_name (hist_field,0 );if (strcmp (field_name, test_name) ==0 ) { - sort_key->field_idx = j; + sort_key->field_idx = idx; descending =is_descending (field_str);if (descending <0 ) { ret = descending;
@@ -836,6 +1066,7 @@ static int create_sort_keys(struct hist_trigger_data *hist_data)break ; } } + hist_data->n_sort_keys = i; out:return ret;
@@ -876,12 +1107,19 @@ static int create_tracing_map_fields(struct hist_trigger_data *hist_data) idx =tracing_map_add_key_field (map, hist_field->offset, cmp_fn); - - }else + }else if (!(hist_field->flags &HIST_FIELD_FL_VAR )) idx =tracing_map_add_sum_field (map);if (idx <0 )return idx; + +if (hist_field->flags &HIST_FIELD_FL_VAR ) { + idx =tracing_map_add_var (map); +if (idx <0 ) +return idx; + hist_field->var.idx = idx; + hist_field->var.hist_data = hist_data; + } }return 0 ;
@@ -905,7 +1143,8 @@ static bool need_tracing_map_ops(struct hist_trigger_data *hist_data)static struct hist_trigger_data *create_hist_data (unsigned int map_bits,struct hist_trigger_attrs *attrs, -struct trace_event_file *file) +struct trace_event_file *file, +bool remove) {const struct tracing_map_ops *map_ops =NULL ;struct hist_trigger_data *hist_data;
@@ -916,6 +1155,7 @@ static bool need_tracing_map_ops(struct hist_trigger_data *hist_data)return ERR_PTR (-ENOMEM ); hist_data->attrs = attrs; + hist_data->remove = remove; ret =create_hist_fields (hist_data, file);if (ret)
@@ -962,14 +1202,29 @@ static void hist_trigger_elt_update(struct hist_trigger_data *hist_data, struct ring_buffer_event *rbe) {struct hist_field *hist_field; -unsigned int i; +unsigned int i, var_idx;u64 hist_val;for_each_hist_val_field (i, hist_data) { hist_field = hist_data->fields[i]; - hist_val = hist_field->fn (hist_field, rec, rbe); + hist_val = hist_field->fn (hist_field, rbe, rec); +if (hist_field->flags &HIST_FIELD_FL_VAR ) { + var_idx = hist_field->var.idx; +tracing_map_set_var (elt, var_idx, hist_val); +if (hist_field->flags &HIST_FIELD_FL_VAR_ONLY ) +continue ; + }tracing_map_update_sum (elt, i, hist_val); } + +for_each_hist_key_field (i, hist_data) { + hist_field = hist_data->fields[i]; +if (hist_field->flags &HIST_FIELD_FL_VAR ) { + hist_val = hist_field->fn (hist_field, rbe, rec); + var_idx = hist_field->var.idx; +tracing_map_set_var (elt, var_idx, hist_val); + } + } } static inline void add_to_key(char *compound_key, void *key,
@@ -1144,6 +1399,9 @@ static void hist_trigger_stacktrace_print(struct seq_file *m,for (i =1 ; i < hist_data->n_vals; i++) { field_name =hist_field_name (hist_data->fields[i],0 ); +if (hist_data->fields[i]->flags &HIST_FIELD_FL_VAR ) +continue ; +if (hist_data->fields[i]->flags &HIST_FIELD_FL_HEX ) {seq_printf (m," %s: %10llx" , field_name,tracing_map_read_sum (elt, i));
@@ -1267,6 +1525,9 @@ static void hist_field_print(struct seq_file *m, struct hist_field *hist_field) {const char *field_name =hist_field_name (hist_field,0 ); +if (hist_field->var.name) +seq_printf (m,"%s=" , hist_field->var.name); +if (hist_field->flags &HIST_FIELD_FL_TIMESTAMP )seq_puts (m,"$common_timestamp" );else if (field_name)
@@ -1285,7 +1546,8 @@ static int event_hist_trigger_print(struct seq_file *m, struct event_trigger_data *data) {struct hist_trigger_data *hist_data = data->private_data; -struct hist_field *key_field; +bool have_var_only = false; +struct hist_field *field;unsigned int i;seq_puts (m,"hist:" );
@@ -1296,25 +1558,47 @@ static int event_hist_trigger_print(struct seq_file *m,seq_puts (m,"keys=" );for_each_hist_key_field (i, hist_data) { - key_field = hist_data->fields[i]; + field = hist_data->fields[i];if (i > hist_data->n_vals)seq_puts (m,"," ); -if (key_field->flags &HIST_FIELD_FL_STACKTRACE ) +if (field->flags &HIST_FIELD_FL_STACKTRACE )seq_puts (m,"stacktrace" );else -hist_field_print (m, key_field); +hist_field_print (m, field); }seq_puts (m,":vals=" );for_each_hist_val_field (i, hist_data) { + field = hist_data->fields[i]; +if (field->flags &HIST_FIELD_FL_VAR_ONLY ) { + have_var_only = true; +continue ; + } +if (i ==HITCOUNT_IDX )seq_puts (m,"hitcount" );else {seq_puts (m,"," ); -hist_field_print (m, hist_data->fields[i]); +hist_field_print (m, field); + } + } + +if (have_var_only) { +unsigned int n =0 ; + +seq_puts (m,":" ); + +for_each_hist_val_field (i, hist_data) { + field = hist_data->fields[i]; + +if (field->flags &HIST_FIELD_FL_VAR_ONLY ) { +if (n++) +seq_puts (m,"," ); +hist_field_print (m, field); + } } }
@@ -1322,7 +1606,10 @@ static int event_hist_trigger_print(struct seq_file *m,for (i =0 ; i < hist_data->n_sort_keys; i++) {struct tracing_map_sort_key *sort_key; -unsigned int idx; +unsigned int idx, first_key_idx; + +/* skip VAR_ONLY vals */ + first_key_idx = hist_data->n_vals - hist_data->n_var_only; sort_key = &hist_data->sort_keys[i]; idx = sort_key->field_idx;
@@ -1335,8 +1622,11 @@ static int event_hist_trigger_print(struct seq_file *m,if (idx ==HITCOUNT_IDX )seq_puts (m,"hitcount" ); -else +else { +if (idx >= first_key_idx) + idx += hist_data->n_var_only;hist_field_print (m, hist_data->fields[idx]); + }if (sort_key->descending)seq_puts (m,".descending" );
@@ -1633,7 +1923,7 @@ static void hist_unregister_trigger(char *glob, struct event_trigger_ops *ops, test->ops->free (test->ops, test);if (hist_data->enable_timestamps) { -if (unregistered) +if (!hist_data->remove || unregistered)tracing_set_time_stamp_abs (file->tr, false); } }
@@ -1662,12 +1952,16 @@ static int event_hist_trigger_func(struct event_command *cmd_ops,struct hist_trigger_attrs *attrs;struct event_trigger_ops *trigger_ops;struct hist_trigger_data *hist_data; +bool remove = false;char *trigger;int ret =0 ;if (!param)return -EINVAL ; +if (glob[0 ] =='!' ) + remove = true; +/* separate the trigger from the filter (k:v [if filter]) */ trigger =strsep (¶m," \t" );if (!trigger)
@@ -1680,7 +1974,7 @@ static int event_hist_trigger_func(struct event_command *cmd_ops,if (attrs->map_bits) hist_trigger_bits = attrs->map_bits; - hist_data =create_hist_data (hist_trigger_bits, attrs, file); + hist_data =create_hist_data (hist_trigger_bits, attrs, file, remove);if (IS_ERR (hist_data)) {destroy_hist_trigger_attrs (attrs);return PTR_ERR (hist_data);
@@ -1709,7 +2003,7 @@ static int event_hist_trigger_func(struct event_command *cmd_ops, goto out_free; } -if (glob[0 ] =='!' ) { +if (remove) { cmd_ops->unreg (glob+1 , trigger_ops, trigger_data, file); ret =0 ; goto out_free;
Signature · 1 line
1.9.3
Re: [PATCH v3 12/33] tracing: Add variable support to hist triggers
Hi Tom,
Quoted 10 lines · Tom Zanussi
Add support for saving the value of a current event's event field by assigning it to a variable that can be read by a subsequent event.
The basic syntax for saving a variable is to simply prefix a unique variable name not corresponding to any keyword along with an '=' sign to any event field.
Both keys and values can be saved and retrieved in this way:
# echo 'hist:keys=next_pid:vals=$ts0:ts0=common_timestamp ...
The 'common_timestamp' needs a '$' prefix, right?
Quoted 7 lines
# echo 'hist:timer_pid=common_pid:key=$timer_pid ...'If a variable isn't a key variable or prefixed with 'vals=', the associated event field will be saved in a variable but won't be summed as a value:
# echo 'hist:keys=next_pid:ts1=common_timestamp:...
Ditto.
Quoted 9 lines
Multiple variables can be assigned at the same time:
# echo 'hist:keys=pid:vals=$ts0,$b,field2:ts0=common_timestamp,b=field1 ...Multiple (or single) variables can also be assigned at the same time using separate assignments:
# echo 'hist:keys=pid:vals=$ts0:ts0=common_timestamp:b=field1:c=field2 ...
Same here..
So you separated the variable definition and it should not be a part of key or value field, right?
Quoted 3 lines
Variables set as above can be used by being referenced from another event, as described in a subsequent patch.
That means, a variable should be unique (in a tracing instance at least).
Quoted 34 lines
Signed-off-by Tom Zanussi <[email protected]>Signed-off-by Baohong Liu <[email protected]>--- kernel/trace/trace_events_hist.c | 374 ++++++++++++++++++++++++++++++++++----- 1 file changed, 334 insertions(+), 40 deletions(-)kernel/trace/trace_events_hist.c +8
-0@@ -30,6 +30,13 @@ typedef u64 (*hist_field_fn_t) (struct hist_field *field, void *event, struct ring_buffer_event *rbe);#define HIST_FIELD_OPERANDS_MAX 2 +#define HIST_FIELDS_MAX (TRACING_MAP_FIELDS_MAX + TRACING_MAP_VARS_MAX) + +struct hist_var { +char *name; +struct hist_trigger_data *hist_data; +unsigned int idx; +};struct hist_field {struct ftrace_event_field *field;@@ -40,6 +47,7 @@ struct hist_field {unsigned int is_signed;struct hist_field *operands[HIST_FIELD_OPERANDS_MAX ];struct hist_trigger_data *hist_data; +struct hist_var var; }; static u64 hist_field_none(struct hist_field *field, void *event,
kernel/trace/trace_events_hist.c enum hist_field_flags { · line 150
Quoted 5 lines
kernel/trace/trace_events_hist.c +2
-0@@ -138,6 +146,14 @@ enum hist_field_flags {HIST_FIELD_FL_LOG2 =1 <<9 ,HIST_FIELD_FL_TIMESTAMP =1 <<10 ,HIST_FIELD_FL_TIMESTAMP_USECS =1 <<11 , +HIST_FIELD_FL_VAR =1 <<12 , + HIST_FIELD_FL_VAR_ONLY = 1 << 13,
And then I'm not sure what _VAR_ONLY flag is for. IIUC it's used to identify pure variable definition from a definition in value fields. But it's not possible anymore, no?
Quoted 8 lines
kernel/trace/trace_events_hist.c +6
-0+}; + +struct var_defs { +unsigned int n_vars; +char *name[TRACING_MAP_VARS_MAX ]; +char *expr[TRACING_MAP_VARS_MAX ]; };
[SNIP]
kernel/trace/trace_events_hist.c static int create_val_field(struct hist_trigger_data *hist_data, · line 690
Quoted 11 lines
kernel/trace/trace_events_hist.c +11
-0+static int create_var_field (struct hist_trigger_data *hist_data, +unsigned int val_idx, +struct trace_event_file *file, +char *var_name,char *expr_str) +{ +unsigned long flags =0 ; + +if (WARN_ON (val_idx >=TRACING_MAP_VALS_MAX +TRACING_MAP_VARS_MAX )) +return -EINVAL ; + +if (find_var (file, var_name) && !hist_data->remove) {
Is this for the uniqueness check? But I think it only checks variables in the current event (file).
Quoted 14 lines
kernel/trace/trace_events_hist.c +14
-0+return -EINVAL ; + } + + flags |=HIST_FIELD_FL_VAR ; + hist_data->n_vars++; +if (hist_data->n_vars >TRACING_MAP_VARS_MAX ) { +return -EINVAL ; + } + + flags |=HIST_FIELD_FL_VAR_ONLY ; + +return __create_val_field (hist_data, val_idx, file, var_name, expr_str, flags); +} +
[SNIP]
Quoted 20 lines
kernel/trace/trace_events_hist.c +20
-0+static int parse_var_defs (struct hist_trigger_data *hist_data) +{ +char *s, *str, *var_name, *field_str; +unsigned int i, j, n_vars =0 ; +int ret =0 ; + +for (i =0 ; i < hist_data->attrs->n_assignments; i++) { + str = hist_data->attrs->assignment_str[i]; +for (j =0 ; j <TRACING_MAP_VARS_MAX ; j++) { + field_str =strsep (&str,"," ); +if (!field_str) +break ; + + var_name =strsep (&field_str,"=" ); +if (!var_name || !field_str) { + ret = -EINVAL ; + goto free; + } + + s =kstrdup (var_name,GFP_KERNEL );
kernel/trace/trace_events_hist.c static int create_key_fields(struct hist_trigger_data *hist_data, · line 931
Quoted 10 lines
kernel/trace/trace_events_hist.c +10
-0+if (!s) { + ret = -ENOMEM ; + goto free; + } + hist_data->attrs->var_defs.name[n_vars] = s; + + s =kstrdup (field_str,GFP_KERNEL ); +if (!s) { + ret = -ENOMEM ; + goto free;
It seems that it might leak the copy of var_name here..
Thanks,
Namhyung
Quoted 17 lines
kernel/trace/trace_events_hist.c +17
-0+ } + hist_data->attrs->var_defs.expr[n_vars++] = s; + + hist_data->attrs->var_defs.n_vars = n_vars; + +if (n_vars ==TRACING_MAP_VARS_MAX ) + goto free; + } + } + +return ret; + free: +free_var_defs (hist_data); + +return ret; +} +
Re: [PATCH v3 12/33] tracing: Add variable support to hist triggers
Hi Namhyung,
Quoted 10 lines · Namhyung Kim
Hi Tom,
Quoted 7 lines · Tom Zanussi
Add support for saving the value of a current event's event field by assigning it to a variable that can be read by a subsequent event.
The basic syntax for saving a variable is to simply prefix a unique variable name not corresponding to any keyword along with an '=' sign to any event field.
Quoted 6 lines
Quoted 3 lines
Both keys and values can be saved and retrieved in this way:
# echo 'hist:keys=next_pid:vals=$ts0:ts0=common_timestamp ...The '
common_timestamp' needs a '$' prefix, right?
Yeah, thanks for pointing this out - the variables got fixed, but missed the common_timestamp bits.
Quoted 18 lines
Quoted 7 lines
# echo 'hist:timer_pid=common_pid:key=$timer_pid ...'If a variable isn't a key variable or prefixed with 'vals=', the associated event field will be saved in a variable but won't be summed as a value:
# echo 'hist:keys=next_pid:ts1=common_timestamp:...Ditto.
Quoted 8 lines
Multiple variables can be assigned at the same time:
# echo 'hist:keys=pid:vals=$ts0,$b,field2:ts0=common_timestamp,b=field1 ...Multiple (or single) variables can also be assigned at the same time using separate assignments:
Quoted 7 lines
Quoted 1 line
# echo 'hist:keys=pid:vals=$ts0:ts0=common_timestamp:b=field1:c=field2 ...Same here..
So you separated the variable definition and it should not be a part of key or value field, right?
Right.
Quoted 6 lines
Quoted 3 lines
Variables set as above can be used by being referenced from another event, as described in a subsequent patch.
That means, a variable should be unique (in a tracing instance at least).
Not exactly - I thought it would be too restrictive to force users to come up with names that would be unique across an instance, so made them unique to events. So users can use the same variable name for triggers on multiple events, and in cases where there are multiple events with the same name, use the fully-qualified subsys.event_name.variable_name name to disambiguate them. If there is only one event with the given name, thus no ambiguity, specifying the bare variable name suffices. Again the intent is to make the common case as simple as possible, but allow for more complex cases without requiring naming gymnastics from the user.
Quoted 33 lines
Quoted 33 lines
Signed-off-by Tom Zanussi <[email protected]>Signed-off-by Baohong Liu <[email protected]>--- kernel/trace/trace_events_hist.c | 374 ++++++++++++++++++++++++++++++++++----- 1 file changed, 334 insertions(+), 40 deletions(-)kernel/trace/trace_events_hist.c +8
-0@@ -30,6 +30,13 @@ typedef u64 (*hist_field_fn_t) (struct hist_field *field, void *event, struct ring_buffer_event *rbe);#define HIST_FIELD_OPERANDS_MAX 2 +#define HIST_FIELDS_MAX (TRACING_MAP_FIELDS_MAX + TRACING_MAP_VARS_MAX) + +struct hist_var { +char *name; +struct hist_trigger_data *hist_data; +unsigned int idx; +};struct hist_field {struct ftrace_event_field *field;@@ -40,6 +47,7 @@ struct hist_field {unsigned int is_signed;struct hist_field *operands[HIST_FIELD_OPERANDS_MAX ];struct hist_trigger_data *hist_data; +struct hist_var var; };
Quoted 12 lines
kernel/trace/trace_events_hist.c enum hist_field_flags { · line 150
Quoted 7 lines
kernel/trace/trace_events_hist.c +2
-0static u64 hist_field_none(struct hist_field *field, void *event,@@ -138,6 +146,14 @@ enum hist_field_flags {HIST_FIELD_FL_LOG2 =1 <<9 ,HIST_FIELD_FL_TIMESTAMP =1 <<10 ,HIST_FIELD_FL_TIMESTAMP_USECS =1 <<11 , +HIST_FIELD_FL_VAR =1 <<12 , + HIST_FIELD_FL_VAR_ONLY = 1 << 13,And then I'm not sure what
_VAR_ONLYflag is for. IIUC it's used to identify pure variable definition from a definition in value fields. But it's not possible anymore, no?
Right, the recent change to separate them makes the VAR_ONLY redundant, will remove it.
Quoted 12 lines
Quoted 9 lines
kernel/trace/trace_events_hist.c +6
-0+}; + +struct var_defs { +unsigned int n_vars; +char *name[TRACING_MAP_VARS_MAX ]; +char *expr[TRACING_MAP_VARS_MAX ]; };[SNIP]
Quoted 15 lines
kernel/trace/trace_events_hist.c static int create_val_field(struct hist_trigger_data *hist_data, · line 690
Quoted 11 lines
kernel/trace/trace_events_hist.c +11
-0+static int create_var_field (struct hist_trigger_data *hist_data, +unsigned int val_idx, +struct trace_event_file *file, +char *var_name,char *expr_str) +{ +unsigned long flags =0 ; + +if (WARN_ON (val_idx >=TRACING_MAP_VALS_MAX +TRACING_MAP_VARS_MAX )) +return -EINVAL ; + +if (find_var (file, var_name) && !hist_data->remove) {Is this for the uniqueness check? But I think it only checks variables in the current event (file).
Right, hopefully this make sense now in the context discussed above.
Quoted 42 lines
Quoted 15 lines
kernel/trace/trace_events_hist.c +14
-0+return -EINVAL ; + } + + flags |=HIST_FIELD_FL_VAR ; + hist_data->n_vars++; +if (hist_data->n_vars >TRACING_MAP_VARS_MAX ) { +return -EINVAL ; + } + + flags |=HIST_FIELD_FL_VAR_ONLY ; + +return __create_val_field (hist_data, val_idx, file, var_name, expr_str, flags); +} +[SNIP]
Quoted 24 lines
kernel/trace/trace_events_hist.c +24
-0+static int parse_var_defs (struct hist_trigger_data *hist_data) +{ +char *s, *str, *var_name, *field_str; +unsigned int i, j, n_vars =0 ; +int ret =0 ; + +for (i =0 ; i < hist_data->attrs->n_assignments; i++) { + str = hist_data->attrs->assignment_str[i]; +for (j =0 ; j <TRACING_MAP_VARS_MAX ; j++) { + field_str =strsep (&str,"," ); +if (!field_str) +break ; + + var_name =strsep (&field_str,"=" ); +if (!var_name || !field_str) { + ret = -EINVAL ; + goto free; + } + + s =kstrdup (var_name,GFP_KERNEL ); +if (!s) { + ret = -ENOMEM ; + goto free; + }
Quoted 9 lines
kernel/trace/trace_events_hist.c static int create_key_fields(struct hist_trigger_data *hist_data, · line 931
Quoted 6 lines
kernel/trace/trace_events_hist.c +6
-0+ hist_data->attrs->var_defs.name[n_vars] = s; + + s =kstrdup (field_str,GFP_KERNEL ); +if (!s) { + ret = -ENOMEM ; + goto free;It seems that it might leak the copy of
var_namehere..
Unless I'm missing something, the free_var_defs() should handle this case too...
Thanks,
Tom
Quoted 21 lines
Thanks,
NamhyungQuoted 17 lines
kernel/trace/trace_events_hist.c +17
-0+ } + hist_data->attrs->var_defs.expr[n_vars++] = s; + + hist_data->attrs->var_defs.n_vars = n_vars; + +if (n_vars ==TRACING_MAP_VARS_MAX ) + goto free; + } + } + +return ret; + free: +free_var_defs (hist_data); + +return ret; +} +
Re: [PATCH v3 12/33] tracing: Add variable support to hist triggers
Hi Tom,
Quoted 8 lines · Tom Zanussi
Quoted 6 lines · Namhyung Kim
Quoted 2 lines · Tom Zanussi
Variables set as above can be used by being referenced from another event, as described in a subsequent patch.
That means, a variable should be unique (in a tracing instance at least).
Quoted 10 lines
Not exactly - I thought it would be too restrictive to force users to come up with names that would be unique across an instance, so made them unique to events. So users can use the same variable name for triggers on multiple events, and in cases where there are multiple events with the same name, use the fully-qualified subsys.event_name.variable_name name to disambiguate them. If there is only one event with the given name, thus no ambiguity, specifying the bare variable name suffices. Again the intent is to make the common case as simple as possible, but allow for more complex cases without requiring naming gymnastics from the user.
Agreed. Thanks for the explanation.
[SNIP]
Quoted 34 lines
Quoted 24 lines
Quoted 24 lines
kernel/trace/trace_events_hist.c +24
-0+static int parse_var_defs (struct hist_trigger_data *hist_data) +{ +char *s, *str, *var_name, *field_str; +unsigned int i, j, n_vars =0 ; +int ret =0 ; + +for (i =0 ; i < hist_data->attrs->n_assignments; i++) { + str = hist_data->attrs->assignment_str[i]; +for (j =0 ; j <TRACING_MAP_VARS_MAX ; j++) { + field_str =strsep (&str,"," ); +if (!field_str) +break ; + + var_name =strsep (&field_str,"=" ); +if (!var_name || !field_str) { + ret = -EINVAL ; + goto free; + } + + s =kstrdup (var_name,GFP_KERNEL ); +if (!s) { + ret = -ENOMEM ; + goto free; + }Quoted 9 lines
kernel/trace/trace_events_hist.c static int create_key_fields(struct hist_trigger_data *hist_data, · line 931
Quoted 6 lines
kernel/trace/trace_events_hist.c +6
-0+ hist_data->attrs->var_defs.name[n_vars] = s; + + s =kstrdup (field_str,GFP_KERNEL ); +if (!s) { + ret = -ENOMEM ; + goto free;It seems that it might leak the copy of
var_namehere..
Quoted 2 lines
Unless I'm missing something, the
free_var_defs()should handle this case too...
But the var_defs.n_vars was not increased at this point.
Thanks,
Namhyung
Quoted 20 lines
Quoted 18 lines
Quoted 18 lines
kernel/trace/trace_events_hist.c +17
-0+ } + hist_data->attrs->var_defs.expr[n_vars++] = s; + + hist_data->attrs->var_defs.n_vars = n_vars; + +if (n_vars ==TRACING_MAP_VARS_MAX ) + goto free; + } + } + +return ret; + free: +free_var_defs (hist_data); + +return ret; +} +
Re: [PATCH v3 12/33] tracing: Add variable support to hist triggers
Quoted 61 lines · Namhyung Kim
Hi Tom,
Quoted 8 lines · Tom Zanussi
Quoted 6 lines · Namhyung Kim
Quoted 2 lines · Tom Zanussi
Variables set as above can be used by being referenced from another event, as described in a subsequent patch.
That means, a variable should be unique (in a tracing instance at least).
Quoted 10 lines
Not exactly - I thought it would be too restrictive to force users to come up with names that would be unique across an instance, so made them unique to events. So users can use the same variable name for triggers on multiple events, and in cases where there are multiple events with the same name, use the fully-qualified subsys.event_name.variable_name name to disambiguate them. If there is only one event with the given name, thus no ambiguity, specifying the bare variable name suffices. Again the intent is to make the common case as simple as possible, but allow for more complex cases without requiring naming gymnastics from the user.
Agreed. Thanks for the explanation.
[SNIP]
Quoted 34 lines
Quoted 33 lines
Quoted 20 lines
kernel/trace/trace_events_hist.c +20
-0+static int parse_var_defs (struct hist_trigger_data *hist_data) +{ +char *s, *str, *var_name, *field_str; +unsigned int i, j, n_vars =0 ; +int ret =0 ; + +for (i =0 ; i < hist_data->attrs->n_assignments; i++) { + str = hist_data->attrs->assignment_str[i]; +for (j =0 ; j <TRACING_MAP_VARS_MAX ; j++) { + field_str =strsep (&str,"," ); +if (!field_str) +break ; + + var_name =strsep (&field_str,"=" ); +if (!var_name || !field_str) { + ret = -EINVAL ; + goto free; + } + + s =kstrdup (var_name,GFP_KERNEL );kernel/trace/trace_events_hist.c static int create_key_fields(struct hist_trigger_data *hist_data, · line 931
Quoted 10 lines
kernel/trace/trace_events_hist.c +10
-0+if (!s) { + ret = -ENOMEM ; + goto free; + } + hist_data->attrs->var_defs.name[n_vars] = s; + + s =kstrdup (field_str,GFP_KERNEL ); +if (!s) { + ret = -ENOMEM ; + goto free;It seems that it might leak the copy of
var_namehere..
Quoted 5 lines
Quoted 2 lines
Unless I'm missing something, the
free_var_defs()should handle this case too...But the var_defs.n_vars was not increased at this point.
Yep. Got it, thanks.
Tom
Quoted 24 lines
Thanks,
NamhyungQuoted 20 lines
Quoted 18 lines
Quoted 18 lines
kernel/trace/trace_events_hist.c +17
-0+ } + hist_data->attrs->var_defs.expr[n_vars++] = s; + + hist_data->attrs->var_defs.n_vars = n_vars; + +if (n_vars ==TRACING_MAP_VARS_MAX ) + goto free; + } + } + +return ret; + free: +free_var_defs (hist_data); + +return ret; +} +
PATCH v3 13/33 tracing: Account for variables in named trigger compatibility
In reply to Tom ZanussiNamed triggers must also have the same set of variables in order to be considered compatible - update the trigger match test to account for that.
The reason for this requirement is that named triggers with variables are meant to allow one or more events to set the same variable.
--- kernel/trace/trace_events_hist.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-)
kernel/trace/trace_events_hist.c +6 -1
@@ -1614,7 +1614,7 @@ static int event_hist_trigger_print(struct seq_file *m, sort_key = &hist_data->sort_keys[i]; idx = sort_key->field_idx; -if (WARN_ON (idx >=TRACING_MAP_FIELDS_MAX )) +if (WARN_ON (idx >=HIST_FIELDS_MAX ))return -EINVAL ;if (i >0 )
@@ -1802,6 +1802,11 @@ static bool hist_trigger_match(struct event_trigger_data *data,return false;if (key_field->is_signed != key_field_test->is_signed)return false; +if (!!key_field->var.name != !!key_field_test->var.name) +return false; +if (key_field->var.name && +strcmp (key_field->var.name, key_field_test->var.name) !=0 ) +return false; }for (i =0 ; i < hist_data->n_sort_keys; i++) {
Signature · 1 line
1.9.3
PATCH v3 14/33 tracing: Move get_hist_field_flags()
In reply to Tom ZanussiMove get_hist_field_flags() to make it more easily accessible for new code (and keep the move separate from new functionality).
--- kernel/trace/trace_events_hist.c | 44 ++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 22 deletions(-)
kernel/trace/trace_events_hist.c +22 -22
@@ -457,6 +457,28 @@ static void hist_trigger_elt_comm_init(struct tracing_map_elt *elt) .elt_init = hist_trigger_elt_comm_init, }; +static const char *get_hist_field_flags (struct hist_field *hist_field) +{ +const char *flags_str =NULL ; + +if (hist_field->flags &HIST_FIELD_FL_HEX ) + flags_str ="hex" ; +else if (hist_field->flags &HIST_FIELD_FL_SYM ) + flags_str ="sym" ; +else if (hist_field->flags &HIST_FIELD_FL_SYM_OFFSET ) + flags_str ="sym-offset" ; +else if (hist_field->flags &HIST_FIELD_FL_EXECNAME ) + flags_str ="execname" ; +else if (hist_field->flags &HIST_FIELD_FL_SYSCALL ) + flags_str ="syscall" ; +else if (hist_field->flags &HIST_FIELD_FL_LOG2 ) + flags_str ="log2" ; +else if (hist_field->flags &HIST_FIELD_FL_TIMESTAMP_USECS ) + flags_str ="usecs" ; + +return flags_str; +} +static void destroy_hist_field (struct hist_field *hist_field,unsigned int level) {
@@ -1499,28 +1521,6 @@ static int event_hist_open(struct inode *inode, struct file *file) .release = single_release, }; -static const char *get_hist_field_flags (struct hist_field *hist_field) -{ -const char *flags_str =NULL ; - -if (hist_field->flags &HIST_FIELD_FL_HEX ) - flags_str ="hex" ; -else if (hist_field->flags &HIST_FIELD_FL_SYM ) - flags_str ="sym" ; -else if (hist_field->flags &HIST_FIELD_FL_SYM_OFFSET ) - flags_str ="sym-offset" ; -else if (hist_field->flags &HIST_FIELD_FL_EXECNAME ) - flags_str ="execname" ; -else if (hist_field->flags &HIST_FIELD_FL_SYSCALL ) - flags_str ="syscall" ; -else if (hist_field->flags &HIST_FIELD_FL_LOG2 ) - flags_str ="log2" ; -else if (hist_field->flags &HIST_FIELD_FL_TIMESTAMP_USECS ) - flags_str ="usecs" ; - -return flags_str; -} -static void hist_field_print (struct seq_file *m,struct hist_field *hist_field) {const char *field_name =hist_field_name (hist_field,0 );
Signature · 1 line
1.9.3
PATCH v3 15/33 tracing: Add simple expression support to hist triggers
In reply to Tom ZanussiAdd support for simple addition, subtraction, and unary expressions (-(expr) and expr, where expr = b-a, a+b, a+b+c) to hist triggers, in order to support a minimal set of useful inter-event calculations.
These operations are needed for calculating latencies between events (timestamp1-timestamp0) and for combined latencies (latencies over 3 or more events).
In the process, factor out some common code from key and value parsing.
--- kernel/trace/trace_events_hist.c | 514 +++++++++++++++++++++++++++++++++------ 1 file changed, 440 insertions(+), 74 deletions(-)
kernel/trace/trace_events_hist.c +440 -74
@@ -32,6 +32,13 @@ typedef u64 (*hist_field_fn_t) (struct hist_field *field, void *event,#define HIST_FIELD_OPERANDS_MAX 2#define HIST_FIELDS_MAX (TRACING_MAP_FIELDS_MAX + TRACING_MAP_VARS_MAX) +enum field_op_id { +FIELD_OP_NONE , +FIELD_OP_PLUS , +FIELD_OP_MINUS , +FIELD_OP_UNARY_MINUS , +}; +struct hist_var {char *name;struct hist_trigger_data *hist_data;
@@ -48,6 +55,8 @@ struct hist_field {struct hist_field *operands[HIST_FIELD_OPERANDS_MAX ];struct hist_trigger_data *hist_data;struct hist_var var; +enum field_op_id operator; +char *name; }; static u64 hist_field_none(struct hist_field *field, void *event,
@@ -98,6 +107,41 @@ static u64 hist_field_log2(struct hist_field *hist_field, void *event,return (u64 )ilog2 (roundup_pow_of_two (val)); } +static u64 hist_field_plus (struct hist_field *hist_field,void *event, +struct ring_buffer_event *rbe) +{ +struct hist_field *operand1 = hist_field->operands[0 ]; +struct hist_field *operand2 = hist_field->operands[1 ]; + +u64 val1 = operand1->fn (operand1, event, rbe); +u64 val2 = operand2->fn (operand2, event, rbe); + +return val1 + val2; +} + +static u64 hist_field_minus (struct hist_field *hist_field,void *event, +struct ring_buffer_event *rbe) +{ +struct hist_field *operand1 = hist_field->operands[0 ]; +struct hist_field *operand2 = hist_field->operands[1 ]; + +u64 val1 = operand1->fn (operand1, event, rbe); +u64 val2 = operand2->fn (operand2, event, rbe); + +return val1 - val2; +} + +static u64 hist_field_unary_minus (struct hist_field *hist_field,void *event, +struct ring_buffer_event *rbe) +{ +struct hist_field *operand = hist_field->operands[0 ]; + +s64 sval = (s64 )operand->fn (operand, event, rbe); +u64 val = (u64)-sval; + +return val; +} +#define DEFINE_HIST_FIELD_FN (type) \ static u64 hist_field_##type(struct hist_field *hist_field, \ void *event, \
@@ -148,6 +192,7 @@ enum hist_field_flags {HIST_FIELD_FL_TIMESTAMP_USECS =1 <<11 ,HIST_FIELD_FL_VAR =1 <<12 ,HIST_FIELD_FL_VAR_ONLY =1 <<13 , + HIST_FIELD_FL_EXPR = 1 << 14, }; struct var_defs {
@@ -218,6 +263,8 @@ static const char *hist_field_name(struct hist_field *field, field_name =hist_field_name (field->operands[0 ], ++level); else if (field->flags & HIST_FIELD_FL_TIMESTAMP) field_name ="$common_timestamp" ; + else if (field->flags & HIST_FIELD_FL_EXPR) + field_name = field->name;if (field_name ==NULL ) field_name ="" ;
@@ -479,6 +526,93 @@ static const char *get_hist_field_flags(struct hist_field *hist_field)return flags_str; } +static char *expr_str (struct hist_field *field,unsigned int level) +{ +char *expr; + +if (level >1 ) +return NULL ; + + expr =kzalloc (MAX_FILTER_STR_VAL ,GFP_KERNEL ); +if (!expr) +return NULL ; + +if (field->operator ==FIELD_OP_UNARY_MINUS ) { +char *subexpr; + +strcat (expr,"-(" ); + subexpr =expr_str (field->operands[0 ], ++level); +if (!subexpr) { +kfree (expr); +return NULL ; + } +strcat (expr, subexpr); +strcat (expr,")" ); + +return expr; + } + +strcat (expr,hist_field_name (field->operands[0 ],0 )); +if (field->operands[0 ]->flags) { +const char *flags_str =get_hist_field_flags (field->operands[0 ]); + +if (flags_str) { +strcat (expr,"." ); +strcat (expr, flags_str); + } + } + +switch (field->operator) { +case FIELD_OP_MINUS : +strcat (expr,"-" ); +break ; +case FIELD_OP_PLUS : +strcat (expr,"+" ); +break ; +default : +kfree (expr); +return NULL ; + } + +strcat (expr,hist_field_name (field->operands[1 ],0 )); +if (field->operands[1 ]->flags) { +const char *flags_str =get_hist_field_flags (field->operands[1 ]); + +if (flags_str) { +strcat (expr,"." ); +strcat (expr, flags_str); + } + } + +return expr; +} + +static int contains_operator (char *str) +{ +enum field_op_id field_op =FIELD_OP_NONE ; +char *op; + + op =strpbrk (str,"+-" ); +if (!op) +return FIELD_OP_NONE ; + +switch (*op) { +case '-' : +if (*str =='-' ) + field_op =FIELD_OP_UNARY_MINUS ; +else + field_op =FIELD_OP_MINUS ; +break ; +case '+' : + field_op =FIELD_OP_PLUS ; +break ; +default : +break ; + } + +return field_op; +} +static void destroy_hist_field (struct hist_field *hist_field,unsigned int level) {
@@ -494,6 +628,7 @@ static void destroy_hist_field(struct hist_field *hist_field,destroy_hist_field (hist_field->operands[i], level +1 );kfree (hist_field->var.name); +kfree (hist_field->name);kfree (hist_field); }
@@ -514,6 +649,9 @@ static struct hist_field *create_hist_field(struct hist_trigger_data *hist_data, hist_field->hist_data = hist_data; +if (flags &HIST_FIELD_FL_EXPR ) + goto out;/* caller will populate */ +if (flags &HIST_FIELD_FL_HITCOUNT ) { hist_field->fn = hist_field_counter; goto out;
@@ -586,6 +724,289 @@ static void destroy_hist_fields(struct hist_trigger_data *hist_data) } } +static char *field_name_from_var (struct hist_trigger_data *hist_data, +char *var_name) +{ +char *name, *field; +unsigned int i; + +for (i =0 ; i < hist_data->attrs->var_defs.n_vars; i++) { + name = hist_data->attrs->var_defs.name[i]; + +if (strcmp (var_name, name) ==0 ) { + field = hist_data->attrs->var_defs.expr[i]; +if (contains_operator (field)) +continue ; +return field; + } + } + +return NULL ; +} + +static char *local_field_var_ref (struct hist_trigger_data *hist_data, +char *var_name) +{ + var_name++; + +return field_name_from_var (hist_data, var_name); +} + +static struct ftrace_event_field * +parse_field (struct hist_trigger_data *hist_data,struct trace_event_file *file, +char *field_str,unsigned long *flags) +{ +struct ftrace_event_field *field =NULL ; +char *field_name, *modifier, *str; + + modifier = str =kstrdup (field_str,GFP_KERNEL ); +if (!modifier) +return ERR_PTR (-ENOMEM ); + + field_name =strsep (&modifier,"." ); +if (modifier) { +if (strcmp (modifier,"hex" ) ==0 ) + *flags |=HIST_FIELD_FL_HEX ; +else if (strcmp (modifier,"sym" ) ==0 ) + *flags |=HIST_FIELD_FL_SYM ; +else if (strcmp (modifier,"sym-offset" ) ==0 ) + *flags |=HIST_FIELD_FL_SYM_OFFSET ; +else if ((strcmp (modifier,"execname" ) ==0 ) && + (strcmp (field_name,"common_pid" ) ==0 )) + *flags |=HIST_FIELD_FL_EXECNAME ; +else if (strcmp (modifier,"syscall" ) ==0 ) + *flags |=HIST_FIELD_FL_SYSCALL ; +else if (strcmp (modifier,"log2" ) ==0 ) + *flags |=HIST_FIELD_FL_LOG2 ; +else if (strcmp (modifier,"usecs" ) ==0 ) + *flags |=HIST_FIELD_FL_TIMESTAMP_USECS ; +else { + field =ERR_PTR (-EINVAL ); + goto out; + } + } + +if (strcmp (field_name,"$common_timestamp" ) ==0 ) { + *flags |=HIST_FIELD_FL_TIMESTAMP ; + hist_data->enable_timestamps = true; +if (*flags &HIST_FIELD_FL_TIMESTAMP_USECS ) + hist_data->attrs->ts_in_usecs = true; + }else { + field =trace_find_event_field (file->event_call, field_name); +if (!field || !field->size) { + field =ERR_PTR (-EINVAL ); + goto out; + } + } + out: +kfree (str); + +return field; +} + +struct hist_field *parse_atom (struct hist_trigger_data *hist_data, +struct trace_event_file *file,char *str, +unsigned long *flags,char *var_name) +{ +char *s; +struct ftrace_event_field *field =NULL ; +struct hist_field *hist_field =NULL ; +int ret =0 ; + + s =local_field_var_ref (hist_data, str); +if (s) + str = s; + + field =parse_field (hist_data, file, str, flags); +if (IS_ERR (field)) { + ret =PTR_ERR (field); + goto out; + } + + hist_field =create_hist_field (hist_data, field, *flags, var_name); +if (!hist_field) { + ret = -ENOMEM ; + goto out; + } + +return hist_field; + out: +return ERR_PTR (ret); +} + +static struct hist_field *parse_expr (struct hist_trigger_data *hist_data, +struct trace_event_file *file, +char *str,unsigned long flags, +char *var_name,unsigned int level); + +static struct hist_field *parse_unary (struct hist_trigger_data *hist_data, +struct trace_event_file *file, +char *str,unsigned long flags, +char *var_name,unsigned int level) +{ +struct hist_field *operand1, *expr =NULL ; +unsigned long operand_flags; +int ret =0 ; +char *s; + +// we support only -(xxx) i.e. explicit parens required + +if (level >2 ) { + ret = -EINVAL ; + goto free; + } + + str++;// skip leading '-' + + s =strchr (str,'(' ); +if (s) + str++; +else { + ret = -EINVAL ; + goto free; + } + + s =strchr (str,')' ); +if (s) + *s ='\0' ; +else { + ret = -EINVAL ;// no closing ')' + goto free; + } + +strsep (&str,"(" ); +if (!str) { + ret = -EINVAL ; + goto free; + } + + flags |=HIST_FIELD_FL_EXPR ; + expr =create_hist_field (hist_data,NULL , flags, var_name); +if (!expr) { + ret = -ENOMEM ; + goto free; + } + + operand_flags =0 ; + operand1 =parse_expr (hist_data, file, str, operand_flags,NULL , ++level); +if (IS_ERR (operand1)) { + ret =PTR_ERR (operand1); + goto free; + } + + expr->fn = hist_field_unary_minus; + expr->operands[0 ] = operand1; + expr->operator =FIELD_OP_UNARY_MINUS ; + expr->name =expr_str (expr,0 ); + +return expr; + free: +return ERR_PTR (ret); +} + +static int check_expr_operands (struct hist_field *operand1, +struct hist_field *operand2) +{ +unsigned long operand1_flags = operand1->flags; +unsigned long operand2_flags = operand2->flags; + +if ((operand1_flags &HIST_FIELD_FL_TIMESTAMP_USECS ) != + (operand2_flags &HIST_FIELD_FL_TIMESTAMP_USECS )) +return -EINVAL ; + +return 0 ; +} + +static struct hist_field *parse_expr (struct hist_trigger_data *hist_data, +struct trace_event_file *file, +char *str,unsigned long flags, +char *var_name,unsigned int level) +{ +struct hist_field *operand1 =NULL , *operand2 =NULL , *expr =NULL ; +unsigned long operand_flags; +int field_op, ret = -EINVAL ; +char *sep, *operand1_str; + +if (level >2 ) +return ERR_PTR (-EINVAL ); + + field_op =contains_operator (str); + +if (field_op ==FIELD_OP_NONE ) +return parse_atom (hist_data, file, str, &flags, var_name); + +if (field_op ==FIELD_OP_UNARY_MINUS ) +return parse_unary (hist_data, file, str, flags, var_name, ++level); + +switch (field_op) { +case FIELD_OP_MINUS : + sep ="-" ; +break ; +case FIELD_OP_PLUS : + sep ="+" ; +break ; +default : + goto free; + } + + operand1_str =strsep (&str, sep); +if (!operand1_str || !str) + goto free; + + operand_flags =0 ; + operand1 =parse_atom (hist_data, file, operand1_str, + &operand_flags,NULL ); +if (IS_ERR (operand1)) { + ret =PTR_ERR (operand1); + operand1 =NULL ; + goto free; + } + +// rest of string could be another expression e.g. b+c in a+b+c + operand_flags =0 ; + operand2 =parse_expr (hist_data, file, str, operand_flags,NULL , ++level); +if (IS_ERR (operand2)) { + ret =PTR_ERR (operand2); + operand2 =NULL ; + goto free; + } + + ret =check_expr_operands (operand1, operand2); +if (ret) + goto free; + + flags |=HIST_FIELD_FL_EXPR ; + expr =create_hist_field (hist_data,NULL , flags, var_name); +if (!expr) { + ret = -ENOMEM ; + goto free; + } + + expr->operands[0 ] = operand1; + expr->operands[1 ] = operand2; + expr->operator = field_op; + expr->name =expr_str (expr,0 ); + +switch (field_op) { +case FIELD_OP_MINUS : + expr->fn = hist_field_minus; +break ; +case FIELD_OP_PLUS : + expr->fn = hist_field_plus; +break ; +default : + goto free; + } + +return expr; + free: +destroy_hist_field (operand1,0 ); +destroy_hist_field (operand2,0 ); +destroy_hist_field (expr,0 ); + +return ERR_PTR (ret); +} +static int create_hitcount_val (struct hist_trigger_data *hist_data) { hist_data->fields[HITCOUNT_IDX] =
@@ -645,41 +1066,21 @@ static int __create_val_field(struct hist_trigger_data *hist_data, char *var_name, char *field_str, unsigned long flags) { - struct ftrace_event_field *field = NULL; -char *field_name; + struct hist_field *hist_field;int ret =0 ; - field_name =strsep (&field_str,"." ); -if (field_str) { -if (strcmp (field_str,"hex" ) ==0 ) - flags |=HIST_FIELD_FL_HEX ; -else { - ret = -EINVAL ; - goto out; - } - } - -if (strcmp (field_name,"$common_timestamp" ) ==0 ) { - flags |=HIST_FIELD_FL_TIMESTAMP ; - hist_data->enable_timestamps = true; - }else { - field =trace_find_event_field (file->event_call, field_name); -if (!field || !field->size) { - ret = -EINVAL ; - goto out; - } - } - - hist_data->fields[val_idx] =create_hist_field (hist_data, field, flags, var_name); -if (!hist_data->fields[val_idx]) { - ret = -ENOMEM ; + hist_field =parse_expr (hist_data, file, field_str, flags, var_name,0 ); +if (IS_ERR (hist_field)) { + ret =PTR_ERR (hist_field); goto out; } + hist_data->fields[val_idx] = hist_field; + ++hist_data->n_vals; ++hist_data->n_fields; -if (hist_data->fields[val_idx]->flags &HIST_FIELD_FL_VAR_ONLY ) +if (hist_field->flags &HIST_FIELD_FL_VAR_ONLY ) hist_data->n_var_only++;if (WARN_ON (hist_data->n_vals >TRACING_MAP_VALS_MAX +TRACING_MAP_VARS_MAX ))
@@ -769,8 +1170,8 @@ static int create_key_field(struct hist_trigger_data *hist_data,struct trace_event_file *file, char *field_str) { - struct ftrace_event_field *field = NULL; struct hist_field *hist_field = NULL; +unsigned long flags =0 ;unsigned int key_size;int ret =0 ;
@@ -785,60 +1186,24 @@ static int create_key_field(struct hist_trigger_data *hist_data, key_size =sizeof (unsigned long ) *HIST_STACKTRACE_DEPTH ; hist_field =create_hist_field (hist_data,NULL , flags,NULL ); }else { -char *field_name =strsep (&field_str,"." ); - -if (field_str) { -if (strcmp (field_str,"hex" ) ==0 ) - flags |=HIST_FIELD_FL_HEX ; -else if (strcmp (field_str,"sym" ) ==0 ) - flags |=HIST_FIELD_FL_SYM ; -else if (strcmp (field_str,"sym-offset" ) ==0 ) - flags |=HIST_FIELD_FL_SYM_OFFSET ; -else if ((strcmp (field_str,"execname" ) ==0 ) && - (strcmp (field_name,"common_pid" ) ==0 )) - flags |=HIST_FIELD_FL_EXECNAME ; -else if (strcmp (field_str,"syscall" ) ==0 ) - flags |=HIST_FIELD_FL_SYSCALL ; -else if (strcmp (field_str,"log2" ) ==0 ) - flags |=HIST_FIELD_FL_LOG2 ; -else if (strcmp (field_str,"usecs" ) ==0 ) - flags |=HIST_FIELD_FL_TIMESTAMP_USECS ; -else { - ret = -EINVAL ; - goto out; - } + hist_field =parse_expr (hist_data, file, field_str, flags, +NULL ,0 ); +if (IS_ERR (hist_field)) { + ret =PTR_ERR (hist_field); + goto out; } -if (strcmp (field_name,"$common_timestamp" ) ==0 ) { - flags |=HIST_FIELD_FL_TIMESTAMP ; - hist_data->enable_timestamps = true; -if (flags &HIST_FIELD_FL_TIMESTAMP_USECS ) - hist_data->attrs->ts_in_usecs = true; - key_size =sizeof (u64); - }else { - field =trace_find_event_field (file->event_call, field_name); -if (!field || !field->size) { - ret = -EINVAL ; - goto out; - } - -if (is_string_field (field)) - key_size =MAX_FILTER_STR_VAL ; -else - key_size = field->size; - } + key_size = hist_field->size; } - hist_data->fields[key_idx] =create_hist_field (hist_data, field, flags,NULL ); -if (!hist_data->fields[key_idx]) { - ret = -ENOMEM ; - goto out; - } + hist_data->fields[key_idx] = hist_field; key_size =ALIGN (key_size,sizeof (u64)); hist_data->fields[key_idx]->size = key_size; hist_data->fields[key_idx]->offset = key_offset; + hist_data->key_size += key_size; +if (hist_data->key_size >HIST_KEY_SIZE_MAX ) { ret = -EINVAL ; goto out;
@@ -1421,7 +1786,8 @@ static void hist_trigger_stacktrace_print(struct seq_file *m,for (i =1 ; i < hist_data->n_vals; i++) { field_name =hist_field_name (hist_data->fields[i],0 ); -if (hist_data->fields[i]->flags &HIST_FIELD_FL_VAR ) +if (hist_data->fields[i]->flags &HIST_FIELD_FL_VAR || + hist_data->fields[i]->flags &HIST_FIELD_FL_EXPR )continue ;if (hist_data->fields[i]->flags &HIST_FIELD_FL_HEX ) {
Signature · 1 line
1.9.3
Re: [PATCH v3 15/33] tracing: Add simple expression support to hist triggers
Quoted 13 lines · Tom Zanussi
Add support for simple addition, subtraction, and unary expressions (-(expr) and expr, where expr = b-a, a+b, a+b+c) to hist triggers, in order to support a minimal set of useful inter-event calculations.
These operations are needed for calculating latencies between events (timestamp1-timestamp0) and for combined latencies (latencies over 3 or more events).
In the process, factor out some common code from key and value parsing.
Signed-off-by Tom Zanussi <[email protected]>---
[SNIP]
Quoted 52 lines
kernel/trace/trace_events_hist.c +52
-0+static struct hist_field *parse_unary (struct hist_trigger_data *hist_data, +struct trace_event_file *file, +char *str,unsigned long flags, +char *var_name,unsigned int level) +{ +struct hist_field *operand1, *expr =NULL ; +unsigned long operand_flags; +int ret =0 ; +char *s; + +// we support only -(xxx) i.e. explicit parens required + +if (level >2 ) { + ret = -EINVAL ; + goto free; + } + + str++;// skip leading '-' + + s =strchr (str,'(' ); +if (s) + str++; +else { + ret = -EINVAL ; + goto free; + } + + s =strchr (str,')' ); +if (s) + *s ='\0' ; +else { + ret = -EINVAL ;// no closing ')' + goto free; + } + +strsep (&str,"(" ); +if (!str) { + ret = -EINVAL ; + goto free; + } + + flags |=HIST_FIELD_FL_EXPR ; + expr =create_hist_field (hist_data,NULL , flags, var_name); +if (!expr) { + ret = -ENOMEM ; + goto free; + } + + operand_flags =0 ; + operand1 =parse_expr (hist_data, file, str, operand_flags,NULL , ++level); +if (IS_ERR (operand1)) { + ret =PTR_ERR (operand1);
kernel/trace/trace_events_hist.c static void destroy_hist_fields(struct hist_trigger_data *hist_data) · line 903
Quoted 10 lines
kernel/trace/trace_events_hist.c +10
-0+ goto free; + } + + expr->fn = hist_field_unary_minus; + expr->operands[0 ] = operand1; + expr->operator =FIELD_OP_UNARY_MINUS ; + expr->name =expr_str (expr,0 ); + +return expr; + free:
Missing destroy_hist_field(expr, 0)?
Thanks,
Namhyung
Quoted 3 lines
kernel/trace/trace_events_hist.c +3
-0+return ERR_PTR (ret); +} +
Re: [PATCH v3 15/33] tracing: Add simple expression support to hist triggers
Quoted 72 lines · Namhyung Kim
Quoted 13 lines · Tom Zanussi
Add support for simple addition, subtraction, and unary expressions (-(expr) and expr, where expr = b-a, a+b, a+b+c) to hist triggers, in order to support a minimal set of useful inter-event calculations.
These operations are needed for calculating latencies between events (timestamp1-timestamp0) and for combined latencies (latencies over 3 or more events).
In the process, factor out some common code from key and value parsing.
Signed-off-by Tom Zanussi <[email protected]>---
[SNIP]
Quoted 56 lines
kernel/trace/trace_events_hist.c +56
-0+static struct hist_field *parse_unary (struct hist_trigger_data *hist_data, +struct trace_event_file *file, +char *str,unsigned long flags, +char *var_name,unsigned int level) +{ +struct hist_field *operand1, *expr =NULL ; +unsigned long operand_flags; +int ret =0 ; +char *s; + +// we support only -(xxx) i.e. explicit parens required + +if (level >2 ) { + ret = -EINVAL ; + goto free; + } + + str++;// skip leading '-' + + s =strchr (str,'(' ); +if (s) + str++; +else { + ret = -EINVAL ; + goto free; + } + + s =strchr (str,')' ); +if (s) + *s ='\0' ; +else { + ret = -EINVAL ;// no closing ')' + goto free; + } + +strsep (&str,"(" ); +if (!str) { + ret = -EINVAL ; + goto free; + } + + flags |=HIST_FIELD_FL_EXPR ; + expr =create_hist_field (hist_data,NULL , flags, var_name); +if (!expr) { + ret = -ENOMEM ; + goto free; + } + + operand_flags =0 ; + operand1 =parse_expr (hist_data, file, str, operand_flags,NULL , ++level); +if (IS_ERR (operand1)) { + ret =PTR_ERR (operand1); + goto free; + } + + expr->fn = hist_field_unary_minus;
Quoted 9 lines
kernel/trace/trace_events_hist.c static void destroy_hist_fields(struct hist_trigger_data *hist_data) · line 903
Quoted 6 lines
kernel/trace/trace_events_hist.c +6
-0+ expr->operands[0 ] = operand1; + expr->operator =FIELD_OP_UNARY_MINUS ; + expr->name =expr_str (expr,0 ); + +return expr; + free:Missing
destroy_hist_field(expr, 0)?
Yep, will add, thanks.
Tom
PATCH v3 16/33 tracing: Generalize per-element hist trigger data
In reply to Tom ZanussiUp until now, hist triggers only needed per-element support for saving 'comm' data, which was saved directly as a private data pointer.
In anticipation of the need to save other data besides 'comm', add a new hist_elt_data struct for the purpose, and switch the current 'comm'-related code over to that.
--- kernel/trace/trace_events_hist.c | 71 +++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 33 deletions(-)
kernel/trace/trace_events_hist.c +38 -33
@@ -249,6 +249,10 @@ static u64 hist_field_timestamp(struct hist_field *hist_field, void *event,return ts; } +struct hist_elt_data { +char *comm; +}; +static const char *hist_field_name (struct hist_field *field,unsigned int level) {
@@ -463,45 +467,61 @@ static inline void save_comm(char *comm, struct task_struct *task)memcpy (comm, task->comm,TASK_COMM_LEN ); } -static void hist_trigger_elt_comm_free (struct tracing_map_elt *elt) +static void hist_elt_data_free (struct hist_elt_data *elt_data) +{ +kfree (elt_data->comm); +kfree (elt_data); +} + +static void hist_trigger_elt_data_free (struct tracing_map_elt *elt) { -kfree ((char *)elt->private_data); +struct hist_elt_data *elt_data = elt->private_data; + +hist_elt_data_free (elt_data); } -static int hist_trigger_elt_comm_alloc (struct tracing_map_elt *elt) +static int hist_trigger_elt_data_alloc (struct tracing_map_elt *elt) {struct hist_trigger_data *hist_data = elt->map->private_data; +unsigned int size =TASK_COMM_LEN +1 ; +struct hist_elt_data *elt_data;struct hist_field *key_field;unsigned int i; + elt_data =kzalloc (sizeof (*elt_data),GFP_KERNEL ); +if (!elt_data) +return -ENOMEM ; +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) + elt_data->comm =kzalloc (size,GFP_KERNEL ); +if (!elt_data->comm) { +kfree (elt_data);return -ENOMEM ; + }break ; } } + elt->private_data = elt_data; +return 0 ; } -static void hist_trigger_elt_comm_init (struct tracing_map_elt *elt) +static void hist_trigger_elt_data_init (struct tracing_map_elt *elt) { -char *comm = elt->private_data; +struct hist_elt_data *elt_data = elt->private_data; -if (comm) -save_comm (comm, current); +if (elt_data->comm) +save_comm (elt_data->comm, current); } -static const struct tracing_map_ops hist_trigger_elt_comm_ops = { - .elt_alloc = hist_trigger_elt_comm_alloc, - .elt_free = hist_trigger_elt_comm_free, - .elt_init = hist_trigger_elt_comm_init, +static const struct tracing_map_ops hist_trigger_elt_data_ops = { + .elt_alloc = hist_trigger_elt_data_alloc, + .elt_free = hist_trigger_elt_data_free, + .elt_init = hist_trigger_elt_data_init, };static const char *get_hist_field_flags (struct hist_field *hist_field)
@@ -1512,21 +1532,6 @@ static int create_tracing_map_fields(struct hist_trigger_data *hist_data)return 0 ; } -static bool need_tracing_map_ops (struct hist_trigger_data *hist_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 ) -return true; - } - -return false; -} -static struct hist_trigger_data * create_hist_data(unsigned int map_bits, struct hist_trigger_attrs *attrs,
@@ -1552,8 +1557,7 @@ static bool need_tracing_map_ops(struct hist_trigger_data *hist_data)if (ret) goto free; -if (need_tracing_map_ops (hist_data)) - map_ops = &hist_trigger_elt_comm_ops; + map_ops = &hist_trigger_elt_data_ops; hist_data->map =tracing_map_create (map_bits, hist_data->key_size, map_ops, hist_data);
@@ -1742,7 +1746,8 @@ static void hist_trigger_stacktrace_print(struct seq_file *m,seq_printf (m,"%s: [%llx] %-55s" , field_name, uval, str); }else if (key_field->flags &HIST_FIELD_FL_EXECNAME ) { -char *comm = elt->private_data; +struct hist_elt_data *elt_data = elt->private_data; +char *comm = elt_data->comm; uval = *(u64 *)(key + key_field->offset); seq_printf(m, "%s: %-16s[%10llu]", field_name,
Signature · 1 line
1.9.3
Steven Rostedt
maintainerRe: [PATCH v3 16/33] tracing: Generalize per-element hist trigger data
kernel/trace/trace_events_hist.c static void hist_trigger_stacktrace_print(struct seq_file *m, · line 1749
Quoted 6 lines · On Fri, 22 Sep 2017 14:59:56 -0500 Tom Zanussi <[email protected]>…
kernel/trace/trace_events_hist.c +1
-1@@ -1742,7 +1746,8 @@ static void hist_trigger_stacktrace_print(struct seq_file *m,seq_printf (m,"%s: [%llx] %-55s" , field_name, uval, str); }else if (key_field->flags &HIST_FIELD_FL_EXECNAME ) { -char *comm = elt->private_data; +struct hist_elt_data *elt_data = elt->private_data;
Weren't we going to add a "if (WARN_ON_ONCE(!elt_data)) return;" here?
-- Steve
Quoted 5 lines
kernel/trace/trace_events_hist.c +1
-0+char *comm = elt_data->comm; uval = *(u64 *)(key + key_field->offset); seq_printf(m, "%s: %-16s[%10llu]", field_name,
Re: [PATCH v3 16/33] tracing: Generalize per-element hist trigger data
Quoted 12 lines · Steven Rostedt
kernel/trace/trace_events_hist.c static void hist_trigger_stacktrace_print(struct seq_file *m, · line 1749
Quoted 6 lines · On Fri, 22 Sep 2017 14:59:56 -0500 Tom Zanussi <[email protected]>…
kernel/trace/trace_events_hist.c +1
-1@@ -1742,7 +1746,8 @@ static void hist_trigger_stacktrace_print(struct seq_file *m,seq_printf (m,"%s: [%llx] %-55s" , field_name, uval, str); }else if (key_field->flags &HIST_FIELD_FL_EXECNAME ) { -char *comm = elt->private_data; +struct hist_elt_data *elt_data = elt->private_data;Weren't we going to add a "if (
WARN_ON_ONCE(!elt_data)) return;" here?
This I did miss, will update ;-)
Tom
Quoted 7 lines
-- Steve
Quoted 5 lines
kernel/trace/trace_events_hist.c +1
-0+char *comm = elt_data->comm; uval = *(u64 *)(key + key_field->offset); seq_printf(m, "%s: %-16s[%10llu]", field_name,
PATCH v3 17/33 tracing: Pass tracing_map_elt to hist_field accessor functions
In reply to Tom ZanussiSome accessor functions, such as for variable references, require access to a corrsponding tracing_map_elt.
Add a tracing_map_elt param to the function signature and update the accessor functions accordingly.
--- kernel/trace/trace_events_hist.c | 91 +++++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 34 deletions(-)
kernel/trace/trace_events_hist.c +57 -34
@@ -26,8 +26,10 @@struct hist_field ; -typedef u64 (*hist_field_fn_t ) (struct hist_field *field,void *event, -struct ring_buffer_event *rbe); +typedef u64 (*hist_field_fn_t ) (struct hist_field *field, +struct tracing_map_elt *elt, +struct ring_buffer_event *rbe, +void *event);#define HIST_FIELD_OPERANDS_MAX 2#define HIST_FIELDS_MAX (TRACING_MAP_FIELDS_MAX + TRACING_MAP_VARS_MAX)
@@ -59,28 +61,36 @@ struct hist_field {char *name; }; -static u64 hist_field_none (struct hist_field *field,void *event, -struct ring_buffer_event *rbe) +static u64 hist_field_none (struct hist_field *field, +struct tracing_map_elt *elt, +struct ring_buffer_event *rbe, +void *event) {return 0 ; } -static u64 hist_field_counter (struct hist_field *field,void *event, -struct ring_buffer_event *rbe) +static u64 hist_field_counter (struct hist_field *field, +struct tracing_map_elt *elt, +struct ring_buffer_event *rbe, +void *event) {return 1 ; } -static u64 hist_field_string (struct hist_field *hist_field,void *event, -struct ring_buffer_event *rbe) +static u64 hist_field_string (struct hist_field *hist_field, +struct tracing_map_elt *elt, +struct ring_buffer_event *rbe, +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, -struct ring_buffer_event *rbe) +static u64 hist_field_dynstring (struct hist_field *hist_field, +struct tracing_map_elt *elt, +struct ring_buffer_event *rbe, +void *event) {u32 str_item = *(u32 *)(event + hist_field->field->offset);int str_loc = str_item &0xffff ;
@@ -89,54 +99,64 @@ static u64 hist_field_dynstring(struct hist_field *hist_field, void *event,return (u64 )(unsigned long )addr; } -static u64 hist_field_pstring (struct hist_field *hist_field,void *event, -struct ring_buffer_event *rbe) +static u64 hist_field_pstring (struct hist_field *hist_field, +struct tracing_map_elt *elt, +struct ring_buffer_event *rbe, +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, -struct ring_buffer_event *rbe) +static u64 hist_field_log2 (struct hist_field *hist_field, +struct tracing_map_elt *elt, +struct ring_buffer_event *rbe, +void *event) {struct hist_field *operand = hist_field->operands[0 ]; -u64 val = operand->fn (operand, event, rbe); +u64 val = operand->fn (operand, elt, rbe, event);return (u64 )ilog2 (roundup_pow_of_two (val)); } -static u64 hist_field_plus (struct hist_field *hist_field,void *event, -struct ring_buffer_event *rbe) +static u64 hist_field_plus (struct hist_field *hist_field, +struct tracing_map_elt *elt, +struct ring_buffer_event *rbe, +void *event) {struct hist_field *operand1 = hist_field->operands[0 ];struct hist_field *operand2 = hist_field->operands[1 ]; -u64 val1 = operand1->fn (operand1, event, rbe); -u64 val2 = operand2->fn (operand2, event, rbe); +u64 val1 = operand1->fn (operand1, elt, rbe, event); +u64 val2 = operand2->fn (operand2, elt, rbe, event);return val1 + val2; } -static u64 hist_field_minus (struct hist_field *hist_field,void *event, -struct ring_buffer_event *rbe) +static u64 hist_field_minus (struct hist_field *hist_field, +struct tracing_map_elt *elt, +struct ring_buffer_event *rbe, +void *event) {struct hist_field *operand1 = hist_field->operands[0 ];struct hist_field *operand2 = hist_field->operands[1 ]; -u64 val1 = operand1->fn (operand1, event, rbe); -u64 val2 = operand2->fn (operand2, event, rbe); +u64 val1 = operand1->fn (operand1, elt, rbe, event); +u64 val2 = operand2->fn (operand2, elt, rbe, event);return val1 - val2; } -static u64 hist_field_unary_minus (struct hist_field *hist_field,void *event, -struct ring_buffer_event *rbe) +static u64 hist_field_unary_minus (struct hist_field *hist_field, +struct tracing_map_elt *elt, +struct ring_buffer_event *rbe, +void *event) {struct hist_field *operand = hist_field->operands[0 ]; -s64 sval = (s64 )operand->fn (operand, event, rbe); +s64 sval = (s64 )operand->fn (operand, elt, rbe, event);u64 val = (u64)-sval;return val;
@@ -144,8 +164,9 @@ static u64 hist_field_unary_minus(struct hist_field *hist_field, void *event,#define DEFINE_HIST_FIELD_FN (type) \ static u64 hist_field_##type(struct hist_field *hist_field, \ - void *event, \ - struct ring_buffer_event *rbe) \ + struct tracing_map_elt *elt, \ + struct ring_buffer_event *rbe, \ + void *event) \ { \ type *addr = (type *)(event + hist_field->field->offset); \ \
@@ -235,8 +256,10 @@ struct hist_trigger_data {bool remove; }; -static u64 hist_field_timestamp (struct hist_field *hist_field,void *event, -struct ring_buffer_event *rbe) +static u64 hist_field_timestamp (struct hist_field *hist_field, +struct tracing_map_elt *elt, +struct ring_buffer_event *rbe, +void *event) {struct hist_trigger_data *hist_data = hist_field->hist_data;struct trace_array *tr = hist_data->event_file->tr;
@@ -1598,7 +1621,7 @@ static void hist_trigger_elt_update(struct hist_trigger_data *hist_data,for_each_hist_val_field (i, hist_data) { hist_field = hist_data->fields[i]; - hist_val = hist_field->fn (hist_field, rbe, rec); + hist_val = hist_field->fn (hist_field, elt, rbe, rec);if (hist_field->flags &HIST_FIELD_FL_VAR ) { var_idx = hist_field->var.idx;tracing_map_set_var (elt, var_idx, hist_val);
@@ -1611,7 +1634,7 @@ static void hist_trigger_elt_update(struct hist_trigger_data *hist_data,for_each_hist_key_field (i, hist_data) { hist_field = hist_data->fields[i];if (hist_field->flags &HIST_FIELD_FL_VAR ) { - hist_val = hist_field->fn (hist_field, rbe, rec); + hist_val = hist_field->fn (hist_field, elt, rbe, rec); var_idx = hist_field->var.idx;tracing_map_set_var (elt, var_idx, hist_val); }
@@ -1649,9 +1672,9 @@ static void event_hist_trigger(struct event_trigger_data *data, void *rec,bool use_compound_key = (hist_data->n_keys >1 );unsigned long entries[HIST_STACKTRACE_DEPTH ];char compound_key[HIST_KEY_SIZE_MAX ]; +struct tracing_map_elt *elt =NULL ;struct stack_trace stacktrace;struct hist_field *key_field; -struct tracing_map_elt *elt;u64 field_contents;void *key =NULL ;unsigned int i;
@@ -1672,7 +1695,7 @@ static void event_hist_trigger(struct event_trigger_data *data, void *rec, key = entries; }else { - field_contents = key_field->fn (key_field, rec, rbe); + field_contents = key_field->fn (key_field, elt, rbe, rec);if (key_field->flags &HIST_FIELD_FL_STRING ) { key = (void *)(unsigned long )field_contents; use_compound_key = true;
Signature · 1 line
1.9.3
PATCH v3 18/33 tracing: Add hist_field 'type' field
In reply to Tom ZanussiFuture support for synthetic events requires hist_field 'type' information, so add a field for that.
Also, make other hist_field attribute usage consistent (size, is_signed, etc).
--- kernel/trace/trace_events_hist.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+)
kernel/trace/trace_events_hist.c +33 -0
@@ -54,6 +54,7 @@ struct hist_field {unsigned int size;unsigned int offset;unsigned int is_signed; +const char *type;struct hist_field *operands[HIST_FIELD_OPERANDS_MAX ];struct hist_trigger_data *hist_data;struct hist_var var;
@@ -672,6 +673,7 @@ static void destroy_hist_field(struct hist_field *hist_field,kfree (hist_field->var.name);kfree (hist_field->name); +kfree (hist_field->type);kfree (hist_field); }
@@ -697,6 +699,10 @@ static struct hist_field *create_hist_field(struct hist_trigger_data *hist_data,if (flags &HIST_FIELD_FL_HITCOUNT ) { hist_field->fn = hist_field_counter; + hist_field->size =sizeof (u64); + hist_field->type =kstrdup ("u64" ,GFP_KERNEL ); +if (!hist_field->type) + goto free; goto out; }
@@ -710,12 +716,18 @@ static struct hist_field *create_hist_field(struct hist_trigger_data *hist_data, hist_field->fn = hist_field_log2; hist_field->operands[0 ] =create_hist_field (hist_data, field, fl,NULL ); hist_field->size = hist_field->operands[0 ]->size; + hist_field->type =kstrdup (hist_field->operands[0 ]->type,GFP_KERNEL ); +if (!hist_field->type) + goto free; goto out; }if (flags &HIST_FIELD_FL_TIMESTAMP ) { hist_field->fn = hist_field_timestamp; hist_field->size =sizeof (u64); + hist_field->type =kstrdup ("u64" ,GFP_KERNEL ); +if (!hist_field->type) + goto free; goto out; }
@@ -725,6 +737,11 @@ static struct hist_field *create_hist_field(struct hist_trigger_data *hist_data,if (is_string_field (field)) { flags |=HIST_FIELD_FL_STRING ; + hist_field->size =MAX_FILTER_STR_VAL ; + hist_field->type =kstrdup (field->type,GFP_KERNEL ); +if (!hist_field->type) + goto free; +if (field->filter_type ==FILTER_STATIC_STRING ) hist_field->fn = hist_field_string;else if (field->filter_type ==FILTER_DYN_STRING )
@@ -732,6 +749,12 @@ static struct hist_field *create_hist_field(struct hist_trigger_data *hist_data,else hist_field->fn = hist_field_pstring; }else { + hist_field->size = field->size; + hist_field->is_signed = field->is_signed; + hist_field->type =kstrdup (field->type,GFP_KERNEL ); +if (!hist_field->type) + goto free; + hist_field->fn =select_value_fn (field->size, field->is_signed);if (!hist_field->fn) {
@@ -941,6 +964,11 @@ static struct hist_field *parse_unary(struct hist_trigger_data *hist_data, expr->operands[0 ] = operand1; expr->operator =FIELD_OP_UNARY_MINUS ; expr->name =expr_str (expr,0 ); + expr->type =kstrdup (operand1->type,GFP_KERNEL ); +if (!expr->type) { + ret = -ENOMEM ; + goto free; + }return expr; free:
@@ -1029,6 +1057,11 @@ static struct hist_field *parse_expr(struct hist_trigger_data *hist_data, expr->operands[1 ] = operand2; expr->operator = field_op; expr->name =expr_str (expr,0 ); + expr->type =kstrdup (operand1->type,GFP_KERNEL ); +if (!expr->type) { + ret = -ENOMEM ; + goto free; + }switch (field_op) {case FIELD_OP_MINUS :
Signature · 1 line
1.9.3
PATCH v3 19/33 tracing: Add variable reference handling to hist triggers
In reply to Tom ZanussiAdd the necessary infrastructure to allow the variables defined on one event to be referenced in another. This allows variables set by a previous event to be referenced and used in expressions combining the variable values saved by that previous event and the event fields of the current event. For example, here's how a latency can be calculated and saved into yet another variable named 'wakeup_lat':
# echo 'hist:keys=pid,prio:ts0=common_timestamp ...
# echo 'hist:keys=next_pid:wakeup_lat=common_timestamp-$ts0 ...In the first event, the event's timetamp is saved into the variable ts0. In the next line, ts0 is subtracted from the second event's timestamp to produce the latency.
Further users of variable references will be described in subsequent patches, such as for instance how the 'wakeup_lat' variable above can be displayed in a latency histogram.
--- kernel/trace/trace.c | 2 + kernel/trace/trace.h | 3 + kernel/trace/trace_events_hist.c | 613 ++++++++++++++++++++++++++++++++---- kernel/trace/trace_events_trigger.c | 6 + 4 files changed, 568 insertions(+), 56 deletions(-)
kernel/trace/trace.c +2 -0
@@ -7753,6 +7753,7 @@ static int instance_mkdir(const char *name)INIT_LIST_HEAD (&tr->systems);INIT_LIST_HEAD (&tr->events); +INIT_LIST_HEAD (&tr->hist_vars);if (allocate_trace_buffers (tr, trace_buf_size) <0 ) goto out_free_tr;
@@ -8500,6 +8501,7 @@ __init static int tracer_alloc_buffers(void)INIT_LIST_HEAD (&global_trace.systems);INIT_LIST_HEAD (&global_trace.events); +INIT_LIST_HEAD (&global_trace.hist_vars);list_add (&global_trace.list, &ftrace_trace_arrays);apply_trace_boot_options ();
kernel/trace/trace.h +3 -0
@@ -273,6 +273,7 @@ struct trace_array {int function_enabled;#endif int time_stamp_abs_ref; +struct list_head hist_vars; };enum {
@@ -1547,6 +1548,8 @@ extern int save_named_trigger(const char *name,extern void unpause_named_trigger (struct event_trigger_data *data);extern void set_named_trigger_data (struct event_trigger_data *data,struct event_trigger_data *named_data); +extern struct event_trigger_data * +get_named_trigger_data (struct event_trigger_data *data);extern int register_event_command (struct event_command *cmd);extern int unregister_event_command (struct event_command *cmd);extern int register_trigger_hist_enable_disable_cmds (void );
kernel/trace/trace_events_hist.c +557 -56
814 lines not on this page · raw message
kernel/trace/trace_events_trigger.c +6 -0
@@ -909,6 +909,12 @@ void set_named_trigger_data(struct event_trigger_data *data, data->named_data = named_data; } +struct event_trigger_data * +get_named_trigger_data (struct event_trigger_data *data) +{ +return data->named_data; +} +static void traceon_trigger (struct event_trigger_data *data,void *rec,struct ring_buffer_event *event)
Signature · 1 line
1.9.3
Re: [PATCH v3 19/33] tracing: Add variable reference handling to hist triggers
Quoted 134 lines · Tom Zanussi
Add the necessary infrastructure to allow the variables defined on one event to be referenced in another. This allows variables set by a previous event to be referenced and used in expressions combining the variable values saved by that previous event and the event fields of the current event. For example, here's how a latency can be calculated and saved into yet another variable named '
wakeup_lat':# echo 'hist:keys=pid,prio:ts0=common_timestamp ... # echo 'hist:keys=next_pid:wakeup_lat=common_timestamp-$ts0 ...In the first event, the event's timetamp is saved into the variable ts0. In the next line, ts0 is subtracted from the second event's timestamp to produce the latency.
Further users of variable references will be described in subsequent patches, such as for instance how the '
wakeup_lat' variable above can be displayed in a latency histogram.Signed-off-by Tom Zanussi <[email protected]>--- kernel/trace/trace.c | 2 + kernel/trace/trace.h | 3 + kernel/trace/trace_events_hist.c | 613 ++++++++++++++++++++++++++++++++---- kernel/trace/trace_events_trigger.c | 6 + 4 files changed, 568 insertions(+), 56 deletions(-)kernel/trace/trace.c +2
-0@@ -7753,6 +7753,7 @@ static int instance_mkdir(const char *name)INIT_LIST_HEAD (&tr->systems);INIT_LIST_HEAD (&tr->events); +INIT_LIST_HEAD (&tr->hist_vars);if (allocate_trace_buffers (tr, trace_buf_size) <0 ) goto out_free_tr;@@ -8500,6 +8501,7 @@ __init static int tracer_alloc_buffers(void)INIT_LIST_HEAD (&global_trace.systems);INIT_LIST_HEAD (&global_trace.events); +INIT_LIST_HEAD (&global_trace.hist_vars);list_add (&global_trace.list, &ftrace_trace_arrays);apply_trace_boot_options ();kernel/trace/trace.h +3
-0@@ -273,6 +273,7 @@ struct trace_array {int function_enabled;#endif int time_stamp_abs_ref; +struct list_head hist_vars; };enum {@@ -1547,6 +1548,8 @@ extern int save_named_trigger(const char *name,extern void unpause_named_trigger (struct event_trigger_data *data);extern void set_named_trigger_data (struct event_trigger_data *data,struct event_trigger_data *named_data); +extern struct event_trigger_data * +get_named_trigger_data (struct event_trigger_data *data);extern int register_event_command (struct event_command *cmd);extern int unregister_event_command (struct event_command *cmd);extern int register_trigger_hist_enable_disable_cmds (void );kernel/trace/trace_events_hist.c +38
-0@@ -60,6 +60,9 @@ struct hist_field {struct hist_var var;enum field_op_id operator;char *name; +unsigned int var_idx; +unsigned int var_ref_idx; +bool read_once; }; static u64 hist_field_none(struct hist_field *field,@@ -215,6 +218,7 @@ enum hist_field_flags {HIST_FIELD_FL_VAR =1 <<12 ,HIST_FIELD_FL_VAR_ONLY =1 <<13 ,HIST_FIELD_FL_EXPR =1 <<14 , + HIST_FIELD_FL_VAR_REF = 1 << 15, }; struct var_defs {@@ -255,6 +259,8 @@ struct hist_trigger_data {struct tracing_map *map;bool enable_timestamps;bool remove; +struct hist_field *var_refs[TRACING_MAP_VARS_MAX ]; +unsigned int n_var_refs; }; static u64 hist_field_timestamp(struct hist_field *hist_field,@@ -273,10 +279,344 @@ static u64 hist_field_timestamp(struct hist_field *hist_field,return ts; } +struct hist_var_data { +struct list_head list; +struct hist_trigger_data *hist_data; +}; + +static struct hist_field *check_var_ref (struct hist_field *hist_field, +struct hist_trigger_data *var_data, +unsigned int var_idx) +{ +struct hist_field *found =NULL ; + +if (hist_field && hist_field->flags &HIST_FIELD_FL_VAR_REF ) { +if (hist_field->var.idx == var_idx && + hist_field->var.hist_data == var_data) { + found = hist_field; + } + } + +return found; +} + +static struct hist_field *find_var_ref (struct hist_trigger_data *hist_data, +struct hist_trigger_data *var_data, +unsigned int var_idx) +{ +struct hist_field *hist_field, *found =NULL ; +unsigned int i, j; + +for_each_hist_field (i, hist_data) { + hist_field = hist_data->fields[i]; + found =check_var_ref (hist_field, var_data, var_idx); +if (found)
kernel/trace/trace_events_hist.c static u64 hist_field_timestamp(struct hist_field *hist_field, · line 323
Quoted 10 lines
kernel/trace/trace_events_hist.c +10
-0+return found; + +for (j =0 ; j <HIST_FIELD_OPERANDS_MAX ; j++) { +struct hist_field *operand; + + operand = hist_field->operands[j]; + found =check_var_ref (operand, var_data, var_idx); +if (found) +return found; + }
Hmm.. IIUC expression can be nested but it seems to miss var-ref at level 2 - something like "a + b + $c", no?
Quoted 6 lines
kernel/trace/trace_events_hist.c +6
-0+ } + +return found; +} + +static struct hist_field *find_any_var_ref(struct hist_trigger_data *hist_data,
kernel/trace/trace_events_hist.c static u64 hist_field_timestamp(struct hist_field *hist_field, · line 339
Quoted 10 lines
kernel/trace/trace_events_hist.c +10
-0+ unsigned int var_idx) +{ +struct trace_array *tr = hist_data->event_file->tr; +struct hist_field *found =NULL ; +struct hist_var_data *var_data; + +list_for_each_entry (var_data, &tr->hist_vars, list) { + found =find_var_ref (var_data->hist_data, hist_data, var_idx); +if (found) +break ;
Shouldn't it check self-references - i.e. ref from the same hist_data?
Quoted 49 lines
kernel/trace/trace_events_hist.c +49
-0+ } + +return found; +} + +static bool check_var_refs (struct hist_trigger_data *hist_data) +{ +struct hist_field *field; +bool found = false; +int i; + +for_each_hist_field (i, hist_data) { + field = hist_data->fields[i]; +if (field && field->flags &HIST_FIELD_FL_VAR ) { +if (find_any_var_ref (hist_data, field->var.idx)) { + found = true; +break ; + } + } + } + +return found; +} + +static struct hist_var_data *find_hist_vars (struct hist_trigger_data *hist_data) +{ +struct trace_array *tr = hist_data->event_file->tr; +struct hist_var_data *var_data, *found =NULL ; + +list_for_each_entry (var_data, &tr->hist_vars, list) { +if (var_data->hist_data == hist_data) { + found = var_data; +break ; + } + } + +return found; +} + +static bool has_hist_vars (struct hist_trigger_data *hist_data) +{ +struct hist_field *hist_field; +int i, j; + +for_each_hist_field (i, hist_data) { + hist_field = hist_data->fields[i]; +if (hist_field && + (hist_field->flags &HIST_FIELD_FL_VAR || + hist_field->flags &HIST_FIELD_FL_VAR_REF ))
kernel/trace/trace_events_hist.c static u64 hist_field_timestamp(struct hist_field *hist_field, · line 398
Quoted 10 lines
kernel/trace/trace_events_hist.c +10
-0+return true; + +for (j =0 ; j <HIST_FIELD_OPERANDS_MAX ; j++) { +struct hist_field *operand; + + operand = hist_field->operands[j]; +if (operand && + (operand->flags &HIST_FIELD_FL_VAR || + operand->flags &HIST_FIELD_FL_VAR_REF )) +return true;
Same with the above.
Thanks,
Namhyung
Quoted 6 lines
kernel/trace/trace_events_hist.c +6
-0+ } + } + +return false; +} +
Re: [PATCH v3 19/33] tracing: Add variable reference handling to hist triggers
Hi Namhyung,
Quoted 140 lines · Namhyung Kim
Quoted 139 lines · Tom Zanussi
Add the necessary infrastructure to allow the variables defined on one event to be referenced in another. This allows variables set by a previous event to be referenced and used in expressions combining the variable values saved by that previous event and the event fields of the current event. For example, here's how a latency can be calculated and saved into yet another variable named '
wakeup_lat':# echo 'hist:keys=pid,prio:ts0=common_timestamp ... # echo 'hist:keys=next_pid:wakeup_lat=common_timestamp-$ts0 ...In the first event, the event's timetamp is saved into the variable ts0. In the next line, ts0 is subtracted from the second event's timestamp to produce the latency.
Further users of variable references will be described in subsequent patches, such as for instance how the '
wakeup_lat' variable above can be displayed in a latency histogram.Signed-off-by Tom Zanussi <[email protected]>--- kernel/trace/trace.c | 2 + kernel/trace/trace.h | 3 + kernel/trace/trace_events_hist.c | 613 ++++++++++++++++++++++++++++++++---- kernel/trace/trace_events_trigger.c | 6 + 4 files changed, 568 insertions(+), 56 deletions(-)kernel/trace/trace.c +2
-0@@ -7753,6 +7753,7 @@ static int instance_mkdir(const char *name)INIT_LIST_HEAD (&tr->systems);INIT_LIST_HEAD (&tr->events); +INIT_LIST_HEAD (&tr->hist_vars);if (allocate_trace_buffers (tr, trace_buf_size) <0 ) goto out_free_tr;@@ -8500,6 +8501,7 @@ __init static int tracer_alloc_buffers(void)INIT_LIST_HEAD (&global_trace.systems);INIT_LIST_HEAD (&global_trace.events); +INIT_LIST_HEAD (&global_trace.hist_vars);list_add (&global_trace.list, &ftrace_trace_arrays);apply_trace_boot_options ();kernel/trace/trace.h +3
-0@@ -273,6 +273,7 @@ struct trace_array {int function_enabled;#endif int time_stamp_abs_ref; +struct list_head hist_vars; };enum {@@ -1547,6 +1548,8 @@ extern int save_named_trigger(const char *name,extern void unpause_named_trigger (struct event_trigger_data *data);extern void set_named_trigger_data (struct event_trigger_data *data,struct event_trigger_data *named_data); +extern struct event_trigger_data * +get_named_trigger_data (struct event_trigger_data *data);extern int register_event_command (struct event_command *cmd);extern int unregister_event_command (struct event_command *cmd);extern int register_trigger_hist_enable_disable_cmds (void );kernel/trace/trace_events_hist.c +43
-0@@ -60,6 +60,9 @@ struct hist_field {struct hist_var var;enum field_op_id operator;char *name; +unsigned int var_idx; +unsigned int var_ref_idx; +bool read_once; }; static u64 hist_field_none(struct hist_field *field,@@ -215,6 +218,7 @@ enum hist_field_flags {HIST_FIELD_FL_VAR =1 <<12 ,HIST_FIELD_FL_VAR_ONLY =1 <<13 ,HIST_FIELD_FL_EXPR =1 <<14 , + HIST_FIELD_FL_VAR_REF = 1 << 15, }; struct var_defs {@@ -255,6 +259,8 @@ struct hist_trigger_data {struct tracing_map *map;bool enable_timestamps;bool remove; +struct hist_field *var_refs[TRACING_MAP_VARS_MAX ]; +unsigned int n_var_refs; }; static u64 hist_field_timestamp(struct hist_field *hist_field,@@ -273,10 +279,344 @@ static u64 hist_field_timestamp(struct hist_field *hist_field,return ts; } +struct hist_var_data { +struct list_head list; +struct hist_trigger_data *hist_data; +}; + +static struct hist_field *check_var_ref (struct hist_field *hist_field, +struct hist_trigger_data *var_data, +unsigned int var_idx) +{ +struct hist_field *found =NULL ; + +if (hist_field && hist_field->flags &HIST_FIELD_FL_VAR_REF ) { +if (hist_field->var.idx == var_idx && + hist_field->var.hist_data == var_data) { + found = hist_field; + } + } + +return found; +} + +static struct hist_field *find_var_ref (struct hist_trigger_data *hist_data, +struct hist_trigger_data *var_data, +unsigned int var_idx) +{ +struct hist_field *hist_field, *found =NULL ; +unsigned int i, j; + +for_each_hist_field (i, hist_data) { + hist_field = hist_data->fields[i]; + found =check_var_ref (hist_field, var_data, var_idx); +if (found) +return found; + +for (j =0 ; j <HIST_FIELD_OPERANDS_MAX ; j++) { +struct hist_field *operand; +
Quoted 10 lines
kernel/trace/trace_events_hist.c static u64 hist_field_timestamp(struct hist_field *hist_field, · line 323
Quoted 5 lines
kernel/trace/trace_events_hist.c +5
-0+ operand = hist_field->operands[j]; + found =check_var_ref (operand, var_data, var_idx); +if (found) +return found; + }Hmm.. IIUC expression can be nested but it seems to miss var-ref at level 2 - something like "a + b + $c", no?
Yes, thanks for pointing these out, will fix, thanks.
Tom
PATCH v3 20/33 tracing: Add hist trigger action hook
In reply to Tom ZanussiAdd a hook for executing extra actions whenever a histogram entry is added or updated.
The default 'action' when a hist entry is added to a histogram is to update the set of values associated with it. Some applications may want to perform additional actions at that point, such as generate another event, or compare and save a maximum.
Add a simple framework for doing that; specific actions will be implemented on top of it in later patches.
--- kernel/trace/trace_events_hist.c | 91 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 3 deletions(-)
kernel/trace/trace_events_hist.c +88 -3
@@ -33,6 +33,7 @@ typedef u64 (*hist_field_fn_t) (struct hist_field *field,#define HIST_FIELD_OPERANDS_MAX 2#define HIST_FIELDS_MAX (TRACING_MAP_FIELDS_MAX + TRACING_MAP_VARS_MAX) +#define HIST_ACTIONS_MAX 8enum field_op_id {FIELD_OP_NONE ,
@@ -241,6 +242,9 @@ struct hist_trigger_attrs {char *assignment_str[TRACING_MAP_VARS_MAX ];unsigned int n_assignments; +char *action_str[HIST_ACTIONS_MAX ]; +unsigned int n_actions; +struct var_defs var_defs; };
@@ -261,6 +265,21 @@ struct hist_trigger_data {bool remove;struct hist_field *var_refs[TRACING_MAP_VARS_MAX ];unsigned int n_var_refs; + +struct action_data *actions[HIST_ACTIONS_MAX ]; +unsigned int n_actions; +}; + +struct action_data ; + +typedef void (*action_fn_t ) (struct hist_trigger_data *hist_data, +struct tracing_map_elt *elt,void *rec, +struct ring_buffer_event *rbe, +struct action_data *data,u64 *var_ref_vals); + +struct action_data { +action_fn_t fn; +unsigned int var_ref_idx; }; static u64 hist_field_timestamp(struct hist_field *hist_field,
@@ -710,6 +729,9 @@ static void destroy_hist_trigger_attrs(struct hist_trigger_attrs *attrs)for (i =0 ; i < attrs->n_assignments; i++)kfree (attrs->assignment_str[i]); +for (i =0 ; i < attrs->n_actions; i++) +kfree (attrs->action_str[i]); +kfree (attrs->name);kfree (attrs->sort_key_str);kfree (attrs->keys_str);
@@ -717,6 +739,16 @@ static void destroy_hist_trigger_attrs(struct hist_trigger_attrs *attrs)kfree (attrs); } +static int parse_action (char *str,struct hist_trigger_attrs *attrs) +{ +int ret =0 ; + +if (attrs->n_actions >=HIST_ACTIONS_MAX ) +return ret; + +return ret; +} +static int parse_assignment (char *str,struct hist_trigger_attrs *attrs) {int ret =0 ;
@@ -800,8 +832,9 @@ static struct hist_trigger_attrs *parse_hist_trigger_attrs(char *trigger_str)else if (strcmp (str,"clear" ) ==0 ) attrs->clear = true;else { - ret = -EINVAL ; - goto free; + ret =parse_action (str, attrs); +if (ret) + goto free; } }
@@ -1949,11 +1982,42 @@ static int create_sort_keys(struct hist_trigger_data *hist_data)return ret; } +static void destroy_actions (struct hist_trigger_data *hist_data) +{ +unsigned int i; + +for (i =0 ; i < hist_data->n_actions; i++) { +struct action_data *data = hist_data->actions[i]; + +kfree (data); + } +} + +static int create_actions (struct hist_trigger_data *hist_data, +struct trace_event_file *file) +{ +unsigned int i; +int ret =0 ; +char *str; + +for (i =0 ; i < hist_data->attrs->n_actions; i++) { + str = hist_data->attrs->action_str[i]; + } + +return ret; +} +static void destroy_hist_data (struct hist_trigger_data *hist_data) { +if (!hist_data) +return ; +destroy_hist_trigger_attrs (hist_data->attrs);destroy_hist_fields (hist_data);tracing_map_destroy (hist_data->map); + +destroy_actions (hist_data); +kfree (hist_data); }
@@ -2112,6 +2176,20 @@ static inline void add_to_key(char *compound_key, void *key,memcpy (compound_key + key_field->offset, key, size); } +static void +hist_trigger_actions (struct hist_trigger_data *hist_data, +struct tracing_map_elt *elt,void *rec, +struct ring_buffer_event *rbe,u64 *var_ref_vals) +{ +struct action_data *data; +unsigned int i; + +for (i =0 ; i < hist_data->n_actions; i++) { + data = hist_data->actions[i]; + data->fn (hist_data, elt, rec, rbe, data, var_ref_vals); + } +} +static void event_hist_trigger (struct event_trigger_data *data,void *rec,struct ring_buffer_event *rbe) {
@@ -2167,6 +2245,9 @@ static void event_hist_trigger(struct event_trigger_data *data, void *rec,return ;hist_trigger_elt_update (hist_data, elt, rec, rbe, var_ref_vals); + +if (resolve_var_refs (hist_data, key, var_ref_vals, true)) +hist_trigger_actions (hist_data, elt, rec, rbe, var_ref_vals); } static void hist_trigger_stacktrace_print(struct seq_file *m,
@@ -2942,6 +3023,10 @@ static int event_hist_trigger_func(struct event_command *cmd_ops,if (has_hist_vars (hist_data))save_hist_vars (hist_data); + ret =create_actions (hist_data, file); +if (ret) + goto out_unreg; + ret =tracing_map_init (hist_data->map);if (ret) goto out_unreg;
@@ -2963,8 +3048,8 @@ static int event_hist_trigger_func(struct event_command *cmd_ops,remove_hist_vars (hist_data);kfree (trigger_data); -destroy_hist_data (hist_data); + goto out; }
Signature · 1 line
1.9.3
PATCH v3 21/33 tracing: Add support for 'synthetic' events
In reply to Tom ZanussiSynthetic events are user-defined events generated from hist trigger variables saved from one or more other events.
To define a synthetic event, the user writes a simple specification consisting of the name of the new event along with one or more variables and their type(s), to the tracing/synthetic_events file.
For instance, the following creates a new event named 'wakeup_latency' with 3 fields: lat, pid, and prio:
# echo 'wakeup_latency u64 lat; pid_t pid; int prio' >> \
/sys/kernel/debug/tracing/synthetic_eventsReading the tracing/synthetic_events file lists all the currently-defined synthetic events, in this case the event we defined above:
# cat /sys/kernel/debug/tracing/synthetic_events
wakeup_latency u64 lat; pid_t pid; int prioAt this point, the synthetic event is ready to use, and a histogram can be defined using it:
# echo 'hist:keys=pid,prio,lat.log2:sort=pid,lat' >> \
/sys/kernel/debug/tracing/events/synthetic/wakeup_latency/triggerThe new event is created under the tracing/events/synthetic/ directory and looks and behaves just like any other event:
# ls /sys/kernel/debug/tracing/events/synthetic/wakeup_latency
enable filter format hist id triggerAlthough a histogram can be defined for it, nothing will happen until an action tracing that event via the trace_synth() function occurs. The trace_synth() function is very similar to all the other trace_* invocations spread throughout the kernel, except in this case the trace_ function and its corresponding tracepoint isn't statically generated but defined by the user at run-time.
How this can be automatically hooked up via a hist trigger 'action' is discussed in a subsequent patch.
--- kernel/trace/trace_events_hist.c | 865 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 865 insertions(+)
kernel/trace/trace_events_hist.c +865 -0
@@ -20,10 +20,16 @@#include <linux/slab.h> #include <linux/stacktrace.h> #include <linux/rculist.h> +#include <linux/tracefs.h> #include "tracing_map.h" #include "trace.h" +#define SYNTH_SYSTEM "synthetic" +#define SYNTH_FIELDS_MAX 16 + +#define STR_VAR_LEN_MAX 32/* must be multiple of sizeof(u64) */ +struct hist_field ; typedef u64 (*hist_field_fn_t) (struct hist_field *field,
@@ -270,6 +276,26 @@ struct hist_trigger_data {unsigned int n_actions; }; +struct synth_field { +char *type; +char *name; +size_t size; +bool is_signed; +bool is_string; +}; + +struct synth_event { +struct list_head list; +int ref; +char *name; +struct synth_field **fields; +unsigned int n_fields; +unsigned int n_u64; +struct trace_event_class class; +struct trace_event_call call; +struct tracepoint *tp; +}; +struct action_data ; typedef void (*action_fn_t) (struct hist_trigger_data *hist_data,
@@ -282,6 +308,800 @@ struct action_data {unsigned int var_ref_idx; }; +static LIST_HEAD (synth_event_list); +static DEFINE_MUTEX (synth_event_mutex); + +struct synth_trace_event { +struct trace_entry ent; +u64 fields[]; +}; + +static int synth_event_define_fields (struct trace_event_call *call) +{ +struct synth_trace_event trace; +int offset = offsetof(typeof (trace ), fields); +struct synth_event *event = call->data; +unsigned int i, size, n_u64; +char *name, *type; +bool is_signed; +int ret =0 ; + +for (i =0 , n_u64 =0 ; i < event->n_fields; i++) { + size = event->fields[i]->size; + is_signed = event->fields[i]->is_signed; + type = event->fields[i]->type; + name = event->fields[i]->name; + ret =trace_define_field (call, type, name, offset, size, + is_signed,FILTER_OTHER ); +if (ret) +break ; + +if (event->fields[i]->is_string) { + offset +=STR_VAR_LEN_MAX ; + n_u64 +=STR_VAR_LEN_MAX /sizeof (u64); + }else { + offset +=sizeof (u64); + n_u64++; + } + } + + event->n_u64 = n_u64; + +return ret; +} + +static bool synth_field_signed (char *type) +{ +if (strncmp (type,"u" ,1 ) ==0 ) +return false; + +return true; +} + +static int synth_field_is_string (char *type) +{ +if (strstr (type,"char[" ) !=NULL ) +return true; + +return false; +} + +static int synth_field_string_size (char *type) +{ +char buf[4 ], *end, *start; +unsigned int len; +int size, err; + + start =strstr (type,"char[" ); +if (start ==NULL ) +return -EINVAL ; + start +=strlen ("char[" ); + + end =strchr (type,']' ); +if (!end || end < start) +return -EINVAL ; + + len = end - start; +if (len >3 ) +return -EINVAL ; + +strncpy (buf, start, len); + buf[len] ='\0' ; + + err =kstrtouint (buf,0 , &size); +if (err) +return err; + +if (size >STR_VAR_LEN_MAX ) +return -EINVAL ; + +return size; +} + +static int synth_field_size (char *type) +{ +int size =0 ; + +if (strcmp (type,"s64" ) ==0 ) + size =sizeof (s64); +else if (strcmp (type,"u64" ) ==0 ) + size =sizeof (u64); +else if (strcmp (type,"s32" ) ==0 ) + size =sizeof (s32); +else if (strcmp (type,"u32" ) ==0 ) + size =sizeof (u32); +else if (strcmp (type,"s16" ) ==0 ) + size =sizeof (s16); +else if (strcmp (type,"u16" ) ==0 ) + size =sizeof (u16); +else if (strcmp (type,"s8" ) ==0 ) + size =sizeof (s8); +else if (strcmp (type,"u8" ) ==0 ) + size =sizeof (u8); +else if (strcmp (type,"char" ) ==0 ) + size =sizeof (char ); +else if (strcmp (type,"unsigned char" ) ==0 ) + size =sizeof (unsigned char ); +else if (strcmp (type,"int" ) ==0 ) + size =sizeof (int ); +else if (strcmp (type,"unsigned int" ) ==0 ) + size =sizeof (unsigned int ); +else if (strcmp (type,"long" ) ==0 ) + size =sizeof (long ); +else if (strcmp (type,"unsigned long" ) ==0 ) + size =sizeof (unsigned long ); +else if (strcmp (type,"pid_t" ) ==0 ) + size =sizeof (pid_t); +else if (synth_field_is_string (type)) + size =synth_field_string_size (type); + +return size; +} + +static const char *synth_field_fmt (char *type) +{ +const char *fmt ="%llu" ; + +if (strcmp (type,"s64" ) ==0 ) + fmt ="%lld" ; +else if (strcmp (type,"u64" ) ==0 ) + fmt ="%llu" ; +else if (strcmp (type,"s32" ) ==0 ) + fmt ="%d" ; +else if (strcmp (type,"u32" ) ==0 ) + fmt ="%u" ; +else if (strcmp (type,"s16" ) ==0 ) + fmt ="%d" ; +else if (strcmp (type,"u16" ) ==0 ) + fmt ="%u" ; +else if (strcmp (type,"s8" ) ==0 ) + fmt ="%d" ; +else if (strcmp (type,"u8" ) ==0 ) + fmt ="%u" ; +else if (strcmp (type,"char" ) ==0 ) + fmt ="%d" ; +else if (strcmp (type,"unsigned char" ) ==0 ) + fmt ="%u" ; +else if (strcmp (type,"int" ) ==0 ) + fmt ="%d" ; +else if (strcmp (type,"unsigned int" ) ==0 ) + fmt ="%u" ; +else if (strcmp (type,"long" ) ==0 ) + fmt ="%ld" ; +else if (strcmp (type,"unsigned long" ) ==0 ) + fmt ="%lu" ; +else if (strcmp (type,"pid_t" ) ==0 ) + fmt ="%d" ; +else if (strstr (type,"[" ) ==0 ) + fmt ="%s" ; + +return fmt; +} + +static enum print_line_t print_synth_event (struct trace_iterator *iter, +int flags, +struct trace_event *event) +{ +struct trace_array *tr = iter->tr; +struct trace_seq *s = &iter->seq; +struct synth_trace_event *entry; +struct synth_event *se; +unsigned int i, n_u64; +char print_fmt[32 ]; +const char *fmt; + + entry = (struct synth_trace_event *)iter->ent; + se = container_of(event, struct synth_event, call.event); + +trace_seq_printf (s,"%s: " , se->name); + +for (i =0 , n_u64 =0 ; i < se->n_fields; i++) { +if (trace_seq_has_overflowed (s)) + goto end; + + fmt =synth_field_fmt (se->fields[i]->type); + +/* parameter types */ +if (tr->trace_flags &TRACE_ITER_VERBOSE ) +trace_seq_printf (s,"%s " , fmt); + +snprintf (print_fmt,sizeof (print_fmt),"%%s=%s%%s" , fmt); + +/* parameter values */ +if (se->fields[i]->is_string) { +trace_seq_printf (s, print_fmt, se->fields[i]->name, + (char *)(long )entry->fields[n_u64], + i == se->n_fields -1 ?"" :" " ); + n_u64 +=STR_VAR_LEN_MAX /sizeof (u64); + }else { +trace_seq_printf (s, print_fmt, se->fields[i]->name, + entry->fields[n_u64], + i == se->n_fields -1 ?"" :" " ); + n_u64++; + } + } +end: +trace_seq_putc (s,'\n' ); + +return trace_handle_return (s); +} + +static struct trace_event_functions synth_event_funcs = { + .trace = print_synth_event +}; + +static notrace void trace_event_raw_event_synth(void *__data, +u64 *var_ref_vals, +unsigned int var_ref_idx) +{ +struct trace_event_file *trace_file = __data; +struct synth_trace_event *entry; +struct trace_event_buffer fbuffer; +struct synth_event *event; +unsigned int i, n_u64; +int fields_size =0 ; + + event = trace_file->event_call->data; + +if (trace_trigger_soft_disabled (trace_file)) +return ; + + fields_size = event->n_u64 *sizeof (u64); + + entry =trace_event_buffer_reserve (&fbuffer, trace_file, +sizeof (*entry) + fields_size); +if (!entry) +return ; + +for (i =0 , n_u64 =0 ; i < event->n_fields; i++) { +if (event->fields[i]->is_string) { +char *str_val = (char *)(long )var_ref_vals[var_ref_idx + i]; +char *str_field = (char *)&entry->fields[n_u64]; + +strncpy (str_field, str_val,STR_VAR_LEN_MAX ); + n_u64 +=STR_VAR_LEN_MAX /sizeof (u64); + }else { + entry->fields[i] = var_ref_vals[var_ref_idx + i]; + n_u64++; + } + } + +trace_event_buffer_commit (&fbuffer); +} + +static void free_synth_event_print_fmt (struct trace_event_call *call) +{ +if (call) { +kfree (call->print_fmt); + call->print_fmt =NULL ; + } +} + +static int __set_synth_event_print_fmt (struct synth_event *event, +char *buf,int len) +{ +const char *fmt; +int pos =0 ; +int i; + +/* When len=0, we just calculate the needed length */ +#define LEN_OR_ZERO (len ? len - pos : 0) + + pos +=snprintf (buf + pos,LEN_OR_ZERO ,"\"" ); +for (i =0 ; i < event->n_fields; i++) { + fmt =synth_field_fmt (event->fields[i]->type); + pos +=snprintf (buf + pos,LEN_OR_ZERO ,"%s=%s%s" , + event->fields[i]->name, fmt, + i == event->n_fields -1 ?"" :", " ); + } + pos +=snprintf (buf + pos,LEN_OR_ZERO ,"\"" ); + +for (i =0 ; i < event->n_fields; i++) { + pos +=snprintf (buf + pos,LEN_OR_ZERO , +", REC->%s" , event->fields[i]->name); + } + +#undef LEN_OR_ZERO + +/* return the length of print_fmt */ +return pos; +} + +static int set_synth_event_print_fmt (struct trace_event_call *call) +{ +struct synth_event *event = call->data; +char *print_fmt; +int len; + +/* First: called with 0 length to calculate the needed length */ + len =__set_synth_event_print_fmt (event,NULL ,0 ); + + print_fmt =kmalloc (len +1 ,GFP_KERNEL ); +if (!print_fmt) +return -ENOMEM ; + +/* Second: actually write the @print_fmt */ +__set_synth_event_print_fmt (event, print_fmt, len +1 ); + call->print_fmt = print_fmt; + +return 0 ; +} + +static void free_synth_field (struct synth_field *field) +{ +kfree (field->type); +kfree (field->name); +kfree (field); +} + +static struct synth_field *parse_synth_field (char *field_type, +char *field_name) +{ +struct synth_field *field; +int len, ret =0 ; +char *array; + +if (field_type[0 ] ==';' ) + field_type++; + + len =strlen (field_name); +if (field_name[len -1 ] ==';' ) + field_name[len -1 ] ='\0' ; + + field =kzalloc (sizeof (*field),GFP_KERNEL ); +if (!field) +return ERR_PTR (-ENOMEM ); + + len =strlen (field_type) +1 ; + array =strchr (field_name,'[' ); +if (array) + len +=strlen (array); + field->type =kzalloc (len,GFP_KERNEL ); +if (!field->type) { + ret = -ENOMEM ; + goto free; + } +strcat (field->type, field_type); +if (array) { +strcat (field->type, array); + *array ='\0' ; + } + + field->size =synth_field_size (field->type); +if (!field->size) { + ret = -EINVAL ; + goto free; + } + +if (synth_field_is_string (field->type)) + field->is_string = true; + + field->is_signed =synth_field_signed (field->type); + + field->name =kstrdup (field_name,GFP_KERNEL ); +if (!field->name) { + ret = -ENOMEM ; + goto free; + } + out: +return field; + free: +free_synth_field (field); + field =ERR_PTR (ret); + goto out; +} + +static void free_synth_tracepoint (struct tracepoint *tp) +{ +if (!tp) +return ; + +kfree (tp->name); +kfree (tp); +} + +static struct tracepoint *alloc_synth_tracepoint (char *name) +{ +struct tracepoint *tp; + + tp =kzalloc (sizeof (*tp),GFP_KERNEL ); +if (!tp) +return ERR_PTR (-ENOMEM ); + + tp->name =kstrdup (name,GFP_KERNEL ); +if (!tp->name) { +kfree (tp); +return ERR_PTR (-ENOMEM ); + } + +return tp; +} + +typedef void (*synth_probe_func_t ) (void *__data,u64 *var_ref_vals, +unsigned int var_ref_idx); + +static inline void trace_synth (struct synth_event *event,u64 *var_ref_vals, +unsigned int var_ref_idx) +{ +struct tracepoint *tp = event->tp; + +if (unlikely (atomic_read (&tp->key.enabled) >0 )) { +struct tracepoint_func *probe_func_ptr; +synth_probe_func_t probe_func; +void *__data; + +if (!(cpu_online (raw_smp_processor_id ()))) +return ; + + probe_func_ptr =rcu_dereference_sched ((tp)->funcs); +if (probe_func_ptr) { +do { + probe_func = probe_func_ptr->func; + __data = probe_func_ptr->data; +probe_func (__data, var_ref_vals, var_ref_idx); + }while ((++probe_func_ptr)->func); + } + } +} + +static struct synth_event *find_synth_event (const char *name) +{ +struct synth_event *event; + +list_for_each_entry (event, &synth_event_list, list) { +if (strcmp (event->name, name) ==0 ) +return event; + } + +return NULL ; +} + +/* This function releases synth_event_mutex */ +static int register_synth_event (struct synth_event *event) +{ +struct trace_event_call *call = &event->call; +int ret =0 ; + + event->call.class = &event->class; + event->class.system =kstrdup (SYNTH_SYSTEM ,GFP_KERNEL ); +if (!event->class.system) { + ret = -ENOMEM ; + goto out; + } + + event->tp =alloc_synth_tracepoint (event->name); +if (IS_ERR (event->tp)) { + ret =PTR_ERR (event->tp); + event->tp =NULL ; + goto out; + } + +INIT_LIST_HEAD (&call->class->fields); + call->event.funcs = &synth_event_funcs; + call->class->define_fields = synth_event_define_fields; + + ret =register_trace_event (&call->event); +if (!ret) { + ret = -ENODEV ; + goto out; + } + call->flags =TRACE_EVENT_FL_TRACEPOINT ; + call->class->reg = trace_event_reg; + call->class->probe = trace_event_raw_event_synth; + call->data = event; + call->tp = event->tp; + +/* +* trace_add_event_call() grabs event_mutex, but that can +* deadlock with a hist trigger cmd already holding it that +* can grab synth_event_mutex +*/ +mutex_unlock (&synth_event_mutex); + ret =trace_add_event_call (call); +mutex_lock (&synth_event_mutex); +if (ret) { +pr_warn ("Failed to register synthetic event: %s\n" , +trace_event_name (call)); + goto err; + } + + ret =set_synth_event_print_fmt (call); +if (ret <0 ) { +mutex_unlock (&synth_event_mutex); +trace_remove_event_call (call); +mutex_lock (&synth_event_mutex); + goto err; + } + out: +return ret; + err: +unregister_trace_event (&call->event); + goto out; +} + +/* This function releases synth_event_mutex */ +static int unregister_synth_event (struct synth_event *event) +{ +struct trace_event_call *call = &event->call; +int ret; + +mutex_unlock (&synth_event_mutex); + ret =trace_remove_event_call (call); +mutex_lock (&synth_event_mutex); + +return ret; +} + +static int remove_synth_event (struct synth_event *event) +{ +int ret =unregister_synth_event (event); + +if (!ret) +list_del (&event->list); + +return ret; +} + +static int add_synth_event (struct synth_event *event) +{ +int ret; + + ret =register_synth_event (event); +if (ret) +return ret; + +list_add (&event->list, &synth_event_list); + +return 0 ; +} + +static void free_synth_event (struct synth_event *event) +{ +unsigned int i; + +if (!event) +return ; + +for (i =0 ; i < event->n_fields; i++) +free_synth_field (event->fields[i]); + +kfree (event->fields); +kfree (event->name); +kfree (event->class.system); +free_synth_tracepoint (event->tp); +free_synth_event_print_fmt (&event->call); +kfree (event); +} + +static struct synth_event *alloc_synth_event (char *event_name,int n_fields, +struct synth_field **fields) +{ +struct synth_event *event; +unsigned int i; + + event =kzalloc (sizeof (*event),GFP_KERNEL ); +if (!event) { + event =ERR_PTR (-ENOMEM ); + goto out; + } + + event->name =kstrdup (event_name,GFP_KERNEL ); +if (!event->name) { +kfree (event); + event =ERR_PTR (-ENOMEM ); + goto out; + } + + event->fields =kcalloc (n_fields,sizeof (event->fields),GFP_KERNEL ); +if (!event->fields) { +free_synth_event (event); + event =ERR_PTR (-ENOMEM ); + goto out; + } + +for (i =0 ; i < n_fields; i++) + event->fields[i] = fields[i]; + + event->n_fields = n_fields; + out: +return event; +} + +/* This function releases synth_event_mutex */ +static int create_synth_event (int argc,char **argv) +{ +struct synth_field *field, *fields[SYNTH_FIELDS_MAX ]; +struct synth_event *event =NULL ; +bool delete_event = false; +int i, n_fields =0 , ret =0 ; +char *name; + +mutex_lock (&synth_event_mutex); + +/* +* Argument syntax: +* - Add synthetic event: <event_name> field[;field] ... +* - Remove synthetic event: !<event_name> field[;field] ... +* where 'field' = type field_name +*/ +if (argc <1 ) { + ret = -EINVAL ; + goto out; + } + + name = argv[0 ]; +if (name[0 ] =='!' ) { + delete_event = true; + name++; + } + + event =find_synth_event (name); +if (event) { +if (delete_event) { +if (event->ref) { + ret = -EBUSY ; + goto out; + } + ret =remove_synth_event (event); +if (!ret) +free_synth_event (event); + goto out; + } + ret = -EEXIST ; + goto out; + }else if (delete_event) { + ret = -EINVAL ; + goto out; + } + +if (argc <2 ) { + ret = -EINVAL ; + goto out; + } + +for (i =1 ; i < argc -1 ; i++) { +if (strcmp (argv[i],";" ) ==0 ) +continue ; +if (n_fields ==SYNTH_FIELDS_MAX ) { + ret = -EINVAL ; + goto err; + } + + field =parse_synth_field (argv[i], argv[i +1 ]); +if (IS_ERR (field)) { + ret =PTR_ERR (field); + goto err; + } + fields[n_fields] = field; + i++; n_fields++; + } + +if (i < argc) { + ret = -EINVAL ; + goto err; + } + + event =alloc_synth_event (name, n_fields, fields); +if (IS_ERR (event)) { + ret =PTR_ERR (event); + event =NULL ; + goto err; + } + +add_synth_event (event); + out: +mutex_unlock (&synth_event_mutex); + +return ret; + err: +for (i =0 ; i < n_fields; i++) +free_synth_field (fields[i]); +free_synth_event (event); + + goto out; +} + +/* This function releases synth_event_mutex */ +static int release_all_synth_events (void ) +{ +struct synth_event *event, *e; +int ret =0 ; + +mutex_lock (&synth_event_mutex); + +list_for_each_entry (event, &synth_event_list, list) { +if (event->ref) { + ret = -EBUSY ; + goto out; + } + } + +list_for_each_entry_safe (event, e, &synth_event_list, list) { + ret =remove_synth_event (event); +if (!ret) +free_synth_event (event); + } + out: +mutex_unlock (&synth_event_mutex); + +return ret; +} + + +static void *synth_events_seq_start (struct seq_file *m,loff_t *pos) +{ +mutex_lock (&synth_event_mutex); + +return seq_list_start (&synth_event_list, *pos); +} + +static void *synth_events_seq_next (struct seq_file *m,void *v,loff_t *pos) +{ +return seq_list_next (v, &synth_event_list, pos); +} + +static void synth_events_seq_stop (struct seq_file *m,void *v) +{ +mutex_unlock (&synth_event_mutex); +} + +static int synth_events_seq_show (struct seq_file *m,void *v) +{ +struct synth_field *field; +struct synth_event *event = v; +unsigned int i; + +seq_printf (m,"%s\t" , event->name); + +for (i =0 ; i < event->n_fields; i++) { + field = event->fields[i]; + +/* parameter values */ +seq_printf (m,"%s %s%s" , field->type, field->name, + i == event->n_fields -1 ?"" :"; " ); + } + +seq_putc (m,'\n' ); + +return 0 ; +} + +static const struct seq_operations synth_events_seq_op = { + .start = synth_events_seq_start, + .next = synth_events_seq_next, + .stop = synth_events_seq_stop, + .show = synth_events_seq_show +}; + +static int synth_events_open (struct inode *inode,struct file *file) +{ +int ret; + +if ((file->f_mode &FMODE_WRITE ) && (file->f_flags &O_TRUNC )) { + ret =release_all_synth_events (); +if (ret <0 ) +return ret; + } + +return seq_open (file, &synth_events_seq_op); +} + +static ssize_t synth_events_write (struct file *file, +const char __user *buffer, +size_t count,loff_t *ppos) +{ +return trace_parse_run_command (file, buffer, count, ppos, + create_synth_event); +} + +static const struct file_operations synth_events_fops = { + .open = synth_events_open, + .write = synth_events_write, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + static u64 hist_field_timestamp(struct hist_field *hist_field, struct tracing_map_elt *elt, struct ring_buffer_event *rbe,
@@ -2942,6 +3762,8 @@ static int event_hist_trigger_func(struct event_command *cmd_ops,struct hist_trigger_attrs *attrs;struct event_trigger_ops *trigger_ops;struct hist_trigger_data *hist_data; +struct synth_event *se; +const char *se_name;bool remove = false;char *trigger;int ret =0 ;
@@ -3000,6 +3822,14 @@ static int event_hist_trigger_func(struct event_command *cmd_ops, } cmd_ops->unreg (glob+1 , trigger_ops, trigger_data, file); + +mutex_lock (&synth_event_mutex); + se_name =trace_event_name (file->event_call); + se =find_synth_event (se_name); +if (se) + se->ref--; +mutex_unlock (&synth_event_mutex); + ret =0 ; goto out_free; }
@@ -3017,6 +3847,13 @@ static int event_hist_trigger_func(struct event_command *cmd_ops, }else if (ret <0 ) goto out_free; +mutex_lock (&synth_event_mutex); + se_name =trace_event_name (file->event_call); + se =find_synth_event (se_name); +if (se) + se->ref++; +mutex_unlock (&synth_event_mutex); +if (get_named_trigger_data (trigger_data)) goto enable;
@@ -3207,3 +4044,31 @@ __init int register_trigger_hist_enable_disable_cmds(void)return ret; } + +static __init int trace_events_hist_init (void ) +{ +struct dentry *entry =NULL ; +struct dentry *d_tracer; +int err =0 ; + + d_tracer =tracing_init_dentry (); +if (IS_ERR (d_tracer)) { + err =PTR_ERR (d_tracer); + goto err; + } + + entry =tracefs_create_file ("synthetic_events" ,0644 , d_tracer, +NULL , &synth_events_fops); +if (!entry) { + err = -ENODEV ; + goto err; + } + +return err; + err: +pr_warn ("Could not create tracefs 'synthetic_events' entry\n" ); + +return err; +} + +fs_initcall (trace_events_hist_init);
Signature · 1 line
1.9.3
Steven Rostedt
maintainerRe: [PATCH v3 21/33] tracing: Add support for 'synthetic' events
Quoted 31 lines · On Fri, 22 Sep 2017 15:00:01 -0500 Tom Zanussi <[email protected]>…
kernel/trace/trace_events_hist.c +29
-0+/* This function releases synth_event_mutex */ +static int unregister_synth_event (struct synth_event *event) +{ +struct trace_event_call *call = &event->call; +int ret; + +mutex_unlock (&synth_event_mutex); + ret =trace_remove_event_call (call); +mutex_lock (&synth_event_mutex); + +return ret; +} + +static int remove_synth_event (struct synth_event *event) +{ +int ret =unregister_synth_event (event); + +if (!ret) +list_del (&event->list); + +return ret; +} + +/* This function releases synth_event_mutex */ +static int release_all_synth_events (void ) +{ +struct synth_event *event, *e; +int ret =0 ; +
kernel/trace/trace_events_hist.c struct action_data {
Quoted 10 lines
kernel/trace/trace_events_hist.c +10
-0+mutex_lock (&synth_event_mutex); + +list_for_each_entry (event, &synth_event_list, list) { +if (event->ref) { + ret = -EBUSY ; + goto out; + } + } + +list_for_each_entry_safe (event, e, &synth_event_list, list) {
remove_synth_event() releases synth_event_mutex, which is racy, as more than one instance can do the deletion.
Perhaps we should remove all the events off the synth_event_list under the lock, release the lock, and then remove the trace events attached to them?
-- Steve
Quoted 11 lines
kernel/trace/trace_events_hist.c +11
-0+ ret =remove_synth_event (event); +if (!ret) +free_synth_event (event); + } + out: +mutex_unlock (&synth_event_mutex); + +return ret; +} + +
Re: [PATCH v3 21/33] tracing: Add support for 'synthetic' events
Quoted 50 lines · Steven Rostedt
Quoted 31 lines · On Fri, 22 Sep 2017 15:00:01 -0500 Tom Zanussi <[email protected]>…
kernel/trace/trace_events_hist.c +29
-0+/* This function releases synth_event_mutex */ +static int unregister_synth_event (struct synth_event *event) +{ +struct trace_event_call *call = &event->call; +int ret; + +mutex_unlock (&synth_event_mutex); + ret =trace_remove_event_call (call); +mutex_lock (&synth_event_mutex); + +return ret; +} + +static int remove_synth_event (struct synth_event *event) +{ +int ret =unregister_synth_event (event); + +if (!ret) +list_del (&event->list); + +return ret; +} + +/* This function releases synth_event_mutex */ +static int release_all_synth_events (void ) +{ +struct synth_event *event, *e; +int ret =0 ; +kernel/trace/trace_events_hist.c struct action_data {
Quoted 10 lines
kernel/trace/trace_events_hist.c +10
-0+mutex_lock (&synth_event_mutex); + +list_for_each_entry (event, &synth_event_list, list) { +if (event->ref) { + ret = -EBUSY ; + goto out; + } + } + +list_for_each_entry_safe (event, e, &synth_event_list, list) {
remove_synth_event()releasessynth_event_mutex, which is racy, as more than one instance can do the deletion.
Quoted 4 lines
Perhaps we should remove all the events off the
synth_event_listunder the lock, release the lock, and then remove the trace events attached to them?
Yeah, I think that makes sense, will change..
Thanks,
Tom
Quoted 14 lines
-- Steve
Quoted 11 lines
kernel/trace/trace_events_hist.c +11
-0+ ret =remove_synth_event (event); +if (!ret) +free_synth_event (event); + } + out: +mutex_unlock (&synth_event_mutex); + +return ret; +} + +
Re: [PATCH v3 21/33] tracing: Add support for 'synthetic' events
Quoted 51 lines · Tom Zanussi
Quoted 50 lines · Steven Rostedt
Quoted 31 lines · On Fri, 22 Sep 2017 15:00:01 -0500 Tom Zanussi <[email protected]>…
kernel/trace/trace_events_hist.c +29
-0+/* This function releases synth_event_mutex */ +static int unregister_synth_event (struct synth_event *event) +{ +struct trace_event_call *call = &event->call; +int ret; + +mutex_unlock (&synth_event_mutex); + ret =trace_remove_event_call (call); +mutex_lock (&synth_event_mutex); + +return ret; +} + +static int remove_synth_event (struct synth_event *event) +{ +int ret =unregister_synth_event (event); + +if (!ret) +list_del (&event->list); + +return ret; +} + +/* This function releases synth_event_mutex */ +static int release_all_synth_events (void ) +{ +struct synth_event *event, *e; +int ret =0 ; +kernel/trace/trace_events_hist.c struct action_data {
Quoted 10 lines
kernel/trace/trace_events_hist.c +10
-0+mutex_lock (&synth_event_mutex); + +list_for_each_entry (event, &synth_event_list, list) { +if (event->ref) { + ret = -EBUSY ; + goto out; + } + } + +list_for_each_entry_safe (event, e, &synth_event_list, list) {
remove_synth_event()releasessynth_event_mutex, which is racy, as more than one instance can do the deletion.
Quoted 6 lines
Quoted 4 lines
Perhaps we should remove all the events off the
synth_event_listunder the lock, release the lock, and then remove the trace events attached to them?Yeah, I think that makes sense, will change..
I suspect a similar race in creation too. The create_synth_event() checks existing events in the synth_event_list but it releases the synth_event_mutex before adding a new event to the list. So it's possible to add two (or more) events have same name IMHO.
Thanks,
Namhyung
PATCH v3 22/33 tracing: Add support for 'field variables'
In reply to Tom ZanussiUsers should be able to directly specify event fields in hist trigger 'actions' rather than being forced to explicitly create a variable for that purpose.
Add support allowing fields to be used directly in actions, which essentially does just that - creates 'invisible' variables for each bare field specified in an action. If a bare field refers to a field on another (matching) event, it even creates a special histogram for the purpose (since variables can't be defined on an existing histogram after histogram creation).
Here's a simple example that demonstrates both. Basically the onmatch() action creates a list of variables corresponding to the parameters of the synthetic event to be generated, and then uses those values to generate the event. So for the wakeup_latency synthetic event 'call' below the first param, $wakeup_lat, is a variable defined explicitly on sched_switch, where 'next_pid' is just a normal field on sched_switch, and prio is a normal field on sched_waking.
Since the mechanism works on variables, those two normal fields just have 'invisible' variables created internally for them. In the case of 'prio', which is on another event, we actually need to create an additional hist trigger and define the invisible event on that, since once a hist trigger is defined, variables can't be added to it later.
echo 'wakeup_latency u64 lat; pid_t pid; int prio' >>
/sys/kernel/debug/tracing/synthetic_events
echo 'hist:keys=pid:ts0=$common_timestamp.usecs >>
/sys/kernel/debug/tracing/events/sched/sched_waking/trigger
echo 'hist:keys=next_pid:wakeup_lat=$common_timestamp.usecs-$ts0:
onmatch(sched.sched_waking).wakeup_latency($wakeup_lat,next_pid,prio)
>> /sys/kernel/debug/tracing/events/sched/sched_switch/trigger
--- kernel/trace/trace_events_hist.c | 450 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 449 insertions(+), 1 deletion(-)
kernel/trace/trace_events_hist.c +449 -1
@@ -254,6 +254,16 @@ struct hist_trigger_attrs {struct var_defs var_defs; }; +struct field_var { +struct hist_field *var; +struct hist_field *val; +}; + +struct field_var_hist { +struct hist_trigger_data *hist_data; +char *cmd; +}; +struct hist_trigger_data {struct hist_field *fields[HIST_FIELDS_MAX ];unsigned int n_vals;
@@ -274,6 +284,14 @@ struct hist_trigger_data {struct action_data *actions[HIST_ACTIONS_MAX ];unsigned int n_actions; + +struct hist_field *synth_var_refs[SYNTH_FIELDS_MAX ]; +unsigned int n_synth_var_refs; +struct field_var *field_vars[SYNTH_FIELDS_MAX ]; +unsigned int n_field_vars; +unsigned int n_field_var_str; +struct field_var_hist *field_var_hists[SYNTH_FIELDS_MAX ]; +unsigned int n_field_var_hists; };struct synth_field {
@@ -1394,6 +1412,7 @@ static struct hist_field *find_event_var(struct trace_array *tr,struct hist_elt_data {char *comm;u64 *var_ref_vals; +char *field_var_str[SYNTH_FIELDS_MAX ]; }; static u64 hist_field_var_ref(struct hist_field *hist_field,
@@ -1687,6 +1706,11 @@ static inline void save_comm(char *comm, struct task_struct *task)static void hist_elt_data_free (struct hist_elt_data *elt_data) { +unsigned int i; + +for (i =0 ; i <SYNTH_FIELDS_MAX ; i++) +kfree (elt_data->field_var_str[i]); +kfree (elt_data->comm);kfree (elt_data); }
@@ -1704,7 +1728,7 @@ static int hist_trigger_elt_data_alloc(struct tracing_map_elt *elt)unsigned int size =TASK_COMM_LEN +1 ;struct hist_elt_data *elt_data;struct hist_field *key_field; -unsigned int i; +unsigned int i, n_str; elt_data =kzalloc (sizeof (*elt_data),GFP_KERNEL );if (!elt_data)
@@ -1723,6 +1747,18 @@ static int hist_trigger_elt_data_alloc(struct tracing_map_elt *elt) } } + n_str = hist_data->n_field_var_str; + + size =STR_VAR_LEN_MAX ; + +for (i =0 ; i < n_str; i++) { + elt_data->field_var_str[i] =kzalloc (size,GFP_KERNEL ); +if (!elt_data->field_var_str[i]) { +hist_elt_data_free (elt_data); +return -ENOMEM ; + } + } + elt->private_data = elt_data;return 0 ;
@@ -2381,6 +2417,387 @@ static struct hist_field *parse_expr(struct hist_trigger_data *hist_data,return ERR_PTR (ret); } +static char *find_trigger_filter (struct hist_trigger_data *hist_data, +struct trace_event_file *file) +{ +struct event_trigger_data *test; + +list_for_each_entry_rcu (test, &file->triggers, list) { +if (test->cmd_ops->trigger_type ==ETT_EVENT_HIST ) { +if (test->private_data == hist_data) +return test->filter_str; + } + } + +return NULL ; +} + +static struct event_command trigger_hist_cmd; +static int event_hist_trigger_func (struct event_command *cmd_ops, +struct trace_event_file *file, +char *glob,char *cmd,char *param); + +static bool compatible_keys (struct hist_trigger_data *target_hist_data, +struct hist_trigger_data *hist_data, +unsigned int n_keys) +{ +struct hist_field *target_hist_field, *hist_field; +unsigned int n, i, j; + +if (hist_data->n_fields - hist_data->n_vals != n_keys) +return false; + + i = hist_data->n_vals; + j = target_hist_data->n_vals; + +for (n =0 ; n < n_keys; n++) { + hist_field = hist_data->fields[i + n]; + target_hist_field = hist_data->fields[j + n]; + +if (strcmp (hist_field->type, target_hist_field->type) !=0 ) +return false; +if (hist_field->size != target_hist_field->size) +return false; +if (hist_field->is_signed != target_hist_field->is_signed) +return false; + } + +return true; +} + +static struct hist_trigger_data * +find_compatible_hist (struct hist_trigger_data *target_hist_data, +struct trace_event_file *file) +{ +struct hist_trigger_data *hist_data; +struct event_trigger_data *test; +unsigned int n_keys; + + n_keys = target_hist_data->n_fields - target_hist_data->n_vals; + +list_for_each_entry_rcu (test, &file->triggers, list) { +if (test->cmd_ops->trigger_type ==ETT_EVENT_HIST ) { + hist_data = test->private_data; + +if (compatible_keys (target_hist_data, hist_data, n_keys)) +return hist_data; + } + } + +return NULL ; +} + +static struct trace_event_file *event_file (struct trace_array *tr, +char *system,char *event_name) +{ +struct trace_event_file *file; + + file =find_event_file (tr, system, event_name); +if (!file) +return ERR_PTR (-EINVAL ); + +return file; +} + +static struct hist_field * +create_field_var_hist (struct hist_trigger_data *target_hist_data, +char *system,char *event_name,char *field_name) +{ +struct trace_array *tr = target_hist_data->event_file->tr; +struct hist_field *event_var =ERR_PTR (-EINVAL ); +struct hist_trigger_data *hist_data; +unsigned int i, n, first = true; +struct field_var_hist *var_hist; +struct trace_event_file *file; +struct hist_field *key_field; +char *saved_filter; +char *cmd; +int ret; + +if (target_hist_data->n_field_var_hists >=SYNTH_FIELDS_MAX ) +return ERR_PTR (-EINVAL ); + + file =event_file (tr, system, event_name); + +if (IS_ERR (file)) { + ret =PTR_ERR (file); +return ERR_PTR (ret); + } + + hist_data =find_compatible_hist (target_hist_data, file); +if (!hist_data) +return ERR_PTR (-EINVAL ); + + var_hist =kzalloc (sizeof (*var_hist),GFP_KERNEL ); +if (!var_hist) +return ERR_PTR (-ENOMEM ); + + cmd =kzalloc (MAX_FILTER_STR_VAL ,GFP_KERNEL ); +if (!cmd) { +kfree (var_hist); +return ERR_PTR (-ENOMEM ); + } + +strcat (cmd,"keys=" ); + +for_each_hist_key_field (i, hist_data) { + key_field = hist_data->fields[i]; +if (!first) +strcat (cmd,"," ); +strcat (cmd, key_field->field->name); + first = false; + } + +strcat (cmd,":synthetic_" ); +strcat (cmd, field_name); +strcat (cmd,"=" ); +strcat (cmd, field_name); + + saved_filter =find_trigger_filter (hist_data, file); +if (saved_filter) { +strcat (cmd," if " ); +strcat (cmd, saved_filter); + } + + var_hist->cmd =kstrdup (cmd,GFP_KERNEL ); +if (!var_hist->cmd) { +kfree (cmd); +kfree (var_hist); +return ERR_PTR (-ENOMEM ); + } + + var_hist->hist_data = hist_data; + + ret =event_hist_trigger_func (&trigger_hist_cmd, file, +"" ,"hist" , cmd); +if (ret) { +kfree (cmd); +kfree (var_hist->cmd); +kfree (var_hist); +return ERR_PTR (ret); + } + +strcpy (cmd,"synthetic_" ); +strcat (cmd, field_name); + + event_var =find_event_var (tr, system, event_name, cmd); +if (!event_var) { +kfree (cmd); +kfree (var_hist->cmd); +kfree (var_hist); +return ERR_PTR (-EINVAL ); + } + + n = target_hist_data->n_field_var_hists; + target_hist_data->field_var_hists[n] = var_hist; + target_hist_data->n_field_var_hists++; + +return event_var; +} + +static struct hist_field * +find_target_event_var (struct hist_trigger_data *hist_data, +char *system,char *event_name,char *var_name) +{ +struct trace_event_file *file = hist_data->event_file; +struct hist_field *hist_field =NULL ; + +if (system) { +struct trace_event_call *call; + +if (!event_name) +return NULL ; + + call = file->event_call; + +if (strcmp (system, call->class->system) !=0 ) +return NULL ; + +if (strcmp (event_name,trace_event_name (call)) !=0 ) +return NULL ; + } + + hist_field =find_var_field (hist_data, var_name); + +return hist_field; +} + +static inline void __update_field_vars (struct tracing_map_elt *elt, +struct ring_buffer_event *rbe, +void *rec, +struct field_var **field_vars, +unsigned int n_field_vars, +unsigned int field_var_str_start) +{ +struct hist_elt_data *elt_data = elt->private_data; +unsigned int i, j, var_idx; +u64 var_val; + +for (i =0 , j = field_var_str_start; i < n_field_vars; i++) { +struct field_var *field_var = field_vars[i]; +struct hist_field *var = field_var->var; +struct hist_field *val = field_var->val; + + var_val = val->fn (val, elt, rbe, rec); + var_idx = var->var.idx; + +if (val->flags &HIST_FIELD_FL_STRING ) { +char *str = elt_data->field_var_str[j++]; +char *val_str = (char *)(uintptr_t )var_val; + +strncpy (str, val_str,STR_VAR_LEN_MAX ); + var_val = (u64 )(uintptr_t )str; + } +tracing_map_set_var (elt, var_idx, var_val); + } +} + +static void update_field_vars (struct hist_trigger_data *hist_data, +struct tracing_map_elt *elt, +struct ring_buffer_event *rbe, +void *rec) +{ +__update_field_vars (elt, rbe, rec, hist_data->field_vars, + hist_data->n_field_vars,0 ); +} + +static struct hist_field *create_var (struct hist_trigger_data *hist_data, +struct trace_event_file *file, +char *name,int size,const char *type) +{ +struct hist_field *var; +int idx; + +if (find_var (file, name) && !hist_data->remove) { + var =ERR_PTR (-EINVAL ); + goto out; + } + + var =kzalloc (sizeof (struct hist_field ),GFP_KERNEL ); +if (!var) { + var =ERR_PTR (-ENOMEM ); + goto out; + } + + idx =tracing_map_add_var (hist_data->map); +if (idx <0 ) { +kfree (var); + var =ERR_PTR (-EINVAL ); + goto out; + } + + var->flags =HIST_FIELD_FL_VAR ; + var->var.idx = idx; + var->var.hist_data = var->hist_data = hist_data; + var->size = size; + var->var.name =kstrdup (name,GFP_KERNEL ); + var->type =kstrdup (type,GFP_KERNEL ); +if (!var->var.name || !var->type) { +kfree (var->var.name); +kfree (var->type); +kfree (var); + var =ERR_PTR (-ENOMEM ); + } + out: +return var; +} + +static struct field_var *create_field_var (struct hist_trigger_data *hist_data, +struct trace_event_file *file, +char *field_name) +{ +struct hist_field *val =NULL , *var =NULL ; +unsigned long flags =HIST_FIELD_FL_VAR ; +struct field_var *field_var; +int ret =0 ; + +if (hist_data->n_field_vars >=SYNTH_FIELDS_MAX ) { + ret = -EINVAL ; + goto err; + } + + val =parse_atom (hist_data, file, field_name, &flags,NULL ); +if (IS_ERR (val)) { + ret =PTR_ERR (val); + goto err; + } + + var =create_var (hist_data, file, field_name, val->size, val->type); +if (IS_ERR (var)) { +kfree (val); + ret =PTR_ERR (var); + goto err; + } + + field_var =kzalloc (sizeof (struct field_var ),GFP_KERNEL ); +if (!field_var) { +kfree (val); +kfree (var); + ret = -ENOMEM ; + goto err; + } + + field_var->var = var; + field_var->val = val; + out: +return field_var; + err: + field_var =ERR_PTR (ret); + goto out; +} + +static struct field_var * +create_target_field_var (struct hist_trigger_data *hist_data, +char *system,char *event_name,char *var_name) +{ +struct trace_event_file *file = hist_data->event_file; + +if (system) { +struct trace_event_call *call; + +if (!event_name) +return NULL ; + + call = file->event_call; + +if (strcmp (system, call->class->system) !=0 ) +return NULL ; + +if (strcmp (event_name,trace_event_name (call)) !=0 ) +return NULL ; + } + +return create_field_var (hist_data, file, var_name); +} + +static void destroy_field_var (struct field_var *field_var) +{ +if (!field_var) +return ; + +destroy_hist_field (field_var->var,0 ); +destroy_hist_field (field_var->val,0 ); + +kfree (field_var); +} + +static void destroy_field_vars (struct hist_trigger_data *hist_data) +{ +unsigned int i; + +for (i =0 ; i < hist_data->n_field_vars; i++) +destroy_field_var (hist_data->field_vars[i]); +} + +static void save_field_var (struct hist_trigger_data *hist_data, +struct field_var *field_var) +{ + hist_data->field_vars[hist_data->n_field_vars++] = field_var; + +if (field_var->val->flags &HIST_FIELD_FL_STRING ) + hist_data->n_field_var_str++; +} +static int create_hitcount_val (struct hist_trigger_data *hist_data) { hist_data->fields[HITCOUNT_IDX] =
@@ -2827,6 +3244,16 @@ static int create_actions(struct hist_trigger_data *hist_data,return ret; } +static void destroy_field_var_hists (struct hist_trigger_data *hist_data) +{ +unsigned int i; + +for (i =0 ; i < hist_data->n_field_var_hists; i++) { +kfree (hist_data->field_var_hists[i]->cmd); +kfree (hist_data->field_var_hists[i]); + } +} +static void destroy_hist_data (struct hist_trigger_data *hist_data) {if (!hist_data)
@@ -2837,6 +3264,8 @@ static void destroy_hist_data(struct hist_trigger_data *hist_data)tracing_map_destroy (hist_data->map);destroy_actions (hist_data); +destroy_field_vars (hist_data); +destroy_field_var_hists (hist_data);kfree (hist_data); }
@@ -2970,6 +3399,8 @@ static void hist_trigger_elt_update(struct hist_trigger_data *hist_data,tracing_map_set_var (elt, var_idx, hist_val); } } + +update_field_vars (hist_data, elt, rbe, rec); } static inline void add_to_key(char *compound_key, void *key,
@@ -3687,6 +4118,21 @@ static bool hist_trigger_check_refs(struct event_trigger_data *data,return false; } +static void unregister_field_var_hists (struct hist_trigger_data *hist_data) +{ +struct trace_event_file *file; +unsigned int i; +char *cmd; +int ret; + +for (i =0 ; i < hist_data->n_field_var_hists; i++) { + file = hist_data->field_var_hists[i]->hist_data->event_file; + cmd = hist_data->field_var_hists[i]->cmd; + ret =event_hist_trigger_func (&trigger_hist_cmd, file, +"!hist" ,"hist" , cmd); + } +} +static void hist_unregister_trigger (char *glob,struct event_trigger_ops *ops,struct event_trigger_data *data,struct trace_event_file *file)
@@ -3702,6 +4148,7 @@ static void hist_unregister_trigger(char *glob, struct event_trigger_ops *ops,if (test->cmd_ops->trigger_type ==ETT_EVENT_HIST ) {if (!hist_trigger_match (data, test, named_data, false))continue ; +unregister_field_var_hists (test->private_data); unregistered = true;list_del_rcu (&test->list);trace_event_trigger_enable_disable (file,0 );
@@ -3744,6 +4191,7 @@ static void hist_unreg_all(struct trace_event_file *file)list_for_each_entry_safe (test, n, &file->triggers, list) {if (test->cmd_ops->trigger_type ==ETT_EVENT_HIST ) { +unregister_field_var_hists (test->private_data);list_del_rcu (&test->list);trace_event_trigger_enable_disable (file,0 );update_cond_flag (file);
Signature · 1 line
1.9.3
Re: [PATCH v3 22/33] tracing: Add support for 'field variables'
Quoted 19 lines · Tom Zanussi
Users should be able to directly specify event fields in hist trigger 'actions' rather than being forced to explicitly create a variable for that purpose.
Add support allowing fields to be used directly in actions, which essentially does just that - creates 'invisible' variables for each bare field specified in an action. If a bare field refers to a field on another (matching) event, it even creates a special histogram for the purpose (since variables can't be defined on an existing histogram after histogram creation).
Here's a simple example that demonstrates both. Basically the
onmatch()action creates a list of variables corresponding to the parameters of the synthetic event to be generated, and then uses those values to generate the event. So for thewakeup_latencysynthetic event 'call' below the first param, $wakeup_lat, is a variable defined explicitly onsched_switch, where 'next_pid' is just a normal field onsched_switch, and prio is a normal field onsched_waking.
Quoted 4 lines
Since the mechanism works on variables, those two normal fields just have 'invisible' variables created internally for them. In the case of 'prio', which is on another event, we actually need to create an additional hist trigger and define the invisible event on that, since
s/event/variable/ ?
Quoted 14 lines
once a hist trigger is defined, variables can't be added to it later.
echo '
wakeup_latencyu64 lat;pid_tpid; int prio' >>
/sys/kernel/debug/tracing/synthetic_eventsecho 'hist:keys=pid:ts0=$common_timestamp.usecs >>
/sys/kernel/debug/tracing/events/sched/sched_waking/triggerecho 'hist:keys=next_pid:wakeup_lat=$common_timestamp.usecs-$ts0:
onmatch(sched.sched_waking).wakeup_latency($wakeup_lat,next_pid,prio)
>>/sys/kernel/debug/tracing/events/sched/sched_switch/triggerSigned-off-by Tom Zanussi <[email protected]>---
[SNIP]
Quoted 6 lines
kernel/trace/trace_events_hist.c +6
-0+static bool compatible_keys (struct hist_trigger_data *target_hist_data, +struct hist_trigger_data *hist_data, +unsigned int n_keys) +{ +struct hist_field *target_hist_field, *hist_field; +unsigned int n, i, j;
kernel/trace/trace_events_hist.c static struct hist_field *parse_expr(struct hist_trigger_data *hist_data, · line 2455
Quoted 10 lines
kernel/trace/trace_events_hist.c +10
-0+ +if (hist_data->n_fields - hist_data->n_vals != n_keys) +return false; + + i = hist_data->n_vals; + j = target_hist_data->n_vals; + +for (n =0 ; n < n_keys; n++) { + hist_field = hist_data->fields[i + n]; + target_hist_field = hist_data->fields[j + n];
s/hist_data/target_hist_data/
Quoted 11 lines
kernel/trace/trace_events_hist.c +11
-0+ +if (strcmp (hist_field->type, target_hist_field->type) !=0 ) +return false; +if (hist_field->size != target_hist_field->size) +return false; +if (hist_field->is_signed != target_hist_field->is_signed) +return false; + } + +return true; +}
[SNIP]
Quoted 58 lines
kernel/trace/trace_events_hist.c +58
-0+static struct hist_field * +create_field_var_hist (struct hist_trigger_data *target_hist_data, +char *system,char *event_name,char *field_name) +{ +struct trace_array *tr = target_hist_data->event_file->tr; +struct hist_field *event_var =ERR_PTR (-EINVAL ); +struct hist_trigger_data *hist_data; +unsigned int i, n, first = true; +struct field_var_hist *var_hist; +struct trace_event_file *file; +struct hist_field *key_field; +char *saved_filter; +char *cmd; +int ret; + +if (target_hist_data->n_field_var_hists >=SYNTH_FIELDS_MAX ) +return ERR_PTR (-EINVAL ); + + file =event_file (tr, system, event_name); + +if (IS_ERR (file)) { + ret =PTR_ERR (file); +return ERR_PTR (ret); + } + + hist_data =find_compatible_hist (target_hist_data, file); +if (!hist_data) +return ERR_PTR (-EINVAL ); + + var_hist =kzalloc (sizeof (*var_hist),GFP_KERNEL ); +if (!var_hist) +return ERR_PTR (-ENOMEM ); + + cmd =kzalloc (MAX_FILTER_STR_VAL ,GFP_KERNEL ); +if (!cmd) { +kfree (var_hist); +return ERR_PTR (-ENOMEM ); + } + +strcat (cmd,"keys=" ); + +for_each_hist_key_field (i, hist_data) { + key_field = hist_data->fields[i]; +if (!first) +strcat (cmd,"," ); +strcat (cmd, key_field->field->name); + first = false; + } + +strcat (cmd,":synthetic_" ); +strcat (cmd, field_name); +strcat (cmd,"=" ); +strcat (cmd, field_name); + + saved_filter =find_trigger_filter (hist_data, file); +if (saved_filter) { +strcat (cmd," if " ); +strcat (cmd, saved_filter);
kernel/trace/trace_events_hist.c static struct hist_field *parse_expr(struct hist_trigger_data *hist_data, · line 2569
Quoted 10 lines
kernel/trace/trace_events_hist.c +10
-0+ } + + var_hist->cmd =kstrdup (cmd,GFP_KERNEL ); +if (!var_hist->cmd) { +kfree (cmd); +kfree (var_hist); +return ERR_PTR (-ENOMEM ); + } + + var_hist->hist_data = hist_data;
Hmm.. so it's a compatible hist data, not the newly created one, right? Might confuse someone later.. ;-)
kernel/trace/trace_events_hist.c static struct hist_field *parse_expr(struct hist_trigger_data *hist_data, · line 2578
Quoted 9 lines
kernel/trace/trace_events_hist.c +9
-0+ + ret =event_hist_trigger_func (&trigger_hist_cmd, file, +"" ,"hist" , cmd); +if (ret) { +kfree (cmd); +kfree (var_hist->cmd); +kfree (var_hist); +return ERR_PTR (ret); + }
What if two different event want to create synthetic variables for a same field? I suspect one failed because the variable names are same but it could continue since the variable is there anyway. Probably it would be better to use a different error code for the case..
Thanks,
Namhyung
Quoted 18 lines
kernel/trace/trace_events_hist.c +18
-0+ +strcpy (cmd,"synthetic_" ); +strcat (cmd, field_name); + + event_var =find_event_var (tr, system, event_name, cmd); +if (!event_var) { +kfree (cmd); +kfree (var_hist->cmd); +kfree (var_hist); +return ERR_PTR (-EINVAL ); + } + + n = target_hist_data->n_field_var_hists; + target_hist_data->field_var_hists[n] = var_hist; + target_hist_data->n_field_var_hists++; + +return event_var; +}
Re: [PATCH v3 22/33] tracing: Add support for 'field variables'
Hi Namhyung,
Quoted 20 lines · Namhyung Kim
Quoted 19 lines · Tom Zanussi
Users should be able to directly specify event fields in hist trigger 'actions' rather than being forced to explicitly create a variable for that purpose.
Add support allowing fields to be used directly in actions, which essentially does just that - creates 'invisible' variables for each bare field specified in an action. If a bare field refers to a field on another (matching) event, it even creates a special histogram for the purpose (since variables can't be defined on an existing histogram after histogram creation).
Here's a simple example that demonstrates both. Basically the
onmatch()action creates a list of variables corresponding to the parameters of the synthetic event to be generated, and then uses those values to generate the event. So for thewakeup_latencysynthetic event 'call' below the first param, $wakeup_lat, is a variable defined explicitly onsched_switch, where 'next_pid' is just a normal field onsched_switch, and prio is a normal field onsched_waking.
Quoted 7 lines
Quoted 4 lines
Since the mechanism works on variables, those two normal fields just have 'invisible' variables created internally for them. In the case of 'prio', which is on another event, we actually need to create an additional hist trigger and define the invisible event on that, since
s/event/variable/ ?
Right, thanks for pointing that out.
Quoted 28 lines
Quoted 15 lines
once a hist trigger is defined, variables can't be added to it later.
echo '
wakeup_latencyu64 lat;pid_tpid; int prio' >>
/sys/kernel/debug/tracing/synthetic_eventsecho 'hist:keys=pid:ts0=$common_timestamp.usecs >>
/sys/kernel/debug/tracing/events/sched/sched_waking/triggerecho 'hist:keys=next_pid:wakeup_lat=$common_timestamp.usecs-$ts0:
onmatch(sched.sched_waking).wakeup_latency($wakeup_lat,next_pid,prio)
>>/sys/kernel/debug/tracing/events/sched/sched_switch/triggerSigned-off-by Tom Zanussi <[email protected]>---
[SNIP]
Quoted 10 lines
kernel/trace/trace_events_hist.c +10
-0+static bool compatible_keys (struct hist_trigger_data *target_hist_data, +struct hist_trigger_data *hist_data, +unsigned int n_keys) +{ +struct hist_field *target_hist_field, *hist_field; +unsigned int n, i, j; + +if (hist_data->n_fields - hist_data->n_vals != n_keys) +return false; +
Quoted 9 lines
kernel/trace/trace_events_hist.c static struct hist_field *parse_expr(struct hist_trigger_data *hist_data, · line 2455
Quoted 6 lines
kernel/trace/trace_events_hist.c +6
-0+ i = hist_data->n_vals; + j = target_hist_data->n_vals; + +for (n =0 ; n < n_keys; n++) { + hist_field = hist_data->fields[i + n]; + target_hist_field = hist_data->fields[j + n];s/hist_data/target_hist_data/
Yeah, I actually fixed this, but put it in the onmatch patch, will move it to this patch.
Quoted 77 lines
Quoted 11 lines
kernel/trace/trace_events_hist.c +11
-0+ +if (strcmp (hist_field->type, target_hist_field->type) !=0 ) +return false; +if (hist_field->size != target_hist_field->size) +return false; +if (hist_field->is_signed != target_hist_field->is_signed) +return false; + } + +return true; +}[SNIP]
Quoted 63 lines
kernel/trace/trace_events_hist.c +63
-0+static struct hist_field * +create_field_var_hist (struct hist_trigger_data *target_hist_data, +char *system,char *event_name,char *field_name) +{ +struct trace_array *tr = target_hist_data->event_file->tr; +struct hist_field *event_var =ERR_PTR (-EINVAL ); +struct hist_trigger_data *hist_data; +unsigned int i, n, first = true; +struct field_var_hist *var_hist; +struct trace_event_file *file; +struct hist_field *key_field; +char *saved_filter; +char *cmd; +int ret; + +if (target_hist_data->n_field_var_hists >=SYNTH_FIELDS_MAX ) +return ERR_PTR (-EINVAL ); + + file =event_file (tr, system, event_name); + +if (IS_ERR (file)) { + ret =PTR_ERR (file); +return ERR_PTR (ret); + } + + hist_data =find_compatible_hist (target_hist_data, file); +if (!hist_data) +return ERR_PTR (-EINVAL ); + + var_hist =kzalloc (sizeof (*var_hist),GFP_KERNEL ); +if (!var_hist) +return ERR_PTR (-ENOMEM ); + + cmd =kzalloc (MAX_FILTER_STR_VAL ,GFP_KERNEL ); +if (!cmd) { +kfree (var_hist); +return ERR_PTR (-ENOMEM ); + } + +strcat (cmd,"keys=" ); + +for_each_hist_key_field (i, hist_data) { + key_field = hist_data->fields[i]; +if (!first) +strcat (cmd,"," ); +strcat (cmd, key_field->field->name); + first = false; + } + +strcat (cmd,":synthetic_" ); +strcat (cmd, field_name); +strcat (cmd,"=" ); +strcat (cmd, field_name); + + saved_filter =find_trigger_filter (hist_data, file); +if (saved_filter) { +strcat (cmd," if " ); +strcat (cmd, saved_filter); + } + + var_hist->cmd =kstrdup (cmd,GFP_KERNEL ); +if (!var_hist->cmd) { +kfree (cmd);
Quoted 9 lines
kernel/trace/trace_events_hist.c static struct hist_field *parse_expr(struct hist_trigger_data *hist_data, · line 2569
Quoted 5 lines
kernel/trace/trace_events_hist.c +5
-0+kfree (var_hist); +return ERR_PTR (-ENOMEM ); + } + + var_hist->hist_data = hist_data;Hmm.. so it's a compatible hist data, not the newly created one, right? Might confuse someone later.. ;-)
Yeah, will add some more comments to this and related functions to make it less confusing.
Quoted 16 lines
kernel/trace/trace_events_hist.c static struct hist_field *parse_expr(struct hist_trigger_data *hist_data, · line 2578
Quoted 10 lines
kernel/trace/trace_events_hist.c +9
-0+ + ret =event_hist_trigger_func (&trigger_hist_cmd, file, +"" ,"hist" , cmd); +if (ret) { +kfree (cmd); +kfree (var_hist->cmd); +kfree (var_hist); +return ERR_PTR (ret); + }What if two different event want to create synthetic variables for a same field? I suspect one failed because the variable names are same but it could continue since the variable is there anyway. Probably it would be better to use a different error code for the case..
Yeah, in this case, one will fail when there's no reason it should, will fix, thanks for thinking of these things ;-)
Tom
PATCH v3 23/33 tracing: Add 'onmatch' hist trigger action support
In reply to Tom ZanussiAdd an 'onmatch(matching.event).<synthetic_event_name>(param list)' hist trigger action which is invoked with the set of variables or event fields named in the 'param list'. The result is the generation of a synthetic event that consists of the values contained in those variables and/or fields at the time the invoking event was hit.
As an example the below defines a simple synthetic event using a variable defined on the sched_wakeup_new event, and shows the event definition with unresolved fields, since the sched_wakeup_new event with the testpid variable hasn't been defined yet:
# echo 'wakeup_new_test pid_t pid; int prio' >> \
/sys/kernel/debug/tracing/synthetic_events # cat /sys/kernel/debug/tracing/synthetic_events
wakeup_new_test pid_t pid; int prioThe following hist trigger both defines a testpid variable and specifies an onmatch() trace action that uses that variable along with a non-variable field to generate a wakeup_new_test synthetic event whenever a sched_wakeup_new event occurs, which because of the 'if comm == "cyclictest"' filter only happens when the executable is cyclictest:
# echo 'hist:testpid=pid:keys=$testpid:\
onmatch(sched.sched_wakeup_new).wakeup_new_test($testpid, prio) \
if comm=="cyclictest"' >> \
/sys/kernel/debug/tracing/events/sched/sched_wakeup_new/triggerCreating and displaying a histogram based on those events is now just a matter of using the fields and new synthetic event in the tracing/events/synthetic directory, as usual:
# echo 'hist:keys=pid,prio:sort=pid,prio' >> \
/sys/kernel/debug/tracing/events/synthetic/wakeup_new_test/trigger--- kernel/trace/trace_events_hist.c | 406 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 394 insertions(+), 12 deletions(-)
kernel/trace/trace_events_hist.c +394 -12
@@ -323,7 +323,18 @@ typedef void (*action_fn_t) (struct hist_trigger_data *hist_data,struct action_data {action_fn_t fn; -unsigned int var_ref_idx; +unsigned int n_params; +char *params[SYNTH_FIELDS_MAX ]; + +union { +struct { +unsigned int var_ref_idx; +char *match_event; +char *match_event_system; +char *synth_event_name; +struct synth_event *synth_event; + } onmatch; + }; };static LIST_HEAD (synth_event_list);
@@ -925,6 +936,21 @@ static struct synth_event *alloc_synth_event(char *event_name, int n_fields,return event; } +static void action_trace (struct hist_trigger_data *hist_data, +struct tracing_map_elt *elt,void *rec, +struct ring_buffer_event *rbe, +struct action_data *data,u64 *var_ref_vals) +{ +struct synth_event *event = data->onmatch.synth_event; + +trace_synth (event, var_ref_vals, data->onmatch.var_ref_idx); +} + +struct hist_var_data { +struct list_head list; +struct hist_trigger_data *hist_data; +}; +/* This function releases synth_event_mutex */ static int create_synth_event (int argc,char **argv) {
@@ -967,10 +993,8 @@ static int create_synth_event(int argc, char **argv) } ret = -EEXIST ; goto out; - }else if (delete_event) { - ret = -EINVAL ; + }else if (delete_event) goto out; - }if (argc <2 ) { ret = -EINVAL ;
@@ -1136,11 +1160,6 @@ static u64 hist_field_timestamp(struct hist_field *hist_field,return ts; } -struct hist_var_data { -struct list_head list; -struct hist_trigger_data *hist_data; -}; -static struct hist_field *check_var_ref (struct hist_field *hist_field,struct hist_trigger_data *var_data,unsigned int var_idx)
@@ -1580,11 +1599,21 @@ static void destroy_hist_trigger_attrs(struct hist_trigger_attrs *attrs)static int parse_action (char *str,struct hist_trigger_attrs *attrs) { -int ret =0 ; +int ret = -EINVAL ;if (attrs->n_actions >=HIST_ACTIONS_MAX )return ret; +if ((strncmp (str,"onmatch(" ,strlen ("onmatch(" )) ==0 )) { + attrs->action_str[attrs->n_actions] =kstrdup (str,GFP_KERNEL ); +if (!attrs->action_str[attrs->n_actions]) { + ret = -ENOMEM ; +return ret; + } + attrs->n_actions++; + ret =0 ; + } +return ret; }
@@ -2452,7 +2481,7 @@ static bool compatible_keys(struct hist_trigger_data *target_hist_data,for (n =0 ; n < n_keys; n++) { hist_field = hist_data->fields[i + n]; - target_hist_field = hist_data->fields[j + n]; + target_hist_field = target_hist_data->fields[j + n];if (strcmp (hist_field->type, target_hist_field->type) !=0 )return false;
@@ -2770,6 +2799,27 @@ static struct field_var *create_field_var(struct hist_trigger_data *hist_data,return create_field_var (hist_data, file, var_name); } +static void onmatch_destroy (struct action_data *data) +{ +unsigned int i; + +mutex_lock (&synth_event_mutex); + +kfree (data->onmatch.match_event); +kfree (data->onmatch.match_event_system); +kfree (data->onmatch.synth_event_name); + +for (i =0 ; i < data->n_params; i++) +kfree (data->params[i]); + +if (data->onmatch.synth_event) + data->onmatch.synth_event->ref--; + +kfree (data); + +mutex_unlock (&synth_event_mutex); +} +static void destroy_field_var (struct field_var *field_var) {if (!field_var)
@@ -2798,6 +2848,283 @@ static void save_field_var(struct hist_trigger_data *hist_data, hist_data->n_field_var_str++; } + +static void destroy_synth_var_refs (struct hist_trigger_data *hist_data) +{ +unsigned int i; + +for (i =0 ; i < hist_data->n_synth_var_refs; i++) +destroy_hist_field (hist_data->synth_var_refs[i],0 ); +} + +static void save_synth_var_ref (struct hist_trigger_data *hist_data, +struct hist_field *var_ref) +{ + hist_data->synth_var_refs[hist_data->n_synth_var_refs++] = var_ref; + + hist_data->var_refs[hist_data->n_var_refs] = var_ref; + var_ref->var_ref_idx = hist_data->n_var_refs++; +} + +static int check_synth_field (struct synth_event *event, +struct hist_field *hist_field, +unsigned int field_pos) +{ +struct synth_field *field; + +if (field_pos >= event->n_fields) +return -EINVAL ; + + field = event->fields[field_pos]; + +if (strcmp (field->type, hist_field->type) !=0 ) +return -EINVAL ; + +return 0 ; +} + +static int parse_action_params (char *params,struct action_data *data) +{ +char *param, *saved_param; +int ret =0 ; + +while (params) { +if (data->n_params >=SYNTH_FIELDS_MAX ) + goto out; + + param =strsep (¶ms,"," ); +if (!param) { + ret = -EINVAL ; + goto out; + } + + param =strstrip (param); +if (strlen (param) <2 ) { + ret = -EINVAL ; + goto out; + } + + saved_param =kstrdup (param,GFP_KERNEL ); +if (!saved_param) { + ret = -ENOMEM ; + goto out; + } + + data->params[data->n_params++] = saved_param; + } + out: +return ret; +} + +static struct hist_field * +onmatch_find_var (struct hist_trigger_data *hist_data,struct action_data *data, +char *system,char *event,char *var) +{ +struct trace_array *tr = hist_data->event_file->tr; +struct hist_field *hist_field; + + var++;/* skip '$' */ + + hist_field =find_target_event_var (hist_data, system, event, var); +if (!hist_field) { +if (!system) { + system = data->onmatch.match_event_system; + event = data->onmatch.match_event; + } + + hist_field =find_event_var (tr, system, event, var); + } + +return hist_field; +} + +static struct hist_field * +onmatch_create_field_var (struct hist_trigger_data *hist_data, +struct action_data *data,char *system, +char *event,char *var) +{ +struct hist_field *hist_field =NULL ; +struct field_var *field_var; + + field_var =create_target_field_var (hist_data, system, event, var); +if (IS_ERR (field_var)) + goto out; + +if (field_var) { +save_field_var (hist_data, field_var); + hist_field = field_var->var; + }else { +if (!system) { + system = data->onmatch.match_event_system; + event = data->onmatch.match_event; + } + + hist_field =create_field_var_hist (hist_data, system, event, var); +if (IS_ERR (hist_field)) + goto free; + } + out: +return hist_field; + free: +destroy_field_var (field_var); + hist_field =NULL ; + goto out; +} + +static int onmatch_create (struct hist_trigger_data *hist_data, +struct trace_event_file *file, +struct action_data *data) +{ +char *event_name, *param, *system =NULL ; +struct hist_field *hist_field, *var_ref; +unsigned int i, var_ref_idx; +unsigned int field_pos =0 ; +struct synth_event *event; +int ret =0 ; + +mutex_lock (&synth_event_mutex); + event =find_synth_event (data->onmatch.synth_event_name); +if (!event) { +mutex_unlock (&synth_event_mutex); +return -EINVAL ; + } +mutex_unlock (&synth_event_mutex); + + var_ref_idx = hist_data->n_var_refs; + +for (i =0 ; i < data->n_params; i++) { +char *p; + + p = param =kstrdup (data->params[i],GFP_KERNEL ); +if (!param) { + ret = -ENOMEM ; + goto out; + } + + system =strsep (¶m,"." ); +if (!param) { + param = (char *)system; + system = event_name =NULL ; + }else { + event_name =strsep (¶m,"." ); +if (!param) { +kfree (p); + ret = -EINVAL ; + goto out; + } + } + +if (param[0 ] =='$' ) + hist_field =onmatch_find_var (hist_data, data, system, + event_name, param); +else + hist_field =onmatch_create_field_var (hist_data, data, + system, + event_name, + param); + +if (!hist_field) { +kfree (p); + ret = -EINVAL ; + goto out; + } + +if (check_synth_field (event, hist_field, field_pos) ==0 ) { + var_ref =create_var_ref (hist_field); +if (!var_ref) { +kfree (p); + ret = -ENOMEM ; + goto out; + } + +save_synth_var_ref (hist_data, var_ref); + field_pos++; +kfree (p); +continue ; + } + +kfree (p); + ret = -EINVAL ; + goto out; + } + +if (field_pos != event->n_fields) { + ret = -EINVAL ; + goto out; + } + + data->fn = action_trace; + data->onmatch.synth_event = event; + data->onmatch.var_ref_idx = var_ref_idx; + hist_data->actions[hist_data->n_actions++] = data; + event->ref++; + out: +return ret; +} + +static struct action_data *onmatch_parse (struct trace_array *tr,char *str) +{ +char *match_event, *match_event_system; +char *synth_event_name, *params; +struct action_data *data; +int ret = -EINVAL ; + + data =kzalloc (sizeof (*data),GFP_KERNEL ); +if (!data) +return ERR_PTR (-ENOMEM ); + + match_event =strsep (&str,")" ); +if (!match_event || !str) + goto free; + + match_event_system =strsep (&match_event,"." ); +if (!match_event) + goto free; + +if (IS_ERR (event_file (tr, match_event_system, match_event))) + goto free; + + data->onmatch.match_event =kstrdup (match_event,GFP_KERNEL ); +if (!data->onmatch.match_event) { + ret = -ENOMEM ; + goto free; + } + + data->onmatch.match_event_system =kstrdup (match_event_system,GFP_KERNEL ); +if (!data->onmatch.match_event_system) { + ret = -ENOMEM ; + goto free; + } + +strsep (&str,"." ); +if (!str) + goto free; + + synth_event_name =strsep (&str,"(" ); +if (!synth_event_name || !str) + goto free; + + data->onmatch.synth_event_name =kstrdup (synth_event_name,GFP_KERNEL ); +if (!data->onmatch.synth_event_name) { + ret = -ENOMEM ; + goto free; + } + + params =strsep (&str,")" ); +if (!params || !str || (str &&strlen (str))) + goto free; + + ret =parse_action_params (params, data); +if (ret) + goto free; + out: +return data; + free: +onmatch_destroy (data); + data =ERR_PTR (ret); + goto out; +} +static int create_hitcount_val (struct hist_trigger_data *hist_data) { hist_data->fields[HITCOUNT_IDX] =
@@ -3226,24 +3553,76 @@ static void destroy_actions(struct hist_trigger_data *hist_data)for (i =0 ; i < hist_data->n_actions; i++) {struct action_data *data = hist_data->actions[i]; -kfree (data); +if (data->fn == action_trace) +onmatch_destroy (data); +else +kfree (data); } }static int create_actions (struct hist_trigger_data *hist_data,struct trace_event_file *file) { +struct trace_array *tr = hist_data->event_file->tr; +struct action_data *data;unsigned int i;int ret =0 ;char *str;for (i =0 ; i < hist_data->attrs->n_actions; i++) { str = hist_data->attrs->action_str[i]; + +if (strncmp (str,"onmatch(" ,strlen ("onmatch(" )) ==0 ) { +char *action_str = str +strlen ("onmatch(" ); + + data =onmatch_parse (tr, action_str); +if (IS_ERR (data)) +return PTR_ERR (data); + + ret =onmatch_create (hist_data, file, data); +if (ret) { +onmatch_destroy (data); +return ret; + } + } }return ret; } +static void print_onmatch_spec (struct seq_file *m, +struct hist_trigger_data *hist_data, +struct action_data *data) +{ +unsigned int i; + +seq_printf (m,":onmatch(%s.%s)." , data->onmatch.match_event_system, + data->onmatch.match_event); + +seq_printf (m,"%s(" , data->onmatch.synth_event->name); + +for (i =0 ; i < data->n_params; i++) { +if (i) +seq_puts (m,"," ); +seq_printf (m,"%s" , data->params[i]); + } + +seq_puts (m,")" ); +} + +static void print_actions_spec (struct seq_file *m, +struct hist_trigger_data *hist_data) +{ +unsigned int i; + +for (i =0 ; i < hist_data->n_actions; i++) { +struct action_data *data = hist_data->actions[i]; + +if (data->fn == action_trace) +print_onmatch_spec (m, hist_data, data); + } +} +static void destroy_field_var_hists (struct hist_trigger_data *hist_data) {unsigned int i;
@@ -3266,6 +3645,7 @@ static void destroy_hist_data(struct hist_trigger_data *hist_data)destroy_actions (hist_data);destroy_field_vars (hist_data);destroy_field_var_hists (hist_data); +destroy_synth_var_refs (hist_data);kfree (hist_data); }
@@ -3814,6 +4194,8 @@ static int event_hist_trigger_print(struct seq_file *m, }seq_printf (m,":size=%u" , (1 << hist_data->map->map_bits)); +print_actions_spec (m, hist_data); +if (data->filter_str)seq_printf (m," if %s" , data->filter_str);
Signature · 1 line
1.9.3
PATCH v3 24/33 tracing: Add 'onmax' hist trigger action support
In reply to Tom ZanussiAdd an 'onmax(var).save(field,...)' hist trigger action which is invoked whenever an event exceeds the current maximum.
The end result is that the trace event fields or variables specified as the onmax.save() params will be saved if 'var' exceeds the current maximum for that hist trigger entry. This allows context from the event that exhibited the new maximum to be saved for later reference. When the histogram is displayed, additional fields displaying the saved values will be printed.
As an example the below defines a couple of hist triggers, one for sched_wakeup and another for sched_switch, keyed on pid. Whenever a sched_wakeup occurs, the timestamp is saved in the entry corresponding to the current pid, and when the scheduler switches back to that pid, the timestamp difference is calculated. If the resulting latency exceeds the current maximum latency, the specified save() values are saved:
# echo 'hist:keys=pid:ts0=$common_timestamp.usecs \
if comm=="cyclictest"' >> \
/sys/kernel/debug/tracing/events/sched/sched_wakeup/trigger # echo 'hist:keys=next_pid:\
wakeup_lat=$common_timestamp.usecs-$ts0:\
onmax($wakeup_lat).save(next_comm,prev_pid,prev_prio,prev_comm) \
if next_comm=="cyclictest"' >> \
/sys/kernel/debug/tracing/events/sched/sched_switch/triggerWhen the histogram is displayed, the max value and the saved values corresponding to the max are displayed following the rest of the fields:
# cat /sys/kernel/debug/tracing/events/sched/sched_switch/hist
{ next_pid: 3728 } hitcount: 199 \
max: 123 next_comm: cyclictest prev_pid: 0 \
prev_prio: 120 prev_comm: swapper/3
{ next_pid: 3730 } hitcount: 1321 \
max: 15 next_comm: cyclictest prev_pid: 0 \
prev_prio: 120 prev_comm: swapper/1
{ next_pid: 3729 } hitcount: 1973\
max: 25 next_comm: cyclictest prev_pid: 0 \
prev_prio: 120 prev_comm: swapper/0
Totals:
Hits: 3493
Entries: 3
Dropped: 0--- kernel/trace/trace_events_hist.c | 332 ++++++++++++++++++++++++++++++++++----- 1 file changed, 297 insertions(+), 35 deletions(-)
kernel/trace/trace_events_hist.c +297 -35
@@ -292,6 +292,10 @@ struct hist_trigger_data {unsigned int n_field_var_str;struct field_var_hist *field_var_hists[SYNTH_FIELDS_MAX ];unsigned int n_field_var_hists; + +struct field_var *max_vars[SYNTH_FIELDS_MAX ]; +unsigned int n_max_vars; +unsigned int n_max_var_str; };struct synth_field {
@@ -334,6 +338,14 @@ struct action_data {char *synth_event_name;struct synth_event *synth_event; } onmatch; + +struct { +char *var_str; +char *fn_name; +unsigned int max_var_ref_idx; +struct hist_field *max_var; +struct hist_field *var; + } onmax; }; };
@@ -1604,7 +1616,8 @@ static int parse_action(char *str, struct hist_trigger_attrs *attrs)if (attrs->n_actions >=HIST_ACTIONS_MAX )return ret; -if ((strncmp (str,"onmatch(" ,strlen ("onmatch(" )) ==0 )) { +if ((strncmp (str,"onmatch(" ,strlen ("onmatch(" )) ==0 ) || + (strncmp (str,"onmax(" ,strlen ("onmax(" )) ==0 )) { attrs->action_str[attrs->n_actions] =kstrdup (str,GFP_KERNEL );if (!attrs->action_str[attrs->n_actions]) { ret = -ENOMEM ;
@@ -1776,7 +1789,7 @@ static int hist_trigger_elt_data_alloc(struct tracing_map_elt *elt) } } - n_str = hist_data->n_field_var_str; + n_str = hist_data->n_field_var_str + hist_data->n_max_var_str; size =STR_VAR_LEN_MAX ;
@@ -2690,6 +2703,15 @@ static void update_field_vars(struct hist_trigger_data *hist_data, hist_data->n_field_vars, 0); } +static void update_max_vars (struct hist_trigger_data *hist_data, +struct tracing_map_elt *elt, +struct ring_buffer_event *rbe, +void *rec) +{ +__update_field_vars (elt, rbe, rec, hist_data->max_vars, + hist_data->n_max_vars, hist_data->n_field_var_str); +} +static struct hist_field *create_var (struct hist_trigger_data *hist_data,struct trace_event_file *file,char *name,int size,const char *type)
@@ -2799,6 +2821,229 @@ static struct field_var *create_field_var(struct hist_trigger_data *hist_data,return create_field_var (hist_data, file, var_name); } +static void onmax_print (struct seq_file *m, +struct hist_trigger_data *hist_data, +struct tracing_map_elt *elt, +struct action_data *data) +{ +unsigned int i, save_var_idx, max_idx = data->onmax.max_var->var.idx; + +seq_printf (m,"\n\tmax: %10llu" ,tracing_map_read_var (elt, max_idx)); + +for (i =0 ; i < hist_data->n_max_vars; i++) { +struct hist_field *save_val = hist_data->max_vars[i]->val; +struct hist_field *save_var = hist_data->max_vars[i]->var; +u64 val; + + save_var_idx = save_var->var.idx; + + val =tracing_map_read_var (elt, save_var_idx); + +if (save_val->flags &HIST_FIELD_FL_STRING ) { +seq_printf (m," %s: %-32s" , save_var->var.name, + (char *)(uintptr_t )(val)); + }else +seq_printf (m," %s: %10llu" , save_var->var.name, val); + } +} + +static void onmax_save (struct hist_trigger_data *hist_data, +struct tracing_map_elt *elt,void *rec, +struct ring_buffer_event *rbe, +struct action_data *data,u64 *var_ref_vals) +{ +unsigned int max_idx = data->onmax.max_var->var.idx; +unsigned int max_var_ref_idx = data->onmax.max_var_ref_idx; + +u64 var_val, max_val; + + var_val = var_ref_vals[max_var_ref_idx]; + max_val =tracing_map_read_var (elt, max_idx); + +if (var_val <= max_val) +return ; + +tracing_map_set_var (elt, max_idx, var_val); + +update_max_vars (hist_data, elt, rbe, rec); +} + +static void onmax_destroy (struct action_data *data) +{ +unsigned int i; + +destroy_hist_field (data->onmax.max_var,0 ); +destroy_hist_field (data->onmax.var,0 ); + +kfree (data->onmax.var_str); +kfree (data->onmax.fn_name); + +for (i =0 ; i < data->n_params; i++) +kfree (data->params[i]); + +kfree (data); +} + +static int onmax_create (struct hist_trigger_data *hist_data, +struct action_data *data) +{ +struct trace_event_call *call = hist_data->event_file->event_call; +struct trace_event_file *file = hist_data->event_file; +struct hist_field *var_field, *ref_field, *max_var; +unsigned int var_ref_idx = hist_data->n_var_refs; +struct field_var *field_var; +char *onmax_var_str, *param; +const char *event_name; +unsigned long flags; +unsigned int i; +int ret =0 ; + + onmax_var_str = data->onmax.var_str; +if (onmax_var_str[0 ] !='$' ) +return -EINVAL ; + onmax_var_str++; + + event_name =trace_event_name (call); + var_field =find_target_event_var (hist_data,NULL ,NULL , onmax_var_str); +if (!var_field) +return -EINVAL ; + + flags =HIST_FIELD_FL_VAR_REF ; + ref_field =create_hist_field (hist_data,NULL , flags,NULL ); +if (!ref_field) +return -ENOMEM ; + +if (init_var_ref (ref_field, var_field)) { +destroy_hist_field (ref_field,0 ); + ret = -ENOMEM ; + goto out; + } + hist_data->var_refs[hist_data->n_var_refs] = ref_field; + ref_field->var_ref_idx = hist_data->n_var_refs++; + data->onmax.var = ref_field; + + data->fn = onmax_save; + data->onmax.max_var_ref_idx = var_ref_idx; + max_var =create_var (hist_data, file,"max" ,sizeof (u64),"u64" ); +if (IS_ERR (max_var)) { + ret =PTR_ERR (max_var); + goto out; + } + data->onmax.max_var = max_var; + +for (i =0 ; i < data->n_params; i++) { + param =kstrdup (data->params[i],GFP_KERNEL ); +if (!param) { + ret = -ENOMEM ; + goto out; + } + + field_var =create_target_field_var (hist_data,NULL ,NULL , param); +if (IS_ERR (field_var)) { + ret =PTR_ERR (field_var); +kfree (param); + goto out; + } + + hist_data->max_vars[hist_data->n_max_vars++] = field_var; +if (field_var->val->flags &HIST_FIELD_FL_STRING ) + hist_data->n_max_var_str++; + +kfree (param); + } + + hist_data->actions[hist_data->n_actions++] = data; + out: +return ret; +} + +static int parse_action_params (char *params,struct action_data *data) +{ +char *param, *saved_param; +int ret =0 ; + +while (params) { +if (data->n_params >=SYNTH_FIELDS_MAX ) + goto out; + + param =strsep (¶ms,"," ); +if (!param) { + ret = -EINVAL ; + goto out; + } + + param =strstrip (param); +if (strlen (param) <2 ) { + ret = -EINVAL ; + goto out; + } + + saved_param =kstrdup (param,GFP_KERNEL ); +if (!saved_param) { + ret = -ENOMEM ; + goto out; + } + + data->params[data->n_params++] = saved_param; + } + out: +return ret; +} + +static struct action_data *onmax_parse (char *str) +{ +char *onmax_fn_name, *onmax_var_str; +struct action_data *data; +int ret = -EINVAL ; + + data =kzalloc (sizeof (*data),GFP_KERNEL ); +if (!data) +return ERR_PTR (-ENOMEM ); + + onmax_var_str =strsep (&str,")" ); +if (!onmax_var_str || !str) +return ERR_PTR (-EINVAL ); + data->onmax.var_str =kstrdup (onmax_var_str,GFP_KERNEL ); +if (!data->onmax.var_str) { + ret = -ENOMEM ; + goto free; + } + +strsep (&str,"." ); +if (!str) + goto free; + + onmax_fn_name =strsep (&str,"(" ); +if (!onmax_fn_name || !str) + goto free; + +if (strncmp (onmax_fn_name,"save" ,strlen ("save" )) ==0 ) { +char *params =strsep (&str,")" ); + +if (!params) { + ret = -EINVAL ; + goto free; + } + + ret =parse_action_params (params, data); +if (ret) + goto free; + }else + goto free; + + data->onmax.fn_name =kstrdup (onmax_fn_name,GFP_KERNEL ); +if (!data->onmax.fn_name) { + ret = -ENOMEM ; + goto free; + } + out: +return data; + free: +onmax_destroy (data); + data =ERR_PTR (ret); + goto out; +} +static void onmatch_destroy (struct action_data *data) {unsigned int i;
@@ -2883,39 +3128,6 @@ static int check_synth_field(struct synth_event *event,return 0 ; } -static int parse_action_params (char *params,struct action_data *data) -{ -char *param, *saved_param; -int ret =0 ; - -while (params) { -if (data->n_params >=SYNTH_FIELDS_MAX ) - goto out; - - param =strsep (¶ms,"," ); -if (!param) { - ret = -EINVAL ; - goto out; - } - - param =strstrip (param); -if (strlen (param) <2 ) { - ret = -EINVAL ; - goto out; - } - - saved_param =kstrdup (param,GFP_KERNEL ); -if (!saved_param) { - ret = -ENOMEM ; - goto out; - } - - data->params[data->n_params++] = saved_param; - } - out: -return ret; -} -static struct hist_field *onmatch_find_var (struct hist_trigger_data *hist_data,struct action_data *data,char *system,char *event,char *var)
@@ -3555,6 +3767,8 @@ static void destroy_actions(struct hist_trigger_data *hist_data)if (data->fn == action_trace)onmatch_destroy (data); +else if (data->fn == onmax_save) +onmax_destroy (data);else kfree (data); }
@@ -3584,12 +3798,56 @@ static int create_actions(struct hist_trigger_data *hist_data,onmatch_destroy (data);return ret; } + }else if (strncmp (str,"onmax(" ,strlen ("onmax(" )) ==0 ) { +char *action_str = str +strlen ("onmax(" ); + + data =onmax_parse (action_str); +if (IS_ERR (data)) +return PTR_ERR (data); + + ret =onmax_create (hist_data, data); +if (ret) { +onmax_destroy (data); +return ret; + } } }return ret; } +static void print_actions (struct seq_file *m, +struct hist_trigger_data *hist_data, +struct tracing_map_elt *elt) +{ +unsigned int i; + +for (i =0 ; i < hist_data->n_actions; i++) { +struct action_data *data = hist_data->actions[i]; + +if (data->fn == onmax_save) +onmax_print (m, hist_data, elt, data); + } +} + +static void print_onmax_spec (struct seq_file *m, +struct hist_trigger_data *hist_data, +struct action_data *data) +{ +unsigned int i; + +seq_puts (m,":onmax(" ); +seq_printf (m,"%s" , data->onmax.var_str); +seq_printf (m,").%s(" , data->onmax.fn_name); + +for (i =0 ; i < hist_data->n_max_vars; i++) { +seq_printf (m,"%s" , hist_data->max_vars[i]->var->var.name); +if (i < hist_data->n_max_vars -1 ) +seq_puts (m,"," ); + } +seq_puts (m,")" ); +} +static void print_onmatch_spec (struct seq_file *m,struct hist_trigger_data *hist_data,struct action_data *data)
@@ -3620,6 +3878,8 @@ static void print_actions_spec(struct seq_file *m,if (data->fn == action_trace)print_onmatch_spec (m, hist_data, data); +else if (data->fn == onmax_save) +print_onmax_spec (m, hist_data, data); } }
@@ -3994,6 +4254,8 @@ static void hist_trigger_stacktrace_print(struct seq_file *m, } } +print_actions (m, hist_data, elt); +seq_puts (m,"\n" ); }
Signature · 1 line
1.9.3
PATCH v3 25/33 tracing: Allow whitespace to surround hist trigger filter
In reply to Tom ZanussiThe existing code only allows for one space before and after the 'if' specifying the filter for a hist trigger. Add code to make that more permissive as far as whitespace goes. Specifically, we want to allow spaces in the trigger itself now that we have additional syntax (onmatch/onmax) where spaces are more natural e.g. spaces after commas in param lists.
--- kernel/trace/trace_events_hist.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-)
kernel/trace/trace_events_hist.c +19 -5
@@ -4857,7 +4857,7 @@ static int event_hist_trigger_func(struct event_command *cmd_ops,struct synth_event *se;const char *se_name;bool remove = false; -char *trigger; +char *trigger, *p;int ret =0 ;if (!param)
@@ -4866,10 +4866,23 @@ static int event_hist_trigger_func(struct event_command *cmd_ops,if (glob[0 ] =='!' ) remove = true; -/* separate the trigger from the filter (k:v [if filter]) */ - trigger =strsep (¶m," \t" ); -if (!trigger) -return -EINVAL ; +/* +* separate the trigger from the filter (k:v [if filter]) +* allowing for whitespace in the trigger +*/ + trigger = param; + p =strstr (param," if" ); +if (!p) + p =strstr (param,"\tif" ); +if (p) { +if (p == trigger) +return -EINVAL ; + param = p +1 ; + param =strstrip (param); + *p ='\0' ; + trigger =strstrip (trigger); + }else + param =NULL ; attrs =parse_hist_trigger_attrs (trigger);if (IS_ERR (attrs))
@@ -4927,6 +4940,7 @@ static int event_hist_trigger_func(struct event_command *cmd_ops, } ret = cmd_ops->reg (glob, trigger_ops, trigger_data, file); +/* * The above returns on success the # of triggers registered, * but if it didn't register any it returns zero. Consider no
Signature · 1 line
1.9.3
Steven Rostedt
maintainerRe: [PATCH v3 25/33] tracing: Allow whitespace to surround hist trigger filter
Quoted 40 lines · On Fri, 22 Sep 2017 15:00:05 -0500 Tom Zanussi <[email protected]>…
The existing code only allows for one space before and after the 'if' specifying the filter for a hist trigger. Add code to make that more permissive as far as whitespace goes. Specifically, we want to allow spaces in the trigger itself now that we have additional syntax (onmatch/onmax) where spaces are more natural e.g. spaces after commas in param lists.
Signed-off-by Tom Zanussi <[email protected]>--- kernel/trace/trace_events_hist.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-)kernel/trace/trace_events_hist.c +8
-5@@ -4857,7 +4857,7 @@ static int event_hist_trigger_func(struct event_command *cmd_ops,struct synth_event *se;const char *se_name;bool remove = false; -char *trigger; +char *trigger, *p;int ret =0 ;if (!param)@@ -4866,10 +4866,23 @@ static int event_hist_trigger_func(struct event_command *cmd_ops,if (glob[0 ] =='!' ) remove = true; -/* separate the trigger from the filter (k:v [if filter]) */ - trigger =strsep (¶m," \t" ); -if (!trigger) -return -EINVAL ; +/* +* separate the trigger from the filter (k:v [if filter]) +* allowing for whitespace in the trigger +*/ + trigger = param; + p =strstr (param," if" ); +if (!p)
kernel/trace/trace_events_hist.c static int event_hist_trigger_func(struct event_command *cmd_ops, · line 4885
Quoted 10 lines
kernel/trace/trace_events_hist.c +10
-0+ p =strstr (param,"\tif" ); +if (p) { +if (p == trigger) +return -EINVAL ; + param = p +1 ; + param =strstrip (param); + *p ='\0' ; + trigger =strstrip (trigger); + }else + param =NULL ;
I think you forgot to update this:
Quoted 11 lines
Quoted 7 lines
This seems rather complex. Wouldn't the following work?
param = skip_spaces(param); trigger = strsep(¶m, " \t"); if (param) param = strstrip(param);Yes, much better ;-)
Tom
-- Steve
Quoted 11 lines
kernel/trace/trace_events_hist.c +1
-0attrs =parse_hist_trigger_attrs (trigger);if (IS_ERR (attrs))@@ -4927,6 +4940,7 @@ static int event_hist_trigger_func(struct event_command *cmd_ops, } ret = cmd_ops->reg (glob, trigger_ops, trigger_data, file); +/* * The above returns on success the # of triggers registered, * but if it didn't register any it returns zero. Consider no
Re: [PATCH v3 25/33] tracing: Allow whitespace to surround hist trigger filter
Hi Steve,
Quoted 47 lines · Steven Rostedt
Quoted 44 lines · On Fri, 22 Sep 2017 15:00:05 -0500 Tom Zanussi <[email protected]>…
The existing code only allows for one space before and after the 'if' specifying the filter for a hist trigger. Add code to make that more permissive as far as whitespace goes. Specifically, we want to allow spaces in the trigger itself now that we have additional syntax (onmatch/onmax) where spaces are more natural e.g. spaces after commas in param lists.
Signed-off-by Tom Zanussi <[email protected]>--- kernel/trace/trace_events_hist.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-)kernel/trace/trace_events_hist.c +12
-5@@ -4857,7 +4857,7 @@ static int event_hist_trigger_func(struct event_command *cmd_ops,struct synth_event *se;const char *se_name;bool remove = false; -char *trigger; +char *trigger, *p;int ret =0 ;if (!param)@@ -4866,10 +4866,23 @@ static int event_hist_trigger_func(struct event_command *cmd_ops,if (glob[0 ] =='!' ) remove = true; -/* separate the trigger from the filter (k:v [if filter]) */ - trigger =strsep (¶m," \t" ); -if (!trigger) -return -EINVAL ; +/* +* separate the trigger from the filter (k:v [if filter]) +* allowing for whitespace in the trigger +*/ + trigger = param; + p =strstr (param," if" ); +if (!p) + p =strstr (param,"\tif" ); +if (p) { +if (p == trigger) +return -EINVAL ;
Quoted 9 lines
kernel/trace/trace_events_hist.c static int event_hist_trigger_func(struct event_command *cmd_ops, · line 4885
Quoted 6 lines
kernel/trace/trace_events_hist.c +6
-0+ param = p +1 ; + param =strstrip (param); + *p ='\0' ; + trigger =strstrip (trigger); + }else + param =NULL ;I think you forgot to update this:
I was going to but on closer inspection realized the simpler form wouldn't accomplish the same thing - the problem this is trying to solve is to allow bits of whitespace within the trigger (because we now have function-like syntax, which should allow whitespace after commas for instance) and separating the trigger from the filter ('if'). So we explicitly search for 'if' with preceding whitespace, which strsep won't accomplish.
So while it may not be pretty, it accomplishes that, while the below won't (it would create a truncated trigger if there were e.g. commas followed by spaces).
Tom
Quoted 26 lines
Quoted 11 lines
Quoted 7 lines
This seems rather complex. Wouldn't the following work?
param = skip_spaces(param); trigger = strsep(¶m, " \t"); if (param) param = strstrip(param);Yes, much better ;-)
Tom
-- Steve
Quoted 11 lines
kernel/trace/trace_events_hist.c +1
-0attrs =parse_hist_trigger_attrs (trigger);if (IS_ERR (attrs))@@ -4927,6 +4940,7 @@ static int event_hist_trigger_func(struct event_command *cmd_ops, } ret = cmd_ops->reg (glob, trigger_ops, trigger_data, file); +/* * The above returns on success the # of triggers registered, * but if it didn't register any it returns zero. Consider no
Steven Rostedt
maintainerRe: [PATCH v3 25/33] tracing: Allow whitespace to surround hist trigger filter
Quoted 27 lines · On Wed, 04 Oct 2017 14:05:17 -0500 Tom Zanussi <[email protected]>…
Hi Steve,
Quoted 13 lines · Steven Rostedt
Quoted 7 lines · On Fri, 22 Sep 2017 15:00:05 -0500 Tom Zanussi <[email protected]>…
The existing code only allows for one space before and after the 'if' specifying the filter for a hist trigger. Add code to make that more permissive as far as whitespace goes. Specifically, we want to allow spaces in the trigger itself now that we have additional syntax (onmatch/onmax) where spaces are more natural e.g. spaces after commas in param lists.
Quoted 2 lines
I think you forgot to update this:
I was going to but on closer inspection realized the simpler form wouldn't accomplish the same thing - the problem this is trying to solve is to allow bits of whitespace within the trigger (because we now have function-like syntax, which should allow whitespace after commas for instance) and separating the trigger from the filter ('if'). So we explicitly search for 'if' with preceding whitespace, which strsep won't accomplish.
Quoted 3 lines
So while it may not be pretty, it accomplishes that, while the below won't (it would create a truncated trigger if there were e.g. commas followed by spaces).
OK, and I just noticed that you updated the changelog too, which sorta describes what you are trying to say here.
-- Steve
Re: [PATCH v3 25/33] tracing: Allow whitespace to surround hist trigger filter
In reply to Tom ZanussiHi Tom,
Quoted 60 lines · Tom Zanussi
Hi Steve,
Quoted 47 lines · Steven Rostedt
Quoted 44 lines · On Fri, 22 Sep 2017 15:00:05 -0500 Tom Zanussi <[email protected]>…
The existing code only allows for one space before and after the 'if' specifying the filter for a hist trigger. Add code to make that more permissive as far as whitespace goes. Specifically, we want to allow spaces in the trigger itself now that we have additional syntax (onmatch/onmax) where spaces are more natural e.g. spaces after commas in param lists.
Signed-off-by Tom Zanussi <[email protected]>--- kernel/trace/trace_events_hist.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-)kernel/trace/trace_events_hist.c +12
-5@@ -4857,7 +4857,7 @@ static int event_hist_trigger_func(struct event_command *cmd_ops,struct synth_event *se;const char *se_name;bool remove = false; -char *trigger; +char *trigger, *p;int ret =0 ;if (!param)@@ -4866,10 +4866,23 @@ static int event_hist_trigger_func(struct event_command *cmd_ops,if (glob[0 ] =='!' ) remove = true; -/* separate the trigger from the filter (k:v [if filter]) */ - trigger =strsep (¶m," \t" ); -if (!trigger) -return -EINVAL ; +/* +* separate the trigger from the filter (k:v [if filter]) +* allowing for whitespace in the trigger +*/ + trigger = param; + p =strstr (param," if" ); +if (!p) + p =strstr (param,"\tif" ); +if (p) { +if (p == trigger) +return -EINVAL ;Quoted 9 lines
kernel/trace/trace_events_hist.c static int event_hist_trigger_func(struct event_command *cmd_ops, · line 4885
Quoted 6 lines
kernel/trace/trace_events_hist.c +6
-0+ param = p +1 ; + param =strstrip (param); + *p ='\0' ; + trigger =strstrip (trigger); + }else + param =NULL ;I think you forgot to update this:
Quoted 7 lines
I was going to but on closer inspection realized the simpler form wouldn't accomplish the same thing - the problem this is trying to solve is to allow bits of whitespace within the trigger (because we now have function-like syntax, which should allow whitespace after commas for instance) and separating the trigger from the filter ('if'). So we explicitly search for 'if' with preceding whitespace, which strsep won't accomplish.
What if a field name is started with 'if' (like "ifb")? I think you also need to check whitespace after the 'if'.
Thanks,
Namhyung
Re: [PATCH v3 25/33] tracing: Allow whitespace to surround hist trigger filter
Quoted 71 lines · Namhyung Kim
Hi Tom,
Quoted 60 lines · Tom Zanussi
Hi Steve,
Quoted 47 lines · Steven Rostedt
Quoted 44 lines · On Fri, 22 Sep 2017 15:00:05 -0500 Tom Zanussi <[email protected]>…
The existing code only allows for one space before and after the 'if' specifying the filter for a hist trigger. Add code to make that more permissive as far as whitespace goes. Specifically, we want to allow spaces in the trigger itself now that we have additional syntax (onmatch/onmax) where spaces are more natural e.g. spaces after commas in param lists.
Signed-off-by Tom Zanussi <[email protected]>--- kernel/trace/trace_events_hist.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-)kernel/trace/trace_events_hist.c +12
-5@@ -4857,7 +4857,7 @@ static int event_hist_trigger_func(struct event_command *cmd_ops,struct synth_event *se;const char *se_name;bool remove = false; -char *trigger; +char *trigger, *p;int ret =0 ;if (!param)@@ -4866,10 +4866,23 @@ static int event_hist_trigger_func(struct event_command *cmd_ops,if (glob[0 ] =='!' ) remove = true; -/* separate the trigger from the filter (k:v [if filter]) */ - trigger =strsep (¶m," \t" ); -if (!trigger) -return -EINVAL ; +/* +* separate the trigger from the filter (k:v [if filter]) +* allowing for whitespace in the trigger +*/ + trigger = param; + p =strstr (param," if" ); +if (!p) + p =strstr (param,"\tif" ); +if (p) { +if (p == trigger) +return -EINVAL ;Quoted 9 lines
kernel/trace/trace_events_hist.c static int event_hist_trigger_func(struct event_command *cmd_ops, · line 4885
Quoted 6 lines
kernel/trace/trace_events_hist.c +6
-0+ param = p +1 ; + param =strstrip (param); + *p ='\0' ; + trigger =strstrip (trigger); + }else + param =NULL ;I think you forgot to update this:
Quoted 7 lines
I was going to but on closer inspection realized the simpler form wouldn't accomplish the same thing - the problem this is trying to solve is to allow bits of whitespace within the trigger (because we now have function-like syntax, which should allow whitespace after commas for instance) and separating the trigger from the filter ('if'). So we explicitly search for 'if' with preceding whitespace, which strsep won't accomplish.
Quoted 3 lines
What if a field name is started with 'if' (like "ifb")? I think you also need to check whitespace after the 'if'.
Yeah, it will unnecessarily truncate the trigger, will fix.
Thanks,
Tom
Quoted 2 lines
Thanks,
Namhyung
PATCH v3 26/33 tracing: Add cpu field for hist triggers
In reply to Tom ZanussiA common key to use in a histogram is the cpuid - add a new cpu 'synthetic' field for that purpose. This field is named cpu rather than $cpu or $common_cpu because 'cpu' already exists as a special filter field and it makes more sense to match that rather than add another name for the same thing.
--- Documentation/trace/events.txt | 18 ++++++++++++++++++ kernel/trace/trace_events_hist.c | 28 +++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-)
Documentation/trace/events.txt +18 -0
@@ -668,6 +668,24 @@ The following commands are supported: The examples below provide a more concrete illustration of the concepts and typical usage patterns discussed above. + 'special' event fields + ------------------------ + + There are a number of 'special event fields' available for use as + keys or values in a hist trigger. These look like and behave as if + they were actual event fields, but aren't really part of the event's + field definition or format file. They are however available for any + event, and can be used anywhere an actual event field could be. + 'Special' field names are always prefixed with a '$' character to + indicate that they're not normal fields (with the exception of + 'cpu', for compatibility with existing filter usage): + + $common_timestamp u64 - timestamp (from ring buffer) associated + with the event, in nanoseconds. May be + modified by .usecs to have timestamps + interpreted as microseconds. + cpu int - the cpu on which the event occurred. + 6.2 'hist' trigger examples ---------------------------
kernel/trace/trace_events_hist.c +27 -1
@@ -226,6 +226,7 @@ enum hist_field_flags {HIST_FIELD_FL_VAR_ONLY =1 <<13 ,HIST_FIELD_FL_EXPR =1 <<14 ,HIST_FIELD_FL_VAR_REF =1 <<15 , + HIST_FIELD_FL_CPU = 1 << 16, }; struct var_defs {
@@ -1172,6 +1173,16 @@ static u64 hist_field_timestamp(struct hist_field *hist_field,return ts; } +static u64 hist_field_cpu (struct hist_field *hist_field, +struct tracing_map_elt *elt, +struct ring_buffer_event *rbe, +void *event) +{ +int cpu =smp_processor_id (); + +return cpu; +} +static struct hist_field *check_var_ref (struct hist_field *hist_field,struct hist_trigger_data *var_data,unsigned int var_idx)
@@ -1520,6 +1531,8 @@ static const char *hist_field_name(struct hist_field *field, field_name =hist_field_name (field->operands[0 ], ++level); else if (field->flags & HIST_FIELD_FL_TIMESTAMP) field_name ="$common_timestamp" ; + else if (field->flags & HIST_FIELD_FL_CPU) + field_name ="cpu" ; else if (field->flags & HIST_FIELD_FL_EXPR || field->flags & HIST_FIELD_FL_VAR_REF) field_name = field->name;
@@ -2012,6 +2025,15 @@ static struct hist_field *create_hist_field(struct hist_trigger_data *hist_data, goto out; } +if (flags &HIST_FIELD_FL_CPU ) { + hist_field->fn = hist_field_cpu; + hist_field->size =sizeof (int ); + hist_field->type =kstrdup ("int" ,GFP_KERNEL ); +if (!hist_field->type) + goto free; + goto out; + } +if (WARN_ON_ONCE (!field)) goto out;
@@ -2204,7 +2226,9 @@ static struct hist_field *parse_var_ref(struct trace_array *tr, hist_data->enable_timestamps = true;if (*flags &HIST_FIELD_FL_TIMESTAMP_USECS ) hist_data->attrs->ts_in_usecs = true; - }else { + }else if (strcmp (field_name,"cpu" ) ==0 ) + *flags |=HIST_FIELD_FL_CPU ; +else { field =trace_find_event_field (file->event_call, field_name);if (!field || !field->size) { field =ERR_PTR (-EINVAL );
@@ -4353,6 +4377,8 @@ static void hist_field_print(struct seq_file *m, struct hist_field *hist_field)if (hist_field->flags &HIST_FIELD_FL_TIMESTAMP )seq_puts (m,"$common_timestamp" ); +else if (hist_field->flags &HIST_FIELD_FL_CPU ) +seq_puts (m,"cpu" );else if (field_name)seq_printf (m,"%s" , field_name);
Signature · 1 line
1.9.3
Steven Rostedt
maintainerRe: [PATCH v3 26/33] tracing: Add cpu field for hist triggers
Quoted 29 lines · On Fri, 22 Sep 2017 15:00:06 -0500 Tom Zanussi <[email protected]>…
A common key to use in a histogram is the cpuid - add a new cpu 'synthetic' field for that purpose. This field is named cpu rather than $cpu or $common_cpu because 'cpu' already exists as a special filter field and it makes more sense to match that rather than add another name for the same thing.
Signed-off-by Tom Zanussi <[email protected]>--- Documentation/trace/events.txt | 18 ++++++++++++++++++ kernel/trace/trace_events_hist.c | 28 +++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-)Documentation/trace/events.txt +9
-0@@ -668,6 +668,24 @@ The following commands are supported: The examples below provide a more concrete illustration of the concepts and typical usage patterns discussed above. + 'special' event fields + ------------------------ + + There are a number of 'special event fields' available for use as + keys or values in a hist trigger. These look like and behave as if + they were actual event fields, but aren't really part of the event's + field definition or format file. They are however available for any + event, and can be used anywhere an actual event field could be. + 'Special' field names are always prefixed with a '$' character to
Documentation/trace/events.txt The following commands are supported: · line 689
Quoted 10 lines
Documentation/trace/events.txt +9
-0+ indicate that they're not normal fields (with the exception of + 'cpu', for compatibility with existing filter usage): + + $common_timestamp u64 - timestamp (from ring buffer) associated + with the event, in nanoseconds. May be + modified by .usecs to have timestamps + interpreted as microseconds. + cpu int - the cpu on which the event occurred. +
You were going to update this too.
-- Steve
Re: [PATCH v3 26/33] tracing: Add cpu field for hist triggers
Hi Steve,
Quoted 36 lines · Steven Rostedt
Quoted 33 lines · On Fri, 22 Sep 2017 15:00:06 -0500 Tom Zanussi <[email protected]>…
A common key to use in a histogram is the cpuid - add a new cpu 'synthetic' field for that purpose. This field is named cpu rather than $cpu or $common_cpu because 'cpu' already exists as a special filter field and it makes more sense to match that rather than add another name for the same thing.
Signed-off-by Tom Zanussi <[email protected]>--- Documentation/trace/events.txt | 18 ++++++++++++++++++ kernel/trace/trace_events_hist.c | 28 +++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-)Documentation/trace/events.txt +13
-0@@ -668,6 +668,24 @@ The following commands are supported: The examples below provide a more concrete illustration of the concepts and typical usage patterns discussed above. + 'special' event fields + ------------------------ + + There are a number of 'special event fields' available for use as + keys or values in a hist trigger. These look like and behave as if + they were actual event fields, but aren't really part of the event's + field definition or format file. They are however available for any + event, and can be used anywhere an actual event field could be. + 'Special' field names are always prefixed with a '$' character to + indicate that they're not normal fields (with the exception of + 'cpu', for compatibility with existing filter usage): + + $common_timestamp u64 - timestamp (from ring buffer) associated
Quoted 9 lines
Documentation/trace/events.txt The following commands are supported: · line 689
Quoted 6 lines
Documentation/trace/events.txt +5
-0+ with the event, in nanoseconds. May be + modified by .usecs to have timestamps + interpreted as microseconds. + cpu int - the cpu on which the event occurred. +You were going to update this too.
For this one, I originally and confusingly called these 'synthetic' event fields even though they had nothing to do with synthetic events (I thought of them as 'synthetic' in a different sense, as not being actual fields). So I changed that to 'special' here to avoid the confusion. (and I assumed your comment about moving them to the synthetic was due to that confusion).
I took the rest of your comment as saying that these were ok together, and in fact it does make sense to me to keep them here as part of this patch - because we're now getting more examples of these kinds of fields, adding a new section enumerating them starting with the second seems to make sense here, especially considering that $common_timestamp is mentioned elsewhere in several places in the documentation.
Tom
Quoted 1 line
-- Steve
Steven Rostedt
maintainerRe: [PATCH v3 26/33] tracing: Add cpu field for hist triggers
Quoted 56 lines · On Wed, 04 Oct 2017 14:21:35 -0500 Tom Zanussi <[email protected]>…
Hi Steve,
Quoted 36 lines · Steven Rostedt
Quoted 33 lines · On Fri, 22 Sep 2017 15:00:06 -0500 Tom Zanussi <[email protected]>…
A common key to use in a histogram is the cpuid - add a new cpu 'synthetic' field for that purpose. This field is named cpu rather than $cpu or $common_cpu because 'cpu' already exists as a special filter field and it makes more sense to match that rather than add another name for the same thing.
Signed-off-by Tom Zanussi <[email protected]>--- Documentation/trace/events.txt | 18 ++++++++++++++++++ kernel/trace/trace_events_hist.c | 28 +++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-)Documentation/trace/events.txt +13
-0@@ -668,6 +668,24 @@ The following commands are supported: The examples below provide a more concrete illustration of the concepts and typical usage patterns discussed above. + 'special' event fields + ------------------------ + + There are a number of 'special event fields' available for use as + keys or values in a hist trigger. These look like and behave as if + they were actual event fields, but aren't really part of the event's + field definition or format file. They are however available for any + event, and can be used anywhere an actual event field could be. + 'Special' field names are always prefixed with a '$' character to + indicate that they're not normal fields (with the exception of + 'cpu', for compatibility with existing filter usage): + + $common_timestamp u64 - timestamp (from ring buffer) associatedQuoted 9 lines
Documentation/trace/events.txt The following commands are supported: · line 689
Quoted 6 lines
Documentation/trace/events.txt +5
-0+ with the event, in nanoseconds. May be + modified by .usecs to have timestamps + interpreted as microseconds. + cpu int - the cpu on which the event occurred. +You were going to update this too.
For this one, I originally and confusingly called these 'synthetic' event fields even though they had nothing to do with synthetic events (I thought of them as 'synthetic' in a different sense, as not being actual fields). So I changed that to 'special' here to avoid the confusion. (and I assumed your comment about moving them to the synthetic was due to that confusion).
Quoted 6 lines
I took the rest of your comment as saying that these were ok together, and in fact it does make sense to me to keep them here as part of this patch - because we're now getting more examples of these kinds of fields, adding a new section enumerating them starting with the second seems to make sense here, especially considering that $common_timestamp is mentioned elsewhere in several places in the documentation.
OK, fine with me, I was doing a diff between the series, and missed that this was slightly updated.
-- Steve
PATCH v3 27/33 tracing: Add hist trigger support for variable reference aliases
In reply to Tom ZanussiAdd support for alias=$somevar where alias can be used as onmatch($alias).
Aliases are a way of creating a new name for an existing variable, for flexibly in making naming more clear in certain cases. For example in the below the user perhaps feels that using $new_lat in the synthetic event invocation is opaque or doesn't fit well stylistically with previous triggers, so creates an alias of $new_lat named $latency and uses that in the call instead:
# echo 'hist:keys=next_pid:new_lat=$common_timestamp.usecs' >
/sys/kernel/debug/tracing/events/sched/sched_switch/trigger # echo 'hist:keys=pid:latency=$new_lat:
onmatch(sched.sched_switch).wake2($latency,pid)' >
/sys/kernel/debug/tracing/events/synthetic/wake1/trigger--- kernel/trace/trace_events_hist.c | 61 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-)
kernel/trace/trace_events_hist.c +58 -3
@@ -227,6 +227,7 @@ enum hist_field_flags {HIST_FIELD_FL_EXPR =1 <<14 ,HIST_FIELD_FL_VAR_REF =1 <<15 ,HIST_FIELD_FL_CPU =1 <<16 , + HIST_FIELD_FL_ALIAS = 1 << 17, }; struct var_defs {
@@ -1527,7 +1528,8 @@ static const char *hist_field_name(struct hist_field *field,if (field->field) field_name = field->field->name; -else if (field->flags &HIST_FIELD_FL_LOG2 ) +else if (field->flags &HIST_FIELD_FL_LOG2 || + field->flags &HIST_FIELD_FL_ALIAS ) field_name =hist_field_name (field->operands[0 ], ++level);else if (field->flags &HIST_FIELD_FL_TIMESTAMP ) field_name ="$common_timestamp" ;
@@ -1983,7 +1985,7 @@ static struct hist_field *create_hist_field(struct hist_trigger_data *hist_data, hist_field->hist_data = hist_data; -if (flags &HIST_FIELD_FL_EXPR ) +if (flags &HIST_FIELD_FL_EXPR || flags &HIST_FIELD_FL_ALIAS ) goto out;/* caller will populate */ if (flags &HIST_FIELD_FL_VAR_REF ) {
@@ -2241,6 +2243,29 @@ static struct hist_field *parse_var_ref(struct trace_array *tr,return field; } +static struct hist_field *create_alias (struct hist_trigger_data *hist_data, +struct hist_field *var_ref, +char *var_name) +{ +struct hist_field *alias =NULL ; +unsigned long flags =HIST_FIELD_FL_ALIAS |HIST_FIELD_FL_VAR | +HIST_FIELD_FL_VAR_ONLY ; + + alias =create_hist_field (hist_data,NULL , flags, var_name); +if (!alias) +return NULL ; + + alias->fn = var_ref->fn; + alias->operands[0 ] = var_ref; + +if (init_var_ref (alias, var_ref)) { +destroy_hist_field (alias,0 ); +return NULL ; + } + +return alias; +} +struct hist_field *parse_atom (struct hist_trigger_data *hist_data,struct trace_event_file *file,char *str,unsigned long *flags,char *var_name)
@@ -2275,6 +2300,13 @@ struct hist_field *parse_atom(struct hist_trigger_data *hist_data,if (hist_field) { hist_data->var_refs[hist_data->n_var_refs] = hist_field; hist_field->var_ref_idx = hist_data->n_var_refs++; +if (var_name) { + hist_field =create_alias (hist_data, hist_field, var_name); +if (!hist_field) { + ret = -ENOMEM ; + goto out; + } + }return hist_field; } }else
@@ -2378,6 +2410,26 @@ static int check_expr_operands(struct hist_field *operand1,unsigned long operand1_flags = operand1->flags;unsigned long operand2_flags = operand2->flags; +if ((operand1_flags &HIST_FIELD_FL_VAR_REF ) || + (operand1_flags &HIST_FIELD_FL_ALIAS )) { +struct hist_field *var; + + var =find_var_field (operand1->var.hist_data, operand1->name); +if (!var) +return -EINVAL ; + operand1_flags = var->flags; + } + +if ((operand2_flags &HIST_FIELD_FL_VAR_REF ) || + (operand2_flags &HIST_FIELD_FL_ALIAS )) { +struct hist_field *var; + + var =find_var_field (operand2->var.hist_data, operand2->name); +if (!var) +return -EINVAL ; + operand2_flags = var->flags; + } +if ((operand1_flags &HIST_FIELD_FL_TIMESTAMP_USECS ) != (operand2_flags &HIST_FIELD_FL_TIMESTAMP_USECS ))return -EINVAL ;
@@ -4379,8 +4431,11 @@ static void hist_field_print(struct seq_file *m, struct hist_field *hist_field)seq_puts (m,"$common_timestamp" ); else if (hist_field->flags & HIST_FIELD_FL_CPU) seq_puts(m, "cpu"); - else if (field_name) + else if (field_name) { +if (hist_field->flags &HIST_FIELD_FL_ALIAS ) +seq_putc (m,'$' );seq_printf (m,"%s" , field_name); + }if (hist_field->flags) {const char *flags_str =get_hist_field_flags (hist_field);
Signature · 1 line
1.9.3
PATCH v3 28/33 tracing: Add 'last error' error facility for hist triggers
In reply to Tom ZanussiWith the addition of variables and actions, it's become necessary to provide more detailed error information to users about syntax errors.
Add a 'last error' facility accessible via the erroring event's 'hist' file. Reading the hist file after an error will display more detailed information about what went wrong, if information is available. This extended error information will be available until the next hist trigger command for that event.
# echo xxx > /sys/kernel/debug/tracing/events/sched/sched_wakeup/trigger echo: write error: Invalid argument
# cat /sys/kernel/debug/tracing/events/sched/sched_wakeup/hist
ERROR: Couldn't yyy: zzz
Last command: xxx
Also add specific error messages for variable and action errors.
--- Documentation/trace/events.txt | 19 +++++ kernel/trace/trace_events_hist.c | 162 +++++++++++++++++++++++++++++++++++---- 2 files changed, 168 insertions(+), 13 deletions(-)
Documentation/trace/events.txt +19 -0
@@ -686,6 +686,25 @@ The following commands are supported: interpreted as microseconds. cpu int - the cpu on which the event occurred. + Extended error information + -------------------------- + + For some error conditions encountered when invoking a hist trigger + command, extended error information is available via the + corresponding event's 'hist' file. Reading the hist file after an + error will display more detailed information about what went wrong, + if information is available. This extended error information will + be available until the next hist trigger command for that event. + + If available for a given error condition, the extended error + information and usage takes the following form: + + # echo xxx > /sys/kernel/debug/tracing/events/sched/sched_wakeup/trigger + echo: write error: Invalid argument + + # cat /sys/kernel/debug/tracing/events/sched/sched_wakeup/hist + ERROR: Couldn't yyy: zzz + Last command: xxx 6.2 'hist' trigger examples ---------------------------
kernel/trace/trace_events_hist.c +149 -13
373 lines not on this page · raw message
Signature · 1 line
1.9.3
PATCH v3 29/33 tracing: Add inter-event hist trigger Documentation
In reply to Tom ZanussiAdd background and details on inter-event hist triggers, including hist variables, synthetic events, and actions.
--- Documentation/trace/events.txt | 385 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 385 insertions(+)
Documentation/trace/events.txt +385 -0
@@ -571,6 +571,7 @@ The following commands are supported: .sym-offset display an address as a symbol and offset .syscall display a syscall id as a system call name .execname display a common_pid as a program name + .usecs display a $common_timestamp in microseconds Note that in general the semantics of a given field aren't interpreted when applying a modifier to it, but there are some
@@ -2101,3 +2102,387 @@ The following commands are supported: Hits: 489 Entries: 7 Dropped: 0 + +6.3 Inter-event hist triggers +----------------------------- + +Inter-event hist triggers are hist triggers that combine values from +one or more other events and create a histogram using that data. Data +from an inter-event histogram can in turn become the source for +further combined histograms, thus providing a chain of related +histograms, which is important for some applications. + +The most important example of an inter-event quantity that can be used +in this manner is latency, which is simply a difference in timestamps +between two events (although trace events don't have an externally +visible timestamp field, the inter-event hist trigger support adds a +pseudo-field to all events named '$common_timestamp' which can be used +as if it were an actual event field). Although latency is the most +important inter-event quantity, note that because the support is +completely general across the trace event subsystem, any event field +can be used in an inter-event quantity. + +An example of a histogram that combines data from other histograms +into a useful chain would be a 'wakeupswitch latency' histogram that +combines a 'wakeup latency' histogram and a 'switch latency' +histogram. + +Normally, a hist trigger specification consists of a (possibly +compound) key along with one or more numeric values, which are +continually updated sums associated with that key. A histogram +specification in this case consists of individual key and value +specifications that refer to trace event fields associated with a +single event type. + +The inter-event hist trigger extension allows fields from multiple +events to be referenced and combined into a multi-event histogram +specification. In support of this overall goal, a few enabling +features have been added to the hist trigger support: + + - In order to compute an inter-event quantity, a value from one + event needs to saved and then referenced from another event. This + requires the introduction of support for histogram 'variables'. + + - The computation of inter-event quantities and their combination + require some minimal amount of support for applying simple + expressions to variables (+ and -). + + - A histogram consisting of inter-event quantities isn't logically a + histogram on either event (so having the 'hist' file for either + event host the histogram output doesn't really make sense). To + address the idea that the histogram is associated with a + combination of events, support is added allowing the creation of + 'synthetic' events that are events derived from other events. + These synthetic events are full-fledged events just like any other + and can be used as such, as for instance to create the + 'combination' histograms mentioned previously. + + - A set of 'actions' can be associated with histogram entries - + these can be used to generate the previously mentioned synthetic + events, but can also be used for other purposes, such as for + example saving context when a 'max' latency has been hit. + + - Trace events don't have a 'timestamp' associated with them, but + there is an implicit timestamp saved along with an event in the + underlying ftrace ring buffer. This timestamp is now exposed as a + a synthetic field named '$common_timestamp' which can be used in + histograms as if it were any other event field. Note that it has + a '$' prefixed to it - this is meant to indicate that it isn't an + actual field in the trace format but rather is a synthesized value + that nonetheless can be used as if it were an actual field. By + default it is in units of nanoseconds; appending '.usecs' to a + common_timestamp field changes the units to microseconds. + +These features are decribed in more detail in the following sections. + +6.3.1 Histogram Variables +------------------------- + +Variables are simply named locations used for saving and retrieving +values between matching events. A 'matching' event is defined as an +event that has a matching key - if a variable is saved for a histogram +entry corresponding to that key, any subsequent event with a matching +key can access that variable. + +A variable's value is normally available to any subsequent event until +it is set to something else by a subsequent event. The one exception +to that rule is that any variable used in an expression is essentially +'read-once' - once it's used by an expression in a subsequent event, +it's reset to its 'unset' state, which means it can't be used again +unless it's set again. This ensures not only that an event doesn't +use an uninitialized variable in a calculation, but that that variable +is used only once and not for any unrelated subsequent match. + +The basic syntax for saving a variable is to simply prefix a unique +variable name not corresponding to any keyword along with an '=' sign +to any event field. + +Either keys or values can be saved and retrieved in this way. This +creates a variable named 'ts0' for a histogram entry with the key +'next_pid': + + # echo 'hist:keys=next_pid:vals=$ts0:ts0=$common_timestamp ... >> \ + event/trigger + +The ts0 variable can be accessed by any subsequent event having the +same pid as 'next_pid'. + +Variable references are formed by prepending the variable name with +the '$' sign. Thus for example, the ts0 variable above would be +referenced as '$ts0' in expressions. + +Because 'vals=' is used, the $common_timestamp variable value above +will also be summed as a normal histogram value would (though for a +timestamp it makes little sense). + +The below shows that a key value can also be saved in the same way: + + # echo 'hist:timer_pid=common_pid:key=timer_pid ...' >> event/trigger + +If a variable isn't a key variable or prefixed with 'vals=', the +associated event field will be saved in a variable but won't be summed +as a value: + + # echo 'hist:keys=next_pid:ts1=$common_timestamp ... >> event/trigger + +Multiple variables can be assigned at the same time. The below would +result in both ts0 and b being created as variables, with both +common_timestamp and field1 additionally being summed as values: + + # echo 'hist:keys=pid:vals=$ts0,$b:ts0=$common_timestamp,b=field1 ... >> \ + event/trigger + +Note that variable assignments can appear either preceding or +following their use. The command below behaves identically to the +command above: + + # echo 'hist:keys=pid:ts0=$common_timestamp,b=field1:vals=$ts0,$b ... >> \ + event/trigger + +Any number of variables not bound to a 'vals=' prefix can also be +assigned by simply separating them with colons. Below is the same +thing but without the values being summed in the histogram: + + # echo 'hist:keys=pid:ts0=$common_timestamp:b=field1 ... >> event/trigger + +Variables set as above can be referenced and used in expressions on +another event. + +For example, here's how a latency can be calculated: + + # echo 'hist:keys=pid,prio:ts0=$common_timestamp ... >> event1/trigger + # echo 'hist:keys=next_pid:wakeup_lat=$common_timestamp-$ts0 ... >> event2/trigger + +In the first line above, the event's timetamp is saved into the +variable ts0. In the next line, ts0 is subtracted from the second +event's timestamp to produce the latency, which is then assigned into +yet another variable, 'wakeup_lat'. The hist trigger below in turn +makes use of the wakeup_lat variable to compute a combined latency +using the same key and variable from yet another event: + + # echo 'hist:key=pid:wakeupswitch_lat=$wakeup_lat+$switchtime_lat ... >> event3/trigger + +6.3.2 Synthetic Events +---------------------- + +Synthetic events are user-defined events generated from hist trigger +variables or fields associated with one or more other events. Their +purpose is to provide a mechanism for displaying data spanning +multiple events consistent with the existing and already familiar +usage for normal events. + +To define a synthetic event, the user writes a simple specification +consisting of the name of the new event along with one or more +variables and their types, which can be any valid field type, +separated by semicolons, to the tracing/synthetic_events file. + +For instance, the following creates a new event named 'wakeup_latency' +with 3 fields: lat, pid, and prio. Each of those fields is simply a +variable reference to a variable on another event: + + # echo 'wakeup_latency \ + u64 lat; \ + pid_t pid; \ + int prio' >> \ + /sys/kernel/debug/tracing/synthetic_events + +Reading the tracing/synthetic_events file lists all the currently +defined synthetic events, in this case the event defined above: + + # cat /sys/kernel/debug/tracing/synthetic_events + wakeup_latency u64 lat; pid_t pid; int prio + +An existing synthetic event definition can be removed by prepending +the command that defined it with a '!': + + # echo '!wakeup_latency u64 lat pid_t pid int prio' >> \ + /sys/kernel/debug/tracing/synthetic_events + +At this point, there isn't yet an actual 'wakeup_latency' event +instantiated in the event subsytem - for this to happen, a 'hist +trigger action' needs to be instantiated and bound to actual fields +and variables defined on other events (see Section 6.3.3 below). + +Once that is done, an event instance is created, and a histogram can +be defined using it: + + # echo 'hist:keys=pid,prio,lat.log2:sort=pid,lat' >> \ + /sys/kernel/debug/tracing/events/synthetic/wakeup_latency/trigger + +The new event is created under the tracing/events/synthetic/ directory +and looks and behaves just like any other event: + + # ls /sys/kernel/debug/tracing/events/synthetic/wakeup_latency + enable filter format hist id trigger + +Like any other event, once a histogram is enabled for the event, the +output can be displayed by reading the event's 'hist' file. + +6.3.3 Hist trigger 'actions' +---------------------------- + +A hist trigger 'action' is a function that's executed whenever a +histogram entry is added or updated. + +The default 'action' if no special function is explicity specified is +as it always has been, to simply update the set of values associated +with an entry. Some applications, however, may want to perform +additional actions at that point, such as generate another event, or +compare and save a maximum. + +The following additional actions are available. To specify an action +for a given event, simply specify the action between colons in the +hist trigger specification. + + - onmatch(matching.event).<synthetic_event_name>(param list) + + The 'onmatch(matching.event).<synthetic_event_name>(params)' hist + trigger action is invoked whenever an event matches and the + histogram entry would be added or updated. It causes the named + synthetic event to be generated with the values given in the + 'param list'. The result is the generation of a synthetic event + that consists of the values contained in those variables at the + time the invoking event was hit. + + The 'param list' consists of one or more parameters which may be + either variables or fields defined on either the 'matching.event' + or the target event. The variables or fields specified in the + param list may be either fully-qualified or unqualified. If a + variable is specified as unqualified, it must be unique between + the two events. A field name used as a param can be unqualified + if it refers to the target event, but must be fully qualified if + it refers to the matching event. A fully-qualified name is of the + form 'system.event_name.$var_name' or 'system.event_name.field'. + + The 'matching.event' specification is simply the fully qualified + event name of the event that matches the target event for the + onmatch() functionality, in the form 'system.event_name'. + + Finally, the number and type of variables/fields in the 'param + list' must match the number and types of the fields in the + synthetic event being generated. + + As an example the below defines a simple synthetic event and uses + a variable defined on the sched_wakeup_new event as a parameter + when invoking the synthetic event. Here we define the synthetic + event: + + # echo 'wakeup_new_test pid_t pid' >> \ + /sys/kernel/debug/tracing/synthetic_events + + # cat /sys/kernel/debug/tracing/synthetic_events + wakeup_new_test pid_t pid + + The following hist trigger both defines the missing testpid + variable and specifies an onmatch() action that generates a + wakeup_new_test synthetic event whenever a sched_wakeup_new event + occurs, which because of the 'if comm == "cyclictest"' filter only + happens when the executable is cyclictest: + + # echo 'hist:keys=$testpid:testpid=pid:onmatch(sched.sched_wakeup_new).\ + wakeup_new_test($testpid) if comm=="cyclictest"' >> \ + /sys/kernel/debug/tracing/events/sched/sched_wakeup_new/trigger + + Creating and displaying a histogram based on those events is now + just a matter of using the fields and new synthetic event in the + tracing/events/synthetic directory, as usual: + + # echo 'hist:keys=pid:sort=pid' >> \ + /sys/kernel/debug/tracing/events/synthetic/wakeup_new_test/trigger + + Running 'cyclictest' should cause wakeup_new events to generate + wakeup_new_test synthetic events which should result in histogram + output in the wakeup_new_test event's hist file: + + # cat /sys/kernel/debug/tracing/events/synthetic/wakeup_new_test/hist + + A more typical usage would be to use two events to calculate a + latency. The following example uses a set of hist triggers to + produce a 'wakeup_latency' histogram: + + First, we define a 'wakeup_latency' synthetic event: + + # echo 'wakeup_latency u64 lat; pid_t pid; int prio' >> \ + /sys/kernel/debug/tracing/synthetic_events + + Next, we specify that whenever we see a sched_waking event for a + cyclictest thread, save the timestamp in a 'ts0' variable: + + # echo 'hist:keys=$saved_pid:saved_pid=pid:ts0=$common_timestamp.usecs \ + if comm=="cyclictest"' >> \ + /sys/kernel/debug/tracing/events/sched/sched_waking/trigger + + Then, when the corresponding thread is actually scheduled onto the + CPU by a sched_switch event, calculate the latency and use that + along with another variable and an event field to generate a + wakeup_latency synthetic event: + + # echo 'hist:keys=next_pid:wakeup_lat=$common_timestamp.usecs-$ts0:\ + onmatch(sched.sched_waking).wakeup_latency($wakeup_lat,\ + $saved_pid,next_prio) if next_comm=="cyclictest"' >> \ + /sys/kernel/debug/tracing/events/sched/sched_switch/trigger + + We also need to create a histogram on the wakeup_latency synthetic + event in order to aggregate the generated synthetic event data: + + # echo 'hist:keys=pid,prio,lat:sort=pid,lat' >> \ + /sys/kernel/debug/tracing/events/synthetic/wakeup_latency/trigger + + Finally, once we've run cyclictest to actually generate some + events, we can see the output by looking at the wakeup_latency + synthetic event's hist file: + + # cat /sys/kernel/debug/tracing/events/synthetic/wakeup_latency/hist + + - onmax(var).save(field,.. .) + + The 'onmax(var).save(field,...)' hist trigger action is invoked + whenever the value of 'var' associated with a histogram entry + exceeds the current maximum contained in that variable. + + The end result is that the trace event fields specified as the + onmax.save() params will be saved if 'var' exceeds the current + maximum for that hist trigger entry. This allows context from the + event that exhibited the new maximum to be saved for later + reference. When the histogram is displayed, additional fields + displaying the saved values will be printed. + + As an example the below defines a couple of hist triggers, one for + sched_waking and another for sched_switch, keyed on pid. Whenever + a sched_waking occurs, the timestamp is saved in the entry + corresponding to the current pid, and when the scheduler switches + back to that pid, the timestamp difference is calculated. If the + resulting latency, stored in wakeup_lat, exceeds the current + maximum latency, the values specified in the save() fields are + recoreded: + + # echo 'hist:keys=pid:ts0=$common_timestamp.usecs \ + if comm=="cyclictest"' >> \ + /sys/kernel/debug/tracing/events/sched/sched_waking/trigger + + # echo 'hist:keys=next_pid:\ + wakeup_lat=$common_timestamp.usecs-$ts0:\ + onmax($wakeup_lat).save(next_comm,prev_pid,prev_prio,prev_comm) \ + if next_comm=="cyclictest"' >> \ + /sys/kernel/debug/tracing/events/sched/sched_switch/trigger + + When the histogram is displayed, the max value and the saved + values corresponding to the max are displayed following the rest + of the fields: + + # cat /sys/kernel/debug/tracing/events/sched/sched_switch/hist + { next_pid: 2255 } hitcount: 239 + common_timestamp-ts0: 0 + max: 27 + next_comm: cyclictest + prev_pid: 0 prev_prio: 120 prev_comm: swapper/1 + + { next_pid: 2256 } hitcount: 2355 + common_timestamp-ts0: 0 + max: 49 next_comm: cyclictest + prev_pid: 0 prev_prio: 120 prev_comm: swapper/0 + + Totals: + Hits: 12970 + Entries: 2 + Dropped: 0
Signature · 1 line
1.9.3
Steven Rostedt
maintainerRe: [PATCH v3 29/33] tracing: Add inter-event hist trigger Documentation
Quoted 2 lines · On Fri, 22 Sep 2017 15:00:09 -0500 Tom Zanussi <[email protected]>…
Add background and details on inter-event hist triggers, including hist variables, synthetic events, and actions.
One more thing. Make this a separate document. Don't add it to the events.txt. Have a histogram.txt or something and just have events.txt reference that document.
Thanks!
-- Steve
Quoted 5 lines
Signed-off-by Tom Zanussi <[email protected]>Signed-off-by Baohong Liu <[email protected]>---
Re: [PATCH v3 29/33] tracing: Add inter-event hist trigger Documentation
Quoted 10 lines · Steven Rostedt
Quoted 2 lines · On Fri, 22 Sep 2017 15:00:09 -0500 Tom Zanussi <[email protected]>…
Add background and details on inter-event hist triggers, including hist variables, synthetic events, and actions.
One more thing. Make this a separate document. Don't add it to the events.txt. Have a histogram.txt or something and just have events.txt reference that document.
OK, will do.
Tom
Quoted 9 lines
Thanks!
-- Steve
Quoted 5 lines
Signed-off-by Tom Zanussi <[email protected]>Signed-off-by Baohong Liu <[email protected]>---
PATCH v3 30/33 tracing: Make tracing_set_clock() non-static
In reply to Tom ZanussiAllow tracing code outside of trace.c to access tracing_set_clock().
Some applications may require a particular clock in order to function properly, such as latency calculations.
Also, add an accessor returning the current clock string.
--- kernel/trace/trace.c | 2 +- kernel/trace/trace.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-)
kernel/trace/trace.c +1 -1
@@ -6213,7 +6213,7 @@ static int tracing_clock_show(struct seq_file *m, void *v)return 0 ; } -static int tracing_set_clock (struct trace_array *tr,const char *clockstr) +int tracing_set_clock (struct trace_array *tr,const char *clockstr) {int i;
kernel/trace/trace.h +1 -0
@@ -288,6 +288,7 @@ enum {extern void trace_array_put (struct trace_array *tr);extern int tracing_set_time_stamp_abs (struct trace_array *tr,bool abs); +extern int tracing_set_clock (struct trace_array *tr,const char *clockstr);extern bool trace_clock_in_ns (struct trace_array *tr);
Signature · 1 line
1.9.3
PATCH v3 31/33 tracing: Add a clock attribute for hist triggers
In reply to Tom ZanussiThe default clock if timestamps are used in a histogram is "global". If timestamps aren't used, the clock is irrelevant.
Use the "clock=" param only if you want to override the default "global" clock for a histogram with timestamps.
--- Documentation/trace/events.txt | 9 +++++++++ kernel/trace/trace_events_hist.c | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-)
Documentation/trace/events.txt +9 -0
@@ -2173,6 +2173,15 @@ features have been added to the hist trigger support: default it is in units of nanoseconds; appending '.usecs' to a common_timestamp field changes the units to microseconds. +A note on inter-event timestamps: If $common_timestamp is used in a +histogram, the trace buffer is automatically switched over to using +absolute timestamps and the "global" trace clock, in order to avoid +bogus timestamp differences with other clocks that aren't coherent +across CPUs. This can be overridden by specifying one of the other +trace clocks instead, using the "clock=XXX" hist trigger attribute, +where XXX is any of the clocks listed in the tracing/trace_clock +pseudo-file. + These features are decribed in more detail in the following sections. 6.3.1 Histogram Variables
kernel/trace/trace_events_hist.c +36 -2
@@ -241,6 +241,7 @@ struct hist_trigger_attrs {char *vals_str;char *sort_key_str;char *name; +char *clock;bool pause;bool cont;bool clear;
@@ -1680,6 +1681,7 @@ static void destroy_hist_trigger_attrs(struct hist_trigger_attrs *attrs)kfree (attrs->sort_key_str);kfree (attrs->keys_str);kfree (attrs->vals_str); +kfree (attrs->clock);kfree (attrs); }
@@ -1735,6 +1737,19 @@ static int parse_assignment(char *str, struct hist_trigger_attrs *attrs) ret = -ENOMEM ; goto out; } + }else if (strncmp (str,"clock=" ,strlen ("clock=" )) ==0 ) { +strsep (&str,"=" ); +if (!str) { + ret = -EINVAL ; + goto out; + } + + str =strstrip (str); + attrs->clock =kstrdup (str,GFP_KERNEL ); +if (!attrs->clock) { + ret = -ENOMEM ; + goto out; + } }else if (strncmp (str,"size=" ,strlen ("size=" )) ==0 ) {int map_bits =parse_map_size (str);
@@ -1798,6 +1813,14 @@ static struct hist_trigger_attrs *parse_hist_trigger_attrs(char *trigger_str) goto free; } +if (!attrs->clock) { + attrs->clock =kstrdup ("global" ,GFP_KERNEL ); +if (!attrs->clock) { + ret = -ENOMEM ; + goto free; + } + } +return attrs; free:destroy_hist_trigger_attrs (attrs);
@@ -4660,6 +4683,8 @@ static int event_hist_trigger_print(struct seq_file *m,seq_puts (m,".descending" ); }seq_printf (m,":size=%u" , (1 << hist_data->map->map_bits)); +if (hist_data->enable_timestamps) +seq_printf (m,":clock=%s" , hist_data->attrs->clock);print_actions_spec (m, hist_data);
@@ -4922,10 +4947,19 @@ static int hist_register_trigger(char *glob, struct event_trigger_ops *ops, goto out; } - ret++; +if (hist_data->enable_timestamps) { +char *clock = hist_data->attrs->clock; + + ret =tracing_set_clock (file->tr, hist_data->attrs->clock); +if (ret) { +hist_err ("Couldn't set trace_clock: " , clock); + goto out; + } -if (hist_data->enable_timestamps)tracing_set_time_stamp_abs (file->tr, true); + } + + ret++; out:return ret; }
Signature · 1 line
1.9.3
PATCH v3 32/33 tracing: Increase trace_recursive_lock() limit for synthetic events
In reply to Tom ZanussiSynthetic event generation needs to happen while the current event is still in progress, so add 1 to the trace_recursive_lock() recursion limit to account for that.
Because we also want to allow for the possibility of a synthetic event being generated from another synthetic event, add an additional increment for that as well.
--- kernel/trace/ring_buffer.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-)
kernel/trace/ring_buffer.c +5 -5
@@ -2590,16 +2590,16 @@ static void rb_commit(struct ring_buffer_per_cpu *cpu_buffer,* IRQ context * NMI context * -* If for some reason the ring buffer starts to recurse, we -* only allow that to happen at most 4 times (one for each -* context). If it happens 5 times, then we consider this a -* recusive loop and do not let it go further. +* If for some reason the ring buffer starts to recurse, we only allow +* that to happen at most 6 times (one for each context, plus possibly +* two levels of synthetic event generation). If it happens 7 times, +* then we consider this a recusive loop and do not let it go further. */ static __always_inline inttrace_recursive_lock (struct ring_buffer_per_cpu *cpu_buffer) { -if (cpu_buffer->current_context >=4 ) +if (cpu_buffer->current_context >=6 )return 1 ; cpu_buffer->current_context++;
Signature · 1 line
1.9.3
PATCH v3 33/33 tracing: Add inter-event blurb to HIST_TRIGGERS config option
In reply to Tom ZanussiSo that users know that inter-event tracing is supported as part of the HIST_TRIGGERS option, include text to that effect in the help text.
--- kernel/trace/Kconfig | 3 +++ 1 file changed, 3 insertions(+)
kernel/trace/Kconfig +3 -0
@@ -585,6 +585,9 @@ config HIST_TRIGGERS event activity as an initial guide for further investigation using more advanced tools. + Inter-event tracing of quantities such as latencies is also + supported using hist triggers under this option. + See Documentation/trace/events.txt. If in doubt, say N.
Signature · 1 line
1.9.3