perf ordered_events: fix crash in free_dup_event()
PATCH v2 1/1 perf ordered_events: fix crash in free_dup_event()
This patch fixes a bug in ordered_event.c:alloc_event(). An ordered_event struct was not initialized properly potentially causing crashes later on in free_dup_event() depending on the content of the memory. If it was NULL, then it would work fine, otherwise, it could cause crashes such as:
$ perf record -o - -e cycles date | perf inject -b -i - >/dev/null Tue Aug 7 12:03:48 PDT 2018 [ perf record: Woken up 1 times to write data ] [ perf record: Captured and wrote 0.000 MB - ] Segmentation fault (core dumped):
(gdb) r inject -b -i - < tt >/dev/null
Program received signal SIGSEGV, Segmentation fault.free_dup_event (oe=0x26a39a0, event=0xffffffff00000000) at util/ordered-events.c:85 (gdb) bt
#0 free_dup_event (oe=0x26a39a0, event=0xffffffff00000000) at util/ordered-events.c:85 #1 ordered_events__free (oe=0x26a39a0) at util/ordered-events.c:310 #2 0x00000000004b5a56 in __perf_session__process_pipe_events (session=<optimized out>) at util/session.c:1753 #3 perf_session__process_events (session=<optimized out>) at util/session.c:1932 #4 0x000000000043a2eb in __cmd_inject (inject=<optimized out>) at builtin-inject.c:750 #5 cmd_inject (argc=<optimized out>, argv=<optimized out>) at builtin-inject.c:924 #6 0x000000000046b175 in run_builtin (p=0xabc640 <commands+576>, argc=4, argv=0x7fffffffe560) at perf.c:297 #7 0x000000000046b062 in handle_internal_command (argc=4, argv=0x7fffffffe560) at perf.c:349 #8 0x000000000046a5e8 in run_argv (argcp=<optimized out>, argv=<optimized out>) at perf.c:393 #9 main (argc=4, argv=0x7fffffffe560) at perf.c:531
--- tools/perf/util/ordered-events.c | 6 ++++++ 1 file changed, 6 insertions(+)
tools/perf/util/ordered-events.c +6 -0
@@ -118,6 +118,12 @@ static struct ordered_event *alloc_event(struct ordered_events *oe,pr ("alloc size %" PRIu64"B (+%zu), max %" PRIu64"B\n" , oe->cur_alloc_size, size, oe->max_alloc_size); +/* +* must initialize event pointer of commandeered first +* entry to avoid crash in free_dup_event() due to random +* value for this field. +*/ + oe->buffer->event =NULL ; oe->cur_alloc_size += size;list_add (&oe->buffer->list, &oe->to_free);
Signature · 1 line
2.18.0.597.ga71716f1ad-goog
Quoted 5 lines · Stephane Eranian
This patch fixes a bug in ordered_event.c:alloc_event(). An
ordered_eventstruct was not initialized properly potentially causing crashes later on infree_dup_event()depending on the content of the memory. If it was NULL, then it would work fine, otherwise, it could cause crashes such as:
I'm now little puzzled what do we use this first event for.. I can't see anything special about it, other than it's added on the list uninitialized ;-)
it seems to work properly when we ditch it.. might be some prehistoric leftover or I'm terribly missing something
thanks,
jirka
---
tools/perf/util/ordered-events.c +0 -0
@@@ -119,12 -119,8 +119,9 @@@ static struct ordered_event *alloc_evenpr ("alloc size %" PRIu64"B (+%zu), max %" PRIu64"B\n" , oe->cur_alloc_size, size, oe->max_alloc_size); + oe->cur_alloc_size += size; -list_add (&oe->buffer->list, &oe->to_free); - -/* First entry is abused to maintain the to_free list. */ - oe->buffer_idx =2 ; - new = oe->buffer +1 ; + oe->buffer_idx =1 ; + new = oe->buffer; }else {pr ("allocation limit reached %" PRIu64"B\n" , oe->max_alloc_size); }
Quoted 8 lines · Jiri Olsa
Quoted 5 lines · Stephane Eranian
This patch fixes a bug in ordered_event.c:alloc_event(). An
ordered_eventstruct was not initialized properly potentially causing crashes later on infree_dup_event()depending on the content of the memory. If it was NULL, then it would work fine, otherwise, it could cause crashes such as:
Quoted 7 lines
I'm now little puzzled what do we use this first event for.. I can't see anything special about it, other than it's added on the list uninitialized ;-)
it seems to work properly when we ditch it.. might be some prehistoric leftover or I'm terribly missing something
You need to keep track of the buffers to free. You do not free the ordered_event structs
individually. For each oe->buffer, you need one free(). Each buffer is put in the to_free
list. But to link it into the list it needs a list_head. This is what buffer[0] is used for.
But the logic is broken in ordered_events__free(). It does not free individual ordered_event structs, but a buffer with many. Yet, it is missing freeing all of the duped
events.
void ordered_events__free(struct ordered_events *oe)
{
while (!list_empty(&oe->to_free)) {
struct ordered_event *buffer;
buffer = list_entry(oe->to_free.next, struct
ordered_event, list);
list_del(&buffer->list);
----> free_dup_event(oe, event->event);
free(buffer);
}
}This only frees the dup_event of buffer[0] which we know is NULL (well, now). It needs to walk all the entries in buffer[] to free buffer[x].event.
I think the goal was likely to avoid adding another list_head field to each ordered_event
and instead use one per allocated buffer.
This is very convoluted and prone to errors and we are seeing right now. This should
be cleaned. So either you add a list_head to ordered_event or you would buffer[x] inordered_events_free().
At this point, this is my understanding.
Do you agree?
Quoted 25 lines
thanks,
jirka---
tools/perf/util/ordered-events.c +2
-5@@@ -119,12 -119,8 +119,9 @@@ static struct ordered_event *alloc_evenpr ("alloc size %" PRIu64"B (+%zu), max %" PRIu64"B\n" , oe->cur_alloc_size, size, oe->max_alloc_size); + oe->cur_alloc_size += size; -list_add (&oe->buffer->list, &oe->to_free); - -/* First entry is abused to maintain the to_free list. */ - oe->buffer_idx =2 ; - new = oe->buffer +1 ; + oe->buffer_idx =1 ; + new = oe->buffer; }else {pr ("allocation limit reached %" PRIu64"B\n" , oe->max_alloc_size); }
Quoted 39 lines · Stephane Eranian
Quoted 8 lines · Jiri Olsa
Quoted 5 lines · Stephane Eranian
This patch fixes a bug in ordered_event.c:alloc_event(). An
ordered_eventstruct was not initialized properly potentially causing crashes later on infree_dup_event()depending on the content of the memory. If it was NULL, then it would work fine, otherwise, it could cause crashes such as:Quoted 7 lines
I'm now little puzzled what do we use this first event for.. I can't see anything special about it, other than it's added on the list uninitialized ;-)
it seems to work properly when we ditch it.. might be some prehistoric leftover or I'm terribly missing something
You need to keep track of the buffers to free. You do not free the
ordered_eventstructs
individually. For eachoe->buffer, you need onefree(). Each buffer is put in theto_free
list. But to link it into the list it needs alist_head. This is what buffer[0] is used for.
But the logic is broken inordered_events__free(). It does not free individualordered_eventstructs, but a buffer with many. Yet, it is missing freeing all of the duped
events.void
ordered_events__free(struct ordered_events *oe){ while (!list_empty(&oe->to_free)) { struct ordered_event *buffer; buffer = list_entry(oe->to_free.next, struct ordered_event, list); list_del(&buffer->list); ----> free_dup_event(oe, event->event); free(buffer); } }
Quoted 2 lines
This only frees the
dup_eventof buffer[0] which we know is NULL (well, now). It needs to walk all the entries in buffer[] to free buffer[x].event.
yes.. if there's copy_on_queue set, we need to do that, otherwise we're leaking all the events
Quoted 10 lines
I think the goal was likely to avoid adding another
list_headfield to eachordered_event
and instead use one per allocated buffer.
This is very convoluted and prone to errors and we are seeing right now. This should
be cleaned. So either you add alist_headtoordered_eventor you would buffer[x] inordered_events_free().
Quoted 2 lines
At this point, this is my understanding.
Do you agree?
yea, I see it now.. thanks for pointing this out
how about something like below? haven't tested properly yet
jirka
---
tools/perf/util/ordered-events.c +30 -13
@@ -80,14 +80,20 @@ static union perf_event *dup_event(struct ordered_events *oe,return oe->copy_on_queue ?__dup_event (oe, event) : event; } -static void free_dup_event (struct ordered_events *oe,union perf_event *event) +static void __free_dup_event (struct ordered_events *oe,union perf_event *event) { -if (event && oe->copy_on_queue) { +if (event) { oe->cur_alloc_size -= event->header.size;free (event); } } +static void free_dup_event (struct ordered_events *oe,union perf_event *event) +{ +if (oe->copy_on_queue) +__free_dup_event (oe, event); +} +#define MAX_SAMPLE_BUFFER (64 * 1024 / sizeof(struct ordered_event))static struct ordered_event *alloc_event (struct ordered_events *oe,union perf_event *event)
@@ -104,11 +110,11 @@ static struct ordered_event *alloc_event(struct ordered_events *oe, new = list_entry(cache->next, struct ordered_event, list);list_del (&new->list); }else if (oe->buffer) { - new = oe->buffer + oe->buffer_idx; + new = &oe->buffer->event[oe->buffer_idx];if (++oe->buffer_idx ==MAX_SAMPLE_BUFFER ) oe->buffer =NULL ; }else if (oe->cur_alloc_size < oe->max_alloc_size) { -size_t size =MAX_SAMPLE_BUFFER *sizeof (*new); +size_t size =sizeof (*oe->buffer) +MAX_SAMPLE_BUFFER *sizeof (*new); oe->buffer =malloc (size);if (!oe->buffer) {
@@ -122,9 +128,8 @@ static struct ordered_event *alloc_event(struct ordered_events *oe, oe->cur_alloc_size += size;list_add (&oe->buffer->list, &oe->to_free); -/* First entry is abused to maintain the to_free list. */ - oe->buffer_idx =2 ; - new = oe->buffer +1 ; + oe->buffer_idx =1 ; + new = &oe->buffer->event[0 ]; }else {pr ("allocation limit reached %" PRIu64"B\n" , oe->max_alloc_size); }
@@ -300,15 +305,27 @@ void ordered_events__init(struct ordered_events *oe, ordered_events__deliver_t d oe->deliver = deliver; } +static void +ordered_events_buffer__free (struct ordered_events_buffer *buffer, +struct ordered_events *oe) +{ +if (oe->copy_on_queue) { +unsigned int i; + +for (i =0 ; i <MAX_SAMPLE_BUFFER ; i++) +__free_dup_event (oe, buffer->event[i].event); + } + +free (buffer); +} +void ordered_events__free (struct ordered_events *oe) { -while (!list_empty (&oe->to_free)) { -struct ordered_event *event; +struct ordered_events_buffer *buffer, *tmp; - event = list_entry(oe->to_free.next, struct ordered_event, list); -list_del (&event->list); -free_dup_event (oe, event->event); -free (event); +list_for_each_entry_safe (buffer, tmp, &oe->to_free, list) { +list_del (&buffer->list); +ordered_events_buffer__free (buffer, oe); } }
tools/perf/util/ordered-events.h +21 -16
@@ -25,23 +25,28 @@ struct ordered_events;typedef int (*ordered_events__deliver_t )(struct ordered_events *oe,struct ordered_event *event); +struct ordered_events_buffer { +struct list_head list; +struct ordered_event event[0 ]; +}; +struct ordered_events { -u64 last_flush; -u64 next_flush; -u64 max_timestamp; -u64 max_alloc_size; -u64 cur_alloc_size; -struct list_head events; -struct list_head cache; -struct list_head to_free; -struct ordered_event *buffer; -struct ordered_event *last; -ordered_events__deliver_t deliver; -int buffer_idx; -unsigned int nr_events; -enum oe_flush last_flush_type; -u32 nr_unordered_events; -bool copy_on_queue; +u64 last_flush; +u64 next_flush; +u64 max_timestamp; +u64 max_alloc_size; +u64 cur_alloc_size; +struct list_head events; +struct list_head cache; +struct list_head to_free; +struct ordered_events_buffer *buffer; +struct ordered_event *last; +ordered_events__deliver_t deliver; +int buffer_idx; +unsigned int nr_events; +enum oe_flush last_flush_type; +u32 nr_unordered_events; +bool copy_on_queue; }; int ordered_events__queue(struct ordered_events *oe, union perf_event *event,
PATCH 1/1 perf tools: Add struct ordered_events_buffer layer
Quoted 56 lines · Jiri Olsa
Quoted 39 lines · Stephane Eranian
Quoted 8 lines · Jiri Olsa
Quoted 5 lines · Stephane Eranian
This patch fixes a bug in ordered_event.c:alloc_event(). An
ordered_eventstruct was not initialized properly potentially causing crashes later on infree_dup_event()depending on the content of the memory. If it was NULL, then it would work fine, otherwise, it could cause crashes such as:Quoted 7 lines
I'm now little puzzled what do we use this first event for.. I can't see anything special about it, other than it's added on the list uninitialized ;-)
it seems to work properly when we ditch it.. might be some prehistoric leftover or I'm terribly missing something
You need to keep track of the buffers to free. You do not free the
ordered_eventstructs
individually. For eachoe->buffer, you need onefree(). Each buffer is put in theto_free
list. But to link it into the list it needs alist_head. This is what buffer[0] is used for.
But the logic is broken inordered_events__free(). It does not free individualordered_eventstructs, but a buffer with many. Yet, it is missing freeing all of the duped
events.void
ordered_events__free(struct ordered_events *oe){ while (!list_empty(&oe->to_free)) { struct ordered_event *buffer; buffer = list_entry(oe->to_free.next, struct ordered_event, list); list_del(&buffer->list); ----> free_dup_event(oe, event->event); free(buffer); } }Quoted 2 lines
This only frees the
dup_eventof buffer[0] which we know is NULL (well, now). It needs to walk all the entries in buffer[] to free buffer[x].event.yes.. if there's
copy_on_queueset, we need to do that, otherwise we're leaking all the eventsQuoted 10 lines
I think the goal was likely to avoid adding another
list_headfield to eachordered_event
and instead use one per allocated buffer.
This is very convoluted and prone to errors and we are seeing right now. This should
be cleaned. So either you add alist_headtoordered_eventor you would buffer[x] inordered_events_free().
Quoted 6 lines
Quoted 2 lines
At this point, this is my understanding.
Do you agree?yea, I see it now.. thanks for pointing this out
how about something like below? haven't tested properly yet
attaching full patch
thanks,
jirka
---
When ordering events, we use preallocated buffers to store separated events. Those buffers currently don't have their own struct, but since they are basically array of 'struct ordered_event' objects, we use the first event to hold buffers data - list head, that holds all buffers together:
struct ordered_events {
...
struct ordered_event *buffer;
...
};
struct ordered_event {
u64 timestamp;
u64 file_offset;
union perf_event *event;
struct list_head list;
};This is quite convoluted and error prone as demonstrated by free-ing issue discovered and fixed by Stephane in here [1].
This patch adds the 'struct ordered_events_buffer' object, that holds the buffer data and frees it up properly.
[1] - https://marc.info/?l=linux-kernel&m=153376761329335&w=2
--- tools/perf/util/ordered-events.c | 44 ++++++++++++++++++++++---------- tools/perf/util/ordered-events.h | 37 +++++++++++++++------------ 2 files changed, 52 insertions(+), 29 deletions(-)
tools/perf/util/ordered-events.c +31 -13
@@ -80,14 +80,20 @@ static union perf_event *dup_event(struct ordered_events *oe,return oe->copy_on_queue ?__dup_event (oe, event) : event; } -static void free_dup_event (struct ordered_events *oe,union perf_event *event) +static void __free_dup_event (struct ordered_events *oe,union perf_event *event) { -if (event && oe->copy_on_queue) { +if (event) { oe->cur_alloc_size -= event->header.size;free (event); } } +static void free_dup_event (struct ordered_events *oe,union perf_event *event) +{ +if (oe->copy_on_queue) +__free_dup_event (oe, event); +} +#define MAX_SAMPLE_BUFFER (64 * 1024 / sizeof(struct ordered_event))static struct ordered_event *alloc_event (struct ordered_events *oe,union perf_event *event)
@@ -104,11 +110,12 @@ static struct ordered_event *alloc_event(struct ordered_events *oe, new = list_entry(cache->next, struct ordered_event, list);list_del (&new->list); }else if (oe->buffer) { - new = oe->buffer + oe->buffer_idx; + new = &oe->buffer->event[oe->buffer_idx];if (++oe->buffer_idx ==MAX_SAMPLE_BUFFER ) oe->buffer =NULL ; }else if (oe->cur_alloc_size < oe->max_alloc_size) { -size_t size =MAX_SAMPLE_BUFFER *sizeof (*new); +size_t size =sizeof (*oe->buffer) + +MAX_SAMPLE_BUFFER *sizeof (*new); oe->buffer =malloc (size);if (!oe->buffer) {
@@ -122,9 +129,8 @@ static struct ordered_event *alloc_event(struct ordered_events *oe, oe->cur_alloc_size += size;list_add (&oe->buffer->list, &oe->to_free); -/* First entry is abused to maintain the to_free list. */ - oe->buffer_idx =2 ; - new = oe->buffer +1 ; + oe->buffer_idx =1 ; + new = &oe->buffer->event[0 ]; }else {pr ("allocation limit reached %" PRIu64"B\n" , oe->max_alloc_size); }
@@ -300,15 +306,27 @@ void ordered_events__init(struct ordered_events *oe, ordered_events__deliver_t d oe->deliver = deliver; } +static void +ordered_events_buffer__free (struct ordered_events_buffer *buffer, +struct ordered_events *oe) +{ +if (oe->copy_on_queue) { +unsigned int i; + +for (i =0 ; i <MAX_SAMPLE_BUFFER ; i++) +__free_dup_event (oe, buffer->event[i].event); + } + +free (buffer); +} +void ordered_events__free (struct ordered_events *oe) { -while (!list_empty (&oe->to_free)) { -struct ordered_event *event; +struct ordered_events_buffer *buffer, *tmp; - event = list_entry(oe->to_free.next, struct ordered_event, list); -list_del (&event->list); -free_dup_event (oe, event->event); -free (event); +list_for_each_entry_safe (buffer, tmp, &oe->to_free, list) { +list_del (&buffer->list); +ordered_events_buffer__free (buffer, oe); } }
tools/perf/util/ordered-events.h +21 -16
@@ -25,23 +25,28 @@ struct ordered_events;typedef int (*ordered_events__deliver_t )(struct ordered_events *oe,struct ordered_event *event); +struct ordered_events_buffer { +struct list_head list; +struct ordered_event event[0 ]; +}; +struct ordered_events { -u64 last_flush; -u64 next_flush; -u64 max_timestamp; -u64 max_alloc_size; -u64 cur_alloc_size; -struct list_head events; -struct list_head cache; -struct list_head to_free; -struct ordered_event *buffer; -struct ordered_event *last; -ordered_events__deliver_t deliver; -int buffer_idx; -unsigned int nr_events; -enum oe_flush last_flush_type; -u32 nr_unordered_events; -bool copy_on_queue; +u64 last_flush; +u64 next_flush; +u64 max_timestamp; +u64 max_alloc_size; +u64 cur_alloc_size; +struct list_head events; +struct list_head cache; +struct list_head to_free; +struct ordered_events_buffer *buffer; +struct ordered_event *last; +ordered_events__deliver_t deliver; +int buffer_idx; +unsigned int nr_events; +enum oe_flush last_flush_type; +u32 nr_unordered_events; +bool copy_on_queue; }; int ordered_events__queue(struct ordered_events *oe, union perf_event *event,
Signature · 1 line
2.17.1
Re: [PATCH] perf tools: Add struct ordered_events_buffer layer
Jiri,
Quoted 150 lines · Jiri Olsa
Quoted 56 lines · Jiri Olsa
Quoted 39 lines · Stephane Eranian
Quoted 8 lines · Jiri Olsa
Quoted 5 lines · Stephane Eranian
This patch fixes a bug in ordered_event.c:alloc_event(). An
ordered_eventstruct was not initialized properly potentially causing crashes later on infree_dup_event()depending on the content of the memory. If it was NULL, then it would work fine, otherwise, it could cause crashes such as:Quoted 7 lines
I'm now little puzzled what do we use this first event for.. I can't see anything special about it, other than it's added on the list uninitialized ;-)
it seems to work properly when we ditch it.. might be some prehistoric leftover or I'm terribly missing something
You need to keep track of the buffers to free. You do not free the
ordered_eventstructs
individually. For eachoe->buffer, you need onefree(). Each buffer is put in theto_free
list. But to link it into the list it needs alist_head. This is what buffer[0] is used for.
But the logic is broken inordered_events__free(). It does not free individualordered_eventstructs, but a buffer with many. Yet, it is missing freeing all of the duped
events.void
ordered_events__free(struct ordered_events *oe){ while (!list_empty(&oe->to_free)) { struct ordered_event *buffer; buffer = list_entry(oe->to_free.next, struct ordered_event, list); list_del(&buffer->list); ----> free_dup_event(oe, event->event); free(buffer); } }Quoted 2 lines
This only frees the
dup_eventof buffer[0] which we know is NULL (well, now). It needs to walk all the entries in buffer[] to free buffer[x].event.yes.. if there's
copy_on_queueset, we need to do that, otherwise we're leaking all the eventsQuoted 10 lines
I think the goal was likely to avoid adding another
list_headfield to eachordered_event
and instead use one per allocated buffer.
This is very convoluted and prone to errors and we are seeing right now. This should
be cleaned. So either you add alist_headtoordered_eventor you would buffer[x] inordered_events_free().Quoted 6 lines
Quoted 2 lines
At this point, this is my understanding.
Do you agree?yea, I see it now.. thanks for pointing this out
how about something like below? haven't tested properly yet
attaching full patch
thanks,
jirka---
When ordering events, we use preallocated buffers to store separated events. Those buffers currently don't have their own struct, but since they are basically array of 'structordered_event' objects, we use the first event to hold buffers data - list head, that holds all buffers together:struct ordered_events { ... struct ordered_event *buffer; ... }; struct ordered_event { u64 timestamp; u64 file_offset; union perf_event *event; struct list_head list; };This is quite convoluted and error prone as demonstrated by free-ing issue discovered and fixed by Stephane in here [1].
This patch adds the 'struct
ordered_events_buffer' object, that holds the buffer data and frees it up properly.[1] - https://marc.info/?l=linux-kernel&m=153376761329335&w=2
Reported-by Stephane Eranian <[email protected]>Signed-off-by Jiri Olsa <[email protected]>--- tools/perf/util/ordered-events.c | 44 ++++++++++++++++++++++---------- tools/perf/util/ordered-events.h | 37 +++++++++++++++------------ 2 files changed, 52 insertions(+), 29 deletions(-)tools/perf/util/ordered-events.c +11
-4@@ -80,14 +80,20 @@ static union perf_event *dup_event(struct ordered_events *oe,return oe->copy_on_queue ?__dup_event (oe, event) : event; } -static void free_dup_event (struct ordered_events *oe,union perf_event *event) +static void __free_dup_event (struct ordered_events *oe,union perf_event *event) { -if (event && oe->copy_on_queue) { +if (event) { oe->cur_alloc_size -= event->header.size;free (event); } } +static void free_dup_event (struct ordered_events *oe,union perf_event *event) +{ +if (oe->copy_on_queue) +__free_dup_event (oe, event); +} +#define MAX_SAMPLE_BUFFER (64 * 1024 / sizeof(struct ordered_event))static struct ordered_event *alloc_event (struct ordered_events *oe,union perf_event *event)@@ -104,11 +110,12 @@ static struct ordered_event *alloc_event(struct ordered_events *oe, new = list_entry(cache->next, struct ordered_event, list);list_del (&new->list); }else if (oe->buffer) { - new = oe->buffer + oe->buffer_idx; + new = &oe->buffer->event[oe->buffer_idx];if (++oe->buffer_idx ==MAX_SAMPLE_BUFFER ) oe->buffer =NULL ; }else if (oe->cur_alloc_size < oe->max_alloc_size) { -size_t size =MAX_SAMPLE_BUFFER *sizeof (*new); +size_t size =sizeof (*oe->buffer) + +MAX_SAMPLE_BUFFER *sizeof (*new); oe->buffer =malloc (size);if (!oe->buffer) {
tools/perf/util/ordered-events.c static struct ordered_event *alloc_event(struct ordered_events *oe, · line 133
Quoted 8 lines
tools/perf/util/ordered-events.c +2
-3@@ -122,9 +129,8 @@ static struct ordered_event *alloc_event(struct ordered_events *oe, oe->cur_alloc_size += size;list_add (&oe->buffer->list, &oe->to_free); -/* First entry is abused to maintain the to_free list. */ - oe->buffer_idx =2 ; - new = oe->buffer +1 ; + oe->buffer_idx =1 ; + new = &oe->buffer->event[0 ];
Ok, but I think this section between the malloc() and the line above needs some comments to clarify what is going on.
It is still hard to read.
Quoted 8 lines
tools/perf/util/ordered-events.c +1
-0}else {pr ("allocation limit reached %" PRIu64"B\n" , oe->max_alloc_size); }@@ -300,15 +306,27 @@ void ordered_events__init(struct ordered_events *oe, ordered_events__deliver_t d oe->deliver = deliver; } +static void
tools/perf/util/ordered-events.c void ordered_events__init(struct ordered_events *oe, ordered_events__deliver_t d · line 319
Quoted 10 lines
tools/perf/util/ordered-events.c +10
-0+ordered_events_buffer__free(struct ordered_events_buffer *buffer, + struct ordered_events *oe) +{ + if (oe->copy_on_queue) { + unsigned int i; + +for (i =0 ; i <MAX_SAMPLE_BUFFER ; i++) +__free_dup_event (oe, buffer->event[i].event); + } +
I have a problem with this one, given that the buffer->event[] is never actually zeroed.
So what happens if you do not use all the entries by the time you have to free? I think one way to avoid this is by iterating only all the way to oe->buffer_idx.
Quoted 71 lines
tools/perf/util/ordered-events.c +7
-6+free (buffer); +} +void ordered_events__free (struct ordered_events *oe) { -while (!list_empty (&oe->to_free)) { -struct ordered_event *event; +struct ordered_events_buffer *buffer, *tmp; - event = list_entry(oe->to_free.next, struct ordered_event, list); -list_del (&event->list); -free_dup_event (oe, event->event); -free (event); +list_for_each_entry_safe (buffer, tmp, &oe->to_free, list) { +list_del (&buffer->list); +ordered_events_buffer__free (buffer, oe); } }tools/perf/util/ordered-events.h +21
-17@@ -25,23 +25,28 @@ struct ordered_events;typedef int (*ordered_events__deliver_t )(struct ordered_events *oe,struct ordered_event *event); +struct ordered_events_buffer { +struct list_head list; +struct ordered_event event[0 ]; +}; +struct ordered_events { -u64 last_flush; -u64 next_flush; -u64 max_timestamp; -u64 max_alloc_size; -u64 cur_alloc_size; -struct list_head events; -struct list_head cache; -struct list_head to_free; -struct ordered_event *buffer; -struct ordered_event *last; -ordered_events__deliver_t deliver; -int buffer_idx; -unsigned int nr_events; -enum oe_flush last_flush_type; -u32 nr_unordered_events; -bool copy_on_queue; +u64 last_flush; +u64 next_flush; +u64 max_timestamp; +u64 max_alloc_size; +u64 cur_alloc_size; +struct list_head events; +struct list_head cache; +struct list_head to_free; +struct ordered_events_buffer *buffer; +struct ordered_event *last; +ordered_events__deliver_t deliver; +int buffer_idx; +unsigned int nr_events; +enum oe_flush last_flush_type; +u32 nr_unordered_events; +bool copy_on_queue; }; int ordered_events__queue(struct ordered_events *oe, union perf_event *event, --2.17.1
Re: [PATCH] perf tools: Add struct ordered_events_buffer layer
On Tue, Aug 14, 2018 at 12:14:19AM -0700, Stephane Eranian wrote:
SNIP
Quoted 19 lines
Quoted 19 lines
(quoted diff) +3
-2@@ -104,11 +110,12 @@ static struct ordered_event *alloc_event(struct ordered_events *oe, new = list_entry(cache->next, struct ordered_event, list); list_del(&new->list); } else if (oe->buffer) { - new = oe->buffer + oe->buffer_idx; + new = &oe->buffer->event[oe->buffer_idx]; if (++oe->buffer_idx == MAX_SAMPLE_BUFFER) oe->buffer = NULL; } else if (oe->cur_alloc_size < oe->max_alloc_size) { - size_t size = MAX_SAMPLE_BUFFER * sizeof(*new); + size_t size = sizeof(*oe->buffer) + + MAX_SAMPLE_BUFFER * sizeof(*new); oe->buffer = malloc(size); if (!oe->buffer) {@@ -122,9 +129,8 @@ static struct ordered_event *alloc_event(struct ordered_events *oe, oe->cur_alloc_size += size; list_add(&oe->buffer->list, &oe->to_free);
Quoted 9 lines
Review static struct ordered_event *alloc_event(struct ordered_events *oe, · line 133
Quoted 5 lines
(quoted diff) +2
-3- /* First entry is abused to maintain the to_free list. */ - oe->buffer_idx = 2; - new = oe->buffer + 1; + oe->buffer_idx = 1; + new = &oe->buffer->event[0];Ok, but I think this section between the
malloc()and the line above needs some comments to clarify what is going on.
It is still hard to read.
ok, I put some bigger comment at the top, but I'm not too happy feel free to suggest different one ;-)
Quoted 8 lines
Quoted 8 lines
(quoted diff) +0
-0} else { pr("allocation limit reached %" PRIu64 "B\n", oe->max_alloc_size); }@@ -300,15 +306,27 @@ void ordered_events__init(struct ordered_events *oe, ordered_events__deliver_t d oe->deliver = deliver; }
Quoted 16 lines
Review void ordered_events__init(struct ordered_events *oe, ordered_events__deliver_t d · line 319
Quoted 11 lines
(quoted diff) +11
-0+static void +ordered_events_buffer__free(struct ordered_events_buffer *buffer, + struct ordered_events *oe) +{ + if (oe->copy_on_queue) { + unsigned int i; + + for (i = 0; i < MAX_SAMPLE_BUFFER; i++) + __free_dup_event(oe, buffer->event[i].event); + } +I have a problem with this one, given that the buffer->event[] is never actually zeroed.
So what happens if you do not use all the entries by the time you have to free? I think one way to avoid this is by iterating only all the way tooe->buffer_idx.
right, please check attached patch
thanks,
jirka
---
tools/perf/util/ordered-events.c +66 -13
@@ -80,14 +80,20 @@ static union perf_event *dup_event(struct ordered_events *oe,return oe->copy_on_queue ?__dup_event (oe, event) : event; } -static void free_dup_event (struct ordered_events *oe,union perf_event *event) +static void __free_dup_event (struct ordered_events *oe,union perf_event *event) { -if (event && oe->copy_on_queue) { +if (event) { oe->cur_alloc_size -= event->header.size;free (event); } } +static void free_dup_event (struct ordered_events *oe,union perf_event *event) +{ +if (oe->copy_on_queue) +__free_dup_event (oe, event); +} +#define MAX_SAMPLE_BUFFER (64 * 1024 / sizeof(struct ordered_event))static struct ordered_event *alloc_event (struct ordered_events *oe,union perf_event *event)
@@ -100,15 +106,43 @@ static struct ordered_event *alloc_event(struct ordered_events *oe,if (!new_event)return NULL ; +/* +* We maintain following scheme of buffers for ordered +* event allocation: +* +* to_free list -> buffer1 (64K) +* buffer2 (64K) +* ... +* +* Each buffer keeps an array of ordered events objects: +* buffer -> event[0] +* event[1] +* ... +* +* Each allocated ordered event is linked to one of +* following lists: +* - time ordered list 'events' +* - list of currently removed events 'cache' +* +* Allocation of the ordered event uses following order +* to get the memory: +* - use recently removed object from 'cache' list +* - use available object in current allocation buffer +* - allocate new buffer if the current buffer is full +* +* Removal of ordered event object moves it from events to +* the cache list. +*/ if (!list_empty (cache)) { new = list_entry(cache->next, struct ordered_event, list);list_del (&new->list); }else if (oe->buffer) { - new = oe->buffer + oe->buffer_idx; + new = &oe->buffer->event[oe->buffer_idx];if (++oe->buffer_idx ==MAX_SAMPLE_BUFFER ) oe->buffer =NULL ; }else if (oe->cur_alloc_size < oe->max_alloc_size) { -size_t size =MAX_SAMPLE_BUFFER *sizeof (*new); +size_t size =sizeof (*oe->buffer) + +MAX_SAMPLE_BUFFER *sizeof (*new); oe->buffer =malloc (size);if (!oe->buffer) {
@@ -122,9 +156,8 @@ static struct ordered_event *alloc_event(struct ordered_events *oe, oe->cur_alloc_size += size;list_add (&oe->buffer->list, &oe->to_free); -/* First entry is abused to maintain the to_free list. */ - oe->buffer_idx =2 ; - new = oe->buffer +1 ; + oe->buffer_idx =1 ; + new = &oe->buffer->event[0 ]; }else {pr ("allocation limit reached %" PRIu64"B\n" , oe->max_alloc_size); }
@@ -300,15 +333,35 @@ void ordered_events__init(struct ordered_events *oe, ordered_events__deliver_t d oe->deliver = deliver; } +static void +ordered_events_buffer__free (struct ordered_events_buffer *buffer, +unsigned int max,struct ordered_events *oe) +{ +if (oe->copy_on_queue) { +unsigned int i; + +for (i =0 ; i < max; i++) +__free_dup_event (oe, buffer->event[i].event); + } + +free (buffer); +} +void ordered_events__free (struct ordered_events *oe) { -while (!list_empty (&oe->to_free)) { -struct ordered_event *event; +struct ordered_events_buffer *buffer, *tmp; - event = list_entry(oe->to_free.next, struct ordered_event, list); -list_del (&event->list); -free_dup_event (oe, event->event); -free (event); +/* +* Current buffer might not have all the events allocated +* yet, we need to free only allocated ones ... +*/ +list_del (&oe->buffer->list); +ordered_events_buffer__free (oe->buffer, oe->buffer_idx, oe); + +/* ... and continue with the rest */ +list_for_each_entry_safe (buffer, tmp, &oe->to_free, list) { +list_del (&buffer->list); +ordered_events_buffer__free (buffer,MAX_SAMPLE_BUFFER , oe); } }
tools/perf/util/ordered-events.h +21 -16
@@ -25,23 +25,28 @@ struct ordered_events;typedef int (*ordered_events__deliver_t )(struct ordered_events *oe,struct ordered_event *event); +struct ordered_events_buffer { +struct list_head list; +struct ordered_event event[0 ]; +}; +struct ordered_events { -u64 last_flush; -u64 next_flush; -u64 max_timestamp; -u64 max_alloc_size; -u64 cur_alloc_size; -struct list_head events; -struct list_head cache; -struct list_head to_free; -struct ordered_event *buffer; -struct ordered_event *last; -ordered_events__deliver_t deliver; -int buffer_idx; -unsigned int nr_events; -enum oe_flush last_flush_type; -u32 nr_unordered_events; -bool copy_on_queue; +u64 last_flush; +u64 next_flush; +u64 max_timestamp; +u64 max_alloc_size; +u64 cur_alloc_size; +struct list_head events; +struct list_head cache; +struct list_head to_free; +struct ordered_events_buffer *buffer; +struct ordered_event *last; +ordered_events__deliver_t deliver; +int buffer_idx; +unsigned int nr_events; +enum oe_flush last_flush_type; +u32 nr_unordered_events; +bool copy_on_queue; }; int ordered_events__queue(struct ordered_events *oe, union perf_event *event,
PATCH v2 1/1 perf tools: Add struct ordered_events_buffer layer
Quoted 61 lines · Jiri Olsa
On Tue, Aug 14, 2018 at 12:14:19AM -0700, Stephane Eranian wrote:
SNIP
Quoted 19 lines
Quoted 19 lines
(quoted diff) +3
-2@@ -104,11 +110,12 @@ static struct ordered_event *alloc_event(struct ordered_events *oe, new = list_entry(cache->next, struct ordered_event, list); list_del(&new->list); } else if (oe->buffer) { - new = oe->buffer + oe->buffer_idx; + new = &oe->buffer->event[oe->buffer_idx]; if (++oe->buffer_idx == MAX_SAMPLE_BUFFER) oe->buffer = NULL; } else if (oe->cur_alloc_size < oe->max_alloc_size) { - size_t size = MAX_SAMPLE_BUFFER * sizeof(*new); + size_t size = sizeof(*oe->buffer) + + MAX_SAMPLE_BUFFER * sizeof(*new); oe->buffer = malloc(size); if (!oe->buffer) {@@ -122,9 +129,8 @@ static struct ordered_event *alloc_event(struct ordered_events *oe, oe->cur_alloc_size += size; list_add(&oe->buffer->list, &oe->to_free);Quoted 9 lines
Review static struct ordered_event *alloc_event(struct ordered_events *oe, · line 133
Quoted 5 lines
(quoted diff) +2
-3- /* First entry is abused to maintain the to_free list. */ - oe->buffer_idx = 2; - new = oe->buffer + 1; + oe->buffer_idx = 1; + new = &oe->buffer->event[0];Ok, but I think this section between the
malloc()and the line above needs some comments to clarify what is going on.
It is still hard to read.ok, I put some bigger comment at the top, but I'm not too happy feel free to suggest different one ;-)
Quoted 8 lines
Quoted 8 lines
(quoted diff) +0
-0} else { pr("allocation limit reached %" PRIu64 "B\n", oe->max_alloc_size); }@@ -300,15 +306,27 @@ void ordered_events__init(struct ordered_events *oe, ordered_events__deliver_t d oe->deliver = deliver; }Quoted 16 lines
Review void ordered_events__init(struct ordered_events *oe, ordered_events__deliver_t d · line 319
Quoted 11 lines
(quoted diff) +11
-0+static void +ordered_events_buffer__free(struct ordered_events_buffer *buffer, + struct ordered_events *oe) +{ + if (oe->copy_on_queue) { + unsigned int i; + + for (i = 0; i < MAX_SAMPLE_BUFFER; i++) + __free_dup_event(oe, buffer->event[i].event); + } +I have a problem with this one, given that the buffer->event[] is never actually zeroed.
So what happens if you do not use all the entries by the time you have to free? I think one way to avoid this is by iterating only all the way tooe->buffer_idx.
Quoted 1 line
right, please check attached patch
any comments? attaching v2
thanks,
jirka
---
When ordering events, we use preallocated buffers to store separated events. Those buffers currently don't have their own struct, but since they are basically array of 'struct ordered_event' objects, we use the first event to hold buffers data - list head, that holds all buffers together:
struct ordered_events {
...
struct ordered_event *buffer;
...
};
struct ordered_event {
u64 timestamp;
u64 file_offset;
union perf_event *event;
struct list_head list;
};This is quite convoluted and error prone as demonstrated by free-ing issue discovered and fixed by Stephane in here [1].
This patch adds the 'struct ordered_events_buffer' object, that holds the buffer data and frees it up properly.
[1] - https://marc.info/?l=linux-kernel&m=153376761329335&w=2
--- tools/perf/util/ordered-events.c | 82 +++++++++++++++++++++++++++----- tools/perf/util/ordered-events.h | 37 +++++++------- 2 files changed, 90 insertions(+), 29 deletions(-)
tools/perf/util/ordered-events.c +69 -13
@@ -80,14 +80,20 @@ static union perf_event *dup_event(struct ordered_events *oe,return oe->copy_on_queue ?__dup_event (oe, event) : event; } -static void free_dup_event (struct ordered_events *oe,union perf_event *event) +static void __free_dup_event (struct ordered_events *oe,union perf_event *event) { -if (event && oe->copy_on_queue) { +if (event) { oe->cur_alloc_size -= event->header.size;free (event); } } +static void free_dup_event (struct ordered_events *oe,union perf_event *event) +{ +if (oe->copy_on_queue) +__free_dup_event (oe, event); +} +#define MAX_SAMPLE_BUFFER (64 * 1024 / sizeof(struct ordered_event))static struct ordered_event *alloc_event (struct ordered_events *oe,union perf_event *event)
@@ -100,15 +106,43 @@ static struct ordered_event *alloc_event(struct ordered_events *oe,if (!new_event)return NULL ; +/* +* We maintain following scheme of buffers for ordered +* event allocation: +* +* to_free list -> buffer1 (64K) +* buffer2 (64K) +* ... +* +* Each buffer keeps an array of ordered events objects: +* buffer -> event[0] +* event[1] +* ... +* +* Each allocated ordered event is linked to one of +* following lists: +* - time ordered list 'events' +* - list of currently removed events 'cache' +* +* Allocation of the ordered event uses following order +* to get the memory: +* - use recently removed object from 'cache' list +* - use available object in current allocation buffer +* - allocate new buffer if the current buffer is full +* +* Removal of ordered event object moves it from events to +* the cache list. +*/ if (!list_empty (cache)) { new = list_entry(cache->next, struct ordered_event, list);list_del (&new->list); }else if (oe->buffer) { - new = oe->buffer + oe->buffer_idx; + new = &oe->buffer->event[oe->buffer_idx];if (++oe->buffer_idx ==MAX_SAMPLE_BUFFER ) oe->buffer =NULL ; }else if (oe->cur_alloc_size < oe->max_alloc_size) { -size_t size =MAX_SAMPLE_BUFFER *sizeof (*new); +size_t size =sizeof (*oe->buffer) + +MAX_SAMPLE_BUFFER *sizeof (*new); oe->buffer =malloc (size);if (!oe->buffer) {
@@ -122,9 +156,8 @@ static struct ordered_event *alloc_event(struct ordered_events *oe, oe->cur_alloc_size += size;list_add (&oe->buffer->list, &oe->to_free); -/* First entry is abused to maintain the to_free list. */ - oe->buffer_idx =2 ; - new = oe->buffer +1 ; + oe->buffer_idx =1 ; + new = &oe->buffer->event[0 ]; }else {pr ("allocation limit reached %" PRIu64"B\n" , oe->max_alloc_size); }
@@ -300,15 +333,38 @@ void ordered_events__init(struct ordered_events *oe, ordered_events__deliver_t d oe->deliver = deliver; } +static void +ordered_events_buffer__free (struct ordered_events_buffer *buffer, +unsigned int max,struct ordered_events *oe) +{ +if (oe->copy_on_queue) { +unsigned int i; + +for (i =0 ; i < max; i++) +__free_dup_event (oe, buffer->event[i].event); + } + +free (buffer); +} +void ordered_events__free (struct ordered_events *oe) { -while (!list_empty (&oe->to_free)) { -struct ordered_event *event; +struct ordered_events_buffer *buffer, *tmp; - event = list_entry(oe->to_free.next, struct ordered_event, list); -list_del (&event->list); -free_dup_event (oe, event->event); -free (event); +if (list_empty (&oe->to_free)) +return ; + +/* +* Current buffer might not have all the events allocated +* yet, we need to free only allocated ones ... +*/ +list_del (&oe->buffer->list); +ordered_events_buffer__free (oe->buffer, oe->buffer_idx, oe); + +/* ... and continue with the rest */ +list_for_each_entry_safe (buffer, tmp, &oe->to_free, list) { +list_del (&buffer->list); +ordered_events_buffer__free (buffer,MAX_SAMPLE_BUFFER , oe); } }
tools/perf/util/ordered-events.h +21 -16
@@ -25,23 +25,28 @@ struct ordered_events;typedef int (*ordered_events__deliver_t )(struct ordered_events *oe,struct ordered_event *event); +struct ordered_events_buffer { +struct list_head list; +struct ordered_event event[0 ]; +}; +struct ordered_events { -u64 last_flush; -u64 next_flush; -u64 max_timestamp; -u64 max_alloc_size; -u64 cur_alloc_size; -struct list_head events; -struct list_head cache; -struct list_head to_free; -struct ordered_event *buffer; -struct ordered_event *last; -ordered_events__deliver_t deliver; -int buffer_idx; -unsigned int nr_events; -enum oe_flush last_flush_type; -u32 nr_unordered_events; -bool copy_on_queue; +u64 last_flush; +u64 next_flush; +u64 max_timestamp; +u64 max_alloc_size; +u64 cur_alloc_size; +struct list_head events; +struct list_head cache; +struct list_head to_free; +struct ordered_events_buffer *buffer; +struct ordered_event *last; +ordered_events__deliver_t deliver; +int buffer_idx; +unsigned int nr_events; +enum oe_flush last_flush_type; +u32 nr_unordered_events; +bool copy_on_queue; }; int ordered_events__queue(struct ordered_events *oe, union perf_event *event,
Signature · 1 line
2.17.1
Re: [PATCHv2] perf tools: Add struct ordered_events_buffer layer
Quoted 22 lines · Jiri Olsa
When ordering events, we use preallocated buffers to store separated events. Those buffers currently don't have their own struct, but since they are basically array of 'struct
ordered_event' objects, we use the first event to hold buffers data - list head, that holds all buffers together:struct ordered_events { ... struct ordered_event *buffer; ... }; struct ordered_event { u64 timestamp; u64 file_offset; union perf_event *event; struct list_head list; };This is quite convoluted and error prone as demonstrated by free-ing issue discovered and fixed by Stephane in here [1].
Quoted 8 lines
This patch adds the 'struct
ordered_events_buffer' object, that holds the buffer data and frees it up properly.[1] - https://marc.info/?l=linux-kernel&m=153376761329335&w=2
Reported-by Stephane Eranian <[email protected]>Signed-off-by Jiri Olsa <[email protected]>
Thanks,
Namhyung
Quoted 187 lines
--- tools/perf/util/ordered-events.c | 82 +++++++++++++++++++++++++++----- tools/perf/util/ordered-events.h | 37 +++++++------- 2 files changed, 90 insertions(+), 29 deletions(-)tools/perf/util/ordered-events.c +69
-13@@ -80,14 +80,20 @@ static union perf_event *dup_event(struct ordered_events *oe,return oe->copy_on_queue ?__dup_event (oe, event) : event; } -static void free_dup_event (struct ordered_events *oe,union perf_event *event) +static void __free_dup_event (struct ordered_events *oe,union perf_event *event) { -if (event && oe->copy_on_queue) { +if (event) { oe->cur_alloc_size -= event->header.size;free (event); } } +static void free_dup_event (struct ordered_events *oe,union perf_event *event) +{ +if (oe->copy_on_queue) +__free_dup_event (oe, event); +} +#define MAX_SAMPLE_BUFFER (64 * 1024 / sizeof(struct ordered_event))static struct ordered_event *alloc_event (struct ordered_events *oe,union perf_event *event)@@ -100,15 +106,43 @@ static struct ordered_event *alloc_event(struct ordered_events *oe,if (!new_event)return NULL ; +/* +* We maintain following scheme of buffers for ordered +* event allocation: +* +* to_free list -> buffer1 (64K) +* buffer2 (64K) +* ... +* +* Each buffer keeps an array of ordered events objects: +* buffer -> event[0] +* event[1] +* ... +* +* Each allocated ordered event is linked to one of +* following lists: +* - time ordered list 'events' +* - list of currently removed events 'cache' +* +* Allocation of the ordered event uses following order +* to get the memory: +* - use recently removed object from 'cache' list +* - use available object in current allocation buffer +* - allocate new buffer if the current buffer is full +* +* Removal of ordered event object moves it from events to +* the cache list. +*/ if (!list_empty (cache)) { new = list_entry(cache->next, struct ordered_event, list);list_del (&new->list); }else if (oe->buffer) { - new = oe->buffer + oe->buffer_idx; + new = &oe->buffer->event[oe->buffer_idx];if (++oe->buffer_idx ==MAX_SAMPLE_BUFFER ) oe->buffer =NULL ; }else if (oe->cur_alloc_size < oe->max_alloc_size) { -size_t size =MAX_SAMPLE_BUFFER *sizeof (*new); +size_t size =sizeof (*oe->buffer) + +MAX_SAMPLE_BUFFER *sizeof (*new); oe->buffer =malloc (size);if (!oe->buffer) {@@ -122,9 +156,8 @@ static struct ordered_event *alloc_event(struct ordered_events *oe, oe->cur_alloc_size += size;list_add (&oe->buffer->list, &oe->to_free); -/* First entry is abused to maintain the to_free list. */ - oe->buffer_idx =2 ; - new = oe->buffer +1 ; + oe->buffer_idx =1 ; + new = &oe->buffer->event[0 ]; }else {pr ("allocation limit reached %" PRIu64"B\n" , oe->max_alloc_size); }@@ -300,15 +333,38 @@ void ordered_events__init(struct ordered_events *oe, ordered_events__deliver_t d oe->deliver = deliver; } +static void +ordered_events_buffer__free (struct ordered_events_buffer *buffer, +unsigned int max,struct ordered_events *oe) +{ +if (oe->copy_on_queue) { +unsigned int i; + +for (i =0 ; i < max; i++) +__free_dup_event (oe, buffer->event[i].event); + } + +free (buffer); +} +void ordered_events__free (struct ordered_events *oe) { -while (!list_empty (&oe->to_free)) { -struct ordered_event *event; +struct ordered_events_buffer *buffer, *tmp; - event = list_entry(oe->to_free.next, struct ordered_event, list); -list_del (&event->list); -free_dup_event (oe, event->event); -free (event); +if (list_empty (&oe->to_free)) +return ; + +/* +* Current buffer might not have all the events allocated +* yet, we need to free only allocated ones ... +*/ +list_del (&oe->buffer->list); +ordered_events_buffer__free (oe->buffer, oe->buffer_idx, oe); + +/* ... and continue with the rest */ +list_for_each_entry_safe (buffer, tmp, &oe->to_free, list) { +list_del (&buffer->list); +ordered_events_buffer__free (buffer,MAX_SAMPLE_BUFFER , oe); } }tools/perf/util/ordered-events.h +21
-16@@ -25,23 +25,28 @@ struct ordered_events;typedef int (*ordered_events__deliver_t )(struct ordered_events *oe,struct ordered_event *event); +struct ordered_events_buffer { +struct list_head list; +struct ordered_event event[0 ]; +}; +struct ordered_events { -u64 last_flush; -u64 next_flush; -u64 max_timestamp; -u64 max_alloc_size; -u64 cur_alloc_size; -struct list_head events; -struct list_head cache; -struct list_head to_free; -struct ordered_event *buffer; -struct ordered_event *last; -ordered_events__deliver_t deliver; -int buffer_idx; -unsigned int nr_events; -enum oe_flush last_flush_type; -u32 nr_unordered_events; -bool copy_on_queue; +u64 last_flush; +u64 next_flush; +u64 max_timestamp; +u64 max_alloc_size; +u64 cur_alloc_size; +struct list_head events; +struct list_head cache; +struct list_head to_free; +struct ordered_events_buffer *buffer; +struct ordered_event *last; +ordered_events__deliver_t deliver; +int buffer_idx; +unsigned int nr_events; +enum oe_flush last_flush_type; +u32 nr_unordered_events; +bool copy_on_queue; }; int ordered_events__queue(struct ordered_events *oe, union perf_event *event,Signature · 1 line
2.17.1
Re: [PATCHv2] perf tools: Add struct ordered_events_buffer layer
In reply to Jiri OlsaJiri,
Quoted 181 lines · Jiri Olsa
Quoted 61 lines · Jiri Olsa
On Tue, Aug 14, 2018 at 12:14:19AM -0700, Stephane Eranian wrote:
SNIP
Quoted 19 lines
Quoted 19 lines
tools/perf/util/ordered-events.c +3
-2@@ -104,11 +110,12 @@ static struct ordered_event *alloc_event(struct ordered_events *oe, new = list_entry(cache->next, struct ordered_event, list);list_del (&new->list); }else if (oe->buffer) { - new = oe->buffer + oe->buffer_idx; + new = &oe->buffer->event[oe->buffer_idx];if (++oe->buffer_idx ==MAX_SAMPLE_BUFFER ) oe->buffer =NULL ; }else if (oe->cur_alloc_size < oe->max_alloc_size) { -size_t size =MAX_SAMPLE_BUFFER *sizeof (*new); +size_t size =sizeof (*oe->buffer) + +MAX_SAMPLE_BUFFER *sizeof (*new); oe->buffer =malloc (size);if (!oe->buffer) {@@ -122,9 +129,8 @@ static struct ordered_event *alloc_event(struct ordered_events *oe, oe->cur_alloc_size += size;list_add (&oe->buffer->list, &oe->to_free);Quoted 9 lines
tools/perf/util/ordered-events.c static struct ordered_event *alloc_event(struct ordered_events *oe, · line 160
Quoted 5 lines
tools/perf/util/ordered-events.c +2
-3-/* First entry is abused to maintain the to_free list. */ - oe->buffer_idx =2 ; - new = oe->buffer +1 ; + oe->buffer_idx =1 ; + new = &oe->buffer->event[0 ];Ok, but I think this section between the
malloc()and the line above needs some comments to clarify what is going on.
It is still hard to read.ok, I put some bigger comment at the top, but I'm not too happy feel free to suggest different one ;-)
Quoted 8 lines
Quoted 8 lines
tools/perf/util/ordered-events.c +0
-0}else {pr ("allocation limit reached %" PRIu64"B\n" , oe->max_alloc_size); }@@ -300,15 +306,27 @@ void ordered_events__init(struct ordered_events *oe, ordered_events__deliver_t d oe->deliver = deliver; }Quoted 16 lines
tools/perf/util/ordered-events.c void ordered_events__init(struct ordered_events *oe, ordered_events__deliver_t d · line 346
Quoted 11 lines
tools/perf/util/ordered-events.c +11
-0+static void +ordered_events_buffer__free (struct ordered_events_buffer *buffer, +struct ordered_events *oe) +{ +if (oe->copy_on_queue) { +unsigned int i; + +for (i =0 ; i <MAX_SAMPLE_BUFFER ; i++) +__free_dup_event (oe, buffer->event[i].event); + } +I have a problem with this one, given that the buffer->event[] is never actually zeroed.
So what happens if you do not use all the entries by the time you have to free? I think one way to avoid this is by iterating only all the way tooe->buffer_idx.Quoted 1 line
right, please check attached patch
any comments? attaching v2
thanks,
jirka---
When ordering events, we use preallocated buffers to store separated events. Those buffers currently don't have their own struct, but since they are basically array of 'structordered_event' objects, we use the first event to hold buffers data - list head, that holds all buffers together:struct ordered_events { ... struct ordered_event *buffer; ... }; struct ordered_event { u64 timestamp; u64 file_offset; union perf_event *event; struct list_head list; };This is quite convoluted and error prone as demonstrated by free-ing issue discovered and fixed by Stephane in here [1].
This patch adds the 'struct
ordered_events_buffer' object, that holds the buffer data and frees it up properly.[1] - https://marc.info/?l=linux-kernel&m=153376761329335&w=2
Reported-by Stephane Eranian <[email protected]>Signed-off-by Jiri Olsa <[email protected]>--- tools/perf/util/ordered-events.c | 82 +++++++++++++++++++++++++++----- tools/perf/util/ordered-events.h | 37 +++++++------- 2 files changed, 90 insertions(+), 29 deletions(-)tools/perf/util/ordered-events.c +38
-4@@ -80,14 +80,20 @@ static union perf_event *dup_event(struct ordered_events *oe,return oe->copy_on_queue ?__dup_event (oe, event) : event; } -static void free_dup_event (struct ordered_events *oe,union perf_event *event) +static void __free_dup_event (struct ordered_events *oe,union perf_event *event) { -if (event && oe->copy_on_queue) { +if (event) { oe->cur_alloc_size -= event->header.size;free (event); } } +static void free_dup_event (struct ordered_events *oe,union perf_event *event) +{ +if (oe->copy_on_queue) +__free_dup_event (oe, event); +} +#define MAX_SAMPLE_BUFFER (64 * 1024 / sizeof(struct ordered_event))static struct ordered_event *alloc_event (struct ordered_events *oe,union perf_event *event)@@ -100,15 +106,43 @@ static struct ordered_event *alloc_event(struct ordered_events *oe,if (!new_event)return NULL ; +/* +* We maintain following scheme of buffers for ordered +* event allocation: +* +* to_free list -> buffer1 (64K) +* buffer2 (64K) +* ... +* +* Each buffer keeps an array of ordered events objects: +* buffer -> event[0] +* event[1] +* ... +* +* Each allocated ordered event is linked to one of +* following lists: +* - time ordered list 'events' +* - list of currently removed events 'cache' +* +* Allocation of the ordered event uses following order +* to get the memory: +* - use recently removed object from 'cache' list +* - use available object in current allocation buffer +* - allocate new buffer if the current buffer is full +* +* Removal of ordered event object moves it from events to +* the cache list. +*/ if (!list_empty (cache)) { new = list_entry(cache->next, struct ordered_event, list);list_del (&new->list); }else if (oe->buffer) { - new = oe->buffer + oe->buffer_idx; + new = &oe->buffer->event[oe->buffer_idx];if (++oe->buffer_idx ==MAX_SAMPLE_BUFFER ) oe->buffer =NULL ; }else if (oe->cur_alloc_size < oe->max_alloc_size) { -size_t size =MAX_SAMPLE_BUFFER *sizeof (*new); +size_t size =sizeof (*oe->buffer) + +MAX_SAMPLE_BUFFER *sizeof (*new); oe->buffer =malloc (size);if (!oe->buffer) {
tools/perf/util/ordered-events.c static struct ordered_event *alloc_event(struct ordered_events *oe, · line 162
Quoted 10 lines
tools/perf/util/ordered-events.c +2
-3@@ -122,9 +156,8 @@ static struct ordered_event *alloc_event(struct ordered_events *oe, oe->cur_alloc_size += size;list_add (&oe->buffer->list, &oe->to_free); -/* First entry is abused to maintain the to_free list. */ - oe->buffer_idx =2 ; - new = oe->buffer +1 ; + oe->buffer_idx =1 ; + new = &oe->buffer->event[0 ]; }else {pr ("allocation limit reached %" PRIu64"B\n" , oe->max_alloc_size);
I am wondering about the usefulness of returning a new_event with new_event->event = NULL
in this case. Don't you need new_event->event? If so, then you need return NULL.
Quoted 33 lines
tools/perf/util/ordered-events.c +19
-6}@@ -300,15 +333,38 @@ void ordered_events__init(struct ordered_events *oe, ordered_events__deliver_t d oe->deliver = deliver; } +static void +ordered_events_buffer__free (struct ordered_events_buffer *buffer, +unsigned int max,struct ordered_events *oe) +{ +if (oe->copy_on_queue) { +unsigned int i; + +for (i =0 ; i < max; i++) +__free_dup_event (oe, buffer->event[i].event); + } + +free (buffer); +} +void ordered_events__free (struct ordered_events *oe) { -while (!list_empty (&oe->to_free)) { -struct ordered_event *event; +struct ordered_events_buffer *buffer, *tmp; - event = list_entry(oe->to_free.next, struct ordered_event, list); -list_del (&event->list); -free_dup_event (oe, event->event); -free (event); +if (list_empty (&oe->to_free)) +return ; + +/*
tools/perf/util/ordered-events.c void ordered_events__init(struct ordered_events *oe, ordered_events__deliver_t d · line 367
Quoted 10 lines
tools/perf/util/ordered-events.c +10
-0+* Current buffer might not have all the events allocated +* yet, we need to free only allocated ones ... +*/ +list_del (&oe->buffer->list); +ordered_events_buffer__free (oe->buffer, oe->buffer_idx, oe); + +/* ... and continue with the rest */ +list_for_each_entry_safe (buffer, tmp, &oe->to_free, list) { +list_del (&buffer->list); +ordered_events_buffer__free (buffer,MAX_SAMPLE_BUFFER , oe);
Here you are saying that if it is on the to_free list and not the current buffer, then necessarily
all the entries have been used and it is safe to use MAX_SAMPLE_BUFFER. Is that right?
Quoted 55 lines
tools/perf/util/ordered-events.c +0
-0} }tools/perf/util/ordered-events.h +21
-17@@ -25,23 +25,28 @@ struct ordered_events;typedef int (*ordered_events__deliver_t )(struct ordered_events *oe,struct ordered_event *event); +struct ordered_events_buffer { +struct list_head list; +struct ordered_event event[0 ]; +}; +struct ordered_events { -u64 last_flush; -u64 next_flush; -u64 max_timestamp; -u64 max_alloc_size; -u64 cur_alloc_size; -struct list_head events; -struct list_head cache; -struct list_head to_free; -struct ordered_event *buffer; -struct ordered_event *last; -ordered_events__deliver_t deliver; -int buffer_idx; -unsigned int nr_events; -enum oe_flush last_flush_type; -u32 nr_unordered_events; -bool copy_on_queue; +u64 last_flush; +u64 next_flush; +u64 max_timestamp; +u64 max_alloc_size; +u64 cur_alloc_size; +struct list_head events; +struct list_head cache; +struct list_head to_free; +struct ordered_events_buffer *buffer; +struct ordered_event *last; +ordered_events__deliver_t deliver; +int buffer_idx; +unsigned int nr_events; +enum oe_flush last_flush_type; +u32 nr_unordered_events; +bool copy_on_queue; }; int ordered_events__queue(struct ordered_events *oe, union perf_event *event, --2.17.1
PATCH v3 1/1 perf tools: Add struct ordered_events_buffer layer
On Mon, Aug 27, 2018 at 08:24:56AM -0700, Stephane Eranian wrote:
SNIP
Quoted 9 lines
Review
Quoted 7 lines
(quoted diff) +2
-3- /* First entry is abused to maintain the to_free list. */ - oe->buffer_idx = 2; - new = oe->buffer + 1; + oe->buffer_idx = 1; + new = &oe->buffer->event[0]; } else { pr("allocation limit reached %" PRIu64 "B\n", oe->max_alloc_size);
Quoted 3 lines
I am wondering about the usefulness of returning a
new_eventwithnew_event->event= NULL
in this case. Don't you neednew_event->event? If so, then you need return NULL.
yep, that's a bug.. with new being NULL in here,
we'd get a crash anyway.. so 'return NULL;' it is
SNIP
Quoted 11 lines
Review
Quoted 9 lines
(quoted diff) +9
-0+ * yet, we need to free only allocated ones ... + */ + list_del(&oe->buffer->list); + ordered_events_buffer__free(oe->buffer, oe->buffer_idx, oe); + + /* ... and continue with the rest */ + list_for_each_entry_safe(buffer, tmp, &oe->to_free, list) { + list_del(&buffer->list); + ordered_events_buffer__free(buffer, MAX_SAMPLE_BUFFER, oe);
Quoted 4 lines
Here you are saying that if it is on the
to_freelist and not the current buffer, then necessarily
all the entries have been used and it is safe to useMAX_SAMPLE_BUFFER. Is that right?
yes, at this point they either holds an event or NULL so it's free to call __free_dup_event on it
thanks, v3 attached
added also Namhyung's ack, as the 'return NULL' change wasn't related to the v2 changes
jirka
---
When ordering events, we use preallocated buffers to store separated events. Those buffers currently don't have their own struct, but since they are basically array of 'struct ordered_event' objects, we use the first event to hold buffers data - list head, that holds all buffers together:
struct ordered_events {
...
struct ordered_event *buffer;
...
};
struct ordered_event {
u64 timestamp;
u64 file_offset;
union perf_event *event;
struct list_head list;
};This is quite convoluted and error prone as demonstrated by free-ing issue discovered and fixed by Stephane in here [1].
This patch adds the 'struct ordered_events_buffer' object, that holds the buffer data and frees it up properly.
[1] - https://marc.info/?l=linux-kernel&m=153376761329335&w=2
--- tools/perf/util/ordered-events.c | 83 +++++++++++++++++++++++++++----- tools/perf/util/ordered-events.h | 37 ++++++++------ 2 files changed, 91 insertions(+), 29 deletions(-)
tools/perf/util/ordered-events.c +70 -13
@@ -80,14 +80,20 @@ static union perf_event *dup_event(struct ordered_events *oe,return oe->copy_on_queue ?__dup_event (oe, event) : event; } -static void free_dup_event (struct ordered_events *oe,union perf_event *event) +static void __free_dup_event (struct ordered_events *oe,union perf_event *event) { -if (event && oe->copy_on_queue) { +if (event) { oe->cur_alloc_size -= event->header.size;free (event); } } +static void free_dup_event (struct ordered_events *oe,union perf_event *event) +{ +if (oe->copy_on_queue) +__free_dup_event (oe, event); +} +#define MAX_SAMPLE_BUFFER (64 * 1024 / sizeof(struct ordered_event))static struct ordered_event *alloc_event (struct ordered_events *oe,union perf_event *event)
@@ -100,15 +106,43 @@ static struct ordered_event *alloc_event(struct ordered_events *oe,if (!new_event)return NULL ; +/* +* We maintain following scheme of buffers for ordered +* event allocation: +* +* to_free list -> buffer1 (64K) +* buffer2 (64K) +* ... +* +* Each buffer keeps an array of ordered events objects: +* buffer -> event[0] +* event[1] +* ... +* +* Each allocated ordered event is linked to one of +* following lists: +* - time ordered list 'events' +* - list of currently removed events 'cache' +* +* Allocation of the ordered event uses following order +* to get the memory: +* - use recently removed object from 'cache' list +* - use available object in current allocation buffer +* - allocate new buffer if the current buffer is full +* +* Removal of ordered event object moves it from events to +* the cache list. +*/ if (!list_empty (cache)) { new = list_entry(cache->next, struct ordered_event, list);list_del (&new->list); }else if (oe->buffer) { - new = oe->buffer + oe->buffer_idx; + new = &oe->buffer->event[oe->buffer_idx];if (++oe->buffer_idx ==MAX_SAMPLE_BUFFER ) oe->buffer =NULL ; }else if (oe->cur_alloc_size < oe->max_alloc_size) { -size_t size =MAX_SAMPLE_BUFFER *sizeof (*new); +size_t size =sizeof (*oe->buffer) + +MAX_SAMPLE_BUFFER *sizeof (*new); oe->buffer =malloc (size);if (!oe->buffer) {
@@ -122,11 +156,11 @@ static struct ordered_event *alloc_event(struct ordered_events *oe, oe->cur_alloc_size += size;list_add (&oe->buffer->list, &oe->to_free); -/* First entry is abused to maintain the to_free list. */ - oe->buffer_idx =2 ; - new = oe->buffer +1 ; + oe->buffer_idx =1 ; + new = &oe->buffer->event[0 ]; }else {pr ("allocation limit reached %" PRIu64"B\n" , oe->max_alloc_size); +return NULL ; } new->event = new_event;
@@ -300,15 +334,38 @@ void ordered_events__init(struct ordered_events *oe, ordered_events__deliver_t d oe->deliver = deliver; } +static void +ordered_events_buffer__free (struct ordered_events_buffer *buffer, +unsigned int max,struct ordered_events *oe) +{ +if (oe->copy_on_queue) { +unsigned int i; + +for (i =0 ; i < max; i++) +__free_dup_event (oe, buffer->event[i].event); + } + +free (buffer); +} +void ordered_events__free (struct ordered_events *oe) { -while (!list_empty (&oe->to_free)) { -struct ordered_event *event; +struct ordered_events_buffer *buffer, *tmp; - event = list_entry(oe->to_free.next, struct ordered_event, list); -list_del (&event->list); -free_dup_event (oe, event->event); -free (event); +if (list_empty (&oe->to_free)) +return ; + +/* +* Current buffer might not have all the events allocated +* yet, we need to free only allocated ones ... +*/ +list_del (&oe->buffer->list); +ordered_events_buffer__free (oe->buffer, oe->buffer_idx, oe); + +/* ... and continue with the rest */ +list_for_each_entry_safe (buffer, tmp, &oe->to_free, list) { +list_del (&buffer->list); +ordered_events_buffer__free (buffer,MAX_SAMPLE_BUFFER , oe); } }
tools/perf/util/ordered-events.h +21 -16
@@ -25,23 +25,28 @@ struct ordered_events;typedef int (*ordered_events__deliver_t )(struct ordered_events *oe,struct ordered_event *event); +struct ordered_events_buffer { +struct list_head list; +struct ordered_event event[0 ]; +}; +struct ordered_events { -u64 last_flush; -u64 next_flush; -u64 max_timestamp; -u64 max_alloc_size; -u64 cur_alloc_size; -struct list_head events; -struct list_head cache; -struct list_head to_free; -struct ordered_event *buffer; -struct ordered_event *last; -ordered_events__deliver_t deliver; -int buffer_idx; -unsigned int nr_events; -enum oe_flush last_flush_type; -u32 nr_unordered_events; -bool copy_on_queue; +u64 last_flush; +u64 next_flush; +u64 max_timestamp; +u64 max_alloc_size; +u64 cur_alloc_size; +struct list_head events; +struct list_head cache; +struct list_head to_free; +struct ordered_events_buffer *buffer; +struct ordered_event *last; +ordered_events__deliver_t deliver; +int buffer_idx; +unsigned int nr_events; +enum oe_flush last_flush_type; +u32 nr_unordered_events; +bool copy_on_queue; }; int ordered_events__queue(struct ordered_events *oe, union perf_event *event,
Signature · 1 line
2.17.1
Re: [PATCHv3] perf tools: Add struct ordered_events_buffer layer
Quoted 41 lines · Jiri Olsa
On Mon, Aug 27, 2018 at 08:24:56AM -0700, Stephane Eranian wrote:
SNIP
Quoted 9 lines
tools/perf/util/ordered-events.c static struct ordered_event *alloc_event(struct ordered_events *oe, · line 162
Quoted 7 lines
tools/perf/util/ordered-events.c +2
-3-/* First entry is abused to maintain the to_free list. */ - oe->buffer_idx =2 ; - new = oe->buffer +1 ; + oe->buffer_idx =1 ; + new = &oe->buffer->event[0 ]; }else {pr ("allocation limit reached %" PRIu64"B\n" , oe->max_alloc_size);Quoted 3 lines
I am wondering about the usefulness of returning a
new_eventwithnew_event->event= NULL
in this case. Don't you neednew_event->event? If so, then you need return NULL.yep, that's a bug.. with new being NULL in here,
we'd get a crash anyway.. so 'return NULL;' it isSNIP
Quoted 11 lines
tools/perf/util/ordered-events.c void ordered_events__init(struct ordered_events *oe, ordered_events__deliver_t d · line 368
Quoted 9 lines
tools/perf/util/ordered-events.c +9
-0+* yet, we need to free only allocated ones ... +*/ +list_del (&oe->buffer->list); +ordered_events_buffer__free (oe->buffer, oe->buffer_idx, oe); + +/* ... and continue with the rest */ +list_for_each_entry_safe (buffer, tmp, &oe->to_free, list) { +list_del (&buffer->list); +ordered_events_buffer__free (buffer,MAX_SAMPLE_BUFFER , oe);Quoted 4 lines
Here you are saying that if it is on the
to_freelist and not the current buffer, then necessarily
all the entries have been used and it is safe to useMAX_SAMPLE_BUFFER. Is that right?yes, at this point they either holds an event or NULL so it's free to call
__free_dup_eventon it
Quoted 6 lines
thanks, v3 attached
added also Namhyung's ack, as the 'return NULL' change wasn't related to the v2 changes
jirka
Stephane,
any comments to v3 version?
thanks,
jirka
Quoted 223 lines
---
When ordering events, we use preallocated buffers to store separated events. Those buffers currently don't have their own struct, but since they are basically array of 'structordered_event' objects, we use the first event to hold buffers data - list head, that holds all buffers together:struct ordered_events { ... struct ordered_event *buffer; ... }; struct ordered_event { u64 timestamp; u64 file_offset; union perf_event *event; struct list_head list; };This is quite convoluted and error prone as demonstrated by free-ing issue discovered and fixed by Stephane in here [1].
This patch adds the 'struct
ordered_events_buffer' object, that holds the buffer data and frees it up properly.[1] - https://marc.info/?l=linux-kernel&m=153376761329335&w=2
Reported-by Stephane Eranian <[email protected]>Acked-by Namhyung Kim <[email protected]>Signed-off-by Jiri Olsa <[email protected]>--- tools/perf/util/ordered-events.c | 83 +++++++++++++++++++++++++++----- tools/perf/util/ordered-events.h | 37 ++++++++------ 2 files changed, 91 insertions(+), 29 deletions(-)tools/perf/util/ordered-events.c +70
-13@@ -80,14 +80,20 @@ static union perf_event *dup_event(struct ordered_events *oe,return oe->copy_on_queue ?__dup_event (oe, event) : event; } -static void free_dup_event (struct ordered_events *oe,union perf_event *event) +static void __free_dup_event (struct ordered_events *oe,union perf_event *event) { -if (event && oe->copy_on_queue) { +if (event) { oe->cur_alloc_size -= event->header.size;free (event); } } +static void free_dup_event (struct ordered_events *oe,union perf_event *event) +{ +if (oe->copy_on_queue) +__free_dup_event (oe, event); +} +#define MAX_SAMPLE_BUFFER (64 * 1024 / sizeof(struct ordered_event))static struct ordered_event *alloc_event (struct ordered_events *oe,union perf_event *event)@@ -100,15 +106,43 @@ static struct ordered_event *alloc_event(struct ordered_events *oe,if (!new_event)return NULL ; +/* +* We maintain following scheme of buffers for ordered +* event allocation: +* +* to_free list -> buffer1 (64K) +* buffer2 (64K) +* ... +* +* Each buffer keeps an array of ordered events objects: +* buffer -> event[0] +* event[1] +* ... +* +* Each allocated ordered event is linked to one of +* following lists: +* - time ordered list 'events' +* - list of currently removed events 'cache' +* +* Allocation of the ordered event uses following order +* to get the memory: +* - use recently removed object from 'cache' list +* - use available object in current allocation buffer +* - allocate new buffer if the current buffer is full +* +* Removal of ordered event object moves it from events to +* the cache list. +*/ if (!list_empty (cache)) { new = list_entry(cache->next, struct ordered_event, list);list_del (&new->list); }else if (oe->buffer) { - new = oe->buffer + oe->buffer_idx; + new = &oe->buffer->event[oe->buffer_idx];if (++oe->buffer_idx ==MAX_SAMPLE_BUFFER ) oe->buffer =NULL ; }else if (oe->cur_alloc_size < oe->max_alloc_size) { -size_t size =MAX_SAMPLE_BUFFER *sizeof (*new); +size_t size =sizeof (*oe->buffer) + +MAX_SAMPLE_BUFFER *sizeof (*new); oe->buffer =malloc (size);if (!oe->buffer) {@@ -122,11 +156,11 @@ static struct ordered_event *alloc_event(struct ordered_events *oe, oe->cur_alloc_size += size;list_add (&oe->buffer->list, &oe->to_free); -/* First entry is abused to maintain the to_free list. */ - oe->buffer_idx =2 ; - new = oe->buffer +1 ; + oe->buffer_idx =1 ; + new = &oe->buffer->event[0 ]; }else {pr ("allocation limit reached %" PRIu64"B\n" , oe->max_alloc_size); +return NULL ; } new->event = new_event;@@ -300,15 +334,38 @@ void ordered_events__init(struct ordered_events *oe, ordered_events__deliver_t d oe->deliver = deliver; } +static void +ordered_events_buffer__free (struct ordered_events_buffer *buffer, +unsigned int max,struct ordered_events *oe) +{ +if (oe->copy_on_queue) { +unsigned int i; + +for (i =0 ; i < max; i++) +__free_dup_event (oe, buffer->event[i].event); + } + +free (buffer); +} +void ordered_events__free (struct ordered_events *oe) { -while (!list_empty (&oe->to_free)) { -struct ordered_event *event; +struct ordered_events_buffer *buffer, *tmp; - event = list_entry(oe->to_free.next, struct ordered_event, list); -list_del (&event->list); -free_dup_event (oe, event->event); -free (event); +if (list_empty (&oe->to_free)) +return ; + +/* +* Current buffer might not have all the events allocated +* yet, we need to free only allocated ones ... +*/ +list_del (&oe->buffer->list); +ordered_events_buffer__free (oe->buffer, oe->buffer_idx, oe); + +/* ... and continue with the rest */ +list_for_each_entry_safe (buffer, tmp, &oe->to_free, list) { +list_del (&buffer->list); +ordered_events_buffer__free (buffer,MAX_SAMPLE_BUFFER , oe); } }tools/perf/util/ordered-events.h +21
-16@@ -25,23 +25,28 @@ struct ordered_events;typedef int (*ordered_events__deliver_t )(struct ordered_events *oe,struct ordered_event *event); +struct ordered_events_buffer { +struct list_head list; +struct ordered_event event[0 ]; +}; +struct ordered_events { -u64 last_flush; -u64 next_flush; -u64 max_timestamp; -u64 max_alloc_size; -u64 cur_alloc_size; -struct list_head events; -struct list_head cache; -struct list_head to_free; -struct ordered_event *buffer; -struct ordered_event *last; -ordered_events__deliver_t deliver; -int buffer_idx; -unsigned int nr_events; -enum oe_flush last_flush_type; -u32 nr_unordered_events; -bool copy_on_queue; +u64 last_flush; +u64 next_flush; +u64 max_timestamp; +u64 max_alloc_size; +u64 cur_alloc_size; +struct list_head events; +struct list_head cache; +struct list_head to_free; +struct ordered_events_buffer *buffer; +struct ordered_event *last; +ordered_events__deliver_t deliver; +int buffer_idx; +unsigned int nr_events; +enum oe_flush last_flush_type; +u32 nr_unordered_events; +bool copy_on_queue; }; int ordered_events__queue(struct ordered_events *oe, union perf_event *event,Signature · 1 line
2.17.1
Stephane Eranian
Tested-byRe: [PATCHv3] perf tools: Add struct ordered_events_buffer layer
Jiri,
Quoted 40 lines · Jiri Olsa
Quoted 38 lines · Jiri Olsa
On Mon, Aug 27, 2018 at 08:24:56AM -0700, Stephane Eranian wrote:
SNIP
Quoted 9 lines
tools/perf/util/ordered-events.c static struct ordered_event *alloc_event(struct ordered_events *oe, · line 162
Quoted 7 lines
tools/perf/util/ordered-events.c +2
-3-/* First entry is abused to maintain the to_free list. */ - oe->buffer_idx =2 ; - new = oe->buffer +1 ; + oe->buffer_idx =1 ; + new = &oe->buffer->event[0 ]; }else {pr ("allocation limit reached %" PRIu64"B\n" , oe->max_alloc_size);Quoted 3 lines
I am wondering about the usefulness of returning a
new_eventwithnew_event->event= NULL
in this case. Don't you neednew_event->event? If so, then you need return NULL.yep, that's a bug.. with new being NULL in here,
we'd get a crash anyway.. so 'return NULL;' it isSNIP
Quoted 15 lines
tools/perf/util/ordered-events.c void ordered_events__init(struct ordered_events *oe, ordered_events__deliver_t d · line 368
Quoted 9 lines
tools/perf/util/ordered-events.c +9
-0+* yet, we need to free only allocated ones ... +*/ +list_del (&oe->buffer->list); +ordered_events_buffer__free (oe->buffer, oe->buffer_idx, oe); + +/* ... and continue with the rest */ +list_for_each_entry_safe (buffer, tmp, &oe->to_free, list) { +list_del (&buffer->list); +ordered_events_buffer__free (buffer,MAX_SAMPLE_BUFFER , oe);Here you are saying that if it is on the
to_freelist and not the current buffer, then necessarily
all the entries have been used and it is safe to useMAX_SAMPLE_BUFFER. Is that right?
Quoted 8 lines
Quoted 8 lines
yes, at this point they either holds an event or NULL so it's free to call
__free_dup_eventon itthanks, v3 attached
added also Namhyung's ack, as the 'return NULL' change wasn't related to the v2 changes
I think the code is correct now for the issue related to uninitialized pointer. But there is still one problem I found stressing the code with max_alloc_size. The way the following is written:
if (!list_empty(cache)) {
new = list_entry(cache->next, struct ordered_event, list);
list_del(&new->list);
} else if (oe->buffer) {
new = oe->buffer + oe->buffer_idx;
if (++oe->buffer_idx == MAX_SAMPLE_BUFFER)
oe->buffer = NULL;
} else if (oe->cur_alloc_size < oe->max_alloc_size) {
size_t size = sizeof(*oe->buffer) MAX_SAMPLE_BUFFER *sizeof(*new);
oe->buffer = malloc(size);
if (!oe->buffer) {
free_dup_event(oe, new_event);
return NULL;
}
pr("alloc size %" PRIu64 "B (+%zu), max %" PRIu64 "B\n",
oe->cur_alloc_size, size, oe->max_alloc_size);
oe->cur_alloc_size += size;You can end up with oe->cur_alloc_size > oe->max_alloc_size in case the max limit is
really low (< size_t size = sizeof (*oe->buffer) + MAX_SAMPLE_BUFFER * sizeof(*new);
So I think to make sure you can never allocate more than the max, you have to do:
size_t size = sizeof(*oe->buffer) MAX_SAMPLE_BUFFER * sizeof(*new);
if (!list_empty(cache)) {
new = list_entry(cache->next, struct ordered_event, list);
list_del(&new->list);
} else if (oe->buffer) {
new = oe->buffer + oe->buffer_idx;
if (++oe->buffer_idx == MAX_SAMPLE_BUFFER)
oe->buffer = NULL;
} else if ((oe->cur_alloc_size + size) < oe->max_alloc_size) {Then you will never allocate more than the max.
I think with this change, we are okay.
Quoted 231 lines
jirka
Stephane,
any comments to v3 version?thanks,
jirkaQuoted 223 lines
---
When ordering events, we use preallocated buffers to store separated events. Those buffers currently don't have their own struct, but since they are basically array of 'structordered_event' objects, we use the first event to hold buffers data - list head, that holds all buffers together:struct ordered_events { ... struct ordered_event *buffer; ... }; struct ordered_event { u64 timestamp; u64 file_offset; union perf_event *event; struct list_head list; };This is quite convoluted and error prone as demonstrated by free-ing issue discovered and fixed by Stephane in here [1].
This patch adds the 'struct
ordered_events_buffer' object, that holds the buffer data and frees it up properly.[1] - https://marc.info/?l=linux-kernel&m=153376761329335&w=2
Reported-by Stephane Eranian <[email protected]>Acked-by Namhyung Kim <[email protected]>Signed-off-by Jiri Olsa <[email protected]>--- tools/perf/util/ordered-events.c | 83 +++++++++++++++++++++++++++----- tools/perf/util/ordered-events.h | 37 ++++++++------ 2 files changed, 91 insertions(+), 29 deletions(-)tools/perf/util/ordered-events.c +70
-13@@ -80,14 +80,20 @@ static union perf_event *dup_event(struct ordered_events *oe,return oe->copy_on_queue ?__dup_event (oe, event) : event; } -static void free_dup_event (struct ordered_events *oe,union perf_event *event) +static void __free_dup_event (struct ordered_events *oe,union perf_event *event) { -if (event && oe->copy_on_queue) { +if (event) { oe->cur_alloc_size -= event->header.size;free (event); } } +static void free_dup_event (struct ordered_events *oe,union perf_event *event) +{ +if (oe->copy_on_queue) +__free_dup_event (oe, event); +} +#define MAX_SAMPLE_BUFFER (64 * 1024 / sizeof(struct ordered_event))static struct ordered_event *alloc_event (struct ordered_events *oe,union perf_event *event)@@ -100,15 +106,43 @@ static struct ordered_event *alloc_event(struct ordered_events *oe,if (!new_event)return NULL ; +/* +* We maintain following scheme of buffers for ordered +* event allocation: +* +* to_free list -> buffer1 (64K) +* buffer2 (64K) +* ... +* +* Each buffer keeps an array of ordered events objects: +* buffer -> event[0] +* event[1] +* ... +* +* Each allocated ordered event is linked to one of +* following lists: +* - time ordered list 'events' +* - list of currently removed events 'cache' +* +* Allocation of the ordered event uses following order +* to get the memory: +* - use recently removed object from 'cache' list +* - use available object in current allocation buffer +* - allocate new buffer if the current buffer is full +* +* Removal of ordered event object moves it from events to +* the cache list. +*/ if (!list_empty (cache)) { new = list_entry(cache->next, struct ordered_event, list);list_del (&new->list); }else if (oe->buffer) { - new = oe->buffer + oe->buffer_idx; + new = &oe->buffer->event[oe->buffer_idx];if (++oe->buffer_idx ==MAX_SAMPLE_BUFFER ) oe->buffer =NULL ; }else if (oe->cur_alloc_size < oe->max_alloc_size) { -size_t size =MAX_SAMPLE_BUFFER *sizeof (*new); +size_t size =sizeof (*oe->buffer) + +MAX_SAMPLE_BUFFER *sizeof (*new); oe->buffer =malloc (size);if (!oe->buffer) {@@ -122,11 +156,11 @@ static struct ordered_event *alloc_event(struct ordered_events *oe, oe->cur_alloc_size += size;list_add (&oe->buffer->list, &oe->to_free); -/* First entry is abused to maintain the to_free list. */ - oe->buffer_idx =2 ; - new = oe->buffer +1 ; + oe->buffer_idx =1 ; + new = &oe->buffer->event[0 ]; }else {pr ("allocation limit reached %" PRIu64"B\n" , oe->max_alloc_size); +return NULL ; } new->event = new_event;@@ -300,15 +334,38 @@ void ordered_events__init(struct ordered_events *oe, ordered_events__deliver_t d oe->deliver = deliver; } +static void +ordered_events_buffer__free (struct ordered_events_buffer *buffer, +unsigned int max,struct ordered_events *oe) +{ +if (oe->copy_on_queue) { +unsigned int i; + +for (i =0 ; i < max; i++) +__free_dup_event (oe, buffer->event[i].event); + } + +free (buffer); +} +void ordered_events__free (struct ordered_events *oe) { -while (!list_empty (&oe->to_free)) { -struct ordered_event *event; +struct ordered_events_buffer *buffer, *tmp; - event = list_entry(oe->to_free.next, struct ordered_event, list); -list_del (&event->list); -free_dup_event (oe, event->event); -free (event); +if (list_empty (&oe->to_free)) +return ; + +/* +* Current buffer might not have all the events allocated +* yet, we need to free only allocated ones ... +*/ +list_del (&oe->buffer->list); +ordered_events_buffer__free (oe->buffer, oe->buffer_idx, oe); + +/* ... and continue with the rest */ +list_for_each_entry_safe (buffer, tmp, &oe->to_free, list) { +list_del (&buffer->list); +ordered_events_buffer__free (buffer,MAX_SAMPLE_BUFFER , oe); } }tools/perf/util/ordered-events.h +21
-17@@ -25,23 +25,28 @@ struct ordered_events;typedef int (*ordered_events__deliver_t )(struct ordered_events *oe,struct ordered_event *event); +struct ordered_events_buffer { +struct list_head list; +struct ordered_event event[0 ]; +}; +struct ordered_events { -u64 last_flush; -u64 next_flush; -u64 max_timestamp; -u64 max_alloc_size; -u64 cur_alloc_size; -struct list_head events; -struct list_head cache; -struct list_head to_free; -struct ordered_event *buffer; -struct ordered_event *last; -ordered_events__deliver_t deliver; -int buffer_idx; -unsigned int nr_events; -enum oe_flush last_flush_type; -u32 nr_unordered_events; -bool copy_on_queue; +u64 last_flush; +u64 next_flush; +u64 max_timestamp; +u64 max_alloc_size; +u64 cur_alloc_size; +struct list_head events; +struct list_head cache; +struct list_head to_free; +struct ordered_events_buffer *buffer; +struct ordered_event *last; +ordered_events__deliver_t deliver; +int buffer_idx; +unsigned int nr_events; +enum oe_flush last_flush_type; +u32 nr_unordered_events; +bool copy_on_queue; }; int ordered_events__queue(struct ordered_events *oe, union perf_event *event, --2.17.1
Re: [PATCHv3] perf tools: Add struct ordered_events_buffer layer
On Mon, Sep 03, 2018 at 07:37:56PM -0700, Stephane Eranian wrote:
SNIP
Quoted 44 lines
I think the code is correct now for the issue related to uninitialized pointer. But there is still one problem I found stressing the code with
max_alloc_size. The way the following is written:if (!list_empty(cache)) { new = list_entry(cache->next, struct ordered_event, list); list_del(&new->list); } else if (oe->buffer) { new = oe->buffer + oe->buffer_idx; if (++oe->buffer_idx == MAX_SAMPLE_BUFFER) oe->buffer = NULL; } else if (oe->cur_alloc_size < oe->max_alloc_size) { size_t size = sizeof(*oe->buffer) MAX_SAMPLE_BUFFER *
sizeof(*new);oe->buffer = malloc(size); if (!oe->buffer) { free_dup_event(oe, new_event); return NULL; } pr("alloc size %" PRIu64 "B (+%zu), max %" PRIu64 "B\n", oe->cur_alloc_size, size, oe->max_alloc_size); oe->cur_alloc_size += size;You can end up with
oe->cur_alloc_size>oe->max_alloc_sizein case the max limit is
really low (<size_tsize = sizeof (*oe->buffer) +MAX_SAMPLE_BUFFER*sizeof(*new);
So I think to make sure you can never allocate more than the max, you have to do:size_t size = sizeof(*oe->buffer) MAX_SAMPLE_BUFFER * sizeof(*new); if (!list_empty(cache)) { new = list_entry(cache->next, struct ordered_event, list); list_del(&new->list); } else if (oe->buffer) { new = oe->buffer + oe->buffer_idx; if (++oe->buffer_idx == MAX_SAMPLE_BUFFER) oe->buffer = NULL; } else if ((oe->cur_alloc_size + size) < oe->max_alloc_size) {
Quoted 3 lines
Then you will never allocate more than the max.
I think with this change, we are okay.Tested-by Stephane Eranian <[email protected]>
yep, makes sense.. something like below then
I'll post it on top of the previous patch
thanks,
jirka
---
tools/perf/util/ordered-events.c +4 -4
@@ -101,6 +101,7 @@ static struct ordered_event *alloc_event(struct ordered_events *oe,struct list_head *cache = &oe->cache;struct ordered_event *new =NULL ;union perf_event *new_event; +size_t size; new_event =dup_event (oe, event);if (!new_event)
@@ -133,6 +134,8 @@ static struct ordered_event *alloc_event(struct ordered_events *oe,* Removal of ordered event object moves it from events to * the cache list. */ + size =sizeof (*oe->buffer) +MAX_SAMPLE_BUFFER *sizeof (*new); +if (!list_empty (cache)) { new = list_entry(cache->next, struct ordered_event, list);list_del (&new->list);
@@ -140,10 +143,7 @@ static struct ordered_event *alloc_event(struct ordered_events *oe, new = &oe->buffer->event[oe->buffer_idx];if (++oe->buffer_idx ==MAX_SAMPLE_BUFFER ) oe->buffer =NULL ; - }else if (oe->cur_alloc_size < oe->max_alloc_size) { -size_t size =sizeof (*oe->buffer) + -MAX_SAMPLE_BUFFER *sizeof (*new); - + }else if ((oe->cur_alloc_size + size) < oe->max_alloc_size) { oe->buffer =malloc (size);if (!oe->buffer) {free_dup_event (oe, new_event);
Re: [PATCHv3] perf tools: Add struct ordered_events_buffer layer
Quoted 49 lines · Jiri Olsa
On Mon, Sep 03, 2018 at 07:37:56PM -0700, Stephane Eranian wrote:
SNIP
Quoted 44 lines
I think the code is correct now for the issue related to uninitialized pointer. But there is still one problem I found stressing the code with
max_alloc_size. The way the following is written:if (!list_empty(cache)) { new = list_entry(cache->next, struct ordered_event, list); list_del(&new->list); } else if (oe->buffer) { new = oe->buffer + oe->buffer_idx; if (++oe->buffer_idx == MAX_SAMPLE_BUFFER) oe->buffer = NULL; } else if (oe->cur_alloc_size < oe->max_alloc_size) { size_t size = sizeof(*oe->buffer) MAX_SAMPLE_BUFFER *
sizeof(*new);oe->buffer = malloc(size); if (!oe->buffer) { free_dup_event(oe, new_event); return NULL; } pr("alloc size %" PRIu64 "B (+%zu), max %" PRIu64 "B\n", oe->cur_alloc_size, size, oe->max_alloc_size); oe->cur_alloc_size += size;You can end up with
oe->cur_alloc_size>oe->max_alloc_sizein case the max limit is
really low (<size_tsize = sizeof (*oe->buffer) +MAX_SAMPLE_BUFFER*sizeof(*new);
So I think to make sure you can never allocate more than the max, you have to do:size_t size = sizeof(*oe->buffer) MAX_SAMPLE_BUFFER * sizeof(*new); if (!list_empty(cache)) { new = list_entry(cache->next, struct ordered_event, list); list_del(&new->list); } else if (oe->buffer) { new = oe->buffer + oe->buffer_idx; if (++oe->buffer_idx == MAX_SAMPLE_BUFFER) oe->buffer = NULL; } else if ((oe->cur_alloc_size + size) < oe->max_alloc_size) {
Quoted 7 lines
Quoted 3 lines
Then you will never allocate more than the max.
I think with this change, we are okay.Tested-by Stephane Eranian <[email protected]>yep, makes sense.. something like below then
I'll post it on top of the previous patch
Yes. This works.
Thanks.
Quoted 38 lines
thanks,
jirka---
tools/perf/util/ordered-events.c +4
-4@@ -101,6 +101,7 @@ static struct ordered_event *alloc_event(struct ordered_events *oe,struct list_head *cache = &oe->cache;struct ordered_event *new =NULL ;union perf_event *new_event; +size_t size; new_event =dup_event (oe, event);if (!new_event)@@ -133,6 +134,8 @@ static struct ordered_event *alloc_event(struct ordered_events *oe,* Removal of ordered event object moves it from events to * the cache list. */ + size =sizeof (*oe->buffer) +MAX_SAMPLE_BUFFER *sizeof (*new); +if (!list_empty (cache)) { new = list_entry(cache->next, struct ordered_event, list);list_del (&new->list);@@ -140,10 +143,7 @@ static struct ordered_event *alloc_event(struct ordered_events *oe, new = &oe->buffer->event[oe->buffer_idx];if (++oe->buffer_idx ==MAX_SAMPLE_BUFFER ) oe->buffer =NULL ; - }else if (oe->cur_alloc_size < oe->max_alloc_size) { -size_t size =sizeof (*oe->buffer) + -MAX_SAMPLE_BUFFER *sizeof (*new); - + }else if ((oe->cur_alloc_size + size) < oe->max_alloc_size) { oe->buffer =malloc (size);if (!oe->buffer) {free_dup_event (oe, new_event);