diff options
Diffstat (limited to 'scripts/pybootchartgui/pybootchartgui/draw.py')
-rw-r--r-- | scripts/pybootchartgui/pybootchartgui/draw.py | 710 |
1 files changed, 579 insertions, 131 deletions
diff --git a/scripts/pybootchartgui/pybootchartgui/draw.py b/scripts/pybootchartgui/pybootchartgui/draw.py index 1b872de75e..c3492c7cb9 100644 --- a/scripts/pybootchartgui/pybootchartgui/draw.py +++ b/scripts/pybootchartgui/pybootchartgui/draw.py | |||
@@ -1,6 +1,40 @@ | |||
1 | # This file is part of pybootchartgui. | ||
2 | |||
3 | # pybootchartgui is free software: you can redistribute it and/or modify | ||
4 | # it under the terms of the GNU General Public License as published by | ||
5 | # the Free Software Foundation, either version 3 of the License, or | ||
6 | # (at your option) any later version. | ||
7 | |||
8 | # pybootchartgui is distributed in the hope that it will be useful, | ||
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
11 | # GNU General Public License for more details. | ||
12 | |||
13 | # You should have received a copy of the GNU General Public License | ||
14 | # along with pybootchartgui. If not, see <http://www.gnu.org/licenses/>. | ||
15 | |||
16 | |||
1 | import cairo | 17 | import cairo |
2 | import math | 18 | import math |
3 | import re | 19 | import re |
20 | import random | ||
21 | import colorsys | ||
22 | from operator import itemgetter | ||
23 | |||
24 | class RenderOptions: | ||
25 | |||
26 | def __init__(self, app_options): | ||
27 | # should we render a cumulative CPU time chart | ||
28 | self.cumulative = True | ||
29 | self.charts = True | ||
30 | self.kernel_only = False | ||
31 | self.app_options = app_options | ||
32 | |||
33 | def proc_tree (self, trace): | ||
34 | if self.kernel_only: | ||
35 | return trace.kernel_tree | ||
36 | else: | ||
37 | return trace.proc_tree | ||
4 | 38 | ||
5 | # Process tree background color. | 39 | # Process tree background color. |
6 | BACK_COLOR = (1.0, 1.0, 1.0, 1.0) | 40 | BACK_COLOR = (1.0, 1.0, 1.0, 1.0) |
@@ -12,11 +46,13 @@ BORDER_COLOR = (0.63, 0.63, 0.63, 1.0) | |||
12 | TICK_COLOR = (0.92, 0.92, 0.92, 1.0) | 46 | TICK_COLOR = (0.92, 0.92, 0.92, 1.0) |
13 | # 5-second tick line color. | 47 | # 5-second tick line color. |
14 | TICK_COLOR_BOLD = (0.86, 0.86, 0.86, 1.0) | 48 | TICK_COLOR_BOLD = (0.86, 0.86, 0.86, 1.0) |
49 | # Annotation colour | ||
50 | ANNOTATION_COLOR = (0.63, 0.0, 0.0, 0.5) | ||
15 | # Text color. | 51 | # Text color. |
16 | TEXT_COLOR = (0.0, 0.0, 0.0, 1.0) | 52 | TEXT_COLOR = (0.0, 0.0, 0.0, 1.0) |
17 | 53 | ||
18 | # Font family | 54 | # Font family |
19 | FONT_NAME = "Bitstream Vera Sans" | 55 | FONT_NAME = "Bitstream Vera Sans" |
20 | # Title text font. | 56 | # Title text font. |
21 | TITLE_FONT_SIZE = 18 | 57 | TITLE_FONT_SIZE = 18 |
22 | # Default text font. | 58 | # Default text font. |
@@ -25,7 +61,7 @@ TEXT_FONT_SIZE = 12 | |||
25 | AXIS_FONT_SIZE = 11 | 61 | AXIS_FONT_SIZE = 11 |
26 | # Legend font. | 62 | # Legend font. |
27 | LEGEND_FONT_SIZE = 12 | 63 | LEGEND_FONT_SIZE = 12 |
28 | 64 | ||
29 | # CPU load chart color. | 65 | # CPU load chart color. |
30 | CPU_COLOR = (0.40, 0.55, 0.70, 1.0) | 66 | CPU_COLOR = (0.40, 0.55, 0.70, 1.0) |
31 | # IO wait chart color. | 67 | # IO wait chart color. |
@@ -34,11 +70,19 @@ IO_COLOR = (0.76, 0.48, 0.48, 0.5) | |||
34 | DISK_TPUT_COLOR = (0.20, 0.71, 0.20, 1.0) | 70 | DISK_TPUT_COLOR = (0.20, 0.71, 0.20, 1.0) |
35 | # CPU load chart color. | 71 | # CPU load chart color. |
36 | FILE_OPEN_COLOR = (0.20, 0.71, 0.71, 1.0) | 72 | FILE_OPEN_COLOR = (0.20, 0.71, 0.71, 1.0) |
37 | 73 | # Mem cached color | |
74 | MEM_CACHED_COLOR = CPU_COLOR | ||
75 | # Mem used color | ||
76 | MEM_USED_COLOR = IO_COLOR | ||
77 | # Buffers color | ||
78 | MEM_BUFFERS_COLOR = (0.4, 0.4, 0.4, 0.3) | ||
79 | # Swap color | ||
80 | MEM_SWAP_COLOR = DISK_TPUT_COLOR | ||
81 | |||
38 | # Process border color. | 82 | # Process border color. |
39 | PROC_BORDER_COLOR = (0.71, 0.71, 0.71, 1.0) | 83 | PROC_BORDER_COLOR = (0.71, 0.71, 0.71, 1.0) |
40 | 84 | # Waiting process color. | |
41 | PROC_COLOR_D = (0.76, 0.48, 0.48, 0.125) | 85 | PROC_COLOR_D = (0.76, 0.48, 0.48, 0.5) |
42 | # Running process color. | 86 | # Running process color. |
43 | PROC_COLOR_R = CPU_COLOR | 87 | PROC_COLOR_R = CPU_COLOR |
44 | # Sleeping process color. | 88 | # Sleeping process color. |
@@ -62,8 +106,8 @@ SIG_COLOR = (0.0, 0.0, 0.0, 0.3125) | |||
62 | # Signature font. | 106 | # Signature font. |
63 | SIG_FONT_SIZE = 14 | 107 | SIG_FONT_SIZE = 14 |
64 | # Signature text. | 108 | # Signature text. |
65 | SIGNATURE = "http://code.google.com/p/pybootchartgui" | 109 | SIGNATURE = "http://github.com/mmeeks/bootchart" |
66 | 110 | ||
67 | # Process dependency line color. | 111 | # Process dependency line color. |
68 | DEP_COLOR = (0.75, 0.75, 0.75, 1.0) | 112 | DEP_COLOR = (0.75, 0.75, 0.75, 1.0) |
69 | # Process dependency line stroke. | 113 | # Process dependency line stroke. |
@@ -72,6 +116,10 @@ DEP_STROKE = 1.0 | |||
72 | # Process description date format. | 116 | # Process description date format. |
73 | DESC_TIME_FORMAT = "mm:ss.SSS" | 117 | DESC_TIME_FORMAT = "mm:ss.SSS" |
74 | 118 | ||
119 | # Cumulative coloring bits | ||
120 | HSV_MAX_MOD = 31 | ||
121 | HSV_STEP = 7 | ||
122 | |||
75 | # Configure task color | 123 | # Configure task color |
76 | TASK_COLOR_CONFIGURE = (1.0, 1.0, 0.00, 1.0) | 124 | TASK_COLOR_CONFIGURE = (1.0, 1.0, 0.00, 1.0) |
77 | # Compile task color. | 125 | # Compile task color. |
@@ -91,66 +139,67 @@ STATE_WAITING = 3 | |||
91 | STATE_STOPPED = 4 | 139 | STATE_STOPPED = 4 |
92 | STATE_ZOMBIE = 5 | 140 | STATE_ZOMBIE = 5 |
93 | 141 | ||
94 | STATE_COLORS = [(0,0,0,0), PROC_COLOR_R, PROC_COLOR_S, PROC_COLOR_D, PROC_COLOR_T, PROC_COLOR_Z, PROC_COLOR_X, PROC_COLOR_W] | 142 | STATE_COLORS = [(0, 0, 0, 0), PROC_COLOR_R, PROC_COLOR_S, PROC_COLOR_D, \ |
143 | PROC_COLOR_T, PROC_COLOR_Z, PROC_COLOR_X, PROC_COLOR_W] | ||
144 | |||
145 | # CumulativeStats Types | ||
146 | STAT_TYPE_CPU = 0 | ||
147 | STAT_TYPE_IO = 1 | ||
95 | 148 | ||
96 | # Convert ps process state to an int | 149 | # Convert ps process state to an int |
97 | def get_proc_state(flag): | 150 | def get_proc_state(flag): |
98 | return "RSDTZXW".index(flag) + 1 | 151 | return "RSDTZXW".find(flag) + 1 |
99 | |||
100 | 152 | ||
101 | def draw_text(ctx, text, color, x, y): | 153 | def draw_text(ctx, text, color, x, y): |
102 | ctx.set_source_rgba(*color) | 154 | ctx.set_source_rgba(*color) |
103 | ctx.move_to(x, y) | 155 | ctx.move_to(x, y) |
104 | ctx.show_text(text) | 156 | ctx.show_text(text) |
105 | 157 | ||
106 | |||
107 | def draw_fill_rect(ctx, color, rect): | 158 | def draw_fill_rect(ctx, color, rect): |
108 | ctx.set_source_rgba(*color) | 159 | ctx.set_source_rgba(*color) |
109 | ctx.rectangle(*rect) | 160 | ctx.rectangle(*rect) |
110 | ctx.fill() | 161 | ctx.fill() |
111 | |||
112 | 162 | ||
113 | def draw_rect(ctx, color, rect): | 163 | def draw_rect(ctx, color, rect): |
114 | ctx.set_source_rgba(*color) | 164 | ctx.set_source_rgba(*color) |
115 | ctx.rectangle(*rect) | 165 | ctx.rectangle(*rect) |
116 | ctx.stroke() | 166 | ctx.stroke() |
117 | 167 | ||
118 | |||
119 | def draw_legend_box(ctx, label, fill_color, x, y, s): | 168 | def draw_legend_box(ctx, label, fill_color, x, y, s): |
120 | draw_fill_rect(ctx, fill_color, (x, y - s, s, s)) | 169 | draw_fill_rect(ctx, fill_color, (x, y - s, s, s)) |
121 | draw_rect(ctx, PROC_BORDER_COLOR, (x, y - s, s, s)) | 170 | draw_rect(ctx, PROC_BORDER_COLOR, (x, y - s, s, s)) |
122 | draw_text(ctx, label, TEXT_COLOR, x + s + 5, y) | 171 | draw_text(ctx, label, TEXT_COLOR, x + s + 5, y) |
123 | 172 | ||
124 | |||
125 | def draw_legend_line(ctx, label, fill_color, x, y, s): | 173 | def draw_legend_line(ctx, label, fill_color, x, y, s): |
126 | draw_fill_rect(ctx, fill_color, (x, y - s/2, s + 1, 3)) | 174 | draw_fill_rect(ctx, fill_color, (x, y - s/2, s + 1, 3)) |
127 | ctx.arc(x + (s + 1)/2.0, y - (s - 3)/2.0, 2.5, 0, 2.0 * math.pi) | 175 | ctx.arc(x + (s + 1)/2.0, y - (s - 3)/2.0, 2.5, 0, 2.0 * math.pi) |
128 | ctx.fill() | 176 | ctx.fill() |
129 | draw_text(ctx, label, TEXT_COLOR, x + s + 5, y) | 177 | draw_text(ctx, label, TEXT_COLOR, x + s + 5, y) |
130 | |||
131 | 178 | ||
132 | def draw_label_in_box(ctx, color, label, x, y, w, maxx): | 179 | def draw_label_in_box(ctx, color, label, x, y, w, maxx): |
133 | label_w = ctx.text_extents(label)[2] | 180 | label_w = ctx.text_extents(label)[2] |
134 | label_x = x + w / 2 - label_w / 2 | 181 | label_x = x + w / 2 - label_w / 2 |
135 | if label_w + 10 > w: | 182 | if label_w + 10 > w: |
136 | label_x = x + w + 5 | 183 | label_x = x + w + 5 |
137 | if label_x + label_w > maxx: | 184 | if label_x + label_w > maxx: |
138 | label_x = x - label_w - 5 | 185 | label_x = x - label_w - 5 |
139 | draw_text(ctx, label, color, label_x, y) | 186 | draw_text(ctx, label, color, label_x, y) |
140 | 187 | ||
141 | 188 | def draw_sec_labels(ctx, rect, sec_w, nsecs): | |
142 | def draw_5sec_labels(ctx, rect, sec_w): | 189 | ctx.set_font_size(AXIS_FONT_SIZE) |
143 | ctx.set_font_size(AXIS_FONT_SIZE) | 190 | prev_x = 0 |
144 | for i in range(0, rect[2] + 1, sec_w): | 191 | for i in range(0, rect[2] + 1, sec_w): |
145 | if ((i / sec_w) % 30 == 0) : | 192 | if ((i / sec_w) % nsecs == 0) : |
146 | label = "%ds" % (i / sec_w) | 193 | label = "%ds" % (i / sec_w) |
147 | label_w = ctx.text_extents(label)[2] | 194 | label_w = ctx.text_extents(label)[2] |
148 | draw_text(ctx, label, TEXT_COLOR, rect[0] + i - label_w/2, rect[1] - 2) | 195 | x = rect[0] + i - label_w/2 |
149 | 196 | if x >= prev_x: | |
197 | draw_text(ctx, label, TEXT_COLOR, x, rect[1] - 2) | ||
198 | prev_x = x + label_w | ||
150 | 199 | ||
151 | def draw_box_ticks(ctx, rect, sec_w): | 200 | def draw_box_ticks(ctx, rect, sec_w): |
152 | draw_rect(ctx, BORDER_COLOR, tuple(rect)) | 201 | draw_rect(ctx, BORDER_COLOR, tuple(rect)) |
153 | 202 | ||
154 | ctx.set_line_cap(cairo.LINE_CAP_SQUARE) | 203 | ctx.set_line_cap(cairo.LINE_CAP_SQUARE) |
155 | 204 | ||
156 | for i in range(sec_w, rect[2] + 1, sec_w): | 205 | for i in range(sec_w, rect[2] + 1, sec_w): |
@@ -164,136 +213,211 @@ def draw_box_ticks(ctx, rect, sec_w): | |||
164 | 213 | ||
165 | ctx.set_line_cap(cairo.LINE_CAP_BUTT) | 214 | ctx.set_line_cap(cairo.LINE_CAP_BUTT) |
166 | 215 | ||
167 | def draw_chart(ctx, color, fill, chart_bounds, data, proc_tree): | 216 | def draw_annotations(ctx, proc_tree, times, rect): |
217 | ctx.set_line_cap(cairo.LINE_CAP_SQUARE) | ||
218 | ctx.set_source_rgba(*ANNOTATION_COLOR) | ||
219 | ctx.set_dash([4, 4]) | ||
220 | |||
221 | for time in times: | ||
222 | if time is not None: | ||
223 | x = ((time - proc_tree.start_time) * rect[2] / proc_tree.duration) | ||
224 | |||
225 | ctx.move_to(rect[0] + x, rect[1] + 1) | ||
226 | ctx.line_to(rect[0] + x, rect[1] + rect[3] - 1) | ||
227 | ctx.stroke() | ||
228 | |||
229 | ctx.set_line_cap(cairo.LINE_CAP_BUTT) | ||
230 | ctx.set_dash([]) | ||
231 | |||
232 | def draw_chart(ctx, color, fill, chart_bounds, data, proc_tree, data_range): | ||
168 | ctx.set_line_width(0.5) | 233 | ctx.set_line_width(0.5) |
169 | x_shift = proc_tree.start_time | 234 | x_shift = proc_tree.start_time |
170 | x_scale = proc_tree.duration | 235 | |
171 | 236 | def transform_point_coords(point, x_base, y_base, \ | |
172 | def transform_point_coords(point, x_base, y_base, xscale, yscale, x_trans, y_trans): | 237 | xscale, yscale, x_trans, y_trans): |
173 | x = (point[0] - x_base) * xscale + x_trans | 238 | x = (point[0] - x_base) * xscale + x_trans |
174 | y = (point[1] - y_base) * -yscale + y_trans + bar_h | 239 | y = (point[1] - y_base) * -yscale + y_trans + chart_bounds[3] |
175 | return x, y | 240 | return x, y |
176 | 241 | ||
177 | xscale = float(chart_bounds[2]) / max(x for (x,y) in data) | 242 | max_x = max (x for (x, y) in data) |
178 | yscale = float(chart_bounds[3]) / max(y for (x,y) in data) | 243 | max_y = max (y for (x, y) in data) |
179 | 244 | # avoid divide by zero | |
180 | first = transform_point_coords(data[0], x_shift, 0, xscale, yscale, chart_bounds[0], chart_bounds[1]) | 245 | if max_y == 0: |
181 | last = transform_point_coords(data[-1], x_shift, 0, xscale, yscale, chart_bounds[0], chart_bounds[1]) | 246 | max_y = 1.0 |
182 | 247 | xscale = float (chart_bounds[2]) / max_x | |
248 | # If data_range is given, scale the chart so that the value range in | ||
249 | # data_range matches the chart bounds exactly. | ||
250 | # Otherwise, scale so that the actual data matches the chart bounds. | ||
251 | if data_range: | ||
252 | yscale = float(chart_bounds[3]) / (data_range[1] - data_range[0]) | ||
253 | ybase = data_range[0] | ||
254 | else: | ||
255 | yscale = float(chart_bounds[3]) / max_y | ||
256 | ybase = 0 | ||
257 | |||
258 | first = transform_point_coords (data[0], x_shift, ybase, xscale, yscale, \ | ||
259 | chart_bounds[0], chart_bounds[1]) | ||
260 | last = transform_point_coords (data[-1], x_shift, ybase, xscale, yscale, \ | ||
261 | chart_bounds[0], chart_bounds[1]) | ||
262 | |||
183 | ctx.set_source_rgba(*color) | 263 | ctx.set_source_rgba(*color) |
184 | ctx.move_to(*first) | 264 | ctx.move_to(*first) |
185 | for point in data: | 265 | for point in data: |
186 | x, y = transform_point_coords(point, x_shift, 0, xscale, yscale, chart_bounds[0], chart_bounds[1]) | 266 | x, y = transform_point_coords (point, x_shift, ybase, xscale, yscale, \ |
267 | chart_bounds[0], chart_bounds[1]) | ||
187 | ctx.line_to(x, y) | 268 | ctx.line_to(x, y) |
188 | if fill: | 269 | if fill: |
189 | ctx.stroke_preserve() | 270 | ctx.stroke_preserve() |
190 | ctx.line_to(last[0], chart_bounds[1]+bar_h) | 271 | ctx.line_to(last[0], chart_bounds[1]+chart_bounds[3]) |
191 | ctx.line_to(first[0], chart_bounds[1]+bar_h) | 272 | ctx.line_to(first[0], chart_bounds[1]+chart_bounds[3]) |
192 | ctx.line_to(first[0], first[1]) | 273 | ctx.line_to(first[0], first[1]) |
193 | ctx.fill() | 274 | ctx.fill() |
194 | else: | 275 | else: |
195 | ctx.stroke() | 276 | ctx.stroke() |
196 | ctx.set_line_width(1.0) | 277 | ctx.set_line_width(1.0) |
197 | 278 | ||
198 | header_h = 280 | ||
199 | bar_h = 55 | 279 | bar_h = 55 |
280 | meminfo_bar_h = 2 * bar_h | ||
281 | header_h = 110 + 2 * (30 + bar_h) + 1 * (30 + meminfo_bar_h) | ||
200 | # offsets | 282 | # offsets |
201 | off_x, off_y = 10, 10 | 283 | off_x, off_y = 10, 10 |
202 | sec_w = 1 # the width of a second | 284 | sec_w_base = 1 # the width of a second |
203 | proc_h = 16 # the height of a process | 285 | proc_h = 16 # the height of a process |
204 | leg_s = 10 | 286 | leg_s = 10 |
205 | MIN_IMG_W = 800 | 287 | MIN_IMG_W = 800 |
288 | CUML_HEIGHT = 2000 # Increased value to accomodate CPU and I/O Graphs | ||
289 | OPTIONS = None | ||
206 | 290 | ||
291 | def extents(options, xscale, trace): | ||
292 | start = min(trace.start.keys()) | ||
293 | end = max(trace.end.keys()) | ||
207 | 294 | ||
208 | def extents(res): | 295 | w = int ((end - start) * sec_w_base * xscale) + 2*off_x |
209 | start = min(res.start.keys()) | 296 | h = proc_h * len(trace.processes) + header_h + 2 * off_y |
210 | end = max(res.end.keys()) | ||
211 | 297 | ||
212 | w = ((end - start) * sec_w) + 2*off_x | 298 | return (w, h) |
213 | h = proc_h * len(res.processes) + header_h + 2*off_y | ||
214 | 299 | ||
215 | return (w,h) | 300 | def clip_visible(clip, rect): |
301 | xmax = max (clip[0], rect[0]) | ||
302 | ymax = max (clip[1], rect[1]) | ||
303 | xmin = min (clip[0] + clip[2], rect[0] + rect[2]) | ||
304 | ymin = min (clip[1] + clip[3], rect[1] + rect[3]) | ||
305 | return (xmin > xmax and ymin > ymax) | ||
216 | 306 | ||
217 | # | 307 | def render_charts(ctx, options, clip, trace, curr_y, w, h, sec_w): |
218 | # Render the chart. | 308 | proc_tree = options.proc_tree(trace) |
219 | # | ||
220 | def render(ctx, res): | ||
221 | (w, h) = extents(res) | ||
222 | 309 | ||
223 | ctx.set_line_width(1.0) | ||
224 | ctx.select_font_face(FONT_NAME) | ||
225 | draw_fill_rect(ctx, WHITE, (0, 0, max(w, MIN_IMG_W), h)) | ||
226 | w -= 2*off_x | ||
227 | # draw the title and headers | ||
228 | #curr_y = draw_header(ctx, headers, off_x, proc_tree.duration) | ||
229 | curr_y = 0 | ||
230 | |||
231 | # render bar legend | 310 | # render bar legend |
232 | ctx.set_font_size(LEGEND_FONT_SIZE) | 311 | ctx.set_font_size(LEGEND_FONT_SIZE) |
233 | 312 | ||
234 | #print "w, h %s %s" % (w, h) | 313 | draw_legend_box(ctx, "CPU (user+sys)", CPU_COLOR, off_x, curr_y+20, leg_s) |
235 | 314 | draw_legend_box(ctx, "I/O (wait)", IO_COLOR, off_x + 120, curr_y+20, leg_s) | |
236 | #draw_legend_box(ctx, "CPU (user+sys)", CPU_COLOR, off_x, curr_y+20, leg_s) | ||
237 | #draw_legend_box(ctx, "I/O (wait)", IO_COLOR, off_x + 120, curr_y+20, leg_s) | ||
238 | 315 | ||
239 | # render I/O wait | 316 | # render I/O wait |
240 | #chart_rect = (off_x, curr_y+30, w, bar_h) | 317 | chart_rect = (off_x, curr_y+30, w, bar_h) |
241 | #draw_box_ticks(ctx, chart_rect, sec_w) | 318 | if clip_visible (clip, chart_rect): |
242 | #draw_chart(ctx, IO_COLOR, True, chart_rect, [(sample.time, sample.user + sample.sys + sample.io) for sample in cpu_stats], proc_tree) | 319 | draw_box_ticks (ctx, chart_rect, sec_w) |
243 | # render CPU load | 320 | draw_annotations (ctx, proc_tree, trace.times, chart_rect) |
244 | #draw_chart(ctx, CPU_COLOR, True, chart_rect, [(sample.time, sample.user + sample.sys) for sample in cpu_stats], proc_tree) | 321 | draw_chart (ctx, IO_COLOR, True, chart_rect, \ |
245 | 322 | [(sample.time, sample.user + sample.sys + sample.io) for sample in trace.cpu_stats], \ | |
246 | #curr_y = curr_y + 30 + bar_h | 323 | proc_tree, None) |
324 | # render CPU load | ||
325 | draw_chart (ctx, CPU_COLOR, True, chart_rect, \ | ||
326 | [(sample.time, sample.user + sample.sys) for sample in trace.cpu_stats], \ | ||
327 | proc_tree, None) | ||
328 | |||
329 | curr_y = curr_y + 30 + bar_h | ||
247 | 330 | ||
248 | # render second chart | 331 | # render second chart |
249 | #draw_legend_line(ctx, "Disk throughput", DISK_TPUT_COLOR, off_x, curr_y+20, leg_s) | 332 | draw_legend_line(ctx, "Disk throughput", DISK_TPUT_COLOR, off_x, curr_y+20, leg_s) |
250 | #draw_legend_box(ctx, "Disk utilization", IO_COLOR, off_x + 120, curr_y+20, leg_s) | 333 | draw_legend_box(ctx, "Disk utilization", IO_COLOR, off_x + 120, curr_y+20, leg_s) |
251 | 334 | ||
252 | # render I/O utilization | 335 | # render I/O utilization |
253 | #chart_rect = (off_x, curr_y+30, w, bar_h) | 336 | chart_rect = (off_x, curr_y+30, w, bar_h) |
254 | #draw_box_ticks(ctx, chart_rect, sec_w) | 337 | if clip_visible (clip, chart_rect): |
255 | #draw_chart(ctx, IO_COLOR, True, chart_rect, [(sample.time, sample.util) for sample in disk_stats], proc_tree) | 338 | draw_box_ticks (ctx, chart_rect, sec_w) |
256 | 339 | draw_annotations (ctx, proc_tree, trace.times, chart_rect) | |
340 | draw_chart (ctx, IO_COLOR, True, chart_rect, \ | ||
341 | [(sample.time, sample.util) for sample in trace.disk_stats], \ | ||
342 | proc_tree, None) | ||
343 | |||
257 | # render disk throughput | 344 | # render disk throughput |
258 | #max_sample = max(disk_stats, key=lambda s: s.tput) | 345 | max_sample = max (trace.disk_stats, key = lambda s: s.tput) |
259 | #draw_chart(ctx, DISK_TPUT_COLOR, False, chart_rect, [(sample.time, sample.tput) for sample in disk_stats], proc_tree) | 346 | if clip_visible (clip, chart_rect): |
260 | 347 | draw_chart (ctx, DISK_TPUT_COLOR, False, chart_rect, \ | |
261 | #pos_x = off_x + ((max_sample.time - proc_tree.start_time) * w / proc_tree.duration) | 348 | [(sample.time, sample.tput) for sample in trace.disk_stats], \ |
262 | pos_x = off_x | 349 | proc_tree, None) |
350 | |||
351 | pos_x = off_x + ((max_sample.time - proc_tree.start_time) * w / proc_tree.duration) | ||
263 | 352 | ||
264 | shift_x, shift_y = -20, 20 | 353 | shift_x, shift_y = -20, 20 |
265 | if (pos_x < off_x + 245): | 354 | if (pos_x < off_x + 245): |
266 | shift_x, shift_y = 5, 40 | 355 | shift_x, shift_y = 5, 40 |
267 | |||
268 | #label = "%dMB/s" % round((max_sample.tput) / 1024.0) | ||
269 | #draw_text(ctx, label, DISK_TPUT_COLOR, pos_x + shift_x, curr_y + shift_y) | ||
270 | |||
271 | |||
272 | 356 | ||
357 | label = "%dMB/s" % round ((max_sample.tput) / 1024.0) | ||
358 | draw_text (ctx, label, DISK_TPUT_COLOR, pos_x + shift_x, curr_y + shift_y) | ||
359 | |||
360 | curr_y = curr_y + 30 + bar_h | ||
361 | |||
362 | # render mem usage | ||
363 | chart_rect = (off_x, curr_y+30, w, meminfo_bar_h) | ||
364 | mem_stats = trace.mem_stats | ||
365 | if mem_stats and clip_visible (clip, chart_rect): | ||
366 | mem_scale = max(sample.records['MemTotal'] - sample.records['MemFree'] for sample in mem_stats) | ||
367 | draw_legend_box(ctx, "Mem cached (scale: %u MiB)" % (float(mem_scale) / 1024), MEM_CACHED_COLOR, off_x, curr_y+20, leg_s) | ||
368 | draw_legend_box(ctx, "Used", MEM_USED_COLOR, off_x + 240, curr_y+20, leg_s) | ||
369 | draw_legend_box(ctx, "Buffers", MEM_BUFFERS_COLOR, off_x + 360, curr_y+20, leg_s) | ||
370 | draw_legend_line(ctx, "Swap (scale: %u MiB)" % max([(sample.records['SwapTotal'] - sample.records['SwapFree'])/1024 for sample in mem_stats]), \ | ||
371 | MEM_SWAP_COLOR, off_x + 480, curr_y+20, leg_s) | ||
372 | draw_box_ticks(ctx, chart_rect, sec_w) | ||
373 | draw_annotations(ctx, proc_tree, trace.times, chart_rect) | ||
374 | draw_chart(ctx, MEM_BUFFERS_COLOR, True, chart_rect, \ | ||
375 | [(sample.time, sample.records['MemTotal'] - sample.records['MemFree']) for sample in trace.mem_stats], \ | ||
376 | proc_tree, [0, mem_scale]) | ||
377 | draw_chart(ctx, MEM_USED_COLOR, True, chart_rect, \ | ||
378 | [(sample.time, sample.records['MemTotal'] - sample.records['MemFree'] - sample.records['Buffers']) for sample in mem_stats], \ | ||
379 | proc_tree, [0, mem_scale]) | ||
380 | draw_chart(ctx, MEM_CACHED_COLOR, True, chart_rect, \ | ||
381 | [(sample.time, sample.records['Cached']) for sample in mem_stats], \ | ||
382 | proc_tree, [0, mem_scale]) | ||
383 | draw_chart(ctx, MEM_SWAP_COLOR, False, chart_rect, \ | ||
384 | [(sample.time, float(sample.records['SwapTotal'] - sample.records['SwapFree'])) for sample in mem_stats], \ | ||
385 | proc_tree, None) | ||
386 | |||
387 | curr_y = curr_y + meminfo_bar_h | ||
388 | |||
389 | return curr_y | ||
390 | |||
391 | def render_processes_chart(ctx, options, trace, curr_y, w, h, sec_w): | ||
273 | chart_rect = [off_x, curr_y+60, w, h - 2 * off_y - (curr_y+60) + proc_h] | 392 | chart_rect = [off_x, curr_y+60, w, h - 2 * off_y - (curr_y+60) + proc_h] |
274 | 393 | ||
275 | draw_legend_box(ctx, "Configure", TASK_COLOR_CONFIGURE, off_x , curr_y + 45, leg_s) | 394 | draw_legend_box (ctx, "Configure", \ |
276 | draw_legend_box(ctx, "Compile", TASK_COLOR_COMPILE, off_x+120, curr_y + 45, leg_s) | 395 | TASK_COLOR_CONFIGURE, off_x , curr_y + 45, leg_s) |
277 | draw_legend_box(ctx, "Install", TASK_COLOR_INSTALL, off_x+240, curr_y + 45, leg_s) | 396 | draw_legend_box (ctx, "Compile", \ |
278 | draw_legend_box(ctx, "Package", TASK_COLOR_PACKAGE, off_x+360, curr_y + 45, leg_s) | 397 | TASK_COLOR_COMPILE, off_x+120, curr_y + 45, leg_s) |
279 | draw_legend_box(ctx, "Populate Sysroot", TASK_COLOR_SYSROOT, off_x+480, curr_y + 45, leg_s) | 398 | draw_legend_box (ctx, "Install", \ |
280 | 399 | TASK_COLOR_INSTALL, off_x+240, curr_y + 45, leg_s) | |
400 | draw_legend_box (ctx, "Package", \ | ||
401 | TASK_COLOR_PACKAGE, off_x+360, curr_y + 45, leg_s) | ||
402 | draw_legend_box (ctx, "Populate Sysroot", \ | ||
403 | TASK_COLOR_SYSROOT, off_x+480, curr_y + 45, leg_s) | ||
404 | |||
281 | ctx.set_font_size(PROC_TEXT_FONT_SIZE) | 405 | ctx.set_font_size(PROC_TEXT_FONT_SIZE) |
282 | 406 | ||
283 | draw_box_ticks(ctx, chart_rect, sec_w) | 407 | draw_box_ticks(ctx, chart_rect, sec_w) |
284 | draw_5sec_labels(ctx, chart_rect, sec_w) | 408 | draw_sec_labels(ctx, chart_rect, sec_w, 30) |
285 | 409 | ||
286 | y = curr_y+60 | 410 | y = curr_y+60 |
287 | 411 | ||
288 | offset = min(res.start.keys()) | 412 | offset = min(trace.start.keys()) |
289 | for s in sorted(res.start.keys()): | 413 | for s in sorted(trace.start.keys()): |
290 | for val in sorted(res.start[s]): | 414 | for val in sorted(trace.start[s]): |
291 | task = val.split(":")[1] | 415 | task = val.split(":")[1] |
292 | #print val | 416 | #print val |
293 | #print res.processes[val][1] | 417 | #print trace.processes[val][1] |
294 | #print s | 418 | #print s |
295 | x = (s - offset) * sec_w | 419 | x = (s - offset) * sec_w |
296 | w = ((res.processes[val][1] - s) * sec_w) | 420 | w = ((trace.processes[val][1] - s) * sec_w) |
297 | 421 | ||
298 | #print "proc at %s %s %s %s" % (x, y, w, proc_h) | 422 | #print "proc at %s %s %s %s" % (x, y, w, proc_h) |
299 | col = None | 423 | col = None |
@@ -315,95 +439,419 @@ def render(ctx, res): | |||
315 | draw_label_in_box(ctx, PROC_TEXT_COLOR, val, x, y + proc_h - 4, w, proc_h) | 439 | draw_label_in_box(ctx, PROC_TEXT_COLOR, val, x, y + proc_h - 4, w, proc_h) |
316 | y = y + proc_h | 440 | y = y + proc_h |
317 | 441 | ||
442 | return curr_y | ||
443 | |||
444 | # | ||
445 | # Render the chart. | ||
446 | # | ||
447 | def render(ctx, options, xscale, trace): | ||
448 | (w, h) = extents (options, xscale, trace) | ||
449 | global OPTIONS | ||
450 | OPTIONS = options.app_options | ||
451 | |||
452 | # x, y, w, h | ||
453 | clip = ctx.clip_extents() | ||
454 | |||
455 | sec_w = int (xscale * sec_w_base) | ||
456 | ctx.set_line_width(1.0) | ||
457 | ctx.select_font_face(FONT_NAME) | ||
458 | draw_fill_rect(ctx, WHITE, (0, 0, max(w, MIN_IMG_W), h)) | ||
459 | w -= 2*off_x | ||
460 | curr_y = off_y; | ||
461 | |||
462 | curr_y = render_processes_chart (ctx, options, trace, curr_y, w, h, sec_w) | ||
463 | |||
464 | return | ||
465 | |||
466 | proc_tree = options.proc_tree (trace) | ||
467 | |||
468 | # draw the title and headers | ||
469 | if proc_tree.idle: | ||
470 | duration = proc_tree.idle | ||
471 | else: | ||
472 | duration = proc_tree.duration | ||
473 | |||
474 | if not options.kernel_only: | ||
475 | curr_y = draw_header (ctx, trace.headers, duration) | ||
476 | else: | ||
477 | curr_y = off_y; | ||
478 | |||
479 | if options.charts: | ||
480 | curr_y = render_charts (ctx, options, clip, trace, curr_y, w, h, sec_w) | ||
481 | |||
318 | # draw process boxes | 482 | # draw process boxes |
319 | #draw_process_bar_chart(ctx, proc_tree, curr_y + bar_h, w, h) | 483 | proc_height = h |
484 | if proc_tree.taskstats and options.cumulative: | ||
485 | proc_height -= CUML_HEIGHT | ||
320 | 486 | ||
321 | ctx.set_font_size(SIG_FONT_SIZE) | 487 | draw_process_bar_chart(ctx, clip, options, proc_tree, trace.times, |
322 | draw_text(ctx, SIGNATURE, SIG_COLOR, off_x + 5, h - off_y - 5) | 488 | curr_y, w, proc_height, sec_w) |
323 | 489 | ||
324 | def draw_process_bar_chart(ctx, proc_tree, curr_y, w, h): | 490 | curr_y = proc_height |
491 | ctx.set_font_size(SIG_FONT_SIZE) | ||
492 | draw_text(ctx, SIGNATURE, SIG_COLOR, off_x + 5, proc_height - 8) | ||
493 | |||
494 | # draw a cumulative CPU-time-per-process graph | ||
495 | if proc_tree.taskstats and options.cumulative: | ||
496 | cuml_rect = (off_x, curr_y + off_y, w, CUML_HEIGHT/2 - off_y * 2) | ||
497 | if clip_visible (clip, cuml_rect): | ||
498 | draw_cuml_graph(ctx, proc_tree, cuml_rect, duration, sec_w, STAT_TYPE_CPU) | ||
499 | |||
500 | # draw a cumulative I/O-time-per-process graph | ||
501 | if proc_tree.taskstats and options.cumulative: | ||
502 | cuml_rect = (off_x, curr_y + off_y * 100, w, CUML_HEIGHT/2 - off_y * 2) | ||
503 | if clip_visible (clip, cuml_rect): | ||
504 | draw_cuml_graph(ctx, proc_tree, cuml_rect, duration, sec_w, STAT_TYPE_IO) | ||
505 | |||
506 | def draw_process_bar_chart(ctx, clip, options, proc_tree, times, curr_y, w, h, sec_w): | ||
507 | header_size = 0 | ||
508 | if not options.kernel_only: | ||
509 | draw_legend_box (ctx, "Running (%cpu)", | ||
510 | PROC_COLOR_R, off_x , curr_y + 45, leg_s) | ||
511 | draw_legend_box (ctx, "Unint.sleep (I/O)", | ||
512 | PROC_COLOR_D, off_x+120, curr_y + 45, leg_s) | ||
513 | draw_legend_box (ctx, "Sleeping", | ||
514 | PROC_COLOR_S, off_x+240, curr_y + 45, leg_s) | ||
515 | draw_legend_box (ctx, "Zombie", | ||
516 | PROC_COLOR_Z, off_x+360, curr_y + 45, leg_s) | ||
517 | header_size = 45 | ||
518 | |||
519 | chart_rect = [off_x, curr_y + header_size + 15, | ||
520 | w, h - 2 * off_y - (curr_y + header_size + 15) + proc_h] | ||
521 | ctx.set_font_size (PROC_TEXT_FONT_SIZE) | ||
522 | |||
523 | draw_box_ticks (ctx, chart_rect, sec_w) | ||
524 | if sec_w > 100: | ||
525 | nsec = 1 | ||
526 | else: | ||
527 | nsec = 5 | ||
528 | draw_sec_labels (ctx, chart_rect, sec_w, nsec) | ||
529 | draw_annotations (ctx, proc_tree, times, chart_rect) | ||
325 | 530 | ||
326 | for root in proc_tree.process_tree: | 531 | y = curr_y + 60 |
327 | draw_processes_recursively(ctx, root, proc_tree, y, proc_h, chart_rect) | 532 | for root in proc_tree.process_tree: |
328 | y = y + proc_h * proc_tree.num_nodes([root]) | 533 | draw_processes_recursively(ctx, root, proc_tree, y, proc_h, chart_rect, clip) |
534 | y = y + proc_h * proc_tree.num_nodes([root]) | ||
329 | 535 | ||
330 | 536 | ||
331 | def draw_header(ctx, headers, off_x, duration): | 537 | def draw_header (ctx, headers, duration): |
332 | dur = duration / 100.0 | ||
333 | toshow = [ | 538 | toshow = [ |
334 | ('system.uname', 'uname', lambda s: s), | 539 | ('system.uname', 'uname', lambda s: s), |
335 | ('system.release', 'release', lambda s: s), | 540 | ('system.release', 'release', lambda s: s), |
336 | ('system.cpu', 'CPU', lambda s: re.sub('model name\s*:\s*', '', s, 1)), | 541 | ('system.cpu', 'CPU', lambda s: re.sub('model name\s*:\s*', '', s, 1)), |
337 | ('system.kernel.options', 'kernel options', lambda s: s), | 542 | ('system.kernel.options', 'kernel options', lambda s: s), |
338 | ('pseudo.header', 'time', lambda s: '%02d:%05.2f' % (math.floor(dur/60), dur - 60 * math.floor(dur/60))) | ||
339 | ] | 543 | ] |
340 | 544 | ||
341 | header_y = ctx.font_extents()[2] + 10 | 545 | header_y = ctx.font_extents()[2] + 10 |
342 | ctx.set_font_size(TITLE_FONT_SIZE) | 546 | ctx.set_font_size(TITLE_FONT_SIZE) |
343 | draw_text(ctx, headers['title'], TEXT_COLOR, off_x, header_y) | 547 | draw_text(ctx, headers['title'], TEXT_COLOR, off_x, header_y) |
344 | ctx.set_font_size(TEXT_FONT_SIZE) | 548 | ctx.set_font_size(TEXT_FONT_SIZE) |
345 | 549 | ||
346 | for (headerkey, headertitle, mangle) in toshow: | 550 | for (headerkey, headertitle, mangle) in toshow: |
347 | header_y += ctx.font_extents()[2] | 551 | header_y += ctx.font_extents()[2] |
348 | txt = headertitle + ': ' + mangle(headers.get(headerkey)) | 552 | if headerkey in headers: |
553 | value = headers.get(headerkey) | ||
554 | else: | ||
555 | value = "" | ||
556 | txt = headertitle + ': ' + mangle(value) | ||
349 | draw_text(ctx, txt, TEXT_COLOR, off_x, header_y) | 557 | draw_text(ctx, txt, TEXT_COLOR, off_x, header_y) |
350 | 558 | ||
559 | dur = duration / 100.0 | ||
560 | txt = 'time : %02d:%05.2f' % (math.floor(dur/60), dur - 60 * math.floor(dur/60)) | ||
561 | if headers.get('system.maxpid') is not None: | ||
562 | txt = txt + ' max pid: %s' % (headers.get('system.maxpid')) | ||
563 | |||
564 | header_y += ctx.font_extents()[2] | ||
565 | draw_text (ctx, txt, TEXT_COLOR, off_x, header_y) | ||
566 | |||
351 | return header_y | 567 | return header_y |
352 | 568 | ||
353 | def draw_processes_recursively(ctx, proc, proc_tree, y, proc_h, rect) : | 569 | def draw_processes_recursively(ctx, proc, proc_tree, y, proc_h, rect, clip) : |
354 | x = rect[0] + ((proc.start_time - proc_tree.start_time) * rect[2] / proc_tree.duration) | 570 | x = rect[0] + ((proc.start_time - proc_tree.start_time) * rect[2] / proc_tree.duration) |
355 | w = ((proc.duration) * rect[2] / proc_tree.duration) | 571 | w = ((proc.duration) * rect[2] / proc_tree.duration) |
356 | 572 | ||
357 | draw_process_activity_colors(ctx, proc, proc_tree, x, y, w, proc_h, rect) | 573 | draw_process_activity_colors(ctx, proc, proc_tree, x, y, w, proc_h, rect, clip) |
358 | draw_rect(ctx, PROC_BORDER_COLOR, (x, y, w, proc_h)) | 574 | draw_rect(ctx, PROC_BORDER_COLOR, (x, y, w, proc_h)) |
359 | draw_label_in_box(ctx, PROC_TEXT_COLOR, proc.cmd, x, y + proc_h - 4, w, rect[0] + rect[2]) | 575 | ipid = int(proc.pid) |
576 | if not OPTIONS.show_all: | ||
577 | cmdString = proc.cmd | ||
578 | else: | ||
579 | cmdString = '' | ||
580 | if (OPTIONS.show_pid or OPTIONS.show_all) and ipid is not 0: | ||
581 | cmdString = cmdString + " [" + str(ipid // 1000) + "]" | ||
582 | if OPTIONS.show_all: | ||
583 | if proc.args: | ||
584 | cmdString = cmdString + " '" + "' '".join(proc.args) + "'" | ||
585 | else: | ||
586 | cmdString = cmdString + " " + proc.exe | ||
587 | |||
588 | draw_label_in_box(ctx, PROC_TEXT_COLOR, cmdString, x, y + proc_h - 4, w, rect[0] + rect[2]) | ||
360 | 589 | ||
361 | next_y = y + proc_h | 590 | next_y = y + proc_h |
362 | for child in proc.child_list: | 591 | for child in proc.child_list: |
363 | child_x, child_y = draw_processes_recursively(ctx, child, proc_tree, next_y, proc_h, rect) | 592 | if next_y > clip[1] + clip[3]: |
593 | break | ||
594 | child_x, child_y = draw_processes_recursively(ctx, child, proc_tree, next_y, proc_h, rect, clip) | ||
364 | draw_process_connecting_lines(ctx, x, y, child_x, child_y, proc_h) | 595 | draw_process_connecting_lines(ctx, x, y, child_x, child_y, proc_h) |
365 | next_y = next_y + proc_h * proc_tree.num_nodes([child]) | 596 | next_y = next_y + proc_h * proc_tree.num_nodes([child]) |
366 | 597 | ||
367 | return x, y | 598 | return x, y |
368 | 599 | ||
369 | 600 | ||
370 | def draw_process_activity_colors(ctx, proc, proc_tree, x, y, w, proc_h, rect): | 601 | def draw_process_activity_colors(ctx, proc, proc_tree, x, y, w, proc_h, rect, clip): |
602 | |||
603 | if y > clip[1] + clip[3] or y + proc_h + 2 < clip[1]: | ||
604 | return | ||
605 | |||
371 | draw_fill_rect(ctx, PROC_COLOR_S, (x, y, w, proc_h)) | 606 | draw_fill_rect(ctx, PROC_COLOR_S, (x, y, w, proc_h)) |
372 | 607 | ||
373 | last_tx = -1 | 608 | last_tx = -1 |
374 | for sample in proc.samples : | 609 | for sample in proc.samples : |
375 | tx = rect[0] + round(((sample.time - proc_tree.start_time) * rect[2] / proc_tree.duration)) | 610 | tx = rect[0] + round(((sample.time - proc_tree.start_time) * rect[2] / proc_tree.duration)) |
611 | |||
612 | # samples are sorted chronologically | ||
613 | if tx < clip[0]: | ||
614 | continue | ||
615 | if tx > clip[0] + clip[2]: | ||
616 | break | ||
617 | |||
376 | tw = round(proc_tree.sample_period * rect[2] / float(proc_tree.duration)) | 618 | tw = round(proc_tree.sample_period * rect[2] / float(proc_tree.duration)) |
377 | if last_tx != -1 and abs(last_tx - tx) <= tw: | 619 | if last_tx != -1 and abs(last_tx - tx) <= tw: |
378 | tw -= last_tx - tx | 620 | tw -= last_tx - tx |
379 | tx = last_tx | 621 | tx = last_tx |
380 | 622 | tw = max (tw, 1) # nice to see at least something | |
623 | |||
381 | last_tx = tx + tw | 624 | last_tx = tx + tw |
382 | state = get_proc_state( sample.state ) | 625 | state = get_proc_state( sample.state ) |
383 | 626 | ||
384 | color = STATE_COLORS[state] | 627 | color = STATE_COLORS[state] |
385 | if state == STATE_RUNNING: | 628 | if state == STATE_RUNNING: |
386 | alpha = sample.cpu_sample.user + sample.cpu_sample.sys | 629 | alpha = min (sample.cpu_sample.user + sample.cpu_sample.sys, 1.0) |
387 | color = tuple(list(PROC_COLOR_R[0:3]) + [alpha]) | 630 | color = tuple(list(PROC_COLOR_R[0:3]) + [alpha]) |
631 | # print "render time %d [ tx %d tw %d ], sample state %s color %s alpha %g" % (sample.time, tx, tw, state, color, alpha) | ||
388 | elif state == STATE_SLEEPING: | 632 | elif state == STATE_SLEEPING: |
389 | continue | 633 | continue |
390 | 634 | ||
391 | draw_fill_rect(ctx, color, (tx, y, tw, proc_h)) | 635 | draw_fill_rect(ctx, color, (tx, y, tw, proc_h)) |
392 | 636 | ||
393 | |||
394 | def draw_process_connecting_lines(ctx, px, py, x, y, proc_h): | 637 | def draw_process_connecting_lines(ctx, px, py, x, y, proc_h): |
395 | ctx.set_source_rgba(*DEP_COLOR) | 638 | ctx.set_source_rgba(*DEP_COLOR) |
396 | ctx.set_dash([2,2]) | 639 | ctx.set_dash([2, 2]) |
397 | if abs(px - x) < 3: | 640 | if abs(px - x) < 3: |
398 | dep_off_x = 3 | 641 | dep_off_x = 3 |
399 | dep_off_y = proc_h / 4 | 642 | dep_off_y = proc_h / 4 |
400 | ctx.move_to(x, y + proc_h / 2) | 643 | ctx.move_to(x, y + proc_h / 2) |
401 | ctx.line_to(px - dep_off_x, y + proc_h / 2) | 644 | ctx.line_to(px - dep_off_x, y + proc_h / 2) |
402 | ctx.line_to(px - dep_off_x, py - dep_off_y) | 645 | ctx.line_to(px - dep_off_x, py - dep_off_y) |
403 | ctx.line_to(px, py - dep_off_y) | 646 | ctx.line_to(px, py - dep_off_y) |
404 | else: | 647 | else: |
405 | ctx.move_to(x, y + proc_h / 2) | 648 | ctx.move_to(x, y + proc_h / 2) |
406 | ctx.line_to(px, y + proc_h / 2) | 649 | ctx.line_to(px, y + proc_h / 2) |
407 | ctx.line_to(px, py) | 650 | ctx.line_to(px, py) |
408 | ctx.stroke() | 651 | ctx.stroke() |
409 | ctx.set_dash([]) | 652 | ctx.set_dash([]) |
653 | |||
654 | # elide the bootchart collector - it is quite distorting | ||
655 | def elide_bootchart(proc): | ||
656 | return proc.cmd == 'bootchartd' or proc.cmd == 'bootchart-colle' | ||
657 | |||
658 | class CumlSample: | ||
659 | def __init__(self, proc): | ||
660 | self.cmd = proc.cmd | ||
661 | self.samples = [] | ||
662 | self.merge_samples (proc) | ||
663 | self.color = None | ||
664 | |||
665 | def merge_samples(self, proc): | ||
666 | self.samples.extend (proc.samples) | ||
667 | self.samples.sort (key = lambda p: p.time) | ||
668 | |||
669 | def next(self): | ||
670 | global palette_idx | ||
671 | palette_idx += HSV_STEP | ||
672 | return palette_idx | ||
673 | |||
674 | def get_color(self): | ||
675 | if self.color is None: | ||
676 | i = self.next() % HSV_MAX_MOD | ||
677 | h = 0.0 | ||
678 | if i is not 0: | ||
679 | h = (1.0 * i) / HSV_MAX_MOD | ||
680 | s = 0.5 | ||
681 | v = 1.0 | ||
682 | c = colorsys.hsv_to_rgb (h, s, v) | ||
683 | self.color = (c[0], c[1], c[2], 1.0) | ||
684 | return self.color | ||
685 | |||
686 | |||
687 | def draw_cuml_graph(ctx, proc_tree, chart_bounds, duration, sec_w, stat_type): | ||
688 | global palette_idx | ||
689 | palette_idx = 0 | ||
690 | |||
691 | time_hash = {} | ||
692 | total_time = 0.0 | ||
693 | m_proc_list = {} | ||
694 | |||
695 | if stat_type is STAT_TYPE_CPU: | ||
696 | sample_value = 'cpu' | ||
697 | else: | ||
698 | sample_value = 'io' | ||
699 | for proc in proc_tree.process_list: | ||
700 | if elide_bootchart(proc): | ||
701 | continue | ||
702 | |||
703 | for sample in proc.samples: | ||
704 | total_time += getattr(sample.cpu_sample, sample_value) | ||
705 | if not sample.time in time_hash: | ||
706 | time_hash[sample.time] = 1 | ||
707 | |||
708 | # merge pids with the same cmd | ||
709 | if not proc.cmd in m_proc_list: | ||
710 | m_proc_list[proc.cmd] = CumlSample (proc) | ||
711 | continue | ||
712 | s = m_proc_list[proc.cmd] | ||
713 | s.merge_samples (proc) | ||
714 | |||
715 | # all the sample times | ||
716 | times = sorted(time_hash) | ||
717 | if len (times) < 2: | ||
718 | print("degenerate boot chart") | ||
719 | return | ||
720 | |||
721 | pix_per_ns = chart_bounds[3] / total_time | ||
722 | # print "total time: %g pix-per-ns %g" % (total_time, pix_per_ns) | ||
723 | |||
724 | # FIXME: we have duplicates in the process list too [!] - why !? | ||
725 | |||
726 | # Render bottom up, left to right | ||
727 | below = {} | ||
728 | for time in times: | ||
729 | below[time] = chart_bounds[1] + chart_bounds[3] | ||
730 | |||
731 | # same colors each time we render | ||
732 | random.seed (0) | ||
733 | |||
734 | ctx.set_line_width(1) | ||
735 | |||
736 | legends = [] | ||
737 | labels = [] | ||
738 | |||
739 | # render each pid in order | ||
740 | for cs in m_proc_list.values(): | ||
741 | row = {} | ||
742 | cuml = 0.0 | ||
743 | |||
744 | # print "pid : %s -> %g samples %d" % (proc.cmd, cuml, len (cs.samples)) | ||
745 | for sample in cs.samples: | ||
746 | cuml += getattr(sample.cpu_sample, sample_value) | ||
747 | row[sample.time] = cuml | ||
748 | |||
749 | process_total_time = cuml | ||
750 | |||
751 | # hide really tiny processes | ||
752 | if cuml * pix_per_ns <= 2: | ||
753 | continue | ||
754 | |||
755 | last_time = times[0] | ||
756 | y = last_below = below[last_time] | ||
757 | last_cuml = cuml = 0.0 | ||
758 | |||
759 | ctx.set_source_rgba(*cs.get_color()) | ||
760 | for time in times: | ||
761 | render_seg = False | ||
762 | |||
763 | # did the underlying trend increase ? | ||
764 | if below[time] != last_below: | ||
765 | last_below = below[last_time] | ||
766 | last_cuml = cuml | ||
767 | render_seg = True | ||
768 | |||
769 | # did we move up a pixel increase ? | ||
770 | if time in row: | ||
771 | nc = round (row[time] * pix_per_ns) | ||
772 | if nc != cuml: | ||
773 | last_cuml = cuml | ||
774 | cuml = nc | ||
775 | render_seg = True | ||
776 | |||
777 | # if last_cuml > cuml: | ||
778 | # assert fail ... - un-sorted process samples | ||
779 | |||
780 | # draw the trailing rectangle from the last time to | ||
781 | # before now, at the height of the last segment. | ||
782 | if render_seg: | ||
783 | w = math.ceil ((time - last_time) * chart_bounds[2] / proc_tree.duration) + 1 | ||
784 | x = chart_bounds[0] + round((last_time - proc_tree.start_time) * chart_bounds[2] / proc_tree.duration) | ||
785 | ctx.rectangle (x, below[last_time] - last_cuml, w, last_cuml) | ||
786 | ctx.fill() | ||
787 | # ctx.stroke() | ||
788 | last_time = time | ||
789 | y = below [time] - cuml | ||
790 | |||
791 | row[time] = y | ||
792 | |||
793 | # render the last segment | ||
794 | x = chart_bounds[0] + round((last_time - proc_tree.start_time) * chart_bounds[2] / proc_tree.duration) | ||
795 | y = below[last_time] - cuml | ||
796 | ctx.rectangle (x, y, chart_bounds[2] - x, cuml) | ||
797 | ctx.fill() | ||
798 | # ctx.stroke() | ||
799 | |||
800 | # render legend if it will fit | ||
801 | if cuml > 8: | ||
802 | label = cs.cmd | ||
803 | extnts = ctx.text_extents(label) | ||
804 | label_w = extnts[2] | ||
805 | label_h = extnts[3] | ||
806 | # print "Text extents %g by %g" % (label_w, label_h) | ||
807 | labels.append((label, | ||
808 | chart_bounds[0] + chart_bounds[2] - label_w - off_x * 2, | ||
809 | y + (cuml + label_h) / 2)) | ||
810 | if cs in legends: | ||
811 | print("ARGH - duplicate process in list !") | ||
812 | |||
813 | legends.append ((cs, process_total_time)) | ||
814 | |||
815 | below = row | ||
816 | |||
817 | # render grid-lines over the top | ||
818 | draw_box_ticks(ctx, chart_bounds, sec_w) | ||
819 | |||
820 | # render labels | ||
821 | for l in labels: | ||
822 | draw_text(ctx, l[0], TEXT_COLOR, l[1], l[2]) | ||
823 | |||
824 | # Render legends | ||
825 | font_height = 20 | ||
826 | label_width = 300 | ||
827 | LEGENDS_PER_COL = 15 | ||
828 | LEGENDS_TOTAL = 45 | ||
829 | ctx.set_font_size (TITLE_FONT_SIZE) | ||
830 | dur_secs = duration / 100 | ||
831 | cpu_secs = total_time / 1000000000 | ||
832 | |||
833 | # misleading - with multiple CPUs ... | ||
834 | # idle = ((dur_secs - cpu_secs) / dur_secs) * 100.0 | ||
835 | if stat_type is STAT_TYPE_CPU: | ||
836 | label = "Cumulative CPU usage, by process; total CPU: " \ | ||
837 | " %.5g(s) time: %.3g(s)" % (cpu_secs, dur_secs) | ||
838 | else: | ||
839 | label = "Cumulative I/O usage, by process; total I/O: " \ | ||
840 | " %.5g(s) time: %.3g(s)" % (cpu_secs, dur_secs) | ||
841 | |||
842 | draw_text(ctx, label, TEXT_COLOR, chart_bounds[0] + off_x, | ||
843 | chart_bounds[1] + font_height) | ||
844 | |||
845 | i = 0 | ||
846 | legends = sorted(legends, key=itemgetter(1), reverse=True) | ||
847 | ctx.set_font_size(TEXT_FONT_SIZE) | ||
848 | for t in legends: | ||
849 | cs = t[0] | ||
850 | time = t[1] | ||
851 | x = chart_bounds[0] + off_x + int (i/LEGENDS_PER_COL) * label_width | ||
852 | y = chart_bounds[1] + font_height * ((i % LEGENDS_PER_COL) + 2) | ||
853 | str = "%s - %.0f(ms) (%2.2f%%)" % (cs.cmd, time/1000000, (time/total_time) * 100.0) | ||
854 | draw_legend_box(ctx, str, cs.color, x, y, leg_s) | ||
855 | i = i + 1 | ||
856 | if i >= LEGENDS_TOTAL: | ||
857 | break | ||