Skip to content

Commit

Permalink
perf ordered_events: Add 'struct ordered_events_buffer' layer
Browse files Browse the repository at this point in the history
When ordering events, we use preallocated buffers to store separate
events.  Those buffers currently don't have their own struct, but since
they are basically an 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

Reported-by: Stephane Eranian <[email protected]>
Signed-off-by: Jiri Olsa <[email protected]>
Tested-by: Stephane Eranian <[email protected]>
Acked-by: Namhyung Kim <[email protected]>
Cc: Alexander Shishkin <[email protected]>
Cc: Andi Kleen <[email protected]>
Cc: David Ahern <[email protected]>
Cc: Peter Zijlstra <[email protected]>
Link: http://lkml.kernel.org/r/[email protected]
Signed-off-by: Arnaldo Carvalho de Melo <[email protected]>
  • Loading branch information
olsajiri authored and acmel committed Sep 19, 2018
1 parent 2e85d59 commit d5ceb62
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 29 deletions.
83 changes: 70 additions & 13 deletions tools/perf/util/ordered-events.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -100,15 +106,43 @@ static struct ordered_event *alloc_event(struct ordered_events *oe,
if (!new_event)
return NULL;

/*
* We maintain the 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 the 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) {
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
}

Expand Down
37 changes: 21 additions & 16 deletions tools/perf/util/ordered-events.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit d5ceb62

Please sign in to comment.