| /* |
| * Copyright (C) 2009, 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, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| */ |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdarg.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <gtk/gtk.h> |
| |
| #include "trace-compat.h" |
| #include "trace-cmd.h" |
| #include "trace-local.h" |
| #include "trace-graph.h" |
| |
| #define MAX_WIDTH 10000 |
| |
| #define CPU_MIDDLE(cpu) (80 * (cpu) + 80) |
| #define CPU_TOP(cpu) (CPU_MIDDLE(cpu) - 10) |
| #define CPU_BOTTOM(cpu) (CPU_MIDDLE(cpu) + 10) |
| #define CPU_SPACE(cpus) (80 * (cpus) + 80) |
| |
| static gint ftrace_sched_switch_id = -1; |
| static gint event_sched_switch_id = -1; |
| |
| static gint largest_cpu_label = 0; |
| |
| static void redraw_pixmap_backend(struct graph_info *ginfo); |
| |
| static void convert_nano(unsigned long long time, unsigned long *sec, |
| unsigned long *usec) |
| { |
| *sec = time / 1000000000ULL; |
| *usec = (time / 1000) % 1000000; |
| } |
| |
| static void print_time(unsigned long long time) |
| { |
| unsigned long sec, usec; |
| |
| convert_nano(time, &sec, &usec); |
| printf("%lu.%06lu", sec, usec); |
| } |
| |
| static void __update_with_backend(struct graph_info *ginfo, |
| gint x, gint y, |
| gint width, gint height) |
| { |
| gdk_draw_drawable(ginfo->draw->window, |
| ginfo->draw->style->fg_gc[GTK_WIDGET_STATE(ginfo->draw)], |
| ginfo->curr_pixmap, |
| x, y, x, y, |
| width, height); |
| } |
| |
| static void clear_old_cpu_label(struct graph_info *ginfo, gint cpu) |
| { |
| __update_with_backend(ginfo, |
| ginfo->cpu_x, CPU_TOP(cpu), |
| largest_cpu_label+1, 20); |
| } |
| |
| static void clear_old_cpu_labels(struct graph_info *ginfo) |
| { |
| gint cpu; |
| |
| for (cpu = 0; cpu < ginfo->cpus; cpu++) |
| clear_old_cpu_label(ginfo, cpu); |
| } |
| |
| static void __draw_cpu_label(struct graph_info *ginfo, gint cpu) |
| { |
| PangoLayout *layout; |
| gchar buf[BUFSIZ]; |
| gint width, height; |
| |
| snprintf(buf, BUFSIZ, "CPU %d", cpu); |
| |
| layout = gtk_widget_create_pango_layout(ginfo->draw, buf); |
| pango_layout_get_pixel_size(layout, &width, &height); |
| width += 4; |
| |
| if (width > largest_cpu_label) |
| largest_cpu_label = width; |
| gdk_draw_rectangle(ginfo->draw->window, |
| ginfo->draw->style->white_gc, |
| TRUE, |
| ginfo->cpu_x, CPU_TOP(cpu)+4, |
| width, height); |
| gdk_draw_layout(ginfo->draw->window, |
| ginfo->draw->style->black_gc, |
| ginfo->cpu_x + 2, CPU_TOP(cpu) + 4, |
| layout); |
| g_object_unref(layout); |
| } |
| |
| static void draw_cpu_label(struct graph_info *ginfo, gint cpu) |
| { |
| clear_old_cpu_label(ginfo, cpu); |
| __draw_cpu_label(ginfo, cpu); |
| } |
| |
| |
| static void draw_cpu_labels(struct graph_info *ginfo) |
| { |
| gint cpu; |
| |
| clear_old_cpu_labels(ginfo); |
| ginfo->cpu_x = gtk_adjustment_get_value(ginfo->vadj) + 5; |
| |
| for (cpu = 0; cpu < ginfo->cpus; cpu++) |
| __draw_cpu_label(ginfo, cpu); |
| } |
| |
| static void draw_cursor(struct graph_info *ginfo) |
| { |
| gint x; |
| |
| if (ginfo->cursor < ginfo->view_start_time || |
| ginfo->cursor > ginfo->view_end_time) |
| return; |
| |
| x = (ginfo->cursor - ginfo->view_start_time) |
| * ginfo->resolution; |
| |
| gdk_draw_line(ginfo->draw->window, ginfo->draw->style->mid_gc[3], |
| x, 0, x, ginfo->draw->allocation.width); |
| } |
| |
| static void update_with_backend(struct graph_info *ginfo, |
| gint x, gint y, |
| gint width, gint height) |
| { |
| gint cpu; |
| |
| __update_with_backend(ginfo, x, y, width, height); |
| |
| if (ginfo->cursor) |
| draw_cursor(ginfo); |
| |
| for (cpu = 0; cpu < ginfo->cpus; cpu++ ) { |
| if (y <= CPU_BOTTOM(cpu) && |
| y + height > CPU_TOP(cpu) && |
| x + width > ginfo->cpu_x && |
| x <= ginfo->cpu_x + largest_cpu_label) |
| draw_cpu_label(ginfo, cpu); |
| } |
| } |
| |
| static gboolean |
| expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data) |
| { |
| struct graph_info *ginfo = data; |
| |
| update_with_backend(ginfo, |
| event->area.x, event->area.y, |
| event->area.width, event->area.height); |
| |
| return FALSE; |
| } |
| |
| static void |
| draw_line(GtkWidget *widget, gdouble x, struct graph_info *ginfo) |
| { |
| gdk_draw_line(widget->window, widget->style->black_gc, |
| x, 0, x, widget->allocation.width); |
| } |
| |
| static void clear_last_line(GtkWidget *widget, struct graph_info *ginfo) |
| { |
| gint x; |
| |
| x = ginfo->last_x; |
| if (x) |
| x--; |
| |
| update_with_backend(ginfo, x, 0, x+2, widget->allocation.height); |
| } |
| |
| static gboolean |
| button_press_event(GtkWidget *widget, GdkEventButton *event, gpointer data) |
| { |
| struct graph_info *ginfo = data; |
| |
| printf("button = %d\n", event->button); |
| |
| if (event->button != 1) |
| return TRUE; |
| |
| /* check for double click */ |
| if (event->type==GDK_2BUTTON_PRESS) { |
| if (ginfo->line_active) { |
| ginfo->line_active = FALSE; |
| clear_last_line(widget, ginfo); |
| ginfo->last_x = ginfo->press_x; |
| clear_last_line(widget, ginfo); |
| } |
| if (ginfo->cursor >= ginfo->view_start_time && |
| ginfo->cursor <= ginfo->view_end_time) { |
| ginfo->last_x = (ginfo->cursor - ginfo->view_start_time) |
| * ginfo->resolution; |
| ginfo->cursor = 0; |
| clear_last_line(widget, ginfo); |
| } |
| |
| ginfo->cursor = event->x / ginfo->resolution + |
| ginfo->view_start_time; |
| draw_cursor(ginfo); |
| if (ginfo->callbacks && ginfo->callbacks->select) |
| ginfo->callbacks->select(ginfo, ginfo->cursor); |
| return TRUE; |
| } |
| |
| |
| ginfo->press_x = event->x; |
| ginfo->last_x = 0; |
| |
| draw_line(widget, event->x, ginfo); |
| |
| ginfo->line_active = TRUE; |
| |
| return TRUE; |
| } |
| |
| static void print_rec_info(struct record *record, struct pevent *pevent, int cpu) |
| { |
| struct trace_seq s; |
| struct event_format *event; |
| unsigned long sec, usec; |
| gint type; |
| |
| trace_seq_init(&s); |
| |
| convert_nano(record->ts, &sec, &usec); |
| trace_seq_printf(&s, "%lu.%06lu", sec, usec); |
| |
| type = pevent_data_type(pevent, record); |
| event = pevent_data_event_from_type(pevent, type); |
| if (!event) { |
| printf("No event found for id %d!\n", type); |
| return; |
| } |
| trace_seq_puts(&s, event->name); |
| trace_seq_putc(&s, ':'); |
| pevent_event_info(&s, event, record); |
| trace_seq_putc(&s, '\n'); |
| trace_seq_do_printf(&s); |
| } |
| |
| #define CPU_BOARDER 5 |
| |
| static int check_sched_switch(struct graph_info *ginfo, |
| struct record *record, |
| gint *pid, const char **comm) |
| { |
| static struct format_field *event_pid_field; |
| static struct format_field *event_comm_field; |
| static struct format_field *ftrace_pid_field; |
| static struct format_field *ftrace_comm_field; |
| unsigned long long val; |
| struct event_format *event; |
| gint id; |
| |
| if (event_sched_switch_id < 0) { |
| event = pevent_find_event_by_name(ginfo->pevent, |
| "ftrace", "context_switch"); |
| if (event) { |
| ftrace_sched_switch_id = event->id; |
| ftrace_pid_field = pevent_find_field(event, "next_pid"); |
| ftrace_comm_field = pevent_find_field(event, "next_comm"); |
| } |
| |
| event = pevent_find_event_by_name(ginfo->pevent, |
| "sched", "sched_switch"); |
| if (!event) |
| die("can't find event sched_switch!"); |
| event_sched_switch_id = event->id; |
| event_pid_field = pevent_find_field(event, "next_pid"); |
| event_comm_field = pevent_find_field(event, "next_comm"); |
| } |
| |
| id = pevent_data_type(ginfo->pevent, record); |
| if (id == event_sched_switch_id) { |
| pevent_read_number_field(event_pid_field, record->data, &val); |
| if (comm) |
| *comm = record->data + event_comm_field->offset; |
| if (pid) |
| *pid = val; |
| return 1; |
| } |
| |
| if (id == ftrace_sched_switch_id) { |
| pevent_read_number_field(ftrace_pid_field, record->data, &val); |
| if (comm) |
| *comm = record->data + ftrace_comm_field->offset; |
| if (pid) |
| *pid = val; |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static void draw_cpu_info(struct graph_info *ginfo, gint cpu, gint x, gint y) |
| { |
| PangoLayout *layout; |
| struct record *record = NULL; |
| struct pevent *pevent; |
| struct event_format *event; |
| guint64 time; |
| const char *comm; |
| gint pid = -1; |
| gint type; |
| unsigned long sec, usec; |
| struct trace_seq s; |
| gint width, height; |
| GdkPixmap *pix; |
| static GdkGC *pix_bg; |
| guint64 offset = 0; |
| gint view_width; |
| gint view_start; |
| |
| if (!pix_bg) { |
| GdkColor color; |
| |
| pix_bg = gdk_gc_new(ginfo->draw->window); |
| color.red = (0xff) *(65535/255); |
| color.green = (0xfa) *(65535/255); |
| color.blue = (0xcd) *(65535/255); |
| gdk_color_alloc(gtk_widget_get_colormap(ginfo->draw), &color); |
| gdk_gc_set_foreground(pix_bg, &color); |
| } |
| |
| printf("res=%f\n", ginfo->resolution); |
| time = (x / ginfo->resolution) + ginfo->view_start_time; |
| convert_nano(time, &sec, &usec); |
| |
| pevent = ginfo->pevent; |
| |
| trace_seq_init(&s); |
| |
| printf("start=%zu end=%zu time=%lu\n", ginfo->start_time, ginfo->end_time, time); |
| tracecmd_set_cpu_to_timestamp(ginfo->handle, cpu, time); |
| do { |
| if (record) { |
| offset = record->offset; |
| free_record(record); |
| } |
| record = tracecmd_read_data(ginfo->handle, cpu); |
| } while (record && record->ts <= (time - 1 / ginfo->resolution)); |
| |
| if (record) { |
| |
| if (record->ts > (time + 1 / ginfo->resolution) && offset) { |
| printf("old ts = %llu!\n", record->ts); |
| free_record(record); |
| record = tracecmd_read_at(ginfo->handle, offset, NULL); |
| } |
| |
| if (!check_sched_switch(ginfo, record, &pid, &comm)) { |
| pid = pevent_data_pid(ginfo->pevent, record); |
| comm = pevent_data_comm_from_pid(ginfo->pevent, pid); |
| } |
| |
| printf("record->ts=%llu time=%zu-%zu\n", |
| record->ts, time, time-(gint)(1/ginfo->resolution)); |
| print_rec_info(record, pevent, cpu); |
| |
| /* |
| * The function graph trace reads the next record, which may |
| * unmap the record data. We need to reread the record to |
| * make sure it still exists. |
| */ |
| offset = record->offset; |
| free_record(record); |
| record = tracecmd_read_at(ginfo->handle, offset, NULL); |
| |
| if (record->ts > time - 2/ginfo->resolution && |
| record->ts < time + 2/ginfo->resolution) { |
| convert_nano(record->ts, &sec, &usec); |
| |
| 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_seq_printf(&s, "%lu.%06lu", sec, usec); |
| if (pid) |
| trace_seq_printf(&s, " %s-%d", comm, pid); |
| else |
| trace_seq_puts(&s, " <idle>"); |
| |
| free(record); |
| |
| } else |
| trace_seq_printf(&s, "%lu.%06lu", sec, usec); |
| |
| trace_seq_putc(&s, 0); |
| |
| layout = gtk_widget_create_pango_layout(ginfo->draw, s.buffer); |
| pango_layout_get_pixel_size(layout, &width, &height); |
| |
| width += CPU_BOARDER * 2; |
| height += CPU_BOARDER * 2; |
| |
| if (y > height) |
| y -= height; |
| |
| view_start = gtk_adjustment_get_value(ginfo->vadj); |
| view_width = gtk_adjustment_get_page_size(ginfo->vadj); |
| |
| if (x + width > view_start + view_width) |
| x -= (x + width) - (view_start + view_width); |
| |
| ginfo->cpu_data_x = x; |
| ginfo->cpu_data_y = y; |
| ginfo->cpu_data_w = width; |
| ginfo->cpu_data_h = height; |
| |
| pix = gdk_pixmap_new(ginfo->draw->window, |
| width, |
| height, |
| -1); |
| |
| gdk_draw_rectangle(pix, |
| pix_bg, |
| TRUE, |
| 0, 0, |
| width, height); |
| |
| gdk_draw_rectangle(pix, |
| ginfo->draw->style->black_gc, |
| FALSE, |
| 0, 0, |
| width-1, height-1); |
| |
| gdk_draw_layout(pix, ginfo->draw->style->black_gc, |
| CPU_BOARDER, CPU_BOARDER, layout); |
| gdk_draw_drawable(ginfo->draw->window, |
| ginfo->draw->style->fg_gc[GTK_WIDGET_STATE(ginfo->draw)], |
| pix, 0, 0, x, y, width, height); |
| |
| g_object_unref(layout); |
| g_object_unref(pix); |
| } |
| |
| static gboolean |
| motion_notify_event(GtkWidget *widget, GdkEventMotion *event, gpointer data) |
| { |
| struct graph_info *ginfo = data; |
| GdkModifierType state; |
| gint x, y; |
| gint cpu; |
| |
| update_with_backend(ginfo, ginfo->cpu_data_x, ginfo->cpu_data_y, |
| ginfo->cpu_data_w, ginfo->cpu_data_h); |
| if (event->is_hint) |
| gdk_window_get_pointer(event->window, &x, &y, &state); |
| else { |
| x = event->x; |
| y = event->y; |
| state = event->state; |
| } |
| |
| if (!ginfo->curr_pixmap) |
| return TRUE; |
| |
| if (ginfo->line_active) { |
| if (ginfo->last_x) |
| clear_last_line(widget, ginfo); |
| ginfo->last_x = x; |
| draw_line(widget, ginfo->press_x, ginfo); |
| draw_line(widget, x, ginfo); |
| return TRUE; |
| } |
| |
| for (cpu = 0; cpu < ginfo->cpus; cpu++) { |
| if (y >= CPU_TOP(cpu) && y <= CPU_BOTTOM(cpu)) { |
| draw_cpu_info(ginfo, cpu, x, y); |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| static void update_graph(struct graph_info *ginfo, gdouble percent) |
| { |
| ginfo->full_width *= percent; |
| ginfo->resolution = |
| (gdouble)ginfo->full_width / (gdouble)(ginfo->end_time - |
| ginfo->start_time); |
| ginfo->start_x *= percent; |
| } |
| |
| static void update_graph_to_start_x(struct graph_info *ginfo) |
| { |
| gint width = ginfo->draw_width;; |
| |
| if (!width) { |
| ginfo->view_start_time = ginfo->start_time; |
| ginfo->view_end_time = ginfo->end_time; |
| return; |
| } |
| |
| ginfo->view_start_time = ginfo->start_x / ginfo->resolution + |
| ginfo->start_time; |
| |
| ginfo->view_end_time = width / ginfo->resolution + |
| ginfo->view_start_time; |
| |
| g_assert (ginfo->view_start_time < ginfo->end_time); |
| } |
| |
| static void reset_graph(struct graph_info *ginfo, gdouble view_width) |
| { |
| ginfo->full_width = view_width; |
| ginfo->draw_width = 0; |
| ginfo->view_start_time = ginfo->start_time; |
| ginfo->view_end_time = ginfo->end_time; |
| ginfo->start_x = 0; |
| } |
| |
| static void zoom_in_window(struct graph_info *ginfo, gint start, gint end) |
| { |
| gdouble view_width; |
| gdouble new_width; |
| gdouble select_width; |
| gdouble curr_width; |
| gdouble mid; |
| gdouble percent; |
| gint old_width = ginfo->draw_width; |
| |
| g_assert(start < end); |
| g_assert(ginfo->vadj); |
| |
| printf("*** started with "); |
| print_time(start / ginfo->resolution + ginfo->view_start_time); |
| printf("\n"); |
| |
| view_width = gtk_adjustment_get_page_size(ginfo->vadj); |
| select_width = end - start; |
| percent = view_width / select_width; |
| |
| update_graph(ginfo, percent); |
| |
| curr_width = ginfo->draw->allocation.width; |
| new_width = curr_width * percent; |
| |
| printf("width=%d\n", ginfo->draw->allocation.width); |
| if (ginfo->vadj) { |
| printf("adj:%f-%f\n", gtk_adjustment_get_upper(ginfo->vadj), |
| gtk_adjustment_get_lower(ginfo->vadj)); |
| } else |
| printf("no adjustment\n"); |
| |
| ginfo->draw_width = new_width; |
| printf("zoom in draw_width=%d full_width=%d\n", |
| ginfo->draw_width, ginfo->full_width); |
| |
| if (ginfo->draw_width > MAX_WIDTH) { |
| gint new_start; |
| gint new_end; |
| |
| /* |
| * The drawing is now greater than our max. We must |
| * limit the maximum size of the drawing area or |
| * we risk running out of X resources. |
| * |
| * We will now shorten the trace to that of what will |
| * fit in this zoomed area. |
| */ |
| ginfo->draw_width = MAX_WIDTH; |
| |
| mid = start + (end - start) / 2; |
| mid *= percent; |
| |
| /* |
| * mid now points to the center of the viewable area |
| * if the draw area was of new_width. |
| * |
| * new_start new_end |
| * +------------------------------------------------+ |
| * | | | | |
| * | | | | |
| * +------------------------------------------------+ |
| * ^ ^ |
| * | mid |
| * old view start |
| * |
| */ |
| |
| new_start = mid - MAX_WIDTH / 2; |
| new_end = new_start + MAX_WIDTH; |
| if (new_start < 0) { |
| /* First check if there's a start available in full */ |
| if (ginfo->start_x) { |
| ginfo->start_x += new_start; |
| if (ginfo->start_x < 0) { |
| new_start = ginfo->start_x; |
| ginfo->start_x = 0; |
| } else |
| new_start = 0; |
| } |
| new_end += -new_start; |
| new_start = 0; |
| } else if (new_end > ginfo->full_width) { |
| new_start -= new_end - ginfo->full_width; |
| new_end = ginfo->full_width; |
| g_assert(new_start >= 0); |
| } |
| |
| ginfo->start_x += new_start; |
| |
| update_graph_to_start_x(ginfo); |
| |
| printf("new start/end =%d/%d full:%d start_time:", |
| new_start, new_end, ginfo->full_width); |
| print_time(ginfo->view_start_time); |
| printf("\n"); |
| |
| /* Adjust start to be the location for the vadj */ |
| start = (mid - new_start) / percent - (end - start) / 2; |
| } |
| |
| ginfo->vadj_value = (gdouble)start * view_width / select_width; |
| if (ginfo->vadj_value > (ginfo->draw_width - view_width)) |
| ginfo->vadj_value = ginfo->draw_width - view_width; |
| |
| printf("new width=%d\n", ginfo->draw_width); |
| |
| /* make sure the width is sent */ |
| if (ginfo->draw_width == old_width) |
| gtk_widget_set_size_request(ginfo->draw, ginfo->draw_width - 1, |
| ginfo->draw_height); |
| gtk_widget_set_size_request(ginfo->draw, ginfo->draw_width, ginfo->draw_height); |
| |
| printf("set val %f\n", ginfo->vadj_value); |
| |
| |
| printf("*** ended with with "); |
| print_time(ginfo->vadj_value / ginfo->resolution + ginfo->view_start_time); |
| printf("\n"); |
| |
| } |
| |
| static gboolean |
| value_changed(GtkWidget *widget, gpointer data) |
| { |
| struct graph_info *ginfo = data; |
| GtkAdjustment *adj = GTK_ADJUSTMENT(widget); |
| |
| draw_cpu_labels(ginfo); |
| printf("value = %f\n", |
| gtk_adjustment_get_value(adj)); |
| |
| return TRUE; |
| |
| } |
| |
| static void zoom_out_window(struct graph_info *ginfo, gint start, gint end) |
| { |
| gdouble view_width; |
| gdouble divider; |
| gdouble curr_width; |
| gdouble new_width; |
| gdouble mid; |
| gdouble start_x; |
| unsigned long long time; |
| gint old_width = ginfo->draw_width; |
| |
| g_assert(start > end); |
| g_assert(ginfo->vadj); |
| |
| view_width = gtk_adjustment_get_page_size(ginfo->vadj); |
| start_x = gtk_adjustment_get_value(ginfo->vadj); |
| mid = start_x + view_width / 2; |
| |
| time = mid / ginfo->resolution + ginfo->view_start_time; |
| |
| divider = start - end; |
| |
| curr_width = ginfo->draw->allocation.width; |
| new_width = curr_width / divider; |
| |
| update_graph(ginfo, 1 / divider); |
| |
| printf("width=%d\n", ginfo->draw->allocation.width); |
| |
| ginfo->draw_width = new_width; |
| |
| printf("draw_width=%d full_width=%d\n", ginfo->draw_width, ginfo->full_width); |
| if (ginfo->full_width < view_width) { |
| reset_graph(ginfo, view_width); |
| time = ginfo->view_start_time; |
| |
| } else if (ginfo->draw_width < ginfo->full_width) { |
| if (ginfo->full_width < MAX_WIDTH) { |
| ginfo->draw_width = ginfo->full_width; |
| ginfo->view_start_time = ginfo->start_time; |
| ginfo->view_end_time = ginfo->end_time; |
| ginfo->start_x = 0; |
| } else { |
| ginfo->draw_width = MAX_WIDTH; |
| mid /= divider; |
| mid += ginfo->start_x; |
| |
| /* mid now is the current mid with full_width */ |
| ginfo->start_x = mid - MAX_WIDTH / 2; |
| if (ginfo->start_x < 0) |
| ginfo->start_x = 0; |
| |
| update_graph_to_start_x(ginfo); |
| } |
| } |
| |
| printf("new width=%d\n", ginfo->draw_width); |
| |
| /* make sure the width is sent */ |
| if (ginfo->draw_width == old_width) |
| gtk_widget_set_size_request(ginfo->draw, ginfo->draw_width - 1, |
| ginfo->draw_height); |
| gtk_widget_set_size_request(ginfo->draw, ginfo->draw_width, ginfo->draw_height); |
| |
| mid = (time - ginfo->view_start_time) * ginfo->resolution; |
| start_x = mid - view_width / 2; |
| if (start_x < 0) |
| start_x = 0; |
| |
| ginfo->vadj_value = start_x; |
| } |
| |
| static gboolean |
| button_release_event(GtkWidget *widget, GdkEventMotion *event, gpointer data) |
| { |
| struct graph_info *ginfo = data; |
| |
| if (ginfo->line_active) { |
| ginfo->line_active = FALSE; |
| clear_last_line(widget, ginfo); |
| ginfo->last_x = ginfo->press_x; |
| clear_last_line(widget, ginfo); |
| |
| if (event->x > ginfo->press_x) { |
| /* make a decent zoom */ |
| if (event->x - ginfo->press_x < 10) |
| return TRUE; |
| zoom_in_window(ginfo, ginfo->press_x, event->x); |
| } else if (event->x < ginfo->press_x) |
| zoom_out_window(ginfo, ginfo->press_x, event->x); |
| } |
| |
| return TRUE; |
| } |
| |
| static gint do_hash(gint val) |
| { |
| return (val + (val << 4) + (val << 8) + (val << 12) + |
| (val << 16) + (val << 20) + (val << 24) + |
| (val << 28)) * 3; |
| } |
| |
| static void set_color_by_pid(GtkWidget *widget, GdkGC *gc, gint pid) |
| { |
| GdkColor color; |
| gint hash = do_hash(pid); |
| static gint last_pid = -1; |
| |
| if (!(hash & 0xffffff) && last_pid != pid) { |
| last_pid = pid; |
| printf("pid=%d is black\n", pid); |
| } |
| color.red = (hash & 0xff)*(65535/255); |
| color.blue = ((hash >> 8) & 0xff)*(65535/255); |
| color.green = ((hash >> 16) & 0xff)*(65535/255); |
| gdk_color_alloc(gtk_widget_get_colormap(widget), &color); |
| gdk_gc_set_foreground(gc, &color); |
| } |
| |
| static void draw_event_label(struct graph_info *ginfo, gint cpu, |
| gint event_id, gint pid, |
| gint p1, gint p2, gint p3, |
| gint width_16, PangoFontDescription *font) |
| { |
| struct event_format *event; |
| PangoLayout *layout; |
| struct trace_seq s; |
| gint text_width; |
| gint text_height; |
| gint x, y; |
| |
| |
| /* No room to print */ |
| if ((p2 > width_16 && ((p3 - p2) < width_16 / 2 || |
| (p2 - p1) < width_16 / 2)) || |
| (p2 <= width_16 && (p1 || (p3 - p2) < width_16))) |
| return; |
| |
| /* Check if we can show some data */ |
| |
| event = pevent_data_event_from_type(ginfo->pevent, event_id); |
| |
| trace_seq_init(&s); |
| trace_seq_printf(&s, "%s-%d\n%s\n", |
| pevent_data_comm_from_pid(ginfo->pevent, pid), |
| pid, event->name); |
| |
| layout = gtk_widget_create_pango_layout(ginfo->draw, s.buffer); |
| pango_layout_set_font_description(layout, font); |
| |
| pango_layout_get_pixel_size(layout, &text_width, &text_height); |
| |
| if ((p2 > text_width && ((p3 - p2) < text_width || |
| (p2 - p1) < text_width)) || |
| (p2 < text_width && (p1 || (p3 - p2 < (text_width + |
| text_width / 2))))) { |
| g_object_unref(layout); |
| return; |
| } |
| |
| x = p2 - text_width / 2; |
| if (x < 0) |
| x = 1; |
| |
| y = (CPU_TOP(cpu) - text_height + 5); |
| gdk_draw_layout(ginfo->curr_pixmap, ginfo->draw->style->black_gc, |
| x, y, layout); |
| |
| |
| gdk_draw_line(ginfo->curr_pixmap, ginfo->draw->style->black_gc, |
| p2, CPU_TOP(cpu) - 5, p2, CPU_TOP(cpu) - 1); |
| |
| g_object_unref(layout); |
| } |
| |
| static void draw_cpu(struct graph_info *ginfo, gint cpu, |
| gint new_width, int read_comms) |
| { |
| static PangoFontDescription *font; |
| PangoLayout *layout; |
| gint height = CPU_MIDDLE(cpu); |
| struct record *record; |
| static GdkGC *gc; |
| static gint width_16; |
| guint64 ts; |
| gint last_pid = -1; |
| gint last_x = 0; |
| gint pid; |
| gint x; |
| gint p1 = 0, p2 = 0, p3 = 0; |
| gint last_event_id = 0; |
| gint event_id; |
| const char *comm; |
| |
| /* Calculate the size of 16 characters */ |
| if (!width_16) { |
| gchar buf[17]; |
| gint text_height; |
| |
| memset(buf, 'a', 16); |
| buf[16] = 0; |
| |
| font = pango_font_description_from_string("Sans 8"); |
| layout = gtk_widget_create_pango_layout(ginfo->draw, buf); |
| pango_layout_set_font_description(layout, font); |
| pango_layout_get_pixel_size(layout, &width_16, &text_height); |
| g_object_unref(layout); |
| } |
| |
| if (!gc) |
| gc = gdk_gc_new(ginfo->draw->window); |
| |
| gdk_draw_line(ginfo->curr_pixmap, ginfo->draw->style->black_gc, |
| 0, height, new_width, height); |
| |
| ts = ginfo->view_start_time; |
| |
| tracecmd_set_cpu_to_timestamp(ginfo->handle, cpu, ts); |
| |
| while ((record = tracecmd_read_data(ginfo->handle, cpu))) { |
| |
| if (record->ts < ginfo->view_start_time) { |
| free_record(record); |
| continue; |
| } |
| if (record->ts > ginfo->view_end_time) |
| break; |
| |
| ts = record->ts - ginfo->view_start_time; |
| |
| x = (gint)((gdouble)ts * ginfo->resolution); |
| |
| |
| if (check_sched_switch(ginfo, record, &pid, &comm)) { |
| if (read_comms) { |
| /* |
| * First time through, register any missing |
| * comm / pid mappings. |
| */ |
| if (!pevent_pid_is_registered(ginfo->pevent, pid)) |
| pevent_register_comm(ginfo->pevent, |
| strdup(comm), pid); |
| } |
| } else |
| pid = pevent_data_pid(ginfo->pevent, record); |
| |
| event_id = pevent_data_type(ginfo->pevent, record); |
| |
| if (last_pid != pid) { |
| |
| if (last_pid < 0) |
| last_pid = pid; |
| |
| if (last_pid) { |
| gdk_draw_line(ginfo->curr_pixmap, gc, |
| last_x, CPU_TOP(cpu), |
| x, CPU_TOP(cpu)); |
| gdk_draw_line(ginfo->curr_pixmap, gc, |
| last_x, CPU_BOTTOM(cpu), |
| x, CPU_BOTTOM(cpu)); |
| } |
| |
| last_x = x; |
| last_pid = pid; |
| } |
| |
| set_color_by_pid(ginfo->draw, gc, pid); |
| |
| gdk_draw_line(ginfo->curr_pixmap, gc, // ginfo->draw->style->black_gc, |
| x, CPU_TOP(cpu), x, CPU_BOTTOM(cpu)); |
| |
| /* Figure out if we can show the text for the previous record */ |
| |
| p3 = x; |
| |
| /* Make sure p2 will be non-zero the next iteration */ |
| if (!p3) |
| p3 = 1; |
| |
| /* first record, continue */ |
| if (p2) |
| draw_event_label(ginfo, cpu, last_event_id, last_pid, |
| p1, p2, p3, width_16, font); |
| |
| p1 = p2; |
| p2 = p3; |
| last_event_id = event_id; |
| free(record); |
| } |
| |
| if (p2) |
| draw_event_label(ginfo, cpu, last_event_id, last_pid, |
| p1, p2, ginfo->draw_width, width_16, font); |
| |
| |
| if (last_pid > 0) { |
| x = ginfo->draw_width; |
| |
| gdk_draw_line(ginfo->curr_pixmap, gc, |
| last_x, CPU_TOP(cpu), |
| x, CPU_TOP(cpu)); |
| gdk_draw_line(ginfo->curr_pixmap, gc, |
| last_x, CPU_BOTTOM(cpu), |
| x, CPU_BOTTOM(cpu)); |
| } |
| |
| if (record) |
| free(record); |
| } |
| |
| |
| static void draw_timeline(struct graph_info *ginfo, gint width) |
| { |
| PangoLayout *layout; |
| struct trace_seq s; |
| unsigned long sec, usec; |
| unsigned long long time; |
| gint mid; |
| gint w, h, height; |
| gint view_width; |
| |
| /* --- draw timeline text --- */ |
| |
| layout = gtk_widget_create_pango_layout(ginfo->draw, "Time Line"); |
| pango_layout_get_pixel_size(layout, &w, &h); |
| |
| height = 10 + h; |
| |
| mid = width / 2; |
| gdk_draw_layout(ginfo->curr_pixmap, ginfo->draw->style->black_gc, |
| mid - w / 2, 5, layout); |
| g_object_unref(layout); |
| |
| |
| /* --- draw time line lines --- */ |
| gdk_draw_line(ginfo->curr_pixmap, ginfo->draw->style->black_gc, |
| 0, height, width, height); |
| |
| gdk_draw_line(ginfo->curr_pixmap, ginfo->draw->style->black_gc, |
| 0, height, 0, height + 5); |
| |
| gdk_draw_line(ginfo->curr_pixmap, ginfo->draw->style->black_gc, |
| width-1, height, width-1, height); |
| |
| gdk_draw_line(ginfo->curr_pixmap, ginfo->draw->style->black_gc, |
| width-1, height, width-1, height + 5); |
| |
| /* --- draw starting time --- */ |
| convert_nano(ginfo->view_start_time, &sec, &usec); |
| trace_seq_init(&s); |
| trace_seq_printf(&s, "%lu.%06lu", sec, usec); |
| |
| layout = gtk_widget_create_pango_layout(ginfo->draw, s.buffer); |
| pango_layout_get_pixel_size(layout, &w, &h); |
| |
| gdk_draw_layout(ginfo->curr_pixmap, ginfo->draw->style->black_gc, |
| 1, height+10, layout); |
| g_object_unref(layout); |
| |
| |
| /* --- draw ending time --- */ |
| convert_nano(ginfo->view_end_time, &sec, &usec); |
| trace_seq_init(&s); |
| trace_seq_printf(&s, "%lu.%06lu", sec, usec); |
| |
| layout = gtk_widget_create_pango_layout(ginfo->draw, s.buffer); |
| pango_layout_get_pixel_size(layout, &w, &h); |
| |
| gdk_draw_layout(ginfo->curr_pixmap, ginfo->draw->style->black_gc, |
| width - (w + 2), height+10, layout); |
| g_object_unref(layout); |
| |
| |
| /* --- draw time at intervals --- */ |
| view_width = gtk_adjustment_get_page_size(ginfo->vadj); |
| |
| for (mid = view_width / 2; mid < (width - view_width / 2 + 10); |
| mid += view_width / 2) { |
| time = mid / ginfo->resolution + ginfo->view_start_time; |
| |
| convert_nano(time, &sec, &usec); |
| trace_seq_init(&s); |
| trace_seq_printf(&s, "%lu.%06lu", sec, usec); |
| |
| gdk_draw_line(ginfo->curr_pixmap, ginfo->draw->style->black_gc, |
| mid, height, mid, height + 5); |
| |
| layout = gtk_widget_create_pango_layout(ginfo->draw, s.buffer); |
| pango_layout_get_pixel_size(layout, &w, &h); |
| |
| gdk_draw_layout(ginfo->curr_pixmap, ginfo->draw->style->black_gc, |
| mid - (w / 2), height+10, layout); |
| g_object_unref(layout); |
| } |
| } |
| |
| static void draw_info(struct graph_info *ginfo, |
| gint new_width) |
| { |
| static int read_comms = 1; |
| gint cpu; |
| |
| ginfo->resolution = (gdouble)new_width / (gdouble)(ginfo->view_end_time - |
| ginfo->view_start_time); |
| |
| ginfo->full_width = (ginfo->end_time - ginfo->start_time) * ginfo->resolution; |
| |
| draw_timeline(ginfo, new_width); |
| |
| |
| for (cpu = 0; cpu < ginfo->cpus; cpu++) |
| draw_cpu(ginfo, cpu, new_width, read_comms); |
| |
| read_comms = 0; |
| |
| draw_cpu_labels(ginfo); |
| } |
| |
| void trace_graph_select_by_time(struct graph_info *ginfo, guint64 time) |
| { |
| gint view_width; |
| gint width; |
| gint mid; |
| gint start; |
| gint end; |
| guint64 old_start_time = ginfo->view_start_time; |
| |
| view_width = gtk_adjustment_get_page_size(ginfo->vadj); |
| width = ginfo->draw_width ? : ginfo->full_width; |
| |
| mid = (time - ginfo->start_time) * ginfo->resolution; |
| start = mid - width / 2; |
| if (start < 0) |
| start = 0; |
| end = start + width; |
| |
| /* |
| * Readjust the drawing to be centered on the selection. |
| */ |
| |
| if (end > ginfo->full_width) { |
| start -= end - ginfo->full_width; |
| g_assert(start >= 0); |
| end = ginfo->full_width; |
| } |
| |
| ginfo->start_x = start; |
| |
| update_graph_to_start_x(ginfo); |
| |
| /* force redraw if we changed the time*/ |
| if (old_start_time != ginfo->view_start_time) |
| redraw_pixmap_backend(ginfo); |
| |
| /* Adjust start to be the location for the vadj */ |
| mid = (time - ginfo->view_start_time) * ginfo->resolution; |
| start = mid - view_width / 2; |
| if (start < 0) |
| start = 0; |
| |
| if (start > (width - view_width)) |
| start = width - view_width; |
| gtk_adjustment_set_value(ginfo->vadj, start); |
| |
| ginfo->last_x = (ginfo->cursor - ginfo->view_start_time) |
| * ginfo->resolution; |
| ginfo->cursor = 0; |
| clear_last_line(ginfo->draw, ginfo); |
| ginfo->cursor = time; |
| |
| update_with_backend(ginfo, 0, 0, width, ginfo->draw_height); |
| } |
| |
| static void redraw_pixmap_backend(struct graph_info *ginfo) |
| { |
| GdkPixmap *old_pix; |
| |
| old_pix = ginfo->curr_pixmap; |
| |
| /* initialize full width if needed */ |
| if (!ginfo->full_width) |
| ginfo->full_width = ginfo->draw->allocation.width; |
| |
| ginfo->curr_pixmap = gdk_pixmap_new(ginfo->draw->window, |
| ginfo->draw->allocation.width, |
| ginfo->draw->allocation.height, |
| -1); |
| |
| gdk_draw_rectangle(ginfo->curr_pixmap, |
| ginfo->draw->style->white_gc, |
| TRUE, |
| 0, 0, |
| ginfo->draw->allocation.width, |
| ginfo->draw->allocation.height); |
| |
| draw_info(ginfo, ginfo->draw->allocation.width); |
| |
| if (old_pix) { |
| #if 0 |
| gdk_draw_drawable(ginfo->curr_pixmap, |
| ginfo->draw->style->fg_gc[GTK_WIDGET_STATE(ginfo->draw)], |
| old_pix, |
| 0, 0, 0, 0, |
| old_w, old_h); |
| #endif |
| |
| g_object_unref(old_pix); |
| } |
| |
| if (ginfo->vadj_value) { |
| // gtk_adjustment_set_lower(ginfo->vadj, -100.0); |
| gtk_adjustment_set_value(ginfo->vadj, ginfo->vadj_value); |
| } |
| } |
| |
| static gboolean |
| configure_event(GtkWidget *widget, GdkEventMotion *event, gpointer data) |
| { |
| struct graph_info *ginfo = data; |
| |
| gtk_widget_set_size_request(widget, ginfo->draw_width, ginfo->draw_height); |
| |
| redraw_pixmap_backend(ginfo); |
| |
| /* debug */ |
| ginfo->vadj_value = gtk_adjustment_get_value(ginfo->vadj); |
| printf("get val %f\n", ginfo->vadj_value); |
| ginfo->vadj_value = 0.0; |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| destroy_event(GtkWidget *widget, gpointer data) |
| { |
| struct graph_info *ginfo = data; |
| |
| if (ginfo->test) |
| printf("test = %s\n", ginfo->test); |
| |
| return TRUE; |
| } |
| |
| |
| struct graph_info * |
| trace_graph_create_with_callbacks(struct tracecmd_input *handle, GtkScrolledWindow *scrollwin, |
| struct graph_callbacks *cbs) |
| { |
| struct graph_info *ginfo; |
| unsigned long sec, usec; |
| gint cpu; |
| |
| ginfo = g_new0(typeof(*ginfo), 1); |
| g_assert(ginfo != NULL); |
| ginfo->test = "hello!"; |
| |
| ginfo->handle = handle; |
| ginfo->pevent = tracecmd_get_pevent(handle); |
| ginfo->cpus = tracecmd_cpus(handle); |
| |
| ginfo->callbacks = cbs; |
| |
| ginfo->start_time = -1ULL; |
| ginfo->end_time = 0; |
| |
| ginfo->draw_height = CPU_SPACE(ginfo->cpus); |
| ginfo->vadj = gtk_scrolled_window_get_hadjustment(scrollwin); |
| |
| gtk_signal_connect(GTK_OBJECT(ginfo->vadj), "value_changed", |
| (GtkSignalFunc) value_changed, ginfo); |
| |
| for (cpu = 0; cpu < ginfo->cpus; cpu++) { |
| struct record *record; |
| |
| record = tracecmd_read_cpu_first(handle, cpu); |
| if (!record) |
| continue; |
| |
| if (record->ts < ginfo->start_time) |
| ginfo->start_time = record->ts; |
| |
| record = tracecmd_read_cpu_last(handle, cpu); |
| |
| if (record->ts > ginfo->end_time) |
| ginfo->end_time = record->ts; |
| } |
| |
| convert_nano(ginfo->start_time, &sec, &usec); |
| printf("start=%lu.%06lu ", sec, usec); |
| |
| convert_nano(ginfo->end_time, &sec, &usec); |
| printf("end=%lu.%06lu\n", sec, usec); |
| |
| ginfo->view_start_time = ginfo->start_time; |
| ginfo->view_end_time = ginfo->end_time; |
| |
| ginfo->draw = gtk_drawing_area_new(); |
| |
| gtk_signal_connect(GTK_OBJECT(ginfo->draw), "expose_event", |
| (GtkSignalFunc) expose_event, ginfo); |
| gtk_signal_connect(GTK_OBJECT(ginfo->draw), "button_press_event", |
| (GtkSignalFunc) button_press_event, ginfo); |
| gtk_signal_connect(GTK_OBJECT(ginfo->draw), "configure_event", |
| (GtkSignalFunc) configure_event, ginfo); |
| gtk_signal_connect(GTK_OBJECT(ginfo->draw), "motion_notify_event", |
| (GtkSignalFunc) motion_notify_event, ginfo); |
| gtk_signal_connect(GTK_OBJECT(ginfo->draw), "button_release_event", |
| (GtkSignalFunc) button_release_event, ginfo); |
| gtk_signal_connect(GTK_OBJECT(ginfo->draw), "destroy", |
| (GtkSignalFunc) destroy_event, ginfo); |
| |
| gtk_widget_set_events(ginfo->draw, GDK_EXPOSURE_MASK |
| | GDK_LEAVE_NOTIFY_MASK |
| | GDK_BUTTON_PRESS_MASK |
| | GDK_BUTTON_RELEASE_MASK |
| | GDK_POINTER_MOTION_MASK |
| | GDK_POINTER_MOTION_HINT_MASK); |
| |
| |
| return ginfo; |
| } |
| |
| struct graph_info * |
| trace_graph_create(struct tracecmd_input *handle, GtkScrolledWindow *scrollwin) |
| { |
| return trace_graph_create_with_callbacks(handle, scrollwin, NULL); |
| } |