| /* |
| * 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); |
| } |