Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(708)

Side by Side Diff: tools/traceline/svgui/traceline.js

Issue 20494: Import Traceline, a Windows performance trace event logger. (Closed)
Patch Set: Feedback. Created 11 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « tools/traceline/svgui/traceline.css ('k') | tools/traceline/svgui/traceline.xml » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // TODO
6 // - spacial partitioning of the data so that we don't have to scan the
7 // entire scene every time we render.
8 // - properly clip the SVG elements when they render, right now we are just
9 // letting them go negative or off the screen. This might give us a little
10 // bit better performance?
11 // - make the lines for thread creation work again. Figure out a better UI
12 // than these lines, because they can be a bit distracting.
13 // - Implement filters, so that you can filter on specific event types, etc.
14 // - Make the callstack box collapsable or scrollable or something, it takes
15 // up a lot of screen realestate now.
16 // - Figure out better ways to preserve screen realestate.
17 // - Make the thread bar heights configurable, figure out a better way to
18 // handle overlapping events (the pushdown code).
19 // - "Sticky" info, so you can click on something, and it will stay. Now
20 // if you need to scroll the page you usually lose the info because you
21 // will mouse over something else on your way to scrolling.
22 // - Help / legend
23 // - Loading indicator / debug console.
24 // - OH MAN BETTER COLORS PLEASE
25 //
26 // Dean McNamee <deanm@chromium.org>
27
28 // Man... namespaces are such a pain.
29 var svgNS = 'http://www.w3.org/2000/svg';
30 var xhtmlNS = 'http://www.w3.org/1999/xhtml';
31
32 function toHex(num) {
33 var str = "";
34 var table = "0123456789abcdef";
35 for (var i = 0; i < 8; ++i) {
36 str = table.charAt(num & 0xf) + str;
37 num >>= 4;
38 }
39 return str;
40 }
41
42 // a TLThread represents information about a thread in the traceline data.
43 // A thread has a list of all events that happened on that thread, the start
44 // and end time of the thread, the thread id, and name, etc.
45 function TLThread(id, startms, endms) {
46 this.id = id;
47 // Default the name to the thread id, but if the application uses
48 // thread naming, we might see a THREADNAME event later and update.
49 this.name = "thread_" + id;
50 this.startms = startms;
51 this.endms = endms;
52 this.events = [ ];
53 };
54
55 TLThread.prototype.duration_ms =
56 function() {
57 return this.endms - this.startms;
58 };
59
60 TLThread.prototype.AddEvent =
61 function(e) {
62 this.events.push(e);
63 };
64
65 TLThread.prototype.toString =
66 function() {
67 var res = "TLThread -- id: " + this.id + " name: " + this.name +
68 " startms: " + this.startms + " endms: " + this.endms +
69 " parent: " + this.parent;
70 return res;
71 };
72
73 // A TLEvent represents a single logged event that happened on a thread.
74 function TLEvent(e) {
75 this.eventtype = e['eventtype'];
76 this.thread = toHex(e['thread']);
77 this.cpu = toHex(e['cpu']);
78 this.ms = e['ms'];
79 this.e = e;
80 }
81
82 function HTMLEscape(str) {
83 return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
84 }
85
86 TLEvent.prototype.toString =
87 function() {
88 var res = "<b>ms:</b> " + this.ms + " " +
89 "<b>event:</b> " + this.eventtype + " " +
90 "<b>thread:</b> " + this.thread + " " +
91 "<b>cpu:</b> " + this.cpu + "<br/>";
92 if ('ldrinfo' in this.e) {
93 res += "<b>ldrinfo:</b> " + this.e['ldrinfo'] + "<br/>";
94 }
95 if ('done' in this.e && this.e['done'] > 0) {
96 res += "<b>done:</b> " + this.e['done'] + " ";
97 res += "<b>duration:</b> " + (this.e['done'] - this.ms) + "<br/>";
98 }
99 if ('syscall' in this.e) {
100 res += "<b>syscall:</b> " + this.e['syscall'];
101 if ('syscallname' in this.e) {
102 res += " <b>syscallname:</b> " + this.e['syscallname'];
103 }
104 if ('retval' in this.e) {
105 res += " <b>retval:</b> " + this.e['retval'];
106 }
107 res += "<br/>"
108 }
109 if ('func_addr' in this.e) {
110 res += "<b>func_addr:</b> " + toHex(this.e['func_addr']);
111 if ('func_addr_name' in this.e) {
112 res += " <b>func_addr_name:</b> " + HTMLEscape(this.e['func_addr_name']);
113 }
114 res += "<br/>"
115 }
116 if ('stacktrace' in this.e) {
117 var stack = this.e['stacktrace'];
118 res += "<b>stacktrace:</b><br/>";
119 for (var i = 0; i < stack.length; ++i) {
120 res += "0x" + toHex(stack[i][0]) + " - " +
121 HTMLEscape(stack[i][1]) + "<br/>";
122 }
123 }
124
125 return res;
126 }
127
128 // The trace logger dumps all log events to a simple JSON array. We delay
129 // and background load the JSON, since it can be large. When the JSON is
130 // loaded, parseEvents(...) is called and passed the JSON data. To make
131 // things easier, we do a few passes on the data to group them together by
132 // thread, gather together some useful pieces of data in a single place,
133 // and form more of a structure out of the data. We also build links
134 // between related events, for example a thread creating a new thread, and
135 // the new thread starting to run. This structure is fairly close to what
136 // we want to represent in the interface.
137
138 // Delay load the JSON data. We want to display the order in the order it was
139 // passed to us. Since we have no way of correlating the json callback to
140 // which script element it was called on, we load them one at a time.
141
142 function JSONLoader(json_urls) {
143 this.urls_to_load = json_urls;
144 this.script_element = null;
145 }
146
147 JSONLoader.prototype.IsFinishedLoading =
148 function() { return this.urls_to_load.length == 0; };
149
150 // Start loading of the next JSON URL.
151 JSONLoader.prototype.LoadNext =
152 function() {
153 var sc = document.createElementNS(
154 'http://www.w3.org/1999/xhtml', 'script');
155 this.script_element = sc;
156
157 sc.setAttribute("src", this.urls_to_load[0]);
158 document.getElementsByTagNameNS(xhtmlNS, 'body')[0].appendChild(sc);
159 };
160
161 // Callback counterpart to load_next, should be called when the script element
162 // is finished loading. Returns the URL that was just loaded.
163 JSONLoader.prototype.DoneLoading =
164 function() {
165 // Remove the script element from the DOM.
166 this.script_element.parentNode.removeChild(this.script_element);
167 this.script_element = null;
168 // Return the URL that had just finished loading.
169 return this.urls_to_load.shift();
170 };
171
172 var loader = null;
173
174 function loadJSON(json_urls) {
175 loader = new JSONLoader(json_urls);
176 if (!loader.IsFinishedLoading())
177 loader.LoadNext();
178 }
179
180 var traceline = new Traceline();
181
182 // Called from the JSON with the log event array.
183 function parseEvents(json) {
184 loader.DoneLoading();
185
186 var done = loader.IsFinishedLoading();
187 if (!done)
188 loader.LoadNext();
189
190 traceline.ProcessJSON(json);
191
192 if (done)
193 traceline.Render();
194 }
195
196 // The Traceline class represents our entire state, all of the threads from
197 // all sets of data, all of the events, DOM elements, etc.
198 function Traceline() {
199 // The array of threads that existed in the program. Hopefully in order
200 // they were created. This includes all threads from all sets of data.
201 this.threads = [ ];
202
203 // Keep a mapping of where in the list of threads a set starts...
204 this.thread_set_indexes = [ ];
205
206 // Map a thread id to the index in the threads array. A thread ID is the
207 // unique ID from the OS, along with our set id of which data file we were.
208 this.threads_by_id = { };
209
210 // The last event time of all of our events.
211 this.endms = 0;
212
213 // Constants for SVG rendering...
214 this.kThreadHeightPx = 16;
215 this.kTimelineWidthPx = 1008;
216 }
217
218 // Called to add another set of data into the traceline.
219 Traceline.prototype.ProcessJSON =
220 function(json_data) {
221 // Keep track of which threads belong to which sets of data...
222 var set_id = this.thread_set_indexes.length;
223 this.thread_set_indexes.push(this.threads.length);
224
225 // TODO make this less hacky. Used to connect related events, like creating
226 // a thread and then having that thread run (two separate events which are
227 // related but come in at different times, etc).
228 var tiez = { };
229
230 // Run over the data, building TLThread's and TLEvents, and doing some
231 // processing to put things in an easier to display form...
232 for (var i = 0, il = json_data.length; i < il; ++i) {
233 var e = new TLEvent(json_data[i]);
234
235 // Create a unique identifier for a thread by using the id of this data
236 // set, so that they are isolated from other sets of data with the same
237 // thread id, etc. TODO don't overwrite the original...
238 e.thread = set_id + '_' + e.thread;
239
240 // If this is the first event ever seen on this thread, create a new
241 // thread object and add it to our lists of threads.
242 if (!(e.thread in this.threads_by_id)) {
243 var new_thread = new TLThread(e.thread, e.ms, e.ms);
244 this.threads_by_id[new_thread.id] = this.threads.length;
245 this.threads.push(new_thread);
246 }
247
248 var thread = this.threads[this.threads_by_id[e.thread]];
249 thread.AddEvent(e);
250
251 // Keep trace of the time of the last event seen.
252 if (e.ms > this.endms) this.endms = e.ms;
253 if (e.ms > thread.endms) thread.endms = e.ms;
254
255 switch(e.eventtype) {
256 case 'EVENT_TYPE_THREADNAME':
257 thread.name = e.e['threadname'];
258 break;
259 case 'EVENT_TYPE_CREATETHREAD':
260 tiez[e.e['eventid']] = e;
261 break;
262 case 'EVENT_TYPE_THREADBEGIN':
263 var pei = e.e['parenteventid'];
264 if (pei in tiez) {
265 e.parentevent = tiez[pei];
266 tiez[pei].childevent = e;
267 }
268 break;
269 }
270 }
271 };
272
273 Traceline.prototype.Render =
274 function() { this.RenderSVG(); };
275
276 Traceline.prototype.RenderText =
277 function() {
278 var z = document.getElementsByTagNameNS(xhtmlNS, 'body')[0];
279 for (var i = 0, il = this.threads.length; i < il; ++i) {
280 var p = document.createElementNS(
281 'http://www.w3.org/1999/xhtml', 'p');
282 p.innerHTML = this.threads[i].toString();
283 z.appendChild(p);
284 }
285 };
286
287 // Oh man, so here we go. For two reasons, I implement my own scrolling
288 // system. First off, is that in order to scale, we want to have as little
289 // on the DOM as possible. This means not having off-screen elements in the
290 // DOM, as this slows down everything. This comes at a cost of more expensive
291 // scrolling performance since you have to re-render the scene. The second
292 // reason is a bug I stumbled into:
293 // https://bugs.webkit.org/show_bug.cgi?id=21968
294 // This means that scrolling an SVG element doesn't really work properly
295 // anyway. So what the code does is this. We have our layout that looks like:
296 // [ thread names ] [ svg timeline ]
297 // [ scroll bar ]
298 // We make a fake scrollbar, which doesn't actually have the SVG inside of it,
299 // we want for when this scrolls, with some debouncing, and then when it has
300 // scrolled we rerendering the scene. This means that the SVG element is never
301 // scrolled, and coordinates are always at 0. We keep the scene in millisecond
302 // units which also helps for zooming. We do our own hit testing and decide
303 // what needs to be renderer, convert from milliseconds to SVG pixels, and then
304 // draw the update into the static SVG element... Y coordinates are still
305 // always in pixels (since we aren't paging along the Y axis), but this might
306 // be something to fix up later.
307
308 function SVGSceneLine(msg, klass, x1, y1, x2, y2) {
309 this.type = SVGSceneLine;
310 this.msg = msg;
311 this.klass = klass;
312
313 this.x1 = x1;
314 this.y1 = y1;
315 this.x2 = x2;
316 this.y2 = y2;
317
318 this.hittest = function(startms, dur) {
319 return true;
320 };
321 }
322
323 function SVGSceneRect(msg, klass, x, y, width, height) {
324 this.type = SVGSceneRect;
325 this.msg = msg;
326 this.klass = klass;
327
328 this.x = x;
329 this.y = y;
330 this.width = width;
331 this.height = height;
332
333 this.hittest = function(startms, dur) {
334 return this.x <= (startms + dur) &&
335 (this.x + this.width) >= startms;
336 };
337 }
338
339 Traceline.prototype.RenderSVG =
340 function() {
341 var threadnames = this.RenderSVGCreateThreadNames();
342 var scene = this.RenderSVGCreateScene();
343
344 var curzoom = 8;
345
346 // The height is static after we've created the scene
347 var dom = this.RenderSVGCreateDOM(threadnames, scene.height);
348
349 dom.zoom(curzoom);
350
351 dom.attach();
352
353 var draw = (function(obj) {
354 return function(scroll, total) {
355 var startms = (scroll / total) * obj.endms;
356
357 var start = (new Date).getTime();
358 var count = obj.RenderSVGRenderScene(dom, scene, startms, curzoom);
359 var total = (new Date).getTime() - start;
360
361 dom.infoareadiv.innerHTML =
362 'Scene render of ' + count + ' nodes took: ' + total + ' ms';
363 };
364 })(this, dom, scene);
365
366 // Paint the initial paint with no scroll
367 draw(0, 1);
368
369 // Hook us up to repaint on scrolls.
370 dom.redraw = draw;
371 };
372
373
374 // Create all of the DOM elements for the SVG scene.
375 Traceline.prototype.RenderSVGCreateDOM =
376 function(threadnames, svgheight) {
377
378 // Total div holds the container and the info area.
379 var totaldiv = document.createElementNS(xhtmlNS, 'div');
380
381 // Container holds the thread names, SVG element, and fake scroll bar.
382 var container = document.createElementNS(xhtmlNS, 'div');
383 container.className = 'container';
384
385 // This is the div that holds the thread names along the left side, this is
386 // done in HTML for easier/better text support than SVG.
387 var threadnamesdiv = document.createElementNS(xhtmlNS, 'div');
388 threadnamesdiv.className = 'threadnamesdiv';
389
390 // Add all of the names into the div, these are static and don't update.
391 for (var i = 0, il = threadnames.length; i < il; ++i) {
392 var div = document.createElementNS(xhtmlNS, 'div');
393 div.className = 'threadnamediv';
394 div.appendChild(document.createTextNode(threadnames[i]));
395 threadnamesdiv.appendChild(div);
396 }
397
398 // SVG div goes along the right side, it holds the SVG element and our fake
399 // scroll bar.
400 var svgdiv = document.createElementNS(xhtmlNS, 'div');
401 svgdiv.className = 'svgdiv';
402
403 // The SVG element, static width, and we will update the height after we've
404 // walked through how many threads we have and know the size.
405 var svg = document.createElementNS(svgNS, 'svg');
406 svg.setAttributeNS(null, 'height', svgheight);
407 svg.setAttributeNS(null, 'width', this.kTimelineWidthPx);
408
409 // The fake scroll div is an outer div with a fixed size with a scroll.
410 var fakescrolldiv = document.createElementNS(xhtmlNS, 'div');
411 fakescrolldiv.className = 'fakescrolldiv';
412
413 // Fatty is inside the fake scroll div to give us the size we want to scroll.
414 var fattydiv = document.createElementNS(xhtmlNS, 'div');
415 fattydiv.className = 'fattydiv';
416 fakescrolldiv.appendChild(fattydiv);
417
418 var infoareadiv = document.createElementNS(xhtmlNS, 'div');
419 infoareadiv.className = 'infoareadiv';
420 infoareadiv.innerHTML = 'Hover an event...';
421
422 // Set the SVG mouseover handler to write the data to the infoarea.
423 svg.addEventListener('mouseover', (function(infoarea) {
424 return function(e) {
425 if ('msg' in e.target && e.target.msg) {
426 infoarea.innerHTML = e.target.msg;
427 }
428 e.stopPropagation(); // not really needed, but might as well.
429 };
430 })(infoareadiv), true);
431
432
433 svgdiv.appendChild(svg);
434 svgdiv.appendChild(fakescrolldiv);
435
436 container.appendChild(threadnamesdiv);
437 container.appendChild(svgdiv);
438
439 totaldiv.appendChild(container);
440 totaldiv.appendChild(infoareadiv);
441
442 var widthms = Math.floor(this.endms + 2);
443 // Make member variables out of the things we want to 'export', things that
444 // will need to be updated each time we redraw the scene.
445 var obj = {
446 // The root of our piece of the DOM.
447 'totaldiv': totaldiv,
448 // We will want to listen for scrolling on the fakescrolldiv
449 'fakescrolldiv': fakescrolldiv,
450 // The SVG element will of course need updating.
451 'svg': svg,
452 // The area we update with the info on mouseovers.
453 'infoareadiv': infoareadiv,
454 // Called when we detected new scroll a should redraw
455 'redraw': function() { },
456 'attached': false,
457 'attach': function() {
458 document.getElementsByTagNameNS(xhtmlNS, 'body')[0].appendChild(
459 this.totaldiv);
460 this.attached = true;
461 },
462 // The fatty div will have it's width adjusted based on the zoom level and
463 // the duration of the graph, to get the scrolling correct for the size.
464 'zoom': function(curzoom) {
465 var width = widthms * curzoom;
466 fattydiv.style.width = width + 'px';
467 },
468 'detach': function() {
469 this.totaldiv.parentNode.removeChild(this.totaldiv);
470 this.attached = false;
471 },
472 };
473
474 // Watch when we get scroll events on the fake scrollbar and debounce. We
475 // need to give it a pointer to use in the closer to call this.redraw();
476 fakescrolldiv.addEventListener('scroll', (function(theobj) {
477 var seqnum = 0;
478 return function(e) {
479 seqnum = (seqnum + 1) & 0xffff;
480 window.setTimeout((function(myseqnum) {
481 return function() {
482 if (seqnum == myseqnum) {
483 theobj.redraw(e.target.scrollLeft, e.target.scrollWidth);
484 }
485 };
486 })(seqnum), 100);
487 };
488 })(obj), false);
489
490 return obj;
491 };
492
493 Traceline.prototype.RenderSVGCreateThreadNames =
494 function() {
495 // This names is the list to show along the left hand size.
496 var threadnames = [ ];
497
498 for (var i = 0, il = this.threads.length; i < il; ++i) {
499 var thread = this.threads[i];
500
501 // TODO make this not so stupid...
502 if (i != 0) {
503 for (var j = 0; j < this.thread_set_indexes.length; j++) {
504 if (i == this.thread_set_indexes[j]) {
505 threadnames.push('------');
506 break;
507 }
508 }
509 }
510
511 threadnames.push(thread.name);
512 }
513
514 return threadnames;
515 };
516
517 Traceline.prototype.RenderSVGCreateScene =
518 function() {
519 // This scene is just a list of SVGSceneRect and SVGSceneLine, in no great
520 // order. In the future they should be structured to make range checking
521 // faster.
522 var scene = [ ];
523
524 // Remember, for now, Y (height) coordinates are still in pixels, since we
525 // don't zoom or scroll in this direction. X coordinates are milliseconds.
526
527 var lasty = 0;
528 for (var i = 0, il = this.threads.length; i < il; ++i) {
529 var thread = this.threads[i];
530
531 // TODO make this not so stupid...
532 if (i != 0) {
533 for (var j = 0; j < this.thread_set_indexes.length; j++) {
534 if (i == this.thread_set_indexes[j]) {
535 lasty += this.kThreadHeightPx;
536 break;
537 }
538 }
539 }
540
541 // For this thread, create the background thread (blue band);
542 scene.push(new SVGSceneRect(null,
543 'thread',
544 thread.startms,
545 1 + lasty,
546 thread.duration_ms(),
547 this.kThreadHeightPx - 2));
548
549 // Now create all of the events...
550 var pushdown = [ 0, 0, 0, 0 ];
551 for (var j = 0, jl = thread.events.length; j < jl; ++j) {
552 var e = thread.events[j];
553
554 var y = 2 + lasty;
555
556 // TODO this is a hack just so that we know the correct why position
557 // so we can create the threadline...
558 if (e.childevent) {
559 e.marky = y;
560 }
561
562 // Handle events that we want to represent as lines and not event blocks,
563 // right now this is only thread creation. We map an event back to its
564 // "parent" event, and now lets add a line to represent that.
565 if (e.parentevent) {
566 var eparent = e.parentevent;
567 var msg = eparent.toString() + '<br/>' + e.toString();
568 scene.push(
569 new SVGSceneLine(msg, 'eventline',
570 eparent.ms, eparent.marky + 5, e.ms, lasty + 5));
571 }
572
573 // We get negative done values (well, really, it was 0 and then made
574 // relative to start time) when a syscall never returned...
575 var dur = 0;
576 if ('done' in e.e && e.e['done'] > 0) {
577 dur = e.e['done'] - e.ms;
578 }
579
580 // TODO skip short events for now, but eventually we should figure out
581 // a way to control this from the UI, etc.
582 if (dur < 0.2)
583 continue;
584
585 var width = dur;
586
587 // Try to find an available horizontal slot for our event.
588 for (var z = 0; z < pushdown.length; ++z) {
589 var found = false;
590 var slot = z;
591 if (pushdown[z] < e.ms) {
592 found = true;
593 }
594 if (!found) {
595 if (z != pushdown.length - 1)
596 continue;
597 slot = Math.floor(Math.random() * pushdown.length);
598 alert('blah');
599 }
600
601 pushdown[slot] = e.ms + dur;
602 y += slot * 4;
603 break;
604 }
605
606
607 // Create the event
608 klass = e.e.waiting ? 'eventwaiting' : 'event';
609 scene.push(
610 new SVGSceneRect(e.toString(), klass, e.ms, y, width, 3));
611
612 // If there is a "parentevent", we want to make a line there.
613 // TODO
614 }
615
616 lasty += this.kThreadHeightPx;
617 }
618
619 return {
620 'scene': scene,
621 'width': this.endms + 2,
622 'height': lasty,
623 };
624 };
625
626 Traceline.prototype.RenderSVGRenderScene =
627 function(dom, scene, startms, curzoom) {
628 var stuff = scene.scene;
629 var svg = dom.svg;
630
631 var count = 0;
632
633 // Remove everything from the DOM.
634 while (svg.firstChild)
635 svg.removeChild(svg.firstChild);
636
637 // Don't actually need this, but you can't transform on an svg element,
638 // so it's nice to have a <g> around for transforms...
639 var svgg = document.createElementNS(svgNS, 'g');
640
641 var dur = this.kTimelineWidthPx / curzoom;
642
643 function timeToPixel(x) {
644 var x = Math.floor(x*curzoom);
645 return (x == 0 ? 1 : x);
646 }
647
648 for (var i = 0, il = stuff.length; i < il; ++i) {
649 var thing = stuff[i];
650 if (!thing.hittest(startms, startms+dur))
651 continue;
652
653
654 if (thing.type == SVGSceneRect) {
655 var rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
656 rect.setAttributeNS(null, 'class', thing.klass)
657 // TODO timeToPixel could be negative, clamp it at 0
658 rect.setAttributeNS(null, 'x', timeToPixel(thing.x - startms));
659 rect.setAttributeNS(null, 'y', thing.y);
660 // TODO thing.width can be larger than our current view, clamp it.
661 rect.setAttributeNS(null, 'width', timeToPixel(thing.width));
662 rect.setAttributeNS(null, 'height', thing.height);
663 rect.msg = thing.msg;
664 svgg.appendChild(rect);
665 } else if (thing.type == SVGSceneLine) {
666 var line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
667 line.setAttributeNS(null, 'class', thing.klass)
668 // TODO timeToPixel could be negative, clamp it at 0
669 line.setAttributeNS(null, 'x1', timeToPixel(thing.x1 - startms));
670 line.setAttributeNS(null, 'y1', thing.y1);
671 line.setAttributeNS(null, 'x2', timeToPixel(thing.x2 - startms));
672 line.setAttributeNS(null, 'y2', thing.y2);
673 line.msg = thing.msg;
674 svgg.appendChild(line);
675 }
676
677 ++count;
678 }
679
680 // Append the 'g' element on after we've build it.
681 svg.appendChild(svgg);
682
683 return count;
684 };
OLDNEW
« no previous file with comments | « tools/traceline/svgui/traceline.css ('k') | tools/traceline/svgui/traceline.xml » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698