blob: 3b7e81f5e1e554886338bd00bdf6b101f03773a1 [file] [log] [blame]
/*
* Copyright (C) 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License (not later!)
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <stdlib.h>
#include <string.h>
#include "trace-graph.h"
#include "trace-filter.h"
#include "trace-local.h"
#define RED 0xff
#define GREEN (0xff<<16)
struct task_plot_info {
int pid;
struct cpu_data *cpu_data;
struct pevent_record **last_records;
unsigned long long last_time;
unsigned long long wake_time;
unsigned long long display_wake_time;
int wake_color;
int last_cpu;
gboolean in_irq;
};
static void convert_nano(unsigned long long time, unsigned long *sec,
unsigned long *usec)
{
*sec = time / 1000000000ULL;
*usec = (time / 1000) % 1000000;
}
static gint hash_pid(gint val)
{
/* idle always gets black */
if (!val)
return 0;
return trace_hash(val);
}
static int hash_cpu(int cpu)
{
cpu = (cpu << 3) + cpu * 21;
return trace_hash(cpu);
}
static gboolean is_running(struct graph_info *ginfo, struct pevent_record *record)
{
unsigned long long val;
int id;
id = pevent_data_type(ginfo->pevent, record);
if (id != ginfo->event_sched_switch_id)
return FALSE;
pevent_read_number_field(ginfo->event_prev_state, record->data, &val);
return val & ((1 << 11) - 1)? FALSE : TRUE;
}
static gboolean record_matches_pid(struct graph_info *ginfo,
struct pevent_record *record, int match_pid,
int *pid, int *sched_pid,
gboolean *is_sched,
gboolean *wakeup)
{
const char *comm;
*is_sched = FALSE;
*wakeup = FALSE;
*pid = pevent_data_pid(ginfo->pevent, record);
*sched_pid = *pid;
if (trace_graph_check_sched_switch(ginfo, record, sched_pid, &comm)) {
if (*pid == match_pid || *sched_pid == match_pid) {
*is_sched = TRUE;
return TRUE;
}
}
if (trace_graph_check_sched_wakeup(ginfo, record, sched_pid)) {
if (*sched_pid == match_pid) {
*wakeup = TRUE;
return TRUE;
}
}
if (*pid == match_pid)
return TRUE;
return FALSE;
}
static void set_cpu_to_time(int cpu, struct graph_info *ginfo, unsigned long long time)
{
struct pevent_record *record;
tracecmd_set_cpu_to_timestamp(ginfo->handle, cpu, time);
while ((record = tracecmd_read_data(ginfo->handle, cpu))) {
if (record->ts >= time)
break;
free_record(record);
}
if (record) {
tracecmd_set_cursor(ginfo->handle, cpu, record->offset);
free_record(record);
} else
tracecmd_set_cpu_to_timestamp(ginfo->handle, cpu, time);
}
static void set_cpus_to_time(struct graph_info *ginfo, unsigned long long time)
{
int cpu;
for (cpu = 0; cpu < ginfo->cpus; cpu++)
set_cpu_to_time(cpu, ginfo, time);
}
static int task_plot_match_time(struct graph_info *ginfo, struct graph_plot *plot,
unsigned long long time)
{
struct task_plot_info *task_info = plot->private;
struct pevent_record *record = NULL;
gboolean is_wakeup;
gboolean is_sched;
gboolean match;
int rec_pid;
int sched_pid;
int next_cpu;
int pid;
int ret = 0;
pid = task_info->pid;
set_cpus_to_time(ginfo, time);
do {
free_record(record);
record = tracecmd_read_next_data(ginfo->handle, &next_cpu);
if (!record)
return 0;
match = record_matches_pid(ginfo, record, pid, &rec_pid,
&sched_pid, &is_sched, &is_wakeup);
/* Use +1 to make sure we have a match first */
} while ((!match && record->ts < time + 1) ||
(match && record->ts < time));
if (record && record->ts == time)
ret = 1;
free_record(record);
return ret;
}
struct offset_cache {
guint64 *offsets;
};
static struct offset_cache *save_offsets(struct graph_info *ginfo)
{
struct offset_cache *offsets;
struct pevent_record *record;
int cpu;
offsets = malloc_or_die(sizeof(*offsets));
offsets->offsets = malloc_or_die(sizeof(*offsets->offsets) * ginfo->cpus);
memset(offsets->offsets, 0, sizeof(*offsets->offsets) * ginfo->cpus);
for (cpu = 0; cpu < ginfo->cpus; cpu++) {
record = tracecmd_peek_data(ginfo->handle, cpu);
if (record)
offsets->offsets[cpu] = record->offset;
}
return offsets;
}
static void restore_offsets(struct graph_info *ginfo, struct offset_cache *offsets)
{
struct pevent_record *record;
int cpu;
for (cpu = 0; cpu < ginfo->cpus; cpu++) {
if (offsets->offsets[cpu])
tracecmd_set_cursor(ginfo->handle, cpu, offsets->offsets[cpu]);
else {
/* end of cpu, make sure it stays the end */
record = tracecmd_read_cpu_last(ginfo->handle, cpu);
free_record(record);
}
}
free(offsets->offsets);
free(offsets);
}
static struct pevent_record *
find_record(struct graph_info *ginfo, gint pid, guint64 time)
{
struct pevent_record *record = NULL;
gboolean is_wakeup;
gboolean is_sched;
gboolean match;
int sched_pid;
int rec_pid;
int next_cpu;
set_cpus_to_time(ginfo, time);
do {
free_record(record);
record = tracecmd_read_next_data(ginfo->handle, &next_cpu);
if (!record)
return NULL;
match = record_matches_pid(ginfo, record, pid, &rec_pid,
&sched_pid, &is_sched, &is_wakeup);
/* Use +1 to make sure we have a match first */
} while (!(record->ts > time && match));
return record;
}
static int task_plot_display_last_event(struct graph_info *ginfo,
struct graph_plot *plot,
struct trace_seq *s,
unsigned long long time)
{
struct task_plot_info *task_info = plot->private;
struct event_format *event;
struct pevent_record *record;
struct offset_cache *offsets;
gboolean is_sched;
gboolean is_wakeup;
int sched_pid;
int rec_pid;
int pid;
int type;
pid = task_info->pid;
/*
* Get the next record so we know can save its offset and
* reset the cursor, not to mess up the plotting
*/
offsets = save_offsets(ginfo);
record = find_record(ginfo, pid, time);
restore_offsets(ginfo, offsets);
if (!record)
return 0;
record_matches_pid(ginfo, record, pid, &rec_pid,
&sched_pid, &is_sched, &is_wakeup);
if (is_sched) {
if (sched_pid == pid) {
if (task_info->display_wake_time) {
trace_seq_printf(s, "sched_switch\n"
"CPU %d: lat: %.3fus\n",
record->cpu,
(double)(record->ts -
task_info->display_wake_time) / 1000.0);
task_info->display_wake_time = 0;
} else {
trace_seq_printf(s, "sched_switch\n"
"CPU %d\n",
record->cpu);
}
} else {
trace_seq_printf(s, "sched_switch\n"
"CPU %d %s-%d\n",
record->cpu,
pevent_data_comm_from_pid(ginfo->pevent, pid),
pid);
}
} else {
/* Must have the record we want */
type = pevent_data_type(ginfo->pevent, record);
event = pevent_data_event_from_type(ginfo->pevent, type);
if (pid == rec_pid)
trace_seq_printf(s, "CPU %d\n%s\n",
record->cpu, event->name);
else
trace_seq_printf(s, "%s-%d\n%s\n",
pevent_data_comm_from_pid(ginfo->pevent, rec_pid),
rec_pid, event->name);
}
free_record(record);
return 1;
}
static void task_plot_start(struct graph_info *ginfo, struct graph_plot *plot,
unsigned long long time)
{
struct task_plot_info *task_info = plot->private;
memset(task_info->last_records, 0, sizeof(struct pevent_record *) * ginfo->cpus);
task_info->last_time = 0ULL;
task_info->last_cpu = -1;
task_info->wake_time = 0ULL;
task_info->display_wake_time = 0ULL;
task_info->in_irq = FALSE;
}
static gboolean record_is_interrupt(struct graph_info *ginfo,
struct pevent_record *record,
gboolean check_type)
{
gboolean in_irq;
if (ginfo->no_irqs)
return FALSE;
in_irq = !!(pevent_data_flags(ginfo->pevent, record) &
(TRACE_FLAG_HARDIRQ | TRACE_FLAG_SOFTIRQ));
/*
* An irq exit event can also cause us to exit irq
* even if the next event is an irq.
* Treat exiting irqs (hard and soft) as non interrupts.
*/
if (check_type && in_irq) {
switch (trace_graph_check_irq(ginfo, record)) {
case GRAPH_HARDIRQ_EXIT:
case GRAPH_SOFTIRQ_EXIT:
in_irq = FALSE;
break;
default:
break;
}
}
return in_irq;
}
static void update_last_record(struct graph_info *ginfo,
struct task_plot_info *task_info,
struct pevent_record *record)
{
struct tracecmd_input *handle = ginfo->handle;
struct pevent_record *trecord, *t2record;
struct pevent_record *saved;
unsigned long long ts;
int sched_pid;
int pid;
int rec_pid;
int is_wakeup;
int is_sched;
int this_cpu;
int cpu;
pid = task_info->pid;
if (record) {
ts = record->ts;
this_cpu = record->cpu;
} else {
ts = ginfo->view_end_time;
this_cpu = -1;
}
for (cpu = 0; cpu < ginfo->cpus; cpu++) {
if (task_info->last_records[cpu])
continue;
if (cpu == this_cpu) {
static int once;
trecord = tracecmd_read_prev(handle, record);
/* Set cpu cursor back to what it was */
saved = tracecmd_read_data(handle, cpu);
if (!once && saved->offset != record->offset) {
once++;
warning("failed to reset cursor!");
}
free_record(saved);
} else {
static int once;
saved = tracecmd_read_data(handle, cpu);
set_cpu_to_time(cpu, ginfo, ts);
t2record = tracecmd_read_data(handle, cpu);
trecord = tracecmd_read_prev(handle, t2record);
free_record(t2record);
/* reset cursor back to what it was */
if (saved) {
tracecmd_set_cursor(handle, cpu, saved->offset);
free_record(saved);
} else {
saved = tracecmd_read_data(handle, cpu);
if (!once && saved) {
once++;
warning("failed to reset cursor to end!");
}
/* saved should always be NULL */
free_record(saved);
}
}
if (!trecord)
continue;
if (record_matches_pid(ginfo, trecord, pid, &rec_pid,
&sched_pid, &is_sched, &is_wakeup) &&
!is_wakeup &&
(!is_sched || (is_sched && sched_pid == pid))) {
task_info->last_records[cpu] = trecord;
task_info->last_cpu = trecord->cpu;
task_info->last_time = trecord->ts;
task_info->in_irq = record_is_interrupt(ginfo, trecord, TRUE);
break;
}
free_record(trecord);
}
}
static int task_plot_event(struct graph_info *ginfo,
struct graph_plot *plot,
struct pevent_record *record)
{
struct task_plot_info *task_info = plot->private;
struct plot_info *info = &plot->info;
struct pevent_record *next_record;
gboolean match;
gboolean in_irq;
int sched_pid;
int rec_pid;
int is_wakeup;
int is_sched;
int pid;
int cpu;
pid = task_info->pid;
if (!record) {
update_last_record(ginfo, task_info, record);
/* no more records, finish a box if one was started */
if (task_info->last_cpu >= 0) {
info->box = TRUE;
info->bstart = task_info->last_time;
info->bend = ginfo->view_end_time;
info->bcolor = hash_cpu(task_info->last_cpu);
info->bfill = !task_info->in_irq;
task_info->in_irq = FALSE;
}
for (cpu = 0; cpu < ginfo->cpus; cpu++) {
free_record(task_info->last_records[cpu]);
task_info->last_records[cpu] = NULL;
}
return 0;
}
match = record_matches_pid(ginfo, record, pid, &rec_pid,
&sched_pid, &is_sched, &is_wakeup);
if (!match && record->cpu != task_info->last_cpu) {
if (!task_info->last_records[record->cpu]) {
task_info->last_records[record->cpu] = record;
tracecmd_record_ref(record);
}
return 0;
}
if (match) {
info->line = TRUE;
info->lcolor = hash_pid(rec_pid);
info->ltime = record->ts;
/*
* Is this our first match?
*
* If last record is NULL, then it may exist off the
* viewable range. Search to see if one exists, and if
* it is the record we want to match.
*/
update_last_record(ginfo, task_info, record);
if (is_wakeup) {
/* Wake up but not task */
info->ltime = hash_pid(rec_pid);
/* Another task ? */
if (task_info->last_cpu == record->cpu) {
info->box = TRUE;
info->bcolor = hash_cpu(task_info->last_cpu);
info->bstart = task_info->last_time;
info->bend = record->ts;
task_info->last_cpu = -1;
}
task_info->wake_time = record->ts;
task_info->wake_color = GREEN;
task_info->display_wake_time = record->ts;
return 1;
}
in_irq = record_is_interrupt(ginfo, record, TRUE);
/* It takes two events to be in an irq */
if (in_irq) {
next_record = tracecmd_peek_data(ginfo->handle, record->cpu);
if (next_record)
in_irq = record_is_interrupt(ginfo, next_record, FALSE);
else
in_irq = 0;
}
if (task_info->last_cpu != record->cpu) {
if (task_info->last_cpu >= 0) {
/* Switched CPUs */
info->box = TRUE;
info->bcolor = hash_cpu(task_info->last_cpu);
info->bstart = task_info->last_time;
info->bend = record->ts;
info->bfill = !task_info->in_irq;
}
task_info->last_time = record->ts;
}
task_info->last_cpu = record->cpu;
if (is_sched) {
if (rec_pid != pid) {
/* Just got scheduled in */
task_info->last_cpu = record->cpu;
task_info->last_time = record->ts;
if (task_info->wake_time) {
info->box = TRUE;
info->bfill = FALSE;
info->bstart = task_info->wake_time;
info->bend = record->ts;
info->bcolor = task_info->wake_color;
} else
task_info->wake_time = 0;
} else if (!info->box) {
/* just got scheduled out */
info->box = TRUE;
info->bcolor = hash_cpu(task_info->last_cpu);
info->bstart = task_info->last_time;
info->bend = record->ts;
info->bfill = !task_info->in_irq;
task_info->last_cpu = -1;
if (is_running(ginfo, record)) {
task_info->wake_time = record->ts;
task_info->wake_color = RED;
} else
task_info->wake_time = 0;
} else {
task_info->wake_time = 0;
task_info->in_irq = in_irq;
}
} else {
/* Hollow out when we are in an irq */
if (task_info->in_irq != in_irq) {
info->box = TRUE;
info->bcolor = hash_cpu(task_info->last_cpu);
info->bstart = task_info->last_time;
info->bend = record->ts;
info->bfill = !task_info->in_irq;
task_info->last_time = record->ts;
}
task_info->wake_time = 0;
task_info->in_irq = in_irq;
}
return 1;
}
cpu = record->cpu;
if (!task_info->last_records[cpu]) {
task_info->last_records[cpu] = record;
tracecmd_record_ref(record);
}
/* not a match, and on the last CPU, scheduled out? */
if (task_info->last_cpu >= 0) {
info->box = TRUE;
info->bcolor = hash_cpu(task_info->last_cpu);
info->bstart = task_info->last_time;
info->bend = record->ts;
task_info->last_cpu = -1;
}
return 1;
}
static struct pevent_record *
task_plot_find_record(struct graph_info *ginfo, struct graph_plot *plot,
unsigned long long time)
{
struct task_plot_info *task_info = plot->private;
int pid;
pid = task_info->pid;
return find_record(ginfo, pid, time);
}
#define MAX_SEARCH 20
static struct pevent_record *
find_previous_record(struct graph_info *ginfo, struct pevent_record *start_record,
int pid, int cpu)
{
struct pevent_record *last_record = start_record;
struct pevent_record *record;
gboolean match;
gboolean is_sched;
gboolean is_wakeup;
gint rec_pid;
gint sched_pid;
int count = 0;
if (last_record)
last_record->ref_count++;
else
last_record = tracecmd_read_cpu_last(ginfo->handle, cpu);
while ((record = tracecmd_read_prev(ginfo->handle, last_record))) {
count++;
match = record_matches_pid(ginfo, record, pid, &rec_pid,
&sched_pid, &is_sched, &is_wakeup);
if (match)
break;
free_record(last_record);
if (count > MAX_SEARCH) {
free_record(record);
return NULL;
}
last_record = record;
}
free_record(last_record);
return record;
}
static struct pevent_record *
get_display_record(struct graph_info *ginfo, int pid, unsigned long long time)
{
struct pevent_record *record;
struct pevent_record **records;
unsigned long long ts;
int next_cpu;
int cpu;
record = find_record(ginfo, pid, time);
/* If the time is right at this record, use it */
if (record && record->ts < time + (1 / ginfo->resolution))
return record;
if (record) {
tracecmd_set_cursor(ginfo->handle, record->cpu,
record->offset);
free_record(record);
}
/* find a previous record */
records = malloc_or_die(sizeof(*records) * ginfo->cpus);
for (cpu = 0; cpu < ginfo->cpus; cpu++) {
record = tracecmd_read_data(ginfo->handle, cpu);
records[cpu] = find_previous_record(ginfo, record,
pid, cpu);
free_record(record);
}
record = NULL;
for (;;) {
ts = 0;
next_cpu = -1;
for (cpu = 0; cpu < ginfo->cpus; cpu++) {
if (!records[cpu])
continue;
if (records[cpu]->ts > ts) {
ts = records[cpu]->ts;
next_cpu = cpu;
}
}
if (next_cpu < 0)
break;
if (records[next_cpu]->ts < time + (2 / ginfo->resolution)) {
record = records[next_cpu];
break;
}
record = find_previous_record(ginfo, records[next_cpu],
pid, next_cpu);
free_record(records[next_cpu]);
records[next_cpu] = record;
record = NULL;
}
for (cpu = 0; cpu < ginfo->cpus; cpu++) {
if (records[cpu] == record)
continue;
free_record(records[cpu]);
}
free(records);
return record;
}
int task_plot_display_info(struct graph_info *ginfo,
struct graph_plot *plot,
struct trace_seq *s,
unsigned long long time)
{
struct task_plot_info *task_info = plot->private;
struct event_format *event;
struct pevent_record *record;
struct pevent *pevent;
unsigned long sec, usec;
const char *comm;
int cpu;
int type;
int sched_pid = -1;
int pid;
pid = task_info->pid;
record = get_display_record(ginfo, pid, time);
if (!record)
return 0;
pevent = ginfo->pevent;
pid = pevent_data_pid(ginfo->pevent, record);
cpu = record->cpu;
convert_nano(record->ts, &sec, &usec);
if (record->ts > time - 2/ginfo->resolution &&
record->ts < time + 2/ginfo->resolution) {
type = pevent_data_type(pevent, record);
event = pevent_data_event_from_type(pevent, type);
if (event) {
trace_seq_puts(s, event->name);
trace_seq_putc(s, '\n');
pevent_data_lat_fmt(pevent, s, record);
trace_seq_putc(s, '\n');
pevent_event_info(s, event, record);
trace_seq_putc(s, '\n');
} else
trace_seq_printf(s, "UNKNOW EVENT %d\n", type);
}
trace_graph_check_sched_switch(ginfo, record, &sched_pid, &comm);
trace_seq_printf(s, "%lu.%06lu", sec, usec);
if (pid == task_info->pid || sched_pid == task_info->pid)
trace_seq_printf(s, " CPU: %03d", cpu);
if (record_is_interrupt(ginfo, record, TRUE)) {
struct pevent_record *next_record;
next_record = tracecmd_peek_data(ginfo->handle, record->cpu);
if (record_is_interrupt(ginfo, next_record, FALSE))
trace_seq_puts(s, "\n(in interrupt)");
}
free_record(record);
return 1;
}
void task_plot_destroy(struct graph_info *ginfo, struct graph_plot *plot)
{
struct task_plot_info *task_info = plot->private;
trace_graph_plot_remove_all_recs(ginfo, plot);
free(task_info->last_records);
free(task_info);
}
static const struct plot_callbacks task_plot_cb = {
.match_time = task_plot_match_time,
.plot_event = task_plot_event,
.start = task_plot_start,
.display_last_event = task_plot_display_last_event,
.find_record = task_plot_find_record,
.display_info = task_plot_display_info,
.destroy = task_plot_destroy
};
/**
* graph_plot_task_plotted - return what tasks are plotted
* @ginfo: the graph info structure
* @plotted: returns an allocated array of gints holding the pids.
* the last pid is -1, NULL, if none are.
*
* @plotted must be freed with free() after this is called.
*/
void graph_plot_task_plotted(struct graph_info *ginfo,
gint **plotted)
{
struct task_plot_info *task_info;
struct graph_plot *plot;
int count = 0;
int i;
*plotted = NULL;
for (i = 0; i < ginfo->plots; i++) {
plot = ginfo->plot_array[i];
if (plot->type != PLOT_TYPE_TASK)
continue;
task_info = plot->private;
trace_array_add(plotted, &count, task_info->pid);
}
}
void graph_plot_task_update_callback(gboolean accept,
gint *selected,
gint *non_select,
gpointer data)
{
struct graph_info *ginfo = data;
struct task_plot_info *task_info;
struct graph_plot *plot;
gint select_size = 0;
gint *ptr;
int i;
if (!accept)
return;
/* The selected and non_select are sorted */
if (selected) {
for (i = 0; selected[i] >= 0; i++)
;
select_size = i;
}
/*
* Remove and add task plots.
* Go backwards, since removing a plot shifts the
* array from current position back.
*/
for (i = ginfo->plots - 1; i >= 0; i--) {
plot = ginfo->plot_array[i];
if (plot->type != PLOT_TYPE_TASK)
continue;
/* If non are selected, then remove all */
if (!select_size) {
trace_graph_plot_remove(ginfo, plot);
continue;
}
task_info = plot->private;
ptr = bsearch(&task_info->pid, selected, select_size,
sizeof(gint), id_cmp);
if (ptr) {
/*
* This plot plot already exists, remove it
* from the selected array.
*/
memmove(ptr, ptr + 1,
(unsigned long)(selected + select_size) -
(unsigned long)(ptr + 1));
select_size--;
continue;
}
/* Remove the plot */
trace_graph_plot_remove(ginfo, plot);
}
/* Now add any plots that need to be added */
for (i = 0; i < select_size; i++)
graph_plot_task(ginfo, selected[i], ginfo->plots);
trace_graph_refresh(ginfo);
}
void graph_plot_init_tasks(struct graph_info *ginfo)
{
struct task_plot_info *task_info;
char label[100];
struct pevent_record *record;
int pid;
/* Just for testing */
record = tracecmd_read_cpu_first(ginfo->handle, 0);
while (record) {
pid = pevent_data_pid(ginfo->pevent, record);
free_record(record);
if (pid)
break;
record = tracecmd_read_data(ginfo->handle, 0);
}
task_info = malloc_or_die(sizeof(*task_info));
task_info->last_records =
malloc_or_die(sizeof(struct pevent_record *) * ginfo->cpus);
task_info->pid = pid;
snprintf(label, 100, "TASK %d", pid);
trace_graph_plot_insert(ginfo, 1, label, PLOT_TYPE_TASK,
&task_plot_cb, task_info);
}
void graph_plot_task(struct graph_info *ginfo, int pid, int pos)
{
struct task_plot_info *task_info;
struct graph_plot *plot;
const char *comm;
char *label;
int len;
task_info = malloc_or_die(sizeof(*task_info));
task_info->last_records =
malloc_or_die(sizeof(struct pevent_record *) * ginfo->cpus);
task_info->pid = pid;
comm = pevent_data_comm_from_pid(ginfo->pevent, pid);
len = strlen(comm) + 100;
label = malloc_or_die(len);
snprintf(label, len, "%s-%d", comm, pid);
plot = trace_graph_plot_insert(ginfo, pos, label, PLOT_TYPE_TASK,
&task_plot_cb, task_info);
free(label);
trace_graph_plot_add_all_recs(ginfo, plot);
}