The data contained in this repository can be downloaded to your computer using one of several clients.
Please see the documentation of your version control software client for more information.

Please select the desired protocol below to get the URL.

This URL has Read-Only access.

Statistics
| Branch: | Revision:

main_repo / deps / v8 / tools / profviz / composer.js @ f230a1cf

History | View | Annotate | Download (19 KB)

1
// Copyright 2013 the V8 project authors. All rights reserved.
2
// Redistribution and use in source and binary forms, with or without
3
// modification, are permitted provided that the following conditions are
4
// met:
5
//
6
//     * Redistributions of source code must retain the above copyright
7
//       notice, this list of conditions and the following disclaimer.
8
//     * Redistributions in binary form must reproduce the above
9
//       copyright notice, this list of conditions and the following
10
//       disclaimer in the documentation and/or other materials provided
11
//       with the distribution.
12
//     * Neither the name of Google Inc. nor the names of its
13
//       contributors may be used to endorse or promote products derived
14
//       from this software without specific prior written permission.
15
//
16
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27

    
28
Array.prototype.top = function() {
29
  if (this.length == 0) return undefined;
30
  return this[this.length - 1];
31
}
32

    
33

    
34
function PlotScriptComposer(kResX, kResY, error_output) {
35
  // Constants.
36
  var kV8BinarySuffixes = ["/d8", "/libv8.so"];
37
  var kStackFrames = 8;             // Stack frames to display in the plot.
38

    
39
  var kTimerEventWidth = 0.33;      // Width of each timeline.
40
  var kExecutionFrameWidth = 0.2;   // Width of the top stack frame line.
41
  var kStackFrameWidth = 0.1;       // Width of the lower stack frame lines.
42
  var kGapWidth = 0.05;             // Gap between stack frame lines.
43

    
44
  var kY1Offset = 11;               // Offset for stack frame vs. event lines.
45
  var kDeoptRow = 7;                // Row displaying deopts.
46
  var kMaxDeoptLength = 4;          // Draw size of the largest deopt.
47
  var kPauseLabelPadding = 5;       // Padding for pause time labels.
48
  var kNumPauseLabels = 7;          // Number of biggest pauses to label.
49
  var kCodeKindLabelPadding = 100;  // Padding for code kind labels.
50

    
51
  var kTickHalfDuration = 0.5;      // Duration of half a tick in ms.
52
  var kMinRangeLength = 0.0005;     // Minimum length for an event in ms.
53

    
54
  var kNumThreads = 2;              // Number of threads.
55
  var kExecutionThreadId = 0;       // ID of main thread.
56

    
57
  // Init values.
58
  var num_timer_event = kY1Offset + 0.5;
59

    
60
  // Data structures.
61
  function TimerEvent(label, color, pause, thread_id) {
62
    assert(thread_id >= 0 && thread_id < kNumThreads, "invalid thread id");
63
    this.label = label;
64
    this.color = color;
65
    this.pause = pause;
66
    this.ranges = [];
67
    this.thread_id = thread_id;
68
    this.index = ++num_timer_event;
69
  }
70

    
71
  function CodeKind(color, kinds) {
72
    this.color = color;
73
    this.in_execution = [];
74
    this.stack_frames = [];
75
    for (var i = 0; i < kStackFrames; i++) this.stack_frames.push([]);
76
    this.kinds = kinds;
77
  }
78

    
79
  function Range(start, end) {
80
    this.start = start;  // In milliseconds.
81
    this.end = end;      // In milliseconds.
82
  }
83

    
84
  function Deopt(time, size) {
85
    this.time = time;  // In milliseconds.
86
    this.size = size;  // In bytes.
87
  }
88

    
89
  Range.prototype.duration = function() { return this.end - this.start; }
90

    
91
  function Tick(tick) {
92
    this.tick = tick;
93
  }
94

    
95
  var TimerEvents = {
96
      'V8.Execute':
97
        new TimerEvent("execution", "#000000", false, 0),
98
      'V8.External':
99
        new TimerEvent("external", "#3399FF", false, 0),
100
      'V8.CompileFullCode':
101
        new TimerEvent("compile unopt", "#CC0000",  true, 0),
102
      'V8.RecompileSynchronous':
103
        new TimerEvent("recompile sync", "#CC0044",  true, 0),
104
      'V8.RecompileConcurrent':
105
        new TimerEvent("recompile async", "#CC4499", false, 1),
106
      'V8.CompileEval':
107
        new TimerEvent("compile eval", "#CC4400",  true, 0),
108
      'V8.Parse':
109
        new TimerEvent("parse", "#00CC00",  true, 0),
110
      'V8.PreParse':
111
        new TimerEvent("preparse", "#44CC00",  true, 0),
112
      'V8.ParseLazy':
113
        new TimerEvent("lazy parse", "#00CC44",  true, 0),
114
      'V8.GCScavenger':
115
        new TimerEvent("gc scavenge", "#0044CC",  true, 0),
116
      'V8.GCCompactor':
117
        new TimerEvent("gc compaction", "#4444CC",  true, 0),
118
      'V8.GCContext':
119
        new TimerEvent("gc context", "#4400CC",  true, 0),
120
  };
121

    
122
  var CodeKinds = {
123
      'external ': new CodeKind("#3399FF", [-2]),
124
      'runtime  ': new CodeKind("#000000", [-1]),
125
      'full code': new CodeKind("#DD0000", [0]),
126
      'opt code ': new CodeKind("#00EE00", [1]),
127
      'code stub': new CodeKind("#FF00FF", [2]),
128
      'built-in ': new CodeKind("#AA00AA", [3]),
129
      'inl.cache': new CodeKind("#4444AA",
130
                                [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]),
131
      'reg.exp. ': new CodeKind("#0000FF", [15]),
132
  };
133

    
134
  var code_map = new CodeMap();
135
  var execution_pauses = [];
136
  var deopts = [];
137
  var event_stack = [];
138
  var last_time_stamp = [];
139
  for (var i = 0; i < kNumThreads; i++) {
140
    event_stack[i] = [];
141
    last_time_stamp[i] = -1;
142
  }
143

    
144
  var range_start = undefined;
145
  var range_end = undefined;
146
  var obj_index = 0;
147
  var pause_tolerance = 0.005;  // Milliseconds.
148
  var distortion = 0;
149

    
150
  // Utility functions.
151
  function assert(something, message) {
152
    if (!something) {
153
      var error = new Error(message);
154
      error_output(error.stack);
155
    }
156
  }
157

    
158
  function FindCodeKind(kind) {
159
    for (name in CodeKinds) {
160
      if (CodeKinds[name].kinds.indexOf(kind) >= 0) {
161
        return CodeKinds[name];
162
      }
163
    }
164
  }
165

    
166
  function TicksToRanges(ticks) {
167
    var ranges = [];
168
    for (var i = 0; i < ticks.length; i++) {
169
      var tick = ticks[i].tick;
170
      ranges.push(
171
          new Range(tick - kTickHalfDuration, tick + kTickHalfDuration));
172
    }
173
    return ranges;
174
  }
175

    
176
  function MergeRanges(ranges) {
177
    ranges.sort(function(a, b) { return a.start - b.start; });
178
    var result = [];
179
    var j = 0;
180
    for (var i = 0; i < ranges.length; i = j) {
181
      var merge_start = ranges[i].start;
182
      if (merge_start > range_end) break;  // Out of plot range.
183
      var merge_end = ranges[i].end;
184
      for (j = i + 1; j < ranges.length; j++) {
185
        var next_range = ranges[j];
186
        // Don't merge ranges if there is no overlap (incl. merge tolerance).
187
        if (next_range.start > merge_end + pause_tolerance) break;
188
        // Merge ranges.
189
        if (next_range.end > merge_end) {  // Extend range end.
190
          merge_end = next_range.end;
191
        }
192
      }
193
      if (merge_end < range_start) continue;  // Out of plot range.
194
      if (merge_end < merge_start) continue;  // Not an actual range.
195
      result.push(new Range(merge_start, merge_end));
196
    }
197
    return result;
198
  }
199

    
200
  function RestrictRangesTo(ranges, start, end) {
201
    var result = [];
202
    for (var i = 0; i < ranges.length; i++) {
203
      if (ranges[i].start <= end && ranges[i].end >= start) {
204
        result.push(new Range(Math.max(ranges[i].start, start),
205
                              Math.min(ranges[i].end, end)));
206
      }
207
    }
208
    return result;
209
  }
210

    
211
  // Public methods.
212
  this.collectData = function(input, distortion_per_entry) {
213

    
214
    var last_timestamp = 0;
215

    
216
    // Parse functions.
217
    var parseTimeStamp = function(timestamp) {
218
      int_timestamp = parseInt(timestamp);
219
      assert(int_timestamp >= last_timestamp, "Inconsistent timestamps.");
220
      last_timestamp = int_timestamp;
221
      distortion += distortion_per_entry;
222
      return int_timestamp / 1000 - distortion;
223
    }
224

    
225
    var processTimerEventStart = function(name, start) {
226
      // Find out the thread id.
227
      var new_event = TimerEvents[name];
228
      if (new_event === undefined) return;
229
      var thread_id = new_event.thread_id;
230

    
231
      start = Math.max(last_time_stamp[thread_id] + kMinRangeLength, start);
232

    
233
      // Last event on this thread is done with the start of this event.
234
      var last_event = event_stack[thread_id].top();
235
      if (last_event !== undefined) {
236
        var new_range = new Range(last_time_stamp[thread_id], start);
237
        last_event.ranges.push(new_range);
238
      }
239
      event_stack[thread_id].push(new_event);
240
      last_time_stamp[thread_id] = start;
241
    };
242

    
243
    var processTimerEventEnd = function(name, end) {
244
      // Find out about the thread_id.
245
      var finished_event = TimerEvents[name];
246
      var thread_id = finished_event.thread_id;
247
      assert(finished_event === event_stack[thread_id].pop(),
248
             "inconsistent event stack");
249

    
250
      end = Math.max(last_time_stamp[thread_id] + kMinRangeLength, end);
251

    
252
      var new_range = new Range(last_time_stamp[thread_id], end);
253
      finished_event.ranges.push(new_range);
254
      last_time_stamp[thread_id] = end;
255
    };
256

    
257
    var processCodeCreateEvent = function(type, kind, address, size, name) {
258
      var code_entry = new CodeMap.CodeEntry(size, name);
259
      code_entry.kind = kind;
260
      code_map.addCode(address, code_entry);
261
    };
262

    
263
    var processCodeMoveEvent = function(from, to) {
264
      code_map.moveCode(from, to);
265
    };
266

    
267
    var processCodeDeleteEvent = function(address) {
268
      code_map.deleteCode(address);
269
    };
270

    
271
    var processCodeDeoptEvent = function(time, size) {
272
      deopts.push(new Deopt(time, size));
273
    }
274

    
275
    var processSharedLibrary = function(name, start, end) {
276
      var code_entry = new CodeMap.CodeEntry(end - start, name);
277
      code_entry.kind = -3;  // External code kind.
278
      for (var i = 0; i < kV8BinarySuffixes.length; i++) {
279
        var suffix = kV8BinarySuffixes[i];
280
        if (name.indexOf(suffix, name.length - suffix.length) >= 0) {
281
          code_entry.kind = -1;  // V8 runtime code kind.
282
          break;
283
        }
284
      }
285
      code_map.addLibrary(start, code_entry);
286
    };
287

    
288
    var processTickEvent = function(
289
        pc, timer, unused_x, unused_y, vmstate, stack) {
290
      var tick = new Tick(timer);
291

    
292
      var entry = code_map.findEntry(pc);
293
      if (entry) FindCodeKind(entry.kind).in_execution.push(tick);
294

    
295
      for (var i = 0; i < kStackFrames; i++) {
296
        if (!stack[i]) break;
297
        var entry = code_map.findEntry(stack[i]);
298
        if (entry) FindCodeKind(entry.kind).stack_frames[i].push(tick);
299
      }
300
    };
301
    // Collect data from log.
302
    var logreader = new LogReader(
303
      { 'timer-event-start': { parsers: [null, parseTimeStamp],
304
                               processor: processTimerEventStart },
305
        'timer-event-end':   { parsers: [null, parseTimeStamp],
306
                               processor: processTimerEventEnd },
307
        'shared-library': { parsers: [null, parseInt, parseInt],
308
                            processor: processSharedLibrary },
309
        'code-creation':  { parsers: [null, parseInt, parseInt, parseInt, null],
310
                            processor: processCodeCreateEvent },
311
        'code-move':      { parsers: [parseInt, parseInt],
312
                            processor: processCodeMoveEvent },
313
        'code-delete':    { parsers: [parseInt],
314
                            processor: processCodeDeleteEvent },
315
        'code-deopt':     { parsers: [parseTimeStamp, parseInt],
316
                            processor: processCodeDeoptEvent },
317
        'tick':           { parsers: [parseInt, parseTimeStamp,
318
                                      null, null, parseInt, 'var-args'],
319
                            processor: processTickEvent }
320
      });
321

    
322
    var line;
323
    while (line = input()) {
324
      logreader.processLogLine(line);
325
    }
326

    
327
    // Collect execution pauses.
328
    for (name in TimerEvents) {
329
      var event = TimerEvents[name];
330
      if (!event.pause) continue;
331
      var ranges = event.ranges;
332
      for (var j = 0; j < ranges.length; j++) execution_pauses.push(ranges[j]);
333
    }
334
    execution_pauses = MergeRanges(execution_pauses);
335
  };
336

    
337

    
338
  this.findPlotRange = function(
339
    range_start_override, range_end_override, result_callback) {
340
    var start_found = (range_start_override || range_start_override == 0);
341
    var end_found = (range_end_override || range_end_override == 0);
342
    range_start = start_found ? range_start_override : Infinity;
343
    range_end = end_found ? range_end_override : -Infinity;
344

    
345
    if (!start_found || !end_found) {
346
      for (name in TimerEvents) {
347
        var ranges = TimerEvents[name].ranges;
348
        for (var i = 0; i < ranges.length; i++) {
349
          if (ranges[i].start < range_start && !start_found) {
350
            range_start = ranges[i].start;
351
          }
352
          if (ranges[i].end > range_end && !end_found) {
353
            range_end = ranges[i].end;
354
          }
355
        }
356
      }
357

    
358
      for (codekind in CodeKinds) {
359
        var ticks = CodeKinds[codekind].in_execution;
360
        for (var i = 0; i < ticks.length; i++) {
361
          if (ticks[i].tick < range_start && !start_found) {
362
            range_start = ticks[i].tick;
363
          }
364
          if (ticks[i].tick > range_end && !end_found) {
365
            range_end = ticks[i].tick;
366
          }
367
        }
368
      }
369
    }
370
    // Set pause tolerance to something appropriate for the plot resolution
371
    // to make it easier for gnuplot.
372
    pause_tolerance = (range_end - range_start) / kResX / 10;
373

    
374
    if (typeof result_callback === 'function') {
375
      result_callback(range_start, range_end);
376
    }
377
  };
378

    
379

    
380
  this.assembleOutput = function(output) {
381
    output("set yrange [0:" + (num_timer_event + 1) + "]");
382
    output("set xlabel \"execution time in ms\"");
383
    output("set xrange [" + range_start + ":" + range_end + "]");
384
    output("set style fill pattern 2 bo 1");
385
    output("set style rect fs solid 1 noborder");
386
    output("set style line 1 lt 1 lw 1 lc rgb \"#000000\"");
387
    output("set border 15 lw 0.2");  // Draw thin border box.
388
    output("set style line 2 lt 1 lw 1 lc rgb \"#9944CC\"");
389
    output("set xtics out nomirror");
390
    output("unset key");
391

    
392
    function DrawBarBase(color, start, end, top, bottom) {
393
      obj_index++;
394
      command = "set object " + obj_index + " rect";
395
      command += " from " + start + ", " + top;
396
      command += " to " + end + ", " + bottom;
397
      command += " fc rgb \"" + color + "\"";
398
      output(command);
399
    }
400

    
401
    function DrawBar(row, color, start, end, width) {
402
      DrawBarBase(color, start, end, row + width, row - width);
403
    }
404

    
405
    function DrawHalfBar(row, color, start, end, width) {
406
      DrawBarBase(color, start, end, row, row - width);
407
    }
408

    
409
    var percentages = {};
410
    var total = 0;
411
    for (var name in TimerEvents) {
412
      var event = TimerEvents[name];
413
      var ranges = RestrictRangesTo(event.ranges, range_start, range_end);
414
      ranges = MergeRanges(ranges);
415
      var sum =
416
        ranges.map(function(range) { return range.duration(); })
417
            .reduce(function(a, b) { return a + b; }, 0);
418
      percentages[name] = (sum / (range_end - range_start) * 100).toFixed(1);
419
    }
420

    
421
    // Plot deopts.
422
    deopts.sort(function(a, b) { return b.size - a.size; });
423
    var max_deopt_size = deopts.length > 0 ? deopts[0].size : Infinity;
424

    
425
    for (var i = 0; i < deopts.length; i++) {
426
      var deopt = deopts[i];
427
      DrawHalfBar(kDeoptRow, "#9944CC", deopt.time,
428
                  deopt.time + 10 * pause_tolerance,
429
                  deopt.size / max_deopt_size * kMaxDeoptLength);
430
    }
431

    
432
    // Name Y-axis.
433
    var ytics = [];
434
    for (name in TimerEvents) {
435
      var index = TimerEvents[name].index;
436
      var label = TimerEvents[name].label;
437
      ytics.push('"' + label + ' (' + percentages[name] + '%%)" ' + index);
438
    }
439
    ytics.push('"code kind color coding" ' + kY1Offset);
440
    ytics.push('"code kind in execution" ' + (kY1Offset - 1));
441
    ytics.push('"top ' + kStackFrames + ' js stack frames"' + ' ' +
442
               (kY1Offset - 2));
443
    ytics.push('"pause times" 0');
444
    ytics.push('"max deopt size: ' + (max_deopt_size / 1024).toFixed(1) +
445
               ' kB" ' + kDeoptRow);
446
    output("set ytics out nomirror (" + ytics.join(', ') + ")");
447

    
448
    // Plot timeline.
449
    for (var name in TimerEvents) {
450
      var event = TimerEvents[name];
451
      var ranges = MergeRanges(event.ranges);
452
      for (var i = 0; i < ranges.length; i++) {
453
        DrawBar(event.index, event.color,
454
                ranges[i].start, ranges[i].end,
455
                kTimerEventWidth);
456
      }
457
    }
458

    
459
    // Plot code kind gathered from ticks.
460
    for (var name in CodeKinds) {
461
      var code_kind = CodeKinds[name];
462
      var offset = kY1Offset - 1;
463
      // Top most frame.
464
      var row = MergeRanges(TicksToRanges(code_kind.in_execution));
465
      for (var j = 0; j < row.length; j++) {
466
        DrawBar(offset, code_kind.color,
467
                row[j].start, row[j].end, kExecutionFrameWidth);
468
      }
469
      offset = offset - 2 * kExecutionFrameWidth - kGapWidth;
470
      // Javascript frames.
471
      for (var i = 0; i < kStackFrames; i++) {
472
        offset = offset - 2 * kStackFrameWidth - kGapWidth;
473
        row = MergeRanges(TicksToRanges(code_kind.stack_frames[i]));
474
        for (var j = 0; j < row.length; j++) {
475
          DrawBar(offset, code_kind.color,
476
                  row[j].start, row[j].end, kStackFrameWidth);
477
        }
478
      }
479
    }
480

    
481
    // Add labels as legend for code kind colors.
482
    var padding = kCodeKindLabelPadding * (range_end - range_start) / kResX;
483
    var label_x = range_start;
484
    var label_y = kY1Offset;
485
    for (var name in CodeKinds) {
486
      label_x += padding;
487
      output("set label \"" + name + "\" at " + label_x + "," + label_y +
488
             " textcolor rgb \"" + CodeKinds[name].color + "\"" +
489
             " font \"Helvetica,9'\"");
490
      obj_index++;
491
    }
492

    
493
    if (execution_pauses.length == 0) {
494
      // Force plot and return without plotting execution pause impulses.
495
      output("plot 1/0");
496
      return;
497
    }
498

    
499
    // Label the longest pauses.
500
    execution_pauses =
501
        RestrictRangesTo(execution_pauses, range_start, range_end);
502
    execution_pauses.sort(
503
        function(a, b) { return b.duration() - a.duration(); });
504

    
505
    var max_pause_time = execution_pauses[0].duration();
506
    padding = kPauseLabelPadding * (range_end - range_start) / kResX;
507
    var y_scale = kY1Offset / max_pause_time / 2;
508
    for (var i = 0; i < execution_pauses.length && i < kNumPauseLabels; i++) {
509
      var pause = execution_pauses[i];
510
      var label_content = (pause.duration() | 0) + " ms";
511
      var label_x = pause.end + padding;
512
      var label_y = Math.max(1, (pause.duration() * y_scale));
513
      output("set label \"" + label_content + "\" at " +
514
             label_x + "," + label_y + " font \"Helvetica,7'\"");
515
      obj_index++;
516
    }
517

    
518
    // Scale second Y-axis appropriately.
519
    var y2range = max_pause_time * num_timer_event / kY1Offset * 2;
520
    output("set y2range [0:" + y2range + "]");
521
    // Plot graph with impulses as data set.
522
    output("plot '-' using 1:2 axes x1y2 with impulses ls 1");
523
    for (var i = 0; i < execution_pauses.length; i++) {
524
      var pause = execution_pauses[i];
525
      output(pause.end + " " + pause.duration());
526
      obj_index++;
527
    }
528
    output("e");
529
    return obj_index;
530
  };
531
}