OLD | NEW |
(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, '&').replace(/</g, '<').replace(/>/g, '>'); |
| 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 }; |
OLD | NEW |