trace_event_importer_test.html revision cef7893435aa41160dd1255c43cb8498279738cc
1<!DOCTYPE html>
2<!--
3Copyright (c) 2013 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7
8<link rel="import" href="/tracing/core/test_utils.html">
9<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
10<link rel="import" href="/tracing/extras/measure/measure.html">
11<link rel="import" href="/tracing/importer/import.html">
12<link rel="import" href="/tracing/model/memory_dump_test_utils.html">
13<link rel="import" href="/tracing/model/scoped_id.html">
14<link rel="import" href="/tracing/model/vm_region.html">
15<link rel="import" href="/tracing/value/numeric.html">
16<link rel="import" href="/tracing/value/time_display_mode.html">
17<link rel="import" href="/tracing/value/unit.html">
18
19<script>
20'use strict';
21
22tr.b.unittest.testSuite(function() {
23  var findSliceNamed = tr.c.TestUtils.findSliceNamed;
24  var ColorScheme = tr.b.ColorScheme;
25  var MeasureAsyncSlice = tr.e.measure.MeasureAsyncSlice;
26  var ScopedId = tr.model.ScopedId;
27  var VMRegion = tr.model.VMRegion;
28  var ScalarNumeric = tr.v.ScalarNumeric;
29  var unitlessNumber_smallerIsBetter =
30      tr.v.Unit.byName.unitlessNumber_smallerIsBetter;
31  var checkDumpNumericsAndDiagnostics =
32      tr.model.MemoryDumpTestUtils.checkDumpNumericsAndDiagnostics;
33  var checkVMRegions = tr.model.MemoryDumpTestUtils.checkVMRegions;
34
35  function makeModel(events, opt_shift, opt_prune) {
36    return tr.c.TestUtils.newModelWithEvents([events], {
37      shiftWorldToZero: opt_shift,
38      pruneEmptyContainers: opt_prune
39    });
40  }
41
42  function makeUnshiftedModel(events) {
43    return makeModel(events, false);
44  }
45
46  function checkHeapEntry(entry, expectedDump, expectedSize, expectedTitles,
47      expectedObjectTypeName) {
48    assert.strictEqual(entry.heapDump, expectedDump);
49    assert.strictEqual(entry.size, expectedSize);
50    assert.strictEqual(entry.objectTypeName, expectedObjectTypeName);
51    if (expectedTitles === undefined) {
52      assert.isUndefined(entry.leafStackFrame);
53    } else {
54      assert.deepEqual(
55          entry.leafStackFrame.getUserFriendlyStackTrace(), expectedTitles);
56    }
57  }
58
59  function getFrame(heapEntry, distance) {
60    var frame = heapEntry.leafStackFrame;
61    for (; distance > 0; distance--)
62      frame = frame.parentFrame;
63    return frame;
64  }
65
66  test('canImportEmpty', function() {
67    assert.isFalse(tr.e.importer.TraceEventImporter.canImport([]));
68    assert.isFalse(tr.e.importer.TraceEventImporter.canImport(''));
69  });
70
71  test('basicSingleThreadNonnestedParsing', function() {
72    var events = [
73      {name: 'a', args: {}, pid: 52, ts: 520, cat: 'foo', tid: 53, ph: 'B'},
74      {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'},
75      {name: 'b', args: {}, pid: 52, ts: 629, cat: 'bar', tid: 53, ph: 'B'},
76      {name: 'b', args: {}, pid: 52, ts: 631, cat: 'bar', tid: 53, ph: 'E'}
77    ];
78
79    var m = makeModel(events);
80    assert.equal(m.numProcesses, 1);
81    var p = m.processes[52];
82    assert.isDefined(p);
83
84    assert.equal(p.numThreads, 1);
85    var t = p.threads[53];
86    assert.isDefined(t);
87    assert.equal(t.sliceGroup.length, 2);
88    assert.equal(t.tid, 53);
89    var slice = t.sliceGroup.slices[0];
90    assert.equal(slice.title, 'a');
91    assert.equal(slice.category, 'foo');
92    assert.equal(slice.start, 0);
93    assert.closeTo((560 - 520) / 1000, slice.duration, 1e-5);
94    assert.equal(slice.subSlices.length, 0);
95
96    slice = t.sliceGroup.slices[1];
97    assert.equal(slice.title, 'b');
98    assert.equal(slice.category, 'bar');
99    assert.closeTo((629 - 520) / 1000, slice.start, 1e-5);
100    assert.closeTo((631 - 629) / 1000, slice.duration, 1e-5);
101    assert.equal(slice.subSlices.length, 0);
102  });
103
104  test('basicSingleThreadNonnestedParsingWithCpuDuration', function() {
105    var events = [
106      {name: 'a', args: {}, pid: 52, ts: 520, cat: 'foo', tid: 53, ph: 'B', tts: 221}, // @suppress longLineCheck
107      {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E', tts: 259}, // @suppress longLineCheck
108      {name: 'b', args: {}, pid: 52, ts: 629, cat: 'bar', tid: 53, ph: 'B', tts: 329}, // @suppress longLineCheck
109      {name: 'b', args: {}, pid: 52, ts: 631, cat: 'bar', tid: 53, ph: 'E', tts: 331}  // @suppress longLineCheck
110    ];
111
112    var m = makeModel(events);
113    assert.equal(m.numProcesses, 1);
114    var p = m.processes[52];
115    assert.isDefined(p);
116
117    assert.equal(p.numThreads, 1);
118    var t = p.threads[53];
119    assert.isDefined(t);
120    assert.equal(t.sliceGroup.length, 2);
121    assert.equal(t.tid, 53);
122    var slice = t.sliceGroup.slices[0];
123    assert.equal(slice.title, 'a');
124    assert.equal(slice.category, 'foo');
125    assert.equal(slice.start, 0);
126    assert.closeTo((560 - 520) / 1000, slice.duration, 1e-5);
127    assert.closeTo((259 - 221) / 1000, slice.cpuDuration, 1e-5);
128    assert.equal(slice.subSlices.length, 0);
129
130    slice = t.sliceGroup.slices[1];
131    assert.equal(slice.title, 'b');
132    assert.equal(slice.category, 'bar');
133    assert.closeTo((629 - 520) / 1000, slice.start, 1e-5);
134    assert.closeTo((631 - 629) / 1000, slice.duration, 1e-5);
135    assert.closeTo((331 - 329) / 1000, slice.cpuDuration, 1e-5);
136    assert.equal(slice.subSlices.length, 0);
137  });
138
139  test('argumentDupeCreatesNonFailingImportError', function() {
140    var events = [
141      {name: 'a',
142        args: {'x': 1},
143        pid: 1,
144        ts: 520,
145        cat: 'foo',
146        tid: 1,
147        ph: 'B'},
148      {name: 'a',
149        args: {'x': 2},
150        pid: 1,
151        ts: 560,
152        cat: 'foo',
153        tid: 1,
154        ph: 'E'}
155    ];
156
157    var m = makeModel(events);
158    var t = m.processes[1].threads[1];
159    var sA = findSliceNamed(t.sliceGroup, 'a');
160
161    assert.equal(sA.args.x, 2);
162    assert.isTrue(m.hasImportWarnings);
163    assert.equal(1, m.importWarnings.length);
164  });
165
166  test('importMissingArgs', function() {
167    var events = [
168      {name: 'a', pid: 52, ts: 520, cat: 'foo', tid: 53, ph: 'B'},
169      {name: 'a', pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'},
170      {name: 'b', pid: 52, ts: 629, cat: 'bar', tid: 53, ph: 'I'}
171    ];
172
173    // This should not throw an exception.
174    makeModel(events);
175  });
176
177  test('importDoesNotChokeOnNulls', function() {
178    var events = [
179      {name: 'a', args: { foo: null }, pid: 52, ts: 520, cat: 'foo', tid: 53, ph: 'B'}, // @suppress longLineCheck
180      {name: 'a', pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
181    ];
182
183    // This should not throw an exception.
184    makeModel(events);
185  });
186
187  test('categoryBeginEndMismatchPrefersBegin', function() {
188    var events = [
189      {name: 'a', args: {}, pid: 52, ts: 520, cat: 'foo', tid: 53, ph: 'B'},
190      {name: 'a', args: {}, pid: 52, ts: 560, cat: 'bar', tid: 53, ph: 'E'}
191    ];
192
193    var m = makeModel(events);
194    assert.equal(m.numProcesses, 1);
195    var p = m.processes[52];
196    assert.isDefined(p);
197
198    assert.equal(p.numThreads, 1);
199    var t = p.threads[53];
200    assert.isDefined(t);
201    assert.equal(t.sliceGroup.length, 1);
202    assert.equal(t.tid, 53);
203    var slice = t.sliceGroup.slices[0];
204    assert.equal(slice.title, 'a');
205    assert.equal(slice.category, 'foo');
206  });
207
208  test('beginEndNameMismatch', function() {
209    var events = [
210      {name: 'a', args: {}, pid: 52, ts: 520, cat: 'foo', tid: 53, ph: 'B'},
211      {name: 'b', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
212    ];
213
214    var m = makeModel(events);
215    assert.isTrue(m.hasImportWarnings);
216    assert.equal(m.importWarnings.length, 1);
217  });
218
219  test('nestedParsing', function() {
220    var events = [
221      {name: 'a', args: {}, pid: 1, ts: 1, tts: 1, cat: 'foo', tid: 1, ph: 'B'},
222      {name: 'b', args: {}, pid: 1, ts: 2, tts: 2, cat: 'bar', tid: 1, ph: 'B'},
223      {name: 'b', args: {}, pid: 1, ts: 3, tts: 3, cat: 'bar', tid: 1, ph: 'E'},
224      {name: 'a', args: {}, pid: 1, ts: 4, tts: 3, cat: 'foo', tid: 1, ph: 'E'}
225    ];
226    var m = makeModel(events, false);
227    var t = m.processes[1].threads[1];
228
229    var sA = findSliceNamed(t.sliceGroup, 'a');
230    var sB = findSliceNamed(t.sliceGroup, 'b');
231
232    assert.equal(sA.title, 'a');
233    assert.equal(sA.category, 'foo');
234    assert.equal(sA.start, 0.001);
235    assert.equal(sA.duration, 0.003);
236    assert.equal(sA.selfTime, 0.002);
237    assert.equal(sA.cpuSelfTime, 0.001);
238
239    assert.equal(sB.title, 'b');
240    assert.equal(sB.category, 'bar');
241    assert.equal(sB.start, 0.002);
242    assert.equal(sB.duration, 0.001);
243
244    assert.equal(1, sA.subSlices.length);
245    assert.equal(sB, sA.subSlices[0]);
246    assert.equal(sA, sB.parentSlice);
247  });
248
249  test('nestedParsingWithTwoSubSlices', function() {
250    var events = [
251      {name: 'a', args: {}, pid: 1, ts: 1, tts: 1, cat: 'foo', tid: 1, ph: 'B'},
252      {name: 'b', args: {}, pid: 1, ts: 2, tts: 2, cat: 'bar', tid: 1, ph: 'B'},
253      {name: 'b', args: {}, pid: 1, ts: 3, tts: 3, cat: 'bar', tid: 1, ph: 'E'},
254      {name: 'c', args: {}, pid: 1, ts: 5, tts: 5, cat: 'baz', tid: 1, ph: 'B'},
255      {name: 'c', args: {}, pid: 1, ts: 7, tts: 6, cat: 'baz', tid: 1, ph: 'E'},
256      {name: 'a', args: {}, pid: 1, ts: 8, tts: 8, cat: 'foo', tid: 1, ph: 'E'}
257    ];
258    var m = makeModel(events, false);
259    var t = m.processes[1].threads[1];
260
261    var sA = findSliceNamed(t.sliceGroup, 'a');
262    var sB = findSliceNamed(t.sliceGroup, 'b');
263    var sC = findSliceNamed(t.sliceGroup, 'c');
264
265    assert.equal(sA.title, 'a');
266    assert.equal(sA.category, 'foo');
267    assert.equal(sA.start, 0.001);
268    assert.equal(sA.duration, 0.007);
269    assert.equal(sA.selfTime, 0.004);
270    assert.equal(sA.cpuSelfTime, 0.005);
271
272    assert.equal(sB.title, 'b');
273    assert.equal(sB.category, 'bar');
274    assert.equal(sB.start, 0.002);
275    assert.equal(sB.duration, 0.001);
276
277    assert.equal(sC.title, 'c');
278    assert.equal(sC.category, 'baz');
279    assert.equal(sC.start, 0.005);
280    assert.equal(sC.duration, 0.002);
281
282    assert.equal(sA.subSlices.length, 2);
283    assert.equal(sA.subSlices[0], sB);
284    assert.equal(sA.subSlices[1], sC);
285    assert.equal(sB.parentSlice, sA);
286    assert.equal(sC.parentSlice, sA);
287  });
288
289  test('nestedParsingWithDoubleNesting', function() {
290    var events = [
291      {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
292      {name: 'b', args: {}, pid: 1, ts: 2, cat: 'bar', tid: 1, ph: 'B'},
293      {name: 'c', args: {}, pid: 1, ts: 3, cat: 'baz', tid: 1, ph: 'B'},
294      {name: 'c', args: {}, pid: 1, ts: 5, cat: 'baz', tid: 1, ph: 'E'},
295      {name: 'b', args: {}, pid: 1, ts: 7, cat: 'bar', tid: 1, ph: 'E'},
296      {name: 'a', args: {}, pid: 1, ts: 8, cat: 'foo', tid: 1, ph: 'E'}
297    ];
298    var m = makeModel(events, false);
299    var t = m.processes[1].threads[1];
300
301    var sA = findSliceNamed(t.sliceGroup, 'a');
302    var sB = findSliceNamed(t.sliceGroup, 'b');
303    var sC = findSliceNamed(t.sliceGroup, 'c');
304
305    assert.equal(sA.title, 'a');
306    assert.equal(sA.category, 'foo');
307    assert.equal(sA.start, 0.001);
308    assert.equal(sA.duration, 0.007);
309    assert.equal(sA.selfTime, 0.002);
310
311    assert.equal(sB.title, 'b');
312    assert.equal(sB.category, 'bar');
313    assert.equal(sB.start, 0.002);
314    assert.equal(sB.duration, 0.005);
315    assert.equal(sA.selfTime, 0.002);
316
317    assert.equal(sC.title, 'c');
318    assert.equal(sC.category, 'baz');
319    assert.equal(sC.start, 0.003);
320    assert.equal(sC.duration, 0.002);
321
322    assert.equal(sA.subSlices.length, 1);
323    assert.equal(sA.subSlices[0], sB);
324    assert.equal(sB.parentSlice, sA);
325
326    assert.equal(sB.subSlices.length, 1);
327    assert.equal(sB.subSlices[0], sC);
328    assert.equal(sC.parentSlice, sB);
329  });
330
331
332  test('autoclosing', function() {
333    var events = [
334      // Slice that doesn't finish.
335      {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
336
337      // Slice that does finish to give an 'end time' to make autoclosing work.
338      {name: 'b', args: {}, pid: 1, ts: 1, cat: 'bar', tid: 2, ph: 'B'},
339      {name: 'b', args: {}, pid: 1, ts: 2, cat: 'bar', tid: 2, ph: 'E'}
340    ];
341    var m = makeUnshiftedModel(events);
342    var p = m.processes[1];
343    var t1 = p.threads[1];
344    var t2 = p.threads[2];
345    assert.isTrue(t1.sliceGroup.slices[0].didNotFinish);
346    assert.equal(t1.sliceGroup.slices[0].end, 0.001);
347  });
348
349  test('autoclosingLoneBegin', function() {
350    var events = [
351      // Slice that doesn't finish.
352      {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'}
353    ];
354    var m = makeModel(events);
355    var p = m.processes[1];
356    var t = p.threads[1];
357    var slice = t.sliceGroup.slices[0];
358    assert.equal(slice.title, 'a');
359    assert.equal(slice.category, 'foo');
360    assert.isTrue(slice.didNotFinish);
361    assert.equal(slice.start, 0);
362    assert.equal(slice.duration, 0);
363  });
364
365  test('autoclosingWithSubTasks', function() {
366    var events = [
367      {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
368      {name: 'b1', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'B'},
369      {name: 'b1', args: {}, pid: 1, ts: 3, cat: 'foo', tid: 1, ph: 'E'},
370      {name: 'b2', args: {}, pid: 1, ts: 3, cat: 'foo', tid: 1, ph: 'B'}
371    ];
372    var m = makeModel(events, false);
373    var t = m.processes[1].threads[1];
374
375    var sA = findSliceNamed(t.sliceGroup, 'a');
376    var sB1 = findSliceNamed(t.sliceGroup, 'b1');
377    var sB2 = findSliceNamed(t.sliceGroup, 'b2');
378
379    assert.equal(sA.end, 0.003);
380    assert.equal(sB1.end, 0.003);
381    assert.equal(sB2.end, 0.003);
382  });
383
384  test('autoclosingWithEventsOutsideBounds', function() {
385    var events = [
386      // Slice that begins before min and ends after max of the other threads.
387      {name: 'a', args: {}, pid: 1, ts: 0, cat: 'foo', tid: 1, ph: 'B'},
388      {name: 'b', args: {}, pid: 1, ts: 3, cat: 'foo', tid: 1, ph: 'B'},
389
390      // Slice that does finish to give an 'end time' to establish a basis
391      {name: 'c', args: {}, pid: 1, ts: 1, cat: 'bar', tid: 2, ph: 'B'},
392      {name: 'c', args: {}, pid: 1, ts: 2, cat: 'bar', tid: 2, ph: 'E'}
393    ];
394    var m = makeModel(events);
395    var p = m.processes[1];
396    var t = p.threads[1];
397    assert.equal(t.sliceGroup.length, 2);
398
399    var slice = findSliceNamed(t.sliceGroup, 'a');
400    assert.equal(slice.title, 'a');
401    assert.equal(slice.category, 'foo');
402    assert.equal(slice.start, 0);
403    assert.equal(slice.duration, 0.003);
404
405    var t2 = p.threads[2];
406    var slice2 = findSliceNamed(t2.sliceGroup, 'c');
407    assert.equal(slice2.title, 'c');
408    assert.equal(slice2.category, 'bar');
409    assert.equal(slice2.start, 0.001);
410    assert.equal(slice2.duration, 0.001);
411
412    assert.equal(m.bounds.min, 0.000);
413    assert.equal(m.bounds.max, 0.003);
414  });
415
416  test('nestedAutoclosing', function() {
417    var events = [
418      // Tasks that don't finish.
419      {name: 'a1', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
420      {name: 'a2', args: {}, pid: 1, ts: 1.5, cat: 'foo', tid: 1, ph: 'B'},
421
422      // Slice that does finish to give an 'end time' to make autoclosing work.
423      {name: 'b', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 2, ph: 'B'},
424      {name: 'b', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 2, ph: 'E'}
425    ];
426    var m = makeUnshiftedModel(events, false);
427    var t1 = m.processes[1].threads[1];
428    var t2 = m.processes[1].threads[2];
429
430    var sA1 = findSliceNamed(t1.sliceGroup, 'a1');
431    var sA2 = findSliceNamed(t1.sliceGroup, 'a2');
432    var sB = findSliceNamed(t2.sliceGroup, 'b');
433
434    assert.equal(sA1.end, 0.0015);
435    assert.equal(sA2.end, 0.0015);
436  });
437
438  test('taskColoring', function() {
439    // The test below depends on hashing of 'a' != 'b'. Fail early if that
440    // assumption is incorrect.
441    assert.notEqual(ColorScheme.getStringHash('a'),
442                    ColorScheme.getStringHash('b'));
443
444    var events = [
445      {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
446      {name: 'a', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'E'},
447      {name: 'b', args: {}, pid: 1, ts: 3, cat: 'bar', tid: 1, ph: 'B'},
448      {name: 'b', args: {}, pid: 1, ts: 4, cat: 'bar', tid: 1, ph: 'E'},
449      {name: 'a', args: {}, pid: 1, ts: 5, cat: 'baz', tid: 1, ph: 'B'},
450      {name: 'a', args: {}, pid: 1, ts: 6, cat: 'baz', tid: 1, ph: 'E'}
451    ];
452    var m = makeModel(events);
453    var p = m.processes[1];
454    var t = p.threads[1];
455    var a1 = t.sliceGroup.slices[0];
456    assert.equal(a1.title, 'a');
457    assert.equal(a1.category, 'foo');
458    var b = t.sliceGroup.slices[1];
459    assert.equal(b.title, 'b');
460    assert.equal(b.category, 'bar');
461    assert.notEqual(b.colorId, a1.colorId);
462    var a2 = t.sliceGroup.slices[2];
463    assert.equal(a2.title, 'a');
464    assert.equal(a2.category, 'baz');
465    assert.equal(a1.colorId, a2.colorId);
466  });
467
468  test('durationColorArgument', function() {
469    var events = [
470      {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B', cname: 'thread_state_unknown'}, // @suppress longLineCheck
471      {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'E', cname: 'thread_state_unknown'} // @suppress longLineCheck
472    ];
473
474    var m = makeModel(events);
475    var p = m.processes[1];
476    var t = p.threads[1];
477    assert.equal(t.sliceGroup.slices[0].colorId,
478                 ColorScheme.getColorIdForReservedName('thread_state_unknown'));
479  });
480
481  test('durationColorEnd', function() {
482    var events = [
483      {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B', cname: 'thread_state_sleeping'}, // @suppress longLineCheck
484      {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'E', cname: 'thread_state_unknown'} // @suppress longLineCheck
485    ];
486
487    var m = makeModel(events);
488    var p = m.processes[1];
489    var t = p.threads[1];
490    assert.equal(t.sliceGroup.slices[0].colorId,
491                 ColorScheme.getColorIdForReservedName('thread_state_unknown'));
492  });
493
494  test('completeColorArgument', function() {
495    var events = [
496      {name: 'a', args: {}, pid: 1, ts: 1, dur: 1, cat: 'foo', tid: 1, ph: 'X', cname: 'generic_work'} // @suppress longLineCheck
497    ];
498
499    var m = makeModel(events);
500    var p = m.processes[1];
501    var t = p.threads[1];
502    assert.equal(t.sliceGroup.slices[0].colorId,
503                 ColorScheme.getColorIdForReservedName('generic_work'));
504  });
505
506  test('asyncColorArgument', function() {
507    var events = [
508      {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'b', id: 1, cname: 'generic_work'}, // @suppress longLineCheck
509      {name: 'a', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'e', id: 1, cname: 'generic_work'} // @suppress longLineCheck
510    ];
511
512    var m = makeModel(events);
513    var p = m.processes[1];
514    var t = p.threads[1];
515    assert.equal(t.asyncSliceGroup.slices[0].colorId,
516                 ColorScheme.getColorIdForReservedName('generic_work'));
517  });
518
519  test('asyncColorEnd', function() {
520    var events = [
521      {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'b', id: 1, cname: 'thread_state_unknown'}, // @suppress longLineCheck
522      {name: 'a', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'e', id: 1, cname: 'generic_work'} // @suppress longLineCheck
523    ];
524
525    var m = makeModel(events);
526    var p = m.processes[1];
527    var t = p.threads[1];
528    assert.equal(t.asyncSliceGroup.slices[0].colorId,
529                 ColorScheme.getColorIdForReservedName('generic_work'));
530  });
531
532  test('instantThreadColorArgument', function() {
533    var events = [
534      {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'I', id: 1, cname: 'generic_work'} // @suppress longLineCheck
535    ];
536
537    var m = makeModel(events);
538    var p = m.processes[1];
539    var t = p.threads[1];
540    assert.equal(t.sliceGroup.slices[0].colorId,
541                 ColorScheme.getColorIdForReservedName('generic_work'));
542  });
543
544  test('instantProcessColorArgument', function() {
545    var events = [
546      {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'I', id: 1, s: 'p', cname: 'generic_work'} // @suppress longLineCheck
547    ];
548
549    var m = makeModel(events);
550    var p = m.processes[1];
551    assert.equal(p.instantEvents[0].colorId,
552                 ColorScheme.getColorIdForReservedName('generic_work'));
553  });
554
555  test('counterColorArgument', function() {
556    var events = [
557      {name: 'a', args: {'cats': 10}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'C', id: 1, cname: 'generic_work'} // @suppress longLineCheck
558    ];
559
560    var m = makeModel(events);
561    var p = m.processes[1];
562    assert.equal(p.counters['foo.a[1]'].series[0].color,
563                 ColorScheme.getColorIdForReservedName('generic_work'));
564    assert.equal(p.counters['foo.a[1]'].series.length, 1);
565  });
566
567  test('objectColorArgument', function() {
568    var events = [
569      {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'N', id: 1, cname: 'generic_work'}, // @suppress longLineCheck
570      {name: 'a', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'O', id: 1, cname: 'generic_work'}, // @suppress longLineCheck
571      {name: 'a', args: {}, pid: 1, ts: 3, cat: 'foo', tid: 1, ph: 'D', id: 1, cname: 'generic_work'} // @suppress longLineCheck
572    ];
573
574    var m = makeModel(events);
575    var p = m.processes[1];
576    var i = p.objects.instanceMapsByScopedId_['ptr'][1].instances[0];
577    assert.equal(i.colorId,
578                 ColorScheme.getColorIdForReservedName('generic_work'));
579  });
580
581  test('objectColorEnd', function() {
582    var events = [
583      {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'N', id: 1, cname: 'thread_state_sleeping'}, // @suppress longLineCheck
584      {name: 'a', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'O', id: 1, cname: 'thread_state_unknown'}, // @suppress longLineCheck
585      {name: 'a', args: {}, pid: 1, ts: 3, cat: 'foo', tid: 1, ph: 'D', id: 1, cname: 'generic_work'} // @suppress longLineCheck
586    ];
587
588    var m = makeModel(events);
589    var p = m.processes[1];
590    var i = p.objects.instanceMapsByScopedId_['ptr'][1].instances[0];
591    assert.equal(i.colorId,
592                 ColorScheme.getColorIdForReservedName('generic_work'));
593  });
594
595  test('multipleThreadParsing', function() {
596    var events = [
597      {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
598      {name: 'a', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'E'},
599      {name: 'b', args: {}, pid: 1, ts: 3, cat: 'bar', tid: 2, ph: 'B'},
600      {name: 'b', args: {}, pid: 1, ts: 4, cat: 'bar', tid: 2, ph: 'E'}
601    ];
602    var m = makeModel(events);
603    assert.equal(m.numProcesses, 1);
604    var p = m.processes[1];
605    assert.isDefined(p);
606
607    assert.equal(p.numThreads, 2);
608
609    // Check thread 1.
610    var t = p.threads[1];
611    assert.isDefined(t);
612    assert.equal(t.sliceGroup.length, 1);
613    assert.equal(t.tid, 1);
614
615    var slice = t.sliceGroup.slices[0];
616    assert.equal(slice.title, 'a');
617    assert.equal(slice.category, 'foo');
618    assert.equal(slice.start, 0);
619    assert.equal(slice.duration, (2 - 1) / 1000);
620    assert.equal(slice.subSlices.length, 0);
621
622    // Check thread 2.
623    var t = p.threads[2];
624    assert.isDefined(t);
625    assert.equal(t.sliceGroup.length, 1);
626    assert.equal(t.tid, 2);
627
628    slice = t.sliceGroup.slices[0];
629    assert.equal(slice.title, 'b');
630    assert.equal(slice.category, 'bar');
631    assert.equal(slice.start, (3 - 1) / 1000);
632    assert.equal(slice.duration, (4 - 3) / 1000);
633    assert.equal(slice.subSlices.length, 0);
634  });
635
636  test('multiplePidParsing', function() {
637    var events = [
638      {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
639      {name: 'a', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'E'},
640      {name: 'b', args: {}, pid: 2, ts: 3, cat: 'bar', tid: 2, ph: 'B'},
641      {name: 'b', args: {}, pid: 2, ts: 4, cat: 'bar', tid: 2, ph: 'E'}
642    ];
643    var m = makeModel(events);
644    assert.equal(m.numProcesses, 2);
645    var p = m.processes[1];
646    assert.isDefined(p);
647
648    assert.equal(p.numThreads, 1);
649
650    // Check process 1 thread 1.
651    var t = p.threads[1];
652    assert.isDefined(t);
653    assert.equal(t.sliceGroup.length, 1);
654    assert.equal(t.tid, 1);
655
656    var slice = t.sliceGroup.slices[0];
657    assert.equal(slice.title, 'a');
658    assert.equal(slice.category, 'foo');
659    assert.equal(slice.start, 0);
660    assert.equal(slice.duration, (2 - 1) / 1000);
661    assert.equal(slice.subSlices.length, 0);
662
663    // Check process 2 thread 2.
664    var p = m.processes[2];
665    assert.isDefined(p);
666    assert.equal(p.numThreads, 1);
667    var t = p.threads[2];
668    assert.isDefined(t);
669    assert.equal(t.sliceGroup.length, 1);
670    assert.equal(t.tid, 2);
671
672    slice = t.sliceGroup.slices[0];
673    assert.equal(slice.title, 'b');
674    assert.equal(slice.category, 'bar');
675    assert.equal(slice.start, (3 - 1) / 1000);
676    assert.equal(slice.duration, (4 - 3) / 1000);
677    assert.equal(slice.subSlices.length, 0);
678
679    // Check getAllThreads.
680    assert.deepEqual(m.getAllThreads(),
681                      [m.processes[1].threads[1], m.processes[2].threads[2]]);
682  });
683
684  // Process names.
685  test('processNames', function() {
686    var events = [
687      {name: 'process_name', args: {name: 'SomeProcessName'},
688        pid: 1, ts: 0, tid: 1, ph: 'M'},
689      {name: 'process_name', args: {name: 'SomeProcessName'},
690        pid: 2, ts: 0, tid: 1, ph: 'M'}
691    ];
692    var m = makeModel(events);
693    assert.equal(m.processes[1].name, 'SomeProcessName');
694  });
695
696  // Process labels.
697  test('processLabels', function() {
698    var events = [
699      {name: 'process_labels', args: {labels: 'foo,bar,bar,foo,baz'},
700        pid: 1, ts: 0, tid: 1, ph: 'M'},
701      {name: 'process_labels', args: {labels: 'baz'},
702        pid: 2, ts: 0, tid: 1, ph: 'M'}
703    ];
704    var m = makeModel(events);
705    assert.deepEqual(m.processes[1].labels, ['foo', 'bar', 'baz']);
706    assert.deepEqual(m.processes[2].labels, ['baz']);
707  });
708
709  // Process sort index.
710  test('processSortIndex', function() {
711    var events = [
712      {name: 'process_name', args: {name: 'First'},
713        pid: 2, ts: 0, tid: 1, ph: 'M'},
714      {name: 'process_name', args: {name: 'Second'},
715        pid: 2, ts: 0, tid: 1, ph: 'M'},
716      {name: 'process_sort_index', args: {sort_index: 1},
717        pid: 1, ts: 0, tid: 1, ph: 'M'}
718    ];
719    var m = makeModel(events);
720
721    // By name, p1 is before p2. But, its sort index overrides that.
722    assert.isAbove(m.processes[1].compareTo(m.processes[2]), 0);
723  });
724
725  // Thread names.
726  test('threadNames', function() {
727    var events = [
728      {name: 'thread_name', args: {name: 'Thread 1'},
729        pid: 1, ts: 0, tid: 1, ph: 'M'},
730      {name: 'thread_name', args: {name: 'Thread 2'},
731        pid: 2, ts: 0, tid: 2, ph: 'M'}
732    ];
733    var m = makeModel(events, false, false);
734    assert.equal(m.processes[1].threads[1].name, 'Thread 1');
735    assert.equal(m.processes[2].threads[2].name, 'Thread 2');
736  });
737
738  // Thread sort index.
739  test('threadSortIndex', function() {
740    var events = [
741      {name: 'thread_name', args: {name: 'Thread 1'},
742        pid: 1, ts: 0, tid: 1, ph: 'M'},
743      {name: 'thread_name', args: {name: 'Thread 2'},
744        pid: 1, ts: 0, tid: 2, ph: 'M'},
745      {name: 'thread_sort_index', args: {sort_index: 1},
746        pid: 1, ts: 0, tid: 1, ph: 'M'}
747    ];
748    var m = makeModel(events, false, false);
749
750    // By name, t1 is before t2. But, its sort index overrides that.
751    var t1 = m.processes[1].threads[1];
752    var t2 = m.processes[1].threads[2];
753    assert.isAbove(t1.compareTo(t2), 0);
754  });
755
756  // CPU counts.
757  test('cpuCounts', function() {
758    var events = [
759      {name: 'num_cpus', args: {number: 4},
760        pid: 7, ts: 0, tid: 0, ph: 'M'},
761      {name: 'num_cpus', args: {number: 4},
762        pid: 14, ts: 0, tid: 0, ph: 'M'}
763    ];
764    var m = makeModel(events);
765    assert.equal(m.kernel.softwareMeasuredCpuCount, 4);
766    assert.equal(m.kernel.bestGuessAtCpuCount, 4);
767  });
768
769  test('cpuCountsWithSandboxBeingConfused', function() {
770    var events = [
771      {name: 'num_cpus', args: {number: 4},
772        pid: 7, ts: 0, tid: 0, ph: 'M'},
773      {name: 'num_cpus', args: {number: 1},
774        pid: 14, ts: 0, tid: 0, ph: 'M'}
775    ];
776    var m = makeModel(events);
777    assert.equal(m.kernel.softwareMeasuredCpuCount, 4);
778    assert.equal(m.kernel.bestGuessAtCpuCount, 4);
779  });
780
781  test('parsingWhenEndComesFirst', function() {
782    var events = [
783      {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'E'},
784      {name: 'a', args: {}, pid: 1, ts: 4, cat: 'foo', tid: 1, ph: 'B'},
785      {name: 'a', args: {}, pid: 1, ts: 5, cat: 'foo', tid: 1, ph: 'E'}
786    ];
787    var m = makeModel(events, false);
788    var p = m.processes[1];
789    var t = p.threads[1];
790    assert.equal(t.sliceGroup.length, 1);
791    assert.equal(t.sliceGroup.slices[0].title, 'a');
792    assert.equal(t.sliceGroup.slices[0].category, 'foo');
793    assert.equal(t.sliceGroup.slices[0].start, 0.004);
794    assert.equal(t.sliceGroup.slices[0].duration, 0.001);
795    assert.isTrue(m.hasImportWarnings);
796    assert.equal(m.importWarnings.length, 1);
797  });
798
799  test('immediateParsing', function() {
800    var events = [
801      // Need to include immediates inside a task so the timeline
802      // recentering/zeroing doesn't clobber their timestamp.
803      {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
804      {name: 'immediate', args: {}, pid: 1, ts: 2, cat: 'bar', tid: 1, ph: 'I'},
805      {name: 'slower', args: {}, pid: 1, ts: 4, cat: 'baz', tid: 1, ph: 'i'},
806      {name: 'a', args: {}, pid: 1, ts: 4, cat: 'foo', tid: 1, ph: 'E'}
807    ];
808    var m = makeModel(events, false);
809    var p = m.processes[1];
810    var t = p.threads[1];
811
812    assert.equal(t.sliceGroup.length, 3);
813    assert.equal(t.sliceGroup.slices[0].start, 0.001);
814    assert.equal(t.sliceGroup.slices[0].duration, 0.003);
815    assert.equal(t.sliceGroup.slices[1].start, 0.002);
816    assert.equal(t.sliceGroup.slices[1].duration, 0);
817    assert.equal(t.sliceGroup.slices[2].start, 0.004);
818
819    var slice = findSliceNamed(t.sliceGroup, 'a');
820    assert.equal(slice.title, 'a');
821    assert.equal(slice.category, 'foo');
822    assert.equal(slice.duration, 0.003);
823
824    var immed = findSliceNamed(t.sliceGroup, 'immediate');
825    assert.equal(immed.title, 'immediate');
826    assert.equal(immed.category, 'bar');
827    assert.equal(immed.start, 0.002);
828    assert.equal(immed.duration, 0);
829
830    var slower = findSliceNamed(t.sliceGroup, 'slower');
831    assert.equal(slower.title, 'slower');
832    assert.equal(slower.category, 'baz');
833    assert.equal(slower.start, 0.004);
834    assert.equal(slower.duration, 0);
835  });
836
837  test('simpleCounter', function() {
838    var events = [
839      {name: 'ctr', args: {'value': 0}, pid: 1, ts: 0, cat: 'foo', tid: 1,
840        ph: 'C'},
841      {name: 'ctr', args: {'value': 10}, pid: 1, ts: 10, cat: 'foo', tid: 1,
842        ph: 'C'},
843      {name: 'ctr', args: {'value': 0}, pid: 1, ts: 20, cat: 'foo', tid: 1,
844        ph: 'C'}
845
846    ];
847    var m = makeModel(events);
848    var p = m.processes[1];
849    var ctr = m.processes[1].counters['foo.ctr'];
850
851    assert.equal(ctr.name, 'ctr');
852    assert.equal(ctr.category, 'foo');
853    assert.equal(ctr.numSamples, 3);
854    assert.equal(ctr.numSeries, 1);
855
856    assert.equal(ctr.series[0].name, 'value');
857    assert.equal(ctr.series[0].color,
858                 ColorScheme.getColorIdForGeneralPurposeString('ctr.value'));
859
860    assert.deepEqual(ctr.timestamps, [0, 0.01, 0.02]);
861
862    var samples = [];
863    ctr.series[0].samples.forEach(function(sample) {
864      samples.push(sample.value);
865    });
866    assert.deepEqual(samples, [0, 10, 0]);
867
868    assert.deepEqual(ctr.totals, [0, 10, 0]);
869    assert.equal(ctr.maxTotal, 10);
870  });
871
872  test('instanceCounter', function() {
873    var events = [
874      {name: 'ctr', args: {'value': 0}, pid: 1, ts: 0, cat: 'foo', tid: 1,
875        ph: 'C', id: 0},
876      {name: 'ctr', args: {'value': 10}, pid: 1, ts: 10, cat: 'foo', tid: 1,
877        ph: 'C', id: 0},
878      {name: 'ctr', args: {'value': 10}, pid: 1, ts: 10, cat: 'foo', tid: 1,
879        ph: 'C', id: 1},
880      {name: 'ctr', args: {'value': 20}, pid: 1, ts: 15, cat: 'foo', tid: 1,
881        ph: 'C', id: 1},
882      {name: 'ctr', args: {'value': 30}, pid: 1, ts: 18, cat: 'foo', tid: 1,
883        ph: 'C', id: 1},
884      {name: 'ctr', args: {'value': 40}, pid: 1, ts: 20, cat: 'bar', tid: 1,
885        ph: 'C', id: 2}
886    ];
887    var m = makeModel(events);
888    var p = m.processes[1];
889    var ctr = m.processes[1].counters['foo.ctr[0]'];
890    assert.equal(ctr.name, 'ctr[0]');
891    assert.equal(ctr.category, 'foo');
892    assert.equal(ctr.numSamples, 2);
893    assert.equal(ctr.numSeries, 1);
894
895    assert.deepEqual(ctr.timestamps, [0, 0.01]);
896    var samples = [];
897    ctr.series[0].samples.forEach(function(sample) {
898      samples.push(sample.value);
899    });
900    assert.deepEqual(samples, [0, 10]);
901
902    ctr = m.processes[1].counters['foo.ctr[1]'];
903    assert.equal(ctr.name, 'ctr[1]');
904    assert.equal(ctr.category, 'foo');
905    assert.equal(ctr.numSamples, 3);
906    assert.equal(ctr.numSeries, 1);
907    assert.deepEqual(ctr.timestamps, [0.01, 0.015, 0.018]);
908
909    samples = [];
910    ctr.series[0].samples.forEach(function(sample) {
911      samples.push(sample.value);
912    });
913    assert.deepEqual(samples, [10, 20, 30]);
914
915    ctr = m.processes[1].counters['bar.ctr[2]'];
916    assert.equal(ctr.name, 'ctr[2]');
917    assert.equal(ctr.category, 'bar');
918    assert.equal(ctr.numSamples, 1);
919    assert.equal(ctr.numSeries, 1);
920    assert.deepEqual(ctr.timestamps, [0.02]);
921    var samples = [];
922    ctr.series[0].samples.forEach(function(sample) {
923      samples.push(sample.value);
924    });
925    assert.deepEqual(samples, [40]);
926  });
927
928  test('multiCounterUpdateBounds', function() {
929    var ctr = new tr.model.Counter(undefined, 'testBasicCounter',
930        '', 'testBasicCounter');
931    var value1Series = new tr.model.CounterSeries(
932        'value1', 'testBasicCounter.value1');
933    var value2Series = new tr.model.CounterSeries(
934        'value2', 'testBasicCounter.value2');
935    ctr.addSeries(value1Series);
936    ctr.addSeries(value2Series);
937
938    value1Series.addCounterSample(0, 0);
939    value1Series.addCounterSample(1, 1);
940    value1Series.addCounterSample(2, 1);
941    value1Series.addCounterSample(3, 2);
942    value1Series.addCounterSample(4, 3);
943    value1Series.addCounterSample(5, 1);
944    value1Series.addCounterSample(6, 3);
945    value1Series.addCounterSample(7, 3.1);
946
947    value2Series.addCounterSample(0, 0);
948    value2Series.addCounterSample(1, 0);
949    value2Series.addCounterSample(2, 1);
950    value2Series.addCounterSample(3, 1.1);
951    value2Series.addCounterSample(4, 0);
952    value2Series.addCounterSample(5, 7);
953    value2Series.addCounterSample(6, 0);
954    value2Series.addCounterSample(7, 0.5);
955
956    ctr.updateBounds();
957
958    assert.equal(ctr.bounds.min, 0);
959    assert.equal(ctr.bounds.max, 7);
960    assert.equal(ctr.maxTotal, 8);
961    assert.deepEqual([0, 0,
962                       1, 1,
963                       1, 2,
964                       2, 3.1,
965                       3, 3,
966                       1, 8,
967                       3, 3,
968                       3.1, 3.6], ctr.totals);
969  });
970
971  test('multiCounter', function() {
972    var events = [
973      {name: 'ctr', args: {'value1': 0, 'value2': 7}, pid: 1, ts: 0, cat: 'foo', tid: 1, ph: 'C'}, // @suppress longLineCheck
974      {name: 'ctr', args: {'value1': 10, 'value2': 4}, pid: 1, ts: 10, cat: 'foo', tid: 1, ph: 'C'}, // @suppress longLineCheck
975      {name: 'ctr', args: {'value1': 0, 'value2': 1 }, pid: 1, ts: 20, cat: 'foo', tid: 1, ph: 'C'} // @suppress longLineCheck
976    ];
977    var m = makeModel(events);
978    var p = m.processes[1];
979    var ctr = m.processes[1].counters['foo.ctr'];
980    assert.equal(ctr.name, 'ctr');
981
982    assert.equal(ctr.name, 'ctr');
983    assert.equal(ctr.category, 'foo');
984    assert.equal(ctr.numSamples, 3);
985    assert.equal(ctr.numSeries, 2);
986
987    assert.equal(ctr.series[0].name, 'value1');
988    assert.equal(ctr.series[1].name, 'value2');
989    assert.equal(ctr.series[0].color,
990                 ColorScheme.getColorIdForGeneralPurposeString('ctr.value1'));
991    assert.equal(ctr.series[1].color,
992                 ColorScheme.getColorIdForGeneralPurposeString('ctr.value2'));
993
994    assert.deepEqual(ctr.timestamps, [0, 0.01, 0.02]);
995    var samples = [];
996    ctr.series[0].samples.forEach(function(sample) {
997      samples.push(sample.value);
998    });
999    assert.deepEqual(samples, [0, 10, 0]);
1000
1001    var samples1 = [];
1002    ctr.series[1].samples.forEach(function(sample) {
1003      samples1.push(sample.value);
1004    });
1005    assert.deepEqual(samples1, [7, 4, 1]);
1006    assert.deepEqual([0, 7,
1007                       10, 14,
1008                       0, 1], ctr.totals);
1009    assert.equal(ctr.maxTotal, 14);
1010  });
1011
1012  test('importObjectInsteadOfArray', function() {
1013    var events = { traceEvents: [
1014      {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
1015      {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
1016    ] };
1017
1018    var m = makeModel(events);
1019    assert.equal(m.numProcesses, 1);
1020  });
1021
1022  test('importString', function() {
1023    var events = [
1024      {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
1025      {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
1026    ];
1027
1028    var m = makeModel(JSON.stringify(events));
1029    assert.equal(m.numProcesses, 1);
1030  });
1031
1032  test('importStringWithLeadingSpaces', function() {
1033    var events = [
1034      {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
1035      {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
1036    ];
1037
1038    var m = makeModel(' ' + JSON.stringify(events));
1039    assert.equal(m.numProcesses, 1);
1040  });
1041
1042  test('importStringWithTrailingNewLine', function() {
1043    var events = [
1044      {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
1045      {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
1046    ];
1047
1048    var m = makeModel(JSON.stringify(events) + '\n');
1049    assert.equal(m.numProcesses, 1);
1050  });
1051
1052  test('importStringWithMissingCloseSquareBracket', function() {
1053    var events = [
1054      {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
1055      {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
1056    ];
1057
1058    var tmp = JSON.stringify(events);
1059    assert.equal(tmp[tmp.length - 1], ']');
1060
1061    // Drop off the trailing ]
1062    var dropped = tmp.substring(0, tmp.length - 1);
1063    var m = makeModel(dropped);
1064    assert.equal(m.numProcesses, 1);
1065  });
1066
1067  test('importStringWithEndingCommaButMissingCloseSquareBracket', function() {
1068    var lines = [
1069      '[',
1070      '{"name": "a", "args": {}, "pid": 52, "ts": 524, "cat": "foo", "tid": 53, "ph": "B"},', // @suppress longLineCheck
1071      '{"name": "a", "args": {}, "pid": 52, "ts": 560, "cat": "foo", "tid": 53, "ph": "E"},' // @suppress longLineCheck
1072    ];
1073    var text = lines.join('\n');
1074
1075    var m = makeModel(text);
1076    assert.equal(m.numProcesses, 1);
1077    assert.equal(m.processes[52].threads[53].sliceGroup.length, 1);
1078  });
1079
1080  test('importStringWithMissingCloseSquareBracketAndNewline', function() {
1081    var events = [
1082      {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
1083      {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
1084    ];
1085
1086    var tmp = JSON.stringify(events);
1087    assert.equal(tmp[tmp.length - 1], ']');
1088
1089    // Drop off the trailing ] and add a newline
1090    var dropped = tmp.substring(0, tmp.length - 1);
1091    var m = makeModel(dropped + '\n');
1092    assert.equal(m.numProcesses, 1);
1093  });
1094
1095  test('ImportStringEndingCommaButMissingCloseSquareBracketCRLF', function() {
1096    var lines = [
1097      '[',
1098      '{"name": "a", "args": {}, "pid": 52, "ts": 524, "cat": "foo", "tid": 53, "ph": "B"},', // @suppress longLineCheck
1099      '{"name": "a", "args": {}, "pid": 52, "ts": 560, "cat": "foo", "tid": 53, "ph": "E"},' // @suppress longLineCheck
1100    ];
1101    var text = lines.join('\r\n');
1102
1103    var m = makeModel(text);
1104    assert.equal(m.numProcesses, 1);
1105    assert.equal(m.processes[52].threads[53].sliceGroup.length, 1);
1106  });
1107
1108  test('importOldFormat', function() {
1109    var lines = [
1110      '[',
1111      '{"cat":"a","pid":9,"tid":8,"ts":194,"ph":"E","name":"I","args":{}},',
1112      '{"cat":"b","pid":9,"tid":8,"ts":194,"ph":"B","name":"I","args":{}}',
1113      ']'
1114    ];
1115    var text = lines.join('\n');
1116    var m = makeModel(text);
1117    assert.equal(m.numProcesses, 1);
1118    assert.equal(m.processes[9].threads[8].sliceGroup.length, 1);
1119  });
1120
1121  test('startFinishOneSliceOneThread', function() {
1122    var events = [
1123      // Time is intentionally out of order.
1124      {name: 'a', args: {}, pid: 52, ts: 560, cat: 'cat', tid: 53,
1125        ph: 'F', id: 72},
1126      {name: 'a', pid: 52, ts: 524, cat: 'cat', tid: 53,
1127        ph: 'S', id: 72, args: {'foo': 'bar'}}
1128    ];
1129
1130    var m = makeModel(events);
1131    var t = m.processes[52].threads[53];
1132    assert.isDefined(t);
1133    assert.equal(t.asyncSliceGroup.slices.length, 1);
1134    assert.equal(t.asyncSliceGroup.slices[0].title, 'a');
1135    assert.equal(t.asyncSliceGroup.slices[0].category, 'cat');
1136    assert.isTrue(t.asyncSliceGroup.slices[0].isTopLevel);
1137    assert.equal(t.asyncSliceGroup.slices[0].id, 72);
1138    assert.equal(t.asyncSliceGroup.slices[0].args.foo, 'bar');
1139    assert.equal(t.asyncSliceGroup.slices[0].start, 0);
1140    assert.closeTo(
1141        (60 - 24) / 1000, t.asyncSliceGroup.slices[0].duration, 1e-5);
1142    assert.equal(t.asyncSliceGroup.slices[0].startThread, t);
1143    assert.equal(t.asyncSliceGroup.slices[0].endThread, t);
1144  });
1145
1146  test('endArgsAddedToSlice', function() {
1147    var events = [
1148      {name: 'a', args: {x: 1}, pid: 52, ts: 520, cat: 'foo', tid: 53, ph: 'B'},
1149      {name: 'a', args: {y: 2}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
1150    ];
1151
1152    var m = makeModel(events);
1153    assert.equal(m.numProcesses, 1);
1154    var p = m.processes[52];
1155    assert.isDefined(p);
1156
1157    assert.equal(p.numThreads, 1);
1158    var t = p.threads[53];
1159    assert.isDefined(t);
1160    assert.equal(t.sliceGroup.length, 1);
1161    assert.equal(t.tid, 53);
1162    var slice = t.sliceGroup.slices[0];
1163    assert.equal(slice.title, 'a');
1164    assert.equal(slice.category, 'foo');
1165    assert.equal(slice.start, 0);
1166    assert.equal(slice.subSlices.length, 0);
1167    assert.equal(slice.args['x'], 1);
1168    assert.equal(slice.args['y'], 2);
1169  });
1170
1171  test('endArgOverrwritesOriginalArgValueIfDuplicated', function() {
1172    var events = [
1173      {name: 'b', args: {z: 3}, pid: 52, ts: 629, cat: 'foo', tid: 53, ph: 'B'},
1174      {name: 'b', args: {z: 4}, pid: 52, ts: 631, cat: 'foo', tid: 53, ph: 'E'}
1175    ];
1176
1177    var m = makeModel(events);
1178    assert.equal(m.numProcesses, 1);
1179    var p = m.processes[52];
1180    assert.isDefined(p);
1181
1182    assert.equal(p.numThreads, 1);
1183    var t = p.threads[53];
1184    assert.isDefined(t);
1185    var slice = t.sliceGroup.slices[0];
1186    assert.equal(slice.title, 'b');
1187    assert.equal(slice.category, 'foo');
1188    assert.equal(slice.start, 0);
1189    assert.equal(slice.subSlices.length, 0);
1190    assert.equal(slice.args['z'], 4);
1191  });
1192
1193  test('asyncEndArgsAddedToSlice', function() {
1194    var events = [
1195      // Time is intentionally out of order.
1196      {name: 'c', args: {y: 2}, pid: 52, ts: 560, cat: 'foo', tid: 53,
1197        ph: 'F', id: 72},
1198      {name: 'c', args: {x: 1}, pid: 52, ts: 524, cat: 'foo', tid: 53,
1199        ph: 'S', id: 72}
1200    ];
1201
1202    var m = makeModel(events);
1203    var t = m.processes[52].threads[53];
1204    assert.isDefined(t);
1205    assert.equal(t.asyncSliceGroup.slices.length, 1);
1206    var parentSlice = t.asyncSliceGroup.slices[0];
1207    assert.equal(parentSlice.title, 'c');
1208    assert.equal(parentSlice.category, 'foo');
1209    assert.isTrue(parentSlice.isTopLevel);
1210    assert.equal(parentSlice.args['x'], 1);
1211    assert.equal(parentSlice.args['y'], 2);
1212
1213    assert.isDefined(parentSlice.subSlices);
1214    assert.equal(parentSlice.subSlices.length, 0);
1215  });
1216
1217  test('asyncEndArgOverwritesOriginalArgValueIfDuplicated', function() {
1218    var events = [
1219      // Time is intentionally out of order.
1220      {name: 'd', args: {z: 4}, pid: 52, ts: 560, cat: 'foo', tid: 53,
1221        ph: 'F', id: 72},
1222      {name: 'd', args: {z: 3}, pid: 52, ts: 524, cat: 'foo', tid: 53,
1223        ph: 'S', id: 72}
1224    ];
1225
1226    var m = makeModel(events);
1227    var t = m.processes[52].threads[53];
1228    assert.isDefined(t);
1229    assert.equal(t.asyncSliceGroup.slices.length, 1);
1230    var parentSlice = t.asyncSliceGroup.slices[0];
1231    assert.equal(parentSlice.title, 'd');
1232    assert.equal(parentSlice.category, 'foo');
1233    assert.isTrue(parentSlice.isTopLevel);
1234    assert.equal(parentSlice.args['z'], 4);
1235
1236    assert.isDefined(parentSlice.subSlices);
1237    assert.equal(parentSlice.subSlices.length, 0);
1238  });
1239
1240  test('asyncStepsInOneThread', function() {
1241    var events = [
1242      // Time is intentionally out of order.
1243      {name: 'a', args: {z: 3}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'F', id: 72}, // @suppress longLineCheck
1244      {name: 'a', args: {step: 's1', y: 2}, pid: 52, ts: 548, cat: 'foo', tid: 53, ph: 'T', id: 72}, // @suppress longLineCheck
1245      {name: 'a', args: {x: 1}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'S', id: 72} // @suppress longLineCheck
1246    ];
1247
1248    var m = makeModel(events);
1249    var t = m.processes[52].threads[53];
1250    assert.isDefined(t);
1251    assert.equal(t.asyncSliceGroup.slices.length, 1);
1252    var parentSlice = t.asyncSliceGroup.slices[0];
1253    assert.equal(parentSlice.title, 'a');
1254    assert.equal(parentSlice.category, 'foo');
1255    assert.isTrue(parentSlice.isTopLevel);
1256    assert.equal(parentSlice.start, 0);
1257    assert.equal(parentSlice.args['x'], 1);
1258    assert.isUndefined(parentSlice.args['y']);
1259    assert.equal(parentSlice.args['z'], 3);
1260
1261    assert.isDefined(parentSlice.subSlices);
1262    assert.equal(parentSlice.subSlices.length, 1);
1263
1264    var subSlice = parentSlice.subSlices[0];
1265    assert.equal(subSlice.title, 'a:s1');
1266    assert.equal(subSlice.category, 'foo');
1267    assert.isFalse(subSlice.isTopLevel);
1268    assert.closeTo((548 - 524) / 1000, subSlice.start, 1e-5);
1269    assert.closeTo((560 - 548) / 1000, subSlice.duration, 1e-5);
1270    assert.isUndefined(subSlice.args['x']);
1271    assert.equal(subSlice.args['y'], 2);
1272    assert.isUndefined(subSlice.args['z']);
1273  });
1274
1275  test('asyncStepsMissingStart', function() {
1276    var events = [
1277      // Time is intentionally out of order.
1278      {name: 'a', args: {z: 3}, pid: 52, ts: 560, cat: 'foo', tid: 53,
1279        ph: 'F', id: 72},
1280      {name: 'a', args: {step: 's1', y: 2}, pid: 52, ts: 548, cat: 'foo',
1281        tid: 53, ph: 'T', id: 72}
1282    ];
1283
1284    var m = makeModel(events);
1285    var t = m.processes[52].threads[53];
1286    assert.isUndefined(t);
1287  });
1288
1289  test('asyncStepsMissingFinish', function() {
1290    var events = [
1291      // Time is intentionally out of order.
1292      {name: 'a', args: {step: 's1', y: 2}, pid: 52, ts: 548, cat: 'foo',
1293        tid: 53, ph: 'T', id: 72},
1294      {name: 'a', args: {z: 3}, pid: 52, ts: 560, cat: 'foo', tid: 53,
1295        ph: 'S', id: 72}
1296    ];
1297
1298    var m = makeModel(events);
1299    var t = m.processes[52].threads[53];
1300    assert.isUndefined(t);
1301  });
1302
1303  test('asyncStepEndEvent', function() {
1304    var events = [
1305      // Time is intentionally out of order.
1306      {name: 'a', args: {z: 3}, pid: 52, ts: 560, cat: 'foo', tid: 53,
1307        ph: 'F', id: 72},
1308      {name: 'a', args: {step: 's1', y: 2}, pid: 52, ts: 548, cat: 'foo',
1309        tid: 53, ph: 'p', id: 72},
1310      {name: 'a', args: {x: 1}, pid: 52, ts: 524, cat: 'foo', tid: 53,
1311        ph: 'S', id: 72}
1312    ];
1313
1314    var m = makeModel(events);
1315    var t = m.processes[52].threads[53];
1316    assert.isDefined(t);
1317    assert.equal(t.asyncSliceGroup.slices.length, 1);
1318    var parentSlice = t.asyncSliceGroup.slices[0];
1319    assert.equal(parentSlice.title, 'a');
1320    assert.equal(parentSlice.category, 'foo');
1321    assert.isTrue(parentSlice.isTopLevel);
1322    assert.equal(parentSlice.start, 0);
1323    assert.equal(parentSlice.args['x'], 1);
1324    assert.isUndefined(parentSlice.args['y']);
1325    assert.equal(parentSlice.args['z'], 3);
1326
1327    assert.isDefined(parentSlice.subSlices);
1328    assert.equal(parentSlice.subSlices.length, 1);
1329    var subSlice = parentSlice.subSlices[0];
1330    assert.equal(subSlice.title, 'a:s1');
1331    assert.equal(subSlice.category, 'foo');
1332    assert.isFalse(subSlice.isTopLevel);
1333    assert.equal(subSlice.start, 0);
1334    assert.closeTo((548 - 524) / 1000, subSlice.duration, 1e-5);
1335    assert.isUndefined(subSlice.args['x']);
1336    assert.equal(subSlice.args['y'], 2);
1337    assert.isUndefined(subSlice.args['z']);
1338  });
1339
1340  test('asyncStepMismatch', function() {
1341    var events = [
1342      // Time is intentionally out of order.
1343      {name: 'a', args: {z: 3}, pid: 52, ts: 560, cat: 'foo', tid: 53,
1344        ph: 'F', id: 72},
1345      {name: 'a', args: {step: 's2'}, pid: 52, ts: 548, cat: 'foo', tid: 53,
1346        ph: 'T', id: 72},
1347      {name: 'a', args: {step: 's1'}, pid: 52, ts: 548, cat: 'foo', tid: 53,
1348        ph: 'p', id: 72},
1349      {name: 'a', args: {x: 1}, pid: 52, ts: 524, cat: 'foo', tid: 53,
1350        ph: 'S', id: 72}
1351    ];
1352
1353    var m = makeModel(events);
1354    var t = m.processes[52].threads[53];
1355    assert.isUndefined(t);
1356    assert.isTrue(m.hasImportWarnings);
1357  });
1358
1359  test('asyncSliceWithoutCPUDuration', function() {
1360    var events = [
1361      // Async slice without tts field.
1362      {name: 'a', args: {params: ''}, pid: 52, ts: 10, cat: 'foo', tid: 53,
1363        id: 72, ph: 'b'},
1364      {name: 'a', args: {params: ''}, pid: 52, ts: 20, cat: 'foo', tid: 53,
1365        id: 72, ph: 'e'},
1366      // Async slice with tts field but without use_async_tts marker field.
1367      {name: 'b', args: {params: ''}, pid: 52, ts: 30, cat: 'foo', tid: 53,
1368        id: 72, ph: 'b', tts: 30000},
1369      {name: 'b', args: {params: ''}, pid: 52, ts: 40, cat: 'foo', tid: 53,
1370        id: 72, ph: 'e', tts: 40000},
1371      // Async slice with tts field but with use_async_tts marker set to 0.
1372      {name: 'c', args: {params: ''}, pid: 52, ts: 50000, cat: 'foo', tid: 53,
1373       id: 72, ph: 'b', tts: 50000, use_async_tts: 0},
1374      {name: 'c', args: {params: ''}, pid: 52, ts: 60000, cat: 'foo', tid: 53,
1375        id: 72, ph: 'e', tts: 60000, use_async_tts: 0}
1376    ];
1377
1378    var m = makeModel(events);
1379    var t = m.processes[52].threads[53];
1380    assert.isDefined(t);
1381    assert.equal(t.asyncSliceGroup.slices.length, 3);
1382
1383    var noTTSNoField = t.asyncSliceGroup.slices[0];
1384    assert.isUndefined(noTTSNoField.cpuStart);
1385    assert.isUndefined(noTTSNoField.cpuDuration);
1386
1387    var TTSNoField = t.asyncSliceGroup.slices[1];
1388    assert.isUndefined(TTSNoField.cpuStart);
1389    assert.isUndefined(TTSNoField.cpuDuration);
1390
1391    var TTSZeroField = t.asyncSliceGroup.slices[2];
1392    assert.isUndefined(TTSZeroField.cpuStart);
1393    assert.isUndefined(TTSZeroField.cpuDuration);
1394  });
1395
1396  test('asyncSliceWithCPUDuration', function() {
1397    var events = [
1398      {name: 'a', args: {params: ''}, pid: 52, ts: 50000, cat: 'foo', tid: 53,
1399       id: 72, ph: 'b', tts: 100000, use_async_tts: 1},
1400      {name: 'a', args: {params: ''}, pid: 52, ts: 60000, cat: 'foo', tid: 53,
1401        id: 72, ph: 'e', tts: 105000, use_async_tts: 1}
1402    ];
1403
1404    var m = makeModel(events);
1405    var t = m.processes[52].threads[53];
1406    assert.isDefined(t);
1407    assert.equal(t.asyncSliceGroup.slices.length, 1);
1408
1409    var asyncSlice = t.asyncSliceGroup.slices[0];
1410    assert.isDefined(asyncSlice);
1411    assert.equal(asyncSlice.duration, 10);
1412    assert.equal(asyncSlice.cpuStart, 100);
1413    assert.equal(asyncSlice.cpuDuration, 5);
1414  });
1415
1416  test('nestableAsyncBasic', function() {
1417    var events = [
1418      {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53,
1419        ph: 'b', id: 72},
1420      {name: 'b', args: {x: 1}, pid: 52, ts: 525, cat: 'foo', tid: 53,
1421        ph: 'b', id: 72},
1422      {name: 'b', args: {y: 2}, pid: 52, ts: 560, cat: 'foo', tid: 53,
1423        ph: 'e', id: 72},
1424      {name: 'a', args: {}, pid: 52, ts: 565, cat: 'foo', tid: 53,
1425        ph: 'e', id: 72}
1426    ];
1427
1428    var m = makeModel(events);
1429    var t = m.processes[52].threads[53];
1430    assert.isDefined(t);
1431    assert.equal(t.asyncSliceGroup.slices.length, 1);
1432    var parentSlice = t.asyncSliceGroup.slices[0];
1433    assert.equal(parentSlice.title, 'a');
1434    assert.equal(parentSlice.category, 'foo');
1435    assert.isTrue(parentSlice.isTopLevel);
1436
1437    assert.isDefined(parentSlice.subSlices);
1438    assert.equal(parentSlice.subSlices.length, 1);
1439    var subSlice = parentSlice.subSlices[0];
1440    assert.isFalse(subSlice.isTopLevel);
1441    // Arguments should include both BEGIN and END event.
1442    assert.equal(subSlice.args['x'], 1);
1443    assert.equal(subSlice.args['y'], 2);
1444    assert.sameMembers(subSlice.subSlices, []);
1445  });
1446
1447
1448  test('nestableAsyncNoArgs', function() {
1449    var events = [
1450      {name: 'name', pid: 52, ts: 525, cat: 'foo', tid: 53,
1451        ph: 'b', id: 72},
1452      {name: 'name', pid: 52, ts: 560, cat: 'foo', tid: 53,
1453        ph: 'e', id: 72}
1454    ];
1455
1456    var m = makeModel(events);
1457    var t = m.processes[52].threads[53];
1458    assert.isDefined(t);
1459    assert.equal(t.asyncSliceGroup.slices.length, 1);
1460    var slice = t.asyncSliceGroup.slices[0];
1461    assert.equal(slice.title, 'name');
1462    assert.equal(slice.category, 'foo');
1463    assert.isTrue(slice.isTopLevel);
1464
1465    assert.isDefined(slice.subSlices);
1466    assert.equal(slice.subSlices.length, 0);
1467
1468    assert.deepEqual(slice.args, {});
1469  });
1470
1471  test('nestableAsyncCombinedParams', function() {
1472    var events = [
1473      {name: 'a', args: {x: 1, params: {p1: 'hello', p2: 123}},
1474        pid: 52, ts: 525, cat: 'foo', tid: 53, ph: 'b', id: 72},
1475      {name: 'a', args: {y: 2, params: {p3: 'hi'}}, pid: 52, ts: 560,
1476        cat: 'foo', tid: 53, ph: 'e', id: 72},
1477      {name: 'b', args: {params: {p4: 'foo'}},
1478        pid: 52, ts: 525, cat: 'foo', tid: 53, ph: 'b', id: 73},
1479      {name: 'b', args: {params: ''}, pid: 52, ts: 560,
1480        cat: 'foo', tid: 53, ph: 'e', id: 73},
1481      {name: 'c', args: {params: {p5: 'bar'}},
1482        pid: 52, ts: 525, cat: 'foo', tid: 53, ph: 'b', id: 74},
1483      {name: 'c', args: {}, pid: 52, ts: 560,
1484        cat: 'foo', tid: 53, ph: 'e', id: 74}
1485    ];
1486
1487    var m = makeModel(events);
1488    var t = m.processes[52].threads[53];
1489    assert.isDefined(t);
1490    assert.equal(t.asyncSliceGroup.slices.length, 3);
1491
1492    var sliceA = t.asyncSliceGroup.slices[0];
1493    // Arguments should include both BEGIN and END event.
1494    assert.equal(sliceA.args['x'], 1);
1495    assert.equal(sliceA.args['y'], 2);
1496    var paramsA = sliceA.args['params'];
1497    assert.isDefined(paramsA);
1498    assert.equal(paramsA.p1, 'hello');
1499    assert.equal(paramsA.p2, 123);
1500    assert.equal(paramsA.p3, 'hi');
1501    assert.isTrue(sliceA.isTopLevel);
1502
1503    var sliceB = t.asyncSliceGroup.slices[1];
1504    // Arguments should include both BEGIN and END event.
1505    var paramsB = sliceB.args['params'];
1506    assert.isDefined(paramsB);
1507    assert.equal(paramsB.p4, 'foo');
1508    assert.isTrue(sliceB.isTopLevel);
1509
1510    var sliceC = t.asyncSliceGroup.slices[2];
1511    // Arguments should include both BEGIN and END event.
1512    var paramsC = sliceC.args['params'];
1513    assert.isDefined(paramsC);
1514    assert.equal(paramsC.p5, 'bar');
1515    assert.isTrue(sliceC.isTopLevel);
1516  });
1517
1518  test('nestableAsyncManyLevels', function() {
1519    // There are 5 nested levels.
1520    var events = [
1521      {name: 'l1', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53,
1522        ph: 'b', id: 72},
1523      {name: 'l2', args: {}, pid: 52, ts: 525, cat: 'foo', tid: 53,
1524        ph: 'b', id: 72},
1525      {name: 'l3', args: {}, pid: 52, ts: 526, cat: 'foo', tid: 53,
1526        ph: 'b', id: 72},
1527      {name: 'l4', args: {}, pid: 52, ts: 527, cat: 'foo', tid: 53,
1528        ph: 'b', id: 72},
1529      {name: 'l5', args: {}, pid: 52, ts: 528, cat: 'foo', tid: 53,
1530        ph: 'b', id: 72},
1531      {name: 'l5', args: {}, pid: 52, ts: 529, cat: 'foo', tid: 53,
1532        ph: 'e', id: 72},
1533      {name: 'l4', args: {}, pid: 52, ts: 530, cat: 'foo', tid: 53,
1534        ph: 'e', id: 72},
1535      {name: 'l3', args: {}, pid: 52, ts: 531, cat: 'foo', tid: 53,
1536        ph: 'e', id: 72},
1537      {name: 'l2', args: {}, pid: 52, ts: 532, cat: 'foo', tid: 53,
1538        ph: 'e', id: 72},
1539      {name: 'l1', args: {}, pid: 52, ts: 533, cat: 'foo', tid: 53,
1540        ph: 'e', id: 72}
1541    ];
1542
1543    var m = makeModel(events);
1544    var t = m.processes[52].threads[53];
1545    assert.isDefined(t);
1546    // Perfectly matched events should not produce a warning.
1547    assert.isFalse(m.hasImportWarnings);
1548    assert.equal(t.asyncSliceGroup.slices.length, 1);
1549
1550    var l1Slice = t.asyncSliceGroup.slices[0];
1551    assert.equal(l1Slice.title, 'l1');
1552    assert.closeTo(0, l1Slice.start, 1e-5);
1553    assert.closeTo(9 / 1000, l1Slice.duration, 1e-5);
1554    assert.isTrue(l1Slice.isTopLevel);
1555
1556    assert.isDefined(l1Slice.subSlices);
1557    assert.equal(l1Slice.subSlices.length, 1);
1558    var l2Slice = l1Slice.subSlices[0];
1559    assert.equal(l2Slice.title, 'l2');
1560    assert.closeTo(1 / 1000, l2Slice.start, 1e-5);
1561    assert.closeTo(7 / 1000, l2Slice.duration, 1e-5);
1562    assert.isFalse(l2Slice.isTopLevel);
1563
1564    assert.isDefined(l2Slice.subSlices);
1565    assert.equal(l2Slice.subSlices.length, 1);
1566    var l3Slice = l2Slice.subSlices[0];
1567    assert.equal(l3Slice.title, 'l3');
1568    assert.closeTo(2 / 1000, l3Slice.start, 1e-5);
1569    assert.closeTo(5 / 1000, l3Slice.duration, 1e-5);
1570    assert.isFalse(l3Slice.isTopLevel);
1571
1572    assert.isDefined(l3Slice.subSlices);
1573    assert.equal(l3Slice.subSlices.length, 1);
1574    var l4Slice = l3Slice.subSlices[0];
1575    assert.equal(l4Slice.title, 'l4');
1576    assert.closeTo(3 / 1000, l4Slice.start, 1e-5);
1577    assert.closeTo(3 / 1000, l4Slice.duration, 1e-5);
1578    assert.isFalse(l4Slice.isTopLevel);
1579
1580    assert.isDefined(l4Slice.subSlices);
1581    assert.equal(l4Slice.subSlices.length, 1);
1582    var l5Slice = l4Slice.subSlices[0];
1583    assert.equal(l5Slice.title, 'l5');
1584    assert.closeTo(4 / 1000, l5Slice.start, 1e-5);
1585    assert.closeTo(1 / 1000, l5Slice.duration, 1e-5);
1586    assert.isFalse(l5Slice.isTopLevel);
1587  });
1588
1589  test('nestableAsyncInstantEvent', function() {
1590    var events = [
1591      {name: 'c', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53,
1592        ph: 'n', id: 71},
1593      {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53,
1594        ph: 'b', id: 72},
1595      {name: 'd', args: {}, pid: 52, ts: 525, cat: 'foo', tid: 53,
1596        ph: 'n', id: 72},
1597      {name: 'a', args: {}, pid: 52, ts: 565, cat: 'foo', tid: 53,
1598        ph: 'e', id: 72}
1599    ];
1600
1601    var m = makeModel(events);
1602    var t = m.processes[52].threads[53];
1603    assert.isDefined(t);
1604    assert.equal(t.asyncSliceGroup.slices.length, 2);
1605    var instantSlice = t.asyncSliceGroup.slices[0];
1606    assert.equal(instantSlice.title, 'c');
1607    assert.closeTo(0, instantSlice.start, 1e-5);
1608    assert.closeTo(0, instantSlice.duration, 1e-5);
1609    assert.sameMembers(instantSlice.subSlices, []);
1610    assert.isTrue(instantSlice.isTopLevel);
1611
1612    var nestedSlice = t.asyncSliceGroup.slices[1];
1613    assert.equal(nestedSlice.title, 'a');
1614    assert.closeTo(0, nestedSlice.start, 1e-5);
1615    assert.closeTo((565 - 524) / 1000, nestedSlice.duration, 1e-5);
1616    assert.isTrue(nestedSlice.isTopLevel);
1617    assert.isDefined(nestedSlice.subSlices);
1618    assert.equal(nestedSlice.subSlices.length, 1);
1619    var nestedInstantSlice = nestedSlice.subSlices[0];
1620    assert.sameMembers(nestedInstantSlice.subSlices, []);
1621    assert.equal(nestedInstantSlice.title, 'd');
1622    assert.isFalse(nestedInstantSlice.isTopLevel);
1623  });
1624
1625  test('nestableAsyncUnmatchedOuterBeginEvent', function() {
1626    var events = [
1627      {name: 'a', args: {x: 1}, pid: 52, ts: 524, cat: 'foo', tid: 53,
1628        ph: 'b', id: 72},
1629      {name: 'b', args: {}, pid: 52, ts: 525, cat: 'foo', tid: 53,
1630        ph: 'b', id: 72},
1631      {name: 'b', args: {y: 2}, pid: 52, ts: 560, cat: 'foo', tid: 53,
1632        ph: 'e', id: 72}
1633    ];
1634
1635    var m = makeModel(events);
1636    var t = m.processes[52].threads[53];
1637    assert.isDefined(t);
1638    // Unmatched BEGIN should produce a warning.
1639    assert.isTrue(m.hasImportWarnings);
1640    assert.equal(t.asyncSliceGroup.slices.length, 1);
1641    var parentSlice = t.asyncSliceGroup.slices[0];
1642    assert.equal(parentSlice.title, 'a');
1643    assert.equal(parentSlice.category, 'foo');
1644    assert.isTrue(parentSlice.isTopLevel);
1645    assert.closeTo(0, parentSlice.start, 0.0001);
1646    // Unmatched BEGIN event ends at the last event of that ID.
1647    assert.closeTo(36 / 1000, parentSlice.duration, 0.0001);
1648    // Arguments should include only include its arguments.
1649    assert.isUndefined(parentSlice.args['y']);
1650    assert.equal(parentSlice.args['x'], 1);
1651    assert.isDefined(parentSlice.error);
1652
1653    assert.isDefined(parentSlice.subSlices);
1654    assert.equal(parentSlice.subSlices.length, 1);
1655    var subSlice = parentSlice.subSlices[0];
1656    assert.isFalse(subSlice.isTopLevel);
1657    assert.closeTo(1 / 1000, subSlice.start, 1e-5);
1658    assert.closeTo(35 / 1000, subSlice.duration, 1e-5);
1659    assert.sameMembers(subSlice.subSlices, []);
1660    // Arguments should include those of the END event.
1661    assert.equal(subSlice.args['y'], 2);
1662    assert.sameMembers(subSlice.subSlices, []);
1663  });
1664
1665  test('nestableAsyncUnmatchedInnerBeginEvent', function() {
1666    var events = [
1667      {name: 'a', args: {z: 3}, pid: 52, ts: 524, cat: 'foo', tid: 53,
1668        ph: 'b', id: 72},
1669      {name: 'c', args: {}, pid: 52, ts: 525, cat: 'foo', tid: 53,
1670        ph: 'n', id: 72},
1671      {name: 'b', args: {x: 1}, pid: 52, ts: 525, cat: 'foo', tid: 53,
1672        ph: 'b', id: 72},
1673      {name: 'a', args: {y: 2}, pid: 52, ts: 565, cat: 'foo', tid: 53,
1674        ph: 'e', id: 72}
1675    ];
1676
1677    var m = makeModel(events);
1678    var t = m.processes[52].threads[53];
1679    assert.isDefined(t);
1680    // Unmatched BEGIN should produce a warning.
1681    assert.isTrue(m.hasImportWarnings);
1682    assert.equal(t.asyncSliceGroup.slices.length, 1);
1683    var parentSlice = t.asyncSliceGroup.slices[0];
1684    assert.equal(parentSlice.title, 'a');
1685    assert.equal(parentSlice.category, 'foo');
1686    assert.isTrue(parentSlice.isTopLevel);
1687    assert.closeTo(0, parentSlice.start, 1e-5);
1688    assert.closeTo(41 / 1000, parentSlice.duration, 1e-5);
1689    // Arguments should include both BEGIN and END event.
1690    assert.equal(parentSlice.args['y'], 2);
1691    assert.equal(parentSlice.args['z'], 3);
1692    assert.isUndefined(parentSlice.args['x']);
1693
1694    assert.isDefined(parentSlice.subSlices);
1695    assert.equal(parentSlice.subSlices.length, 2);
1696    var subSliceInstant = parentSlice.subSlices[0];
1697    var subSliceUnmatched = parentSlice.subSlices[1];
1698    assert.equal(subSliceInstant.title, 'c');
1699    assert.isFalse(subSliceInstant.isTopLevel);
1700    assert.equal(subSliceUnmatched.title, 'b');
1701    assert.isFalse(subSliceUnmatched.isTopLevel);
1702    // Unmatched BEGIN ends at the last event of that ID.
1703    assert.closeTo(1 / 1000, subSliceUnmatched.start, 1e-5);
1704    assert.closeTo(40 / 1000, subSliceUnmatched.duration, 1e-5);
1705    assert.sameMembers(subSliceUnmatched.subSlices, []);
1706    assert.equal(subSliceUnmatched.args['x'], 1);
1707    assert.isUndefined(subSliceUnmatched['y']);
1708    assert.isDefined(subSliceUnmatched.error);
1709    assert.closeTo(1 / 1000, subSliceInstant.start, 1e-5);
1710    assert.closeTo(0, subSliceInstant.duration, 1e-5);
1711    assert.sameMembers(subSliceInstant.subSlices, []);
1712  });
1713
1714  test('nestableAsyncUnmatchedOuterEndEvent', function() {
1715    // Events are intentionally out-of-order.
1716    var events = [
1717      {name: 'b', args: {x: 1}, pid: 52, ts: 525, cat: 'foo', tid: 53,
1718        ph: 'b', id: 72},
1719      {name: 'b', args: {y: 2}, pid: 52, ts: 560, cat: 'foo', tid: 53,
1720        ph: 'e', id: 72},
1721      {name: 'a', args: {z: 3}, pid: 52, ts: 524, cat: 'foo', tid: 53,
1722        ph: 'e', id: 72}
1723    ];
1724
1725    var m = makeModel(events);
1726    var t = m.processes[52].threads[53];
1727    assert.isDefined(t);
1728    // Unmatched END should produce a warning.
1729    assert.isTrue(m.hasImportWarnings);
1730    assert.equal(t.asyncSliceGroup.slices.length, 2);
1731    var unmatchedSlice = t.asyncSliceGroup.slices[0];
1732    var slice = t.asyncSliceGroup.slices[1];
1733    assert.equal(unmatchedSlice.title, 'a');
1734    assert.closeTo(0, unmatchedSlice.start, 1e-5);
1735    assert.isTrue(unmatchedSlice.isTopLevel);
1736    // Unmatched END event begins at the first event of that ID. In this
1737    // case, the first event happens to be the same unmatched event.
1738    assert.closeTo(0 / 1000, unmatchedSlice.duration, 1e-5);
1739    assert.isUndefined(unmatchedSlice.args['x']);
1740    assert.isUndefined(unmatchedSlice.args['y']);
1741    assert.equal(unmatchedSlice.args['z'], 3);
1742    assert.isDefined(unmatchedSlice.error);
1743    assert.sameMembers(unmatchedSlice.subSlices, []);
1744
1745    assert.equal(slice.title, 'b');
1746    assert.isTrue(slice.isTopLevel);
1747    assert.closeTo(1 / 1000, slice.start, 1e-5);
1748    assert.closeTo(35 / 1000, slice.duration, 1e-5);
1749    // Arguments should include both BEGIN and END event.
1750    assert.equal(slice.args['x'], 1);
1751    assert.equal(slice.args['y'], 2);
1752    assert.sameMembers(slice.subSlices, []);
1753  });
1754
1755  test('nestableAsyncUnmatchedInnerEndEvent', function() {
1756    var events = [
1757      {name: 'a', args: {x: 1}, pid: 52, ts: 524, cat: 'foo', tid: 53,
1758        ph: 'b', id: 72},
1759      {name: 'c', args: {}, pid: 52, ts: 525, cat: 'foo', tid: 53,
1760        ph: 'n', id: 72},
1761      {name: 'b', args: {z: 3}, pid: 52, ts: 525, cat: 'foo', tid: 53,
1762        ph: 'e', id: 72},
1763      {name: 'a', args: {y: 2}, pid: 52, ts: 565, cat: 'foo', tid: 53,
1764        ph: 'e', id: 72}
1765    ];
1766
1767    var m = makeModel(events);
1768    var t = m.processes[52].threads[53];
1769    assert.isDefined(t);
1770    // Unmatched END should produce a warning.
1771    assert.isTrue(m.hasImportWarnings);
1772    assert.equal(t.asyncSliceGroup.slices.length, 1);
1773    var parentSlice = t.asyncSliceGroup.slices[0];
1774    assert.equal(parentSlice.title, 'a');
1775    assert.isTrue(parentSlice.isTopLevel);
1776    assert.closeTo(0, parentSlice.start, 1e-5);
1777    assert.closeTo(41 / 1000, parentSlice.duration, 1e-5);
1778    // Arguments should include both BEGIN and END event.
1779    assert.equal(parentSlice.args['x'], 1);
1780    assert.equal(parentSlice.args['y'], 2);
1781
1782    assert.isDefined(parentSlice.subSlices);
1783    assert.equal(parentSlice.subSlices.length, 2);
1784    var subSliceInstant = parentSlice.subSlices[0];
1785    var subSliceUnmatched = parentSlice.subSlices[1];
1786    assert.equal(subSliceInstant.title, 'c');
1787    assert.isFalse(subSliceInstant.isTopLevel);
1788    assert.equal(subSliceUnmatched.title, 'b');
1789    assert.isFalse(subSliceUnmatched.isTopLevel);
1790    // Unmatched END begins at the first event of that ID.
1791    assert.closeTo(0 / 1000, subSliceUnmatched.start, 1e-5);
1792    assert.closeTo(1 / 1000, subSliceUnmatched.duration, 1e-5);
1793    // Arguments should include both BEGIN and END event.
1794    assert.isUndefined(subSliceUnmatched.args['x']);
1795    assert.isUndefined(subSliceUnmatched.args['y']);
1796    assert.equal(subSliceUnmatched.args['z'], 3);
1797    assert.isDefined(subSliceUnmatched.error);
1798
1799    assert.sameMembers(subSliceUnmatched.subSlices, []);
1800    assert.closeTo(1 / 1000, subSliceInstant.start, 1e-5);
1801    assert.closeTo(0, subSliceInstant.duration, 1e-5);
1802    assert.sameMembers(subSliceInstant.subSlices, []);
1803  });
1804
1805  test('nestableAsyncSameIDDifferentCategory', function() {
1806    // Events with the same ID, but different categories should not be
1807    // considered as nested.
1808    var events = [
1809      {name: 'EVENT_A', args: {}, pid: 52, ts: 500, cat: 'foo', tid: 53,
1810        ph: 'b', id: 72},
1811      {name: 'EVENT_B', args: {y: 2}, pid: 52, ts: 550, cat: 'bar', tid: 53,
1812        ph: 'b', id: 72},
1813      {name: 'EVENT_B', args: {}, pid: 52, ts: 600, cat: 'bar', tid: 53,
1814        ph: 'e', id: 72},
1815      {name: 'EVENT_A', args: {x: 1}, pid: 52, ts: 650, cat: 'foo', tid: 53,
1816        ph: 'e', id: 72}
1817    ];
1818
1819    var m = makeModel(events);
1820    var t = m.processes[52].threads[53];
1821    assert.isDefined(t);
1822    assert.equal(t.asyncSliceGroup.slices.length, 2);
1823    var eventASlice = t.asyncSliceGroup.slices[0];
1824    assert.equal(eventASlice.title, 'EVENT_A');
1825    assert.equal(eventASlice.category, 'foo');
1826    assert.equal(eventASlice.id, 'foo:72');
1827    assert.isTrue(eventASlice.isTopLevel);
1828    assert.equal(eventASlice.args['x'], 1);
1829    assert.sameMembers(eventASlice.subSlices, []);
1830
1831    var eventBSlice = t.asyncSliceGroup.slices[1];
1832    assert.equal(eventBSlice.title, 'EVENT_B');
1833    assert.equal(eventBSlice.category, 'bar');
1834    assert.equal(eventBSlice.id, 'bar:72');
1835    assert.isTrue(eventBSlice.isTopLevel);
1836    assert.equal(eventBSlice.args['y'], 2);
1837    assert.sameMembers(eventBSlice.subSlices, []);
1838  });
1839
1840  test('nestableAsyncStackFrame', function() {
1841    var events = {
1842      traceEvents: [
1843        {name: 'name', pid: 52, ts: 525, cat: 'foo', tid: 53,
1844          ph: 'b', id: 72, sf: 1},
1845        {name: 'name', pid: 52, ts: 560, cat: 'foo', tid: 53,
1846          ph: 'e', id: 72, sf: 7}
1847      ],
1848      stackFrames: {
1849        '1': {
1850          category: 'm1',
1851          name: 'main'
1852        },
1853        '7': {
1854          category: 'm2',
1855          name: 'frame7',
1856          parent: '1'
1857        }
1858      }
1859    };
1860
1861    var m = makeModel(events);
1862    var t = m.processes[52].threads[53];
1863    assert.isDefined(t);
1864    assert.equal(t.asyncSliceGroup.slices.length, 1);
1865    var slice = t.asyncSliceGroup.slices[0];
1866
1867    assert.equal(slice.startStackFrame.title, 'main');
1868    assert.equal(slice.endStackFrame.title, 'frame7');
1869  });
1870
1871  test('importSamples', function() {
1872    var events = [
1873      {name: 'a', args: {}, pid: 52, ts: 548, cat: 'test', tid: 53, ph: 'P'},
1874      {name: 'b', args: {}, pid: 52, ts: 548, cat: 'test', tid: 53, ph: 'P'},
1875      {name: 'c', args: {}, pid: 52, ts: 558, cat: 'test', tid: 53, ph: 'P'},
1876      {name: 'a', args: {}, pid: 52, ts: 568, cat: 'test', tid: 53, ph: 'P'}
1877    ];
1878    var m = makeModel(events);
1879    var p = m.processes[52];
1880    assert.isDefined(p);
1881    var t = p.threads[53];
1882    assert.isDefined(t);
1883    assert.equal(t.samples_.length, 4);
1884    assert.equal(t.samples_[0].start, 0.0);
1885    assert.equal(t.samples_[1].start, 0.0);
1886    assert.closeTo(0.01, t.samples_[2].start, 1e-5);
1887    assert.equal(t.samples_[0].leafStackFrame.title, 'a');
1888    assert.equal(t.samples_[1].leafStackFrame.title, 'b');
1889    assert.equal(t.samples_[2].leafStackFrame.title, 'c');
1890    assert.equal(t.samples_[3].leafStackFrame, t.samples[0].leafStackFrame);
1891    assert.isFalse(m.hasImportWarnings);
1892  });
1893
1894  test('importSamplesWithStackFrames', function() {
1895    var eventData = {
1896      traceEvents: [
1897        { name: 'a', args: {}, pid: 1, ts: 0, cat: 'test', tid: 2, ph: 'P', sf: 7 } // @suppress longLineCheck
1898      ],
1899      stackFrames: {
1900        '1': {
1901          category: 'm1',
1902          name: 'main'
1903        },
1904        '7': {
1905          category: 'm2',
1906          name: 'frame7',
1907          parent: '1'
1908        }
1909      }
1910    };
1911
1912    var m = makeModel(eventData);
1913
1914    var p = m.processes[1];
1915    var t = p.threads[2];
1916
1917    assert.equal(t.samples.length, 1);
1918    assert.equal(t.samples_[0].start, 0.0);
1919    assert.equal(t.samples_[0].leafStackFrame.title, 'frame7');
1920    assert.isFalse(m.hasImportWarnings);
1921  });
1922
1923  test('importSamplesMissingArgs', function() {
1924    var events = [
1925      {name: 'a', pid: 52, ts: 548, cat: 'test', tid: 53, ph: 'P'},
1926      {name: 'b', pid: 52, ts: 548, cat: 'test', tid: 53, ph: 'P'},
1927      {name: 'c', pid: 52, ts: 549, cat: 'test', tid: 53, ph: 'P'}
1928    ];
1929    var m = makeModel(events);
1930    var p = m.processes[52];
1931    assert.isDefined(p);
1932    var t = p.threads[53];
1933    assert.isDefined(t);
1934    assert.isDefined(t);
1935    assert.equal(t.samples_.length, 3);
1936    assert.isFalse(m.hasImportWarnings);
1937  });
1938
1939  test('importV8Samples', function() {
1940    var eventData = {
1941      traceEvents: [
1942        { name: 'V8Sample', args: {data: {stack: ['0x2a574306061', '0x2a574306224'], vm_state: 'js'}}, pid: 1, ts: 4, cat: 'test', tid: 2, ph: 'P' }, // @suppress longLineCheck
1943        { name: 'V8Sample', args: {data: {stack: [], vm_state: 'gc'}}, pid: 1, ts: 6, cat: 'test', tid: 2, ph: 'P' }, // @suppress longLineCheck
1944        { name: 'JitCodeAdded', args: {data: {code_len: 2, name: 'LazyCompile:~foo http://example.com/bar.js:23', code_start: '0x2a574306060'}}, pid: 1, ts: 1, cat: 'test', tid: 2, ph: 'M' }, // @suppress longLineCheck
1945        { name: 'JitCodeAdded', args: {data: {code_len: 20, name: 'bar', code_start: '0x2a574306220'}}, pid: 1, ts: 2, cat: 'test', tid: 2, ph: 'M' }, // @suppress longLineCheck
1946        { name: 'JitCodeMoved', args: {data: {code_len: 2, old_code_start: '0x2a574306220', code_start: '0x2a574306222'}}, pid: 1, ts: 3, cat: 'test', tid: 2, ph: 'M' }, // @suppress longLineCheck
1947        { name: 'JitCodeAdded', args: {data: {code_len: 20, name: 'baz', code_start: '0xffffffff9f90a1a0'}}, pid: 1, ts: 4, cat: 'test', tid: 2, ph: 'M' } // @suppress longLineCheck
1948      ]
1949    };
1950
1951    var m = makeModel(eventData);
1952    var p = m.processes[1];
1953    var t = p.threads[2];
1954
1955    assert.isFalse(m.hasImportWarnings);
1956    assert.equal(t.samples.length, 2);
1957
1958    var sample = t.samples_[0];
1959    assert.equal(sample.leafStackFrame.title,
1960        'foo http://example.com/bar.js:22');
1961    assert.equal(sample.leafStackFrame.parentFrame.title, 'bar');
1962
1963    var sample = t.samples_[1];
1964    assert.equal(sample.leafStackFrame.title, 'gc');
1965  });
1966
1967  test('importOldFormatV8Samples', function() {
1968    var eventData = {
1969      traceEvents: [
1970        { name: 'JitCodeAdded', args: {data: {code_len: 2, name: 'LazyCompile:~foo http://example.com/bar.js:23', code_start: '0x2a574306060'}}, pid: 1, ts: 0, cat: 'test', tid: 2, ph: 'I' }, // @suppress longLineCheck
1971        { name: 'JitCodeAdded', args: {data: {code_len: 20, name: 'bar', code_start: '0x2a574306220'}}, pid: 1, ts: 0, cat: 'test', tid: 2, ph: 'I' }, // @suppress longLineCheck
1972        { name: 'JitCodeMoved', args: {data: {code_len: 2, old_code_start: '0x2a574306220', code_start: '0x2a574306222'}}, pid: 1, ts: 0, cat: 'test', tid: 2, ph: 'I' }, // @suppress longLineCheck
1973        { name: 'JitCodeAdded', args: {data: {code_len: 20, name: 'baz', code_start: '0xffffffff9f90a1a0'}}, pid: 1, ts: 0, cat: 'test', tid: 2, ph: 'I' }, // @suppress longLineCheck
1974        { name: 'V8Sample', args: {data: {stack: ['0x2a574306061', '0x2a574306224']}}, pid: 1, ts: 0, cat: 'test', tid: 2, ph: 'P' }, // @suppress longLineCheck
1975        { name: 'V8Sample', args: {data: {stack: [], vm_state: 'gc'}}, pid: 1, ts: 10, cat: 'test', tid: 2, ph: 'P' } // @suppress longLineCheck
1976      ]
1977    };
1978
1979    var m = makeModel(eventData);
1980    var p = m.processes[1];
1981    var t = p.threads[2];
1982
1983    assert.isFalse(m.hasImportWarnings);
1984    assert.equal(t.samples.length, 2);
1985
1986    var sample = t.samples_[0];
1987    assert.equal(sample.leafStackFrame.title,
1988        'foo http://example.com/bar.js:22');
1989    assert.equal(sample.leafStackFrame.parentFrame.title, 'bar');
1990
1991    var sample = t.samples_[1];
1992    assert.equal(sample.leafStackFrame.title, 'gc');
1993  });
1994
1995  test('importSimpleObject', function() {
1996    var events = [
1997      {ts: 10000, pid: 1, tid: 1, ph: 'N', cat: 'c', id: '0x1000', name: 'a', args: {}}, // @suppress longLineCheck
1998      {ts: 15000, pid: 1, tid: 1, ph: 'O', cat: 'c', id: '0x1000', name: 'a', args: {snapshot: 15}}, // @suppress longLineCheck
1999      {ts: 20000, pid: 1, tid: 1, ph: 'O', cat: 'c', id: '0x1000', name: 'a', args: {snapshot: 20}}, // @suppress longLineCheck
2000      {ts: 50000, pid: 1, tid: 1, ph: 'D', cat: 'c', id: '0x1000', name: 'a', args: {}} // @suppress longLineCheck
2001    ];
2002    var m = makeModel(events, false);
2003    assert.equal(m.bounds.min, 10);
2004    assert.equal(m.bounds.max, 50);
2005    assert.isFalse(m.hasImportWarnings);
2006
2007    var p = m.processes[1];
2008    assert.isDefined(p);
2009
2010    var i10 = p.objects.getObjectInstanceAt(new ScopedId('ptr', '0x1000'), 10);
2011    assert.equal(i10.category, 'c');
2012    assert.equal(i10.creationTs, 10);
2013    assert.equal(i10.deletionTs, 50);
2014    assert.equal(i10.snapshots.length, 2);
2015
2016    var s15 = i10.snapshots[0];
2017    assert.equal(s15.ts, 15);
2018    assert.equal(s15.args, 15);
2019
2020    var s20 = i10.snapshots[1];
2021    assert.equal(s20.ts, 20);
2022    assert.equal(s20.args, 20);
2023  });
2024
2025  test('importImplicitObjects', function() {
2026    var events = [
2027      {ts: 10000, pid: 1, tid: 1, ph: 'N', cat: 'c', id: '0x1000', name: 'a', args: {}}, // @suppress longLineCheck
2028      {ts: 15000, pid: 1, tid: 1, ph: 'O', cat: 'c', id: '0x1000', name: 'a',
2029        args: { snapshot: [
2030          { id: 'subObject/0x1',
2031            foo: 1
2032          }
2033        ]}},
2034      {ts: 20000, pid: 1, tid: 1, ph: 'O', cat: 'c', id: '0x1000', name: 'a',
2035        args: { snapshot: [
2036          { id: 'subObject/0x1',
2037            foo: 2
2038          },
2039          { id: 'subObject/0x2',
2040            foo: 1
2041          }
2042        ]}}
2043    ];
2044
2045    var m = makeModel(events, false);
2046    var p1 = m.processes[1];
2047
2048    var iA = p1.objects.getObjectInstanceAt(new ScopedId('ptr', '0x1000'), 10);
2049    var subObjectInstances = p1.objects.getAllInstancesByTypeName()[
2050        'subObject'];
2051
2052    assert.equal(subObjectInstances.length, 2);
2053    var subObject1 = p1.objects.getObjectInstanceAt(
2054        new ScopedId('ptr', '0x1'), 15);
2055    assert.equal(subObject1.name, 'subObject');
2056    assert.equal(subObject1.creationTs, 15);
2057
2058    assert.equal(subObject1.snapshots.length, 2);
2059    assert.equal(subObject1.snapshots[0].ts, 15);
2060    assert.equal(subObject1.snapshots[0].args.foo, 1);
2061    assert.equal(subObject1.snapshots[1].ts, 20);
2062    assert.equal(subObject1.snapshots[1].args.foo, 2);
2063
2064    var subObject2 = p1.objects.getObjectInstanceAt(
2065        new ScopedId('ptr', '0x2'), 20);
2066    assert.equal(subObject2.name, 'subObject');
2067    assert.equal(subObject2.creationTs, 20);
2068    assert.equal(subObject2.snapshots.length, 1);
2069    assert.equal(subObject2.snapshots[0].ts, 20);
2070  });
2071
2072  test('importImplicitObjectWithCategoryOverride', function() {
2073    var events = [
2074      {ts: 10000, pid: 1, tid: 1, ph: 'N', cat: 'cat', id: '0x1000', name: 'a', args: {}}, // @suppress longLineCheck
2075      {ts: 15000, pid: 1, tid: 1, ph: 'O', cat: 'otherCat', id: '0x1000', name: 'a', // @suppress longLineCheck
2076        args: { snapshot: [
2077          { id: 'subObject/0x1',
2078            cat: 'cat',
2079            foo: 1
2080          }
2081        ]}}
2082    ];
2083
2084    var m = makeModel(events);
2085    var p1 = m.processes[1];
2086
2087    var iA = p1.objects.getObjectInstanceAt(new ScopedId('ptr', '0x1000'), 10);
2088    var subObjectInstances = p1.objects.getAllInstancesByTypeName()[
2089        'subObject'];
2090
2091    assert.equal(subObjectInstances.length, 1);
2092  });
2093
2094  test('importImplicitObjectWithBaseTypeOverride', function() {
2095    var events = [
2096      {ts: 10000, pid: 1, tid: 1, ph: 'O', cat: 'c', id: '0x1000', name: 'PictureLayerImpl', args: { // @suppress longLineCheck
2097        snapshot: {
2098          base_type: 'LayerImpl'
2099        }
2100      }},
2101      {ts: 50000, pid: 1, tid: 1, ph: 'D', cat: 'c', id: '0x1000', name: 'LayerImpl', args: {}} // @suppress longLineCheck
2102    ];
2103
2104    var m = makeModel(events);
2105    var p1 = m.processes[1];
2106    assert.equal(m.importWarnings.length, 0);
2107
2108    var iA = p1.objects.getObjectInstanceAt(new ScopedId('ptr', '0x1000'), 10);
2109    assert.equal(iA.snapshots.length, 1);
2110  });
2111
2112  test('importIDRefs', function() {
2113    var events = [
2114      // An object with two snapshots.
2115      {ts: 10000, pid: 1, tid: 1, ph: 'N', cat: 'c', id: '0x1000', name: 'a', args: {}}, // @suppress longLineCheck
2116      {ts: 15000, pid: 1, tid: 1, ph: 'O', cat: 'c', id: '0x1000', name: 'a', args: {snapshot: 15}}, // @suppress longLineCheck
2117      {ts: 20000, pid: 1, tid: 1, ph: 'O', cat: 'c', id: '0x1000', name: 'a', args: {snapshot: 20}}, // @suppress longLineCheck
2118      {ts: 50000, pid: 1, tid: 1, ph: 'D', cat: 'c', id: '0x1000', name: 'a', args: {}}, // @suppress longLineCheck
2119
2120      // A slice that references the object.
2121      {ts: 17000, pid: 1, tid: 1, ph: 'B', cat: 'c', name: 'taskSlice', args: {my_object: {id_ref: '0x1000'}}}, // @suppress longLineCheck
2122      {ts: 17500, pid: 1, tid: 1, ph: 'E', cat: 'c', name: 'taskSlice', args: {}} // @suppress longLineCheck
2123    ];
2124
2125    var m = makeModel(events, false);
2126    var p1 = m.processes[1];
2127
2128    var iA = p1.objects.getObjectInstanceAt(new ScopedId('ptr', '0x1000'), 10);
2129    var s15 = iA.getSnapshotAt(15);
2130
2131    var taskSlice = p1.threads[1].sliceGroup.slices[0];
2132    assert.equal(taskSlice.args.my_object, s15);
2133  });
2134
2135  test('importIDRefsThatPointAtEachOther', function() {
2136    var events = [
2137      // An object.
2138      {ts: 10000, pid: 1, tid: 1, ph: 'N', cat: 'c', id: '0x1000', name: 'a', args: {}}, // @suppress longLineCheck
2139      {ts: 15000, pid: 1, tid: 1, ph: 'O', cat: 'c', id: '0x1000', name: 'a', args: { // @suppress longLineCheck
2140        snapshot: { x: {
2141          id: 'foo/0x1001',
2142          value: 'bar'
2143        }}}},
2144      {ts: 50000, pid: 1, tid: 1, ph: 'D', cat: 'c', id: '0x1000', name: 'a', args: {}}, // @suppress longLineCheck
2145
2146      // A slice that references the object.
2147      {ts: 17000, pid: 1, tid: 1, ph: 'B', cat: 'c', name: 'taskSlice', args: {my_object: {id_ref: '0x1001'}}}, // @suppress longLineCheck
2148      {ts: 17500, pid: 1, tid: 1, ph: 'E', cat: 'c', name: 'taskSlice', args: {}} // @suppress longLineCheck
2149    ];
2150
2151    var m = makeModel(events);
2152    var p1 = m.processes[1];
2153
2154    var iA = p1.objects.getObjectInstanceAt(new ScopedId('ptr', '0x1000'), 15);
2155    var iFoo = p1.objects.getObjectInstanceAt(
2156        new ScopedId('ptr', '0x1001'), 15);
2157    assert.isDefined(iA);
2158    assert.isDefined(iFoo);
2159
2160    var a15 = iA.getSnapshotAt(15);
2161    var foo15 = iFoo.getSnapshotAt(15);
2162
2163    var taskSlice = p1.threads[1].sliceGroup.slices[0];
2164    assert.equal(taskSlice.args.my_object, foo15);
2165  });
2166
2167  test('importArrayWithIDs', function() {
2168    var events = [
2169      {ts: 15000, pid: 1, tid: 1, ph: 'O', cat: 'c', id: '0x1000', name: 'a', args: { // @suppress longLineCheck
2170        snapshot: { x: [
2171          {id: 'foo/0x1001', value: 'bar1'},
2172          {id: 'foo/0x1002', value: 'bar2'},
2173          {id: 'foo/0x1003', value: 'bar3'}
2174        ]}}}
2175    ];
2176
2177    var m = makeModel(events, false);
2178    var p1 = m.processes[1];
2179
2180    var sA = p1.objects.getSnapshotAt(new ScopedId('ptr', '0x1000'), 15);
2181    assert.isTrue(sA.args.x instanceof Array);
2182    assert.equal(sA.args.x.length, 3);
2183    assert.isTrue(sA.args.x[0] instanceof tr.model.ObjectSnapshot);
2184    assert.isTrue(sA.args.x[1] instanceof tr.model.ObjectSnapshot);
2185    assert.isTrue(sA.args.x[2] instanceof tr.model.ObjectSnapshot);
2186  });
2187
2188  test('importDoesNotMutateEventList', function() {
2189    var events = [
2190      // An object.
2191      {ts: 10000, pid: 1, tid: 1, ph: 'N', cat: 'c', id: '0x1000', name: 'a', args: {}}, // @suppress longLineCheck
2192      {ts: 15000, pid: 1, tid: 1, ph: 'O', cat: 'c', id: '0x1000', name: 'a', args: { // @suppress longLineCheck
2193        snapshot: {foo: 15}}},
2194      {ts: 50000, pid: 1, tid: 1, ph: 'D', cat: 'c', id: '0x1000', name: 'a', args: {}}, // @suppress longLineCheck
2195
2196      // A slice that references the object.
2197      {ts: 17000, pid: 1, tid: 1, ph: 'B', cat: 'c', name: 'taskSlice', args: {
2198        my_object: {id_ref: '0x1000'}}
2199      },
2200      {ts: 17500, pid: 1, tid: 1, ph: 'E', cat: 'c', name: 'taskSlice', args: {}} // @suppress longLineCheck
2201    ];
2202
2203    // The A type family exists to mutate the args list provided to
2204    // snapshots.
2205    function ASnapshot() {
2206      tr.model.ObjectSnapshot.apply(this, arguments);
2207      this.args.foo = 7;
2208    }
2209    ASnapshot.prototype = {
2210      __proto__: tr.model.ObjectSnapshot.prototype
2211    };
2212
2213    // Import event while the A types are registered, causing the
2214    // arguments of the snapshots to be mutated.
2215    var m;
2216    try {
2217      tr.model.ObjectSnapshot.register(ASnapshot, {typeName: 'a'});
2218      m = makeModel(events);
2219    } finally {
2220      tr.model.ObjectSnapshot.unregister(ASnapshot);
2221    }
2222    assert.isFalse(m.hasImportWarnings);
2223
2224    // Verify that the events array wasn't modified.
2225    assert.deepEqual(
2226        events[1].args,
2227        {snapshot: {foo: 15}});
2228    assert.deepEqual(
2229        events[3].args,
2230        {my_object: {id_ref: '0x1000'}});
2231  });
2232
2233  test('importFlowEvent', function() {
2234    var events = [
2235      { name: 'aSlice', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 547, ph: 'B', args: {} },  // @suppress longLineCheck
2236      { name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 548, ph: 's', args: {} },  // @suppress longLineCheck
2237      { id: 72, pid: 52, tid: 53, ts: 549, ph: 'E', args: {} },  // @suppress longLineCheck
2238
2239      { name: 'bSlice', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 559, ph: 'B', args: {} },  // @suppress longLineCheck
2240      { name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 560, ph: 't', args: {} },  // @suppress longLineCheck
2241      { id: 72, pid: 52, tid: 53, ts: 561, ph: 'E', args: {} },  // @suppress longLineCheck
2242
2243      { name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 580, ph: 'f', args: {} },   // @suppress longLineCheck
2244      { name: 'cSlice', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 581, ph: 'B', args: {} },  // @suppress longLineCheck
2245      { id: 72, pid: 52, tid: 53, ts: 582, ph: 'E', args: {} }  // @suppress longLineCheck
2246    ];
2247
2248    var m = makeModel(events);
2249    var t = m.processes[52].threads[53];
2250
2251    assert.isDefined(t);
2252    assert.equal(m.flowEvents.length, 2);
2253    assert.equal(m.flowIntervalTree.size, 2);
2254
2255    var f0 = m.flowEvents[0];
2256    assert.equal(f0.title, 'a');
2257    assert.equal(f0.category, 'foo');
2258    assert.equal(f0.id, 72);
2259    assert.closeTo(f0.start, 0.001, 1e-5);
2260    assert.closeTo(12 / 1000, f0.duration, 1e-5);
2261    assert.equal(f0.startSlice.title, 'aSlice');
2262    assert.equal(f0.endSlice.title, 'bSlice');
2263
2264    // TODO(nduca): Replace this assertion with something better when
2265    // flow events don't create synthetic slices on their own.
2266    assert.isDefined(f0.startSlice);
2267    assert.isDefined(f0.endSlice);
2268
2269    var f1 = m.flowEvents[1];
2270    assert.equal(f1.title, f0.title);
2271    assert.equal(f1.category, f0.category);
2272    assert.equal(f1.id, f0.id);
2273    assert.closeTo(20 / 1000, f1.duration, 1e-5);
2274
2275    assert.equal(f1.startSlice.title, 'bSlice');
2276    assert.equal(f1.endSlice.title, 'cSlice');
2277
2278    assert.deepEqual(f0.startSlice.outFlowEvents, [f0]);
2279    assert.deepEqual(f0.endSlice.inFlowEvents, [f0]);
2280    assert.deepEqual(f1.startSlice.outFlowEvents, [f1]);
2281    assert.deepEqual(f1.endSlice.inFlowEvents, [f1]);
2282  });
2283
2284  test('importOldFlowEventBindtoNext', function() {
2285    // Old trace format without event.bp, and event.cat doesn't contain input
2286    var events = [
2287      { name: 'slice1', cat: 'foo', pid: 52, tid: 53, ts: 547, ph: 'X', 'dur': 100},   // @suppress longLineCheck
2288      { name: 'flow', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 548, ph: 's', args: {}},  // @suppress longLineCheck
2289
2290      { name: 'flow', cat: 'foo', id: 72, pid: 70, tid: 71, ts: 580, ph: 'f', args: {}},   // @suppress longLineCheck
2291      { name: 'slice2', cat: 'foo', pid: 70, tid: 71, ts: 570, ph: 'X', args: {}, 'dur': 100},   // @suppress longLineCheck
2292      { name: 'slice3', cat: 'foo', pid: 70, tid: 71, ts: 770, ph: 'X', args: {}, 'dur': 1000}   // @suppress longLineCheck
2293
2294    ];
2295
2296    var m = makeModel(events, false, false);
2297    assert.equal(m.flowEvents.length, 1);
2298
2299    var f0 = m.flowEvents[0];
2300
2301    assert.equal(f0.title, 'flow');
2302    assert.equal(f0.category, 'foo');
2303    assert.equal(f0.id, 72);
2304    assert.equal(f0.start, .548);
2305    assert.closeTo(32 / 1000, f0.duration, 1e-5);
2306    assert.equal(f0.startSlice.title, 'slice1');
2307    assert.deepEqual(f0.startSlice.outFlowEvents, [f0]);
2308    assert.equal(f0.endSlice.title, 'slice3');
2309    assert.deepEqual(f0.endSlice.inFlowEvents, [f0]);
2310  });
2311
2312  test('importOldInputFlowEventBindtoParent', function() {
2313    // Old trace format without event.bp, but event.cat contains input
2314    var events = [
2315      { name: 'slice1', cat: 'foo', pid: 52, tid: 53, ts: 547, ph: 'X', 'dur': 100},   // @suppress longLineCheck
2316      { name: 'flow', cat: 'input', id: 72, pid: 52, tid: 53, ts: 548, ph: 's', args: {}},  // @suppress longLineCheck
2317
2318      { name: 'flow', cat: 'input', id: 72, pid: 70, tid: 71, ts: 580, ph: 'f', args: {}},   // @suppress longLineCheck
2319      { name: 'slice2', cat: 'foo', pid: 70, tid: 71, ts: 570, ph: 'X', args: {}, 'dur': 100},   // @suppress longLineCheck
2320      { name: 'slice3', cat: 'foo', pid: 70, tid: 71, ts: 770, ph: 'X', args: {}, 'dur': 1000}   // @suppress longLineCheck
2321
2322    ];
2323
2324    var m = makeModel(events, false, false);
2325    assert.equal(m.flowEvents.length, 1);
2326
2327    var f0 = m.flowEvents[0];
2328
2329    assert.equal(f0.title, 'flow');
2330    assert.equal(f0.category, 'input');
2331    assert.equal(f0.id, 72);
2332    assert.equal(f0.start, .548);
2333    assert.closeTo(32 / 1000, f0.duration, 1e-5);
2334    assert.equal(f0.startSlice.title, 'slice1');
2335    assert.deepEqual(f0.startSlice.outFlowEvents, [f0]);
2336    assert.equal(f0.endSlice.title, 'slice2');
2337    assert.deepEqual(f0.endSlice.inFlowEvents, [f0]);
2338  });
2339
2340  test('importOldIPCFlowEventBindtoParent', function() {
2341    // Old trace format without event.bp, but event.cat contains ipc.flow
2342    var events = [
2343      { name: 'slice1', cat: 'foo', pid: 52, tid: 53, ts: 547, ph: 'X', 'dur': 100},   // @suppress longLineCheck
2344      { name: 'flow', cat: 'disabled-by-default-ipc.flow', id: 72, pid: 52, tid: 53, ts: 548, ph: 's', args: {}},  // @suppress longLineCheck
2345
2346      { name: 'flow', cat: 'disabled-by-default-ipc.flow', id: 72, pid: 70, tid: 71, ts: 580, ph: 'f', args: {}},   // @suppress longLineCheck
2347      { name: 'slice2', cat: 'foo', pid: 70, tid: 71, ts: 570, ph: 'X', args: {}, 'dur': 100},   // @suppress longLineCheck
2348      { name: 'slice3', cat: 'foo', pid: 70, tid: 71, ts: 770, ph: 'X', args: {}, 'dur': 1000}   // @suppress longLineCheck
2349
2350    ];
2351
2352    var m = makeModel(events, false, false);
2353    assert.equal(m.flowEvents.length, 1);
2354
2355    var f0 = m.flowEvents[0];
2356
2357    assert.equal(f0.title, 'flow');
2358    assert.equal(f0.category, 'disabled-by-default-ipc.flow');
2359    assert.equal(f0.id, 72);
2360    assert.equal(f0.start, .548);
2361    assert.closeTo(32 / 1000, f0.duration, 1e-5);
2362    assert.equal(f0.startSlice.title, 'slice1');
2363    assert.deepEqual(f0.startSlice.outFlowEvents, [f0]);
2364    assert.equal(f0.endSlice.title, 'slice2');
2365    assert.deepEqual(f0.endSlice.inFlowEvents, [f0]);
2366  });
2367
2368  test('importNewFlowEventBindtoParent', function() {
2369    // New trace format with event.bp
2370    var events = [
2371      { name: 'slice1', cat: 'foo', pid: 52, tid: 53, ts: 547, ph: 'X', 'dur': 100},   // @suppress longLineCheck
2372      { name: 'flow', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 548, ph: 's', bp: 'e', args: {}},  // @suppress longLineCheck
2373
2374      { name: 'flow', cat: 'foo', id: 72, pid: 70, tid: 71, ts: 580, ph: 'f', bp: 'e', args: {}},   // @suppress longLineCheck
2375      { name: 'slice2', cat: 'foo', pid: 70, tid: 71, ts: 570, ph: 'X', args: {}, 'dur': 100},   // @suppress longLineCheck
2376      { name: 'slice3', cat: 'foo', pid: 70, tid: 71, ts: 770, ph: 'X', args: {}, 'dur': 1000}   // @suppress longLineCheck
2377
2378    ];
2379
2380    var m = makeModel(events, false, false);
2381    assert.equal(m.flowEvents.length, 1);
2382
2383    var f0 = m.flowEvents[0];
2384
2385    assert.equal(f0.title, 'flow');
2386    assert.equal(f0.category, 'foo');
2387    assert.equal(f0.id, 72);
2388    assert.equal(f0.start, .548);
2389    assert.closeTo(32 / 1000, f0.duration, 1e-5);
2390    assert.equal(f0.startSlice.title, 'slice1');
2391    assert.deepEqual(f0.startSlice.outFlowEvents, [f0]);
2392    assert.equal(f0.endSlice.title, 'slice2');
2393    assert.deepEqual(f0.endSlice.inFlowEvents, [f0]);
2394  });
2395
2396  test('importNewFlowEventWithInvalidBindingPoint', function() {
2397    // New trace format with event.bp, which however !== 'e'
2398    var events = [
2399      { name: 'slice1', cat: 'foo', pid: 52, tid: 53, ts: 547, ph: 'X', 'dur': 100},   // @suppress longLineCheck
2400      { name: 'flow', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 548, ph: 's', bp: 'z', args: {}},  // @suppress longLineCheck
2401
2402      { name: 'flow', cat: 'foo', id: 72, pid: 70, tid: 71, ts: 580, ph: 'f', bp: 'z', args: {}},   // @suppress longLineCheck
2403      { name: 'slice2', cat: 'foo', pid: 70, tid: 71, ts: 570, ph: 'X', args: {}, 'dur': 100},   // @suppress longLineCheck
2404      { name: 'slice3', cat: 'foo', pid: 70, tid: 71, ts: 770, ph: 'X', args: {}, 'dur': 1000}   // @suppress longLineCheck
2405
2406    ];
2407
2408    var m = makeModel(events);
2409
2410    assert.equal(m.flowEvents.length, 0);
2411  });
2412
2413  test('importFlowV2OnePair', function() {
2414    // Flow V2: one flow producer one flow consumer
2415    var events = [
2416      { name: 'producer', cat: 'foo', pid: 52, tid: 53, ts: 547, ph: 'X', 'dur': 100, bind_id: '0xaaa', flow_out: true},   // @suppress longLineCheck
2417      { name: 'consumer', cat: 'foo', pid: 70, tid: 71, ts: 770, ph: 'X', 'dur': 1000, bind_id: '0xaaa', flow_in: true}   // @suppress longLineCheck
2418    ];
2419
2420    var m = makeModel(events);
2421
2422    assert.equal(m.flowEvents.length, 1);
2423
2424    var f0 = m.flowEvents[0];
2425
2426    assert.equal(f0.startSlice.title, 'producer');
2427    assert.deepEqual(f0.startSlice.outFlowEvents, [f0]);
2428    assert.equal(f0.endSlice.title, 'consumer');
2429    assert.deepEqual(f0.endSlice.inFlowEvents, [f0]);
2430  });
2431
2432  test('importFlowV2OneFlowStep', function() {
2433    // Flow V2: one flow producer one flow consumer
2434    var events = [
2435      { name: 'producer', cat: 'foo', pid: 52, tid: 53, ts: 547, ph: 'X', 'dur': 100, bind_id: '0xaaa', flow_out: true},   // @suppress longLineCheck
2436      { name: 'step', cat: 'foo', pid: 62, tid: 63, ts: 647, ph: 'X', 'dur': 100, bind_id: '0xaaa', flow_out: true, flow_in: true},   // @suppress longLineCheck
2437      { name: 'consumer', cat: 'foo', pid: 70, tid: 71, ts: 770, ph: 'X', 'dur': 1000, bind_id: '0xaaa', args: { 'queue_duration': 0}, flow_in: true}   // @suppress longLineCheck
2438    ];
2439
2440    var m = makeModel(events);
2441
2442    assert.equal(m.flowEvents.length, 2);
2443
2444    var f0 = m.flowEvents[0];
2445    var f1 = m.flowEvents[1];
2446
2447    assert.equal(f0.startSlice.title, 'producer');
2448    assert.deepEqual(f0.startSlice.outFlowEvents, [f0]);
2449    assert.equal(f0.endSlice.title, 'step');
2450    assert.deepEqual(f0.endSlice.inFlowEvents, [f0]);
2451
2452    assert.equal(f1.startSlice.title, 'step');
2453    assert.deepEqual(f1.startSlice.outFlowEvents, [f1]);
2454    assert.equal(f1.endSlice.title, 'consumer');
2455    assert.deepEqual(f1.endSlice.inFlowEvents, [f1]);
2456  });
2457
2458  test('importFlowV2MultipleConsumers', function() {
2459    // Flow V2: one flow producer multiple flow consumers
2460    var events = [
2461      { name: 'producer', cat: 'foo', pid: 52, tid: 53, ts: 547, ph: 'X', 'dur': 100, bind_id: '0xaaa', flow_out: true},   // @suppress longLineCheck
2462      { name: 'consumer1', cat: 'foo', pid: 70, tid: 71, ts: 770, ph: 'X', 'dur': 1000, bind_id: '0xaaa', flow_in: true},   // @suppress longLineCheck
2463      { name: 'consumer2', cat: 'foo', pid: 70, tid: 72, ts: 870, ph: 'X', 'dur': 1000, bind_id: '0xaaa', flow_in: true}   // @suppress longLineCheck
2464    ];
2465
2466    var m = makeModel(events);
2467
2468    assert.equal(m.flowEvents.length, 2);
2469
2470    var f0 = m.flowEvents[0];
2471    var f1 = m.flowEvents[1];
2472
2473    assert.equal(f0.startSlice.title, 'producer');
2474    assert.equal(f1.startSlice.title, 'producer');
2475
2476    assert.equal(f0.startSlice.outFlowEvents.length, 2);
2477    assert.deepEqual(f0.startSlice.outFlowEvents, [f0, f1]);
2478    assert.deepEqual(f1.startSlice.outFlowEvents, [f0, f1]);
2479
2480    assert.equal(f0.endSlice.title, 'consumer1');
2481    assert.equal(f1.endSlice.title, 'consumer2');
2482    assert.deepEqual(f0.endSlice.inFlowEvents, [f0]);
2483    assert.deepEqual(f1.endSlice.inFlowEvents, [f1]);
2484  });
2485
2486  test('importFlowV2MultipleProducers', function() {
2487    // Flow V2: multiple flow producers, which is not allowed
2488    var events = [
2489      { name: 'producer1', cat: 'foo', pid: 52, tid: 53, ts: 547, ph: 'X', 'dur': 100, bind_id: '0xaaa', flow_out: true},   // @suppress longLineCheck
2490      { name: 'producer2', cat: 'foo', pid: 52, tid: 54, ts: 567, ph: 'X', 'dur': 100, bind_id: '0xaaa', flow_out: true},   // @suppress longLineCheck
2491      { name: 'consumer', cat: 'foo', pid: 70, tid: 71, ts: 770, ph: 'X', 'dur': 1000, bind_id: '0xaaa', flow_in: true}   // @suppress longLineCheck
2492    ];
2493
2494    var m = makeModel(events);
2495
2496    assert.equal(m.flowEvents.length, 1);
2497  });
2498
2499  // This test creates a flow event that stops on the same timestamp that
2500  // the 'X' event which it triggers begins.
2501  test('importFlowEventOverlaps', function() {
2502    var events = [
2503      { name: 'SomeTask', cat: 'foo', pid: 52, tid: 53, ts: 547, ph: 'X', 'dur': 100},   // @suppress longLineCheck
2504      { name: 'PostTask', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 548, ph: 's', args: {}},  // @suppress longLineCheck
2505
2506      { name: 'PostTask', cat: 'foo', id: 72, pid: 70, tid: 71, ts: 580, ph: 'f', args: { 'queue_duration': 0}},   // @suppress longLineCheck
2507      // Note that RunTask has the same time-stamp as PostTask 'f'
2508      { name: 'RunTask', cat: 'foo', pid: 70, tid: 71, ts: 580, ph: 'X', args: {'src_func': 'PostRunTask'}, 'dur': 1000}   // @suppress longLineCheck
2509    ];
2510
2511    var m = makeModel(events, false);
2512    var startT = m.processes[52].threads[53];
2513    var endT = m.processes[70].threads[71];
2514
2515    assert.isDefined(startT);
2516    assert.equal(startT.sliceGroup.slices.length, 1);
2517
2518    assert.isDefined(endT);
2519    assert.equal(endT.sliceGroup.slices.length, 1);
2520
2521    assert.equal(m.flowEvents.length, 1);
2522
2523    // f0 represents 's' to 'f'
2524    var f0 = m.flowEvents[0];
2525
2526    assert.equal(f0.title, 'PostTask');
2527    assert.equal(f0.category, 'foo');
2528    assert.equal(f0.id, 72);
2529    assert.equal(f0.start, .548);
2530    assert.closeTo(32 / 1000, f0.duration, 1e-5);
2531    assert.equal(f0.startSlice.title, 'SomeTask');
2532    assert.deepEqual(f0.startSlice.outFlowEvents, [f0]);
2533    assert.equal(f0.endSlice.title, 'RunTask');
2534    assert.deepEqual(f0.endSlice.inFlowEvents, [f0]);
2535
2536    // TODO(nduca): Add assertions about the flow slices, esp that they were
2537    // found correctly.
2538  });
2539
2540  test('importOutOfOrderFlowEvent', function() {
2541    var events = [
2542      { name: 'SomeTask', cat: 'foo', pid: 52, tid: 53, ts: 548, ph: 'X', 'dur': 10},   // @suppress longLineCheck
2543      { name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 548, ph: 's', args: {} },  // @suppress longLineCheck
2544
2545      { name: 'SomeTask', cat: 'foo', pid: 52, tid: 53, ts: 148, ph: 'X', 'dur': 10},   // @suppress longLineCheck
2546      { name: 'b', cat: 'foo', id: 73, pid: 52, tid: 53, ts: 148, ph: 's', args: {} },  // @suppress longLineCheck
2547
2548      { name: 'b', cat: 'foo', id: 73, pid: 52, tid: 53, ts: 570, ph: 'f', args: {} },   // @suppress longLineCheck
2549      { name: 'SomeTask', cat: 'foo', pid: 52, tid: 53, ts: 571, ph: 'X', 'dur': 10},   // @suppress longLineCheck
2550
2551      { name: 'SomeTask', cat: 'foo', pid: 52, tid: 53, ts: 560, ph: 'X', 'dur': 10},   // @suppress longLineCheck
2552      { name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 560, ph: 't', args: {} },  // @suppress longLineCheck
2553
2554      { name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 580, ph: 'f', args: {} },   // @suppress longLineCheck
2555      { name: 'SomeTask', cat: 'foo', pid: 52, tid: 53, ts: 581, ph: 'X', 'dur': 10}   // @suppress longLineCheck
2556    ];
2557
2558    var expected = [0.4, 0.0, 0.412];
2559    var m = makeModel(events);
2560    assert.equal(m.flowIntervalTree.size, 3);
2561
2562    var order = m.flowEvents.map(function(x) { return x.start });
2563    for (var i = 0; i < expected.length; ++i)
2564      assert.closeTo(expected[i], order[i], 1e-5);
2565  });
2566
2567  test('importCompleteEvent', function() {
2568    var events = [
2569      { name: 'a', args: {}, pid: 52, ts: 629, dur: 1, cat: 'baz', tid: 53, ph: 'X' },  // @suppress longLineCheck
2570      { name: 'b', args: {}, pid: 52, ts: 730, dur: 20, cat: 'foo', tid: 53, ph: 'X' },  // @suppress longLineCheck
2571      { name: 'c', args: {}, pid: 52, ts: 740, cat: 'baz', tid: 53, ph: 'X' }
2572    ];
2573
2574    var m = makeModel(events);
2575    assert.equal(m.numProcesses, 1);
2576    var p = m.processes[52];
2577    assert.isDefined(p);
2578
2579    assert.equal(p.numThreads, 1);
2580    var t = p.threads[53];
2581    assert.isDefined(t);
2582    assert.equal(t.sliceGroup.slices.length, 3);
2583    assert.equal(t.tid, 53);
2584
2585    var slice = t.sliceGroup.slices[0];
2586    assert.equal(slice.title, 'a');
2587    assert.equal(slice.category, 'baz');
2588    assert.closeTo(0, slice.start, 1e-5);
2589    assert.closeTo(1 / 1000, slice.duration, 1e-5);
2590    assert.equal(slice.subSlices.length, 0);
2591
2592    slice = t.sliceGroup.slices[1];
2593    assert.equal(slice.title, 'b');
2594    assert.equal(slice.category, 'foo');
2595    assert.closeTo((730 - 629) / 1000, slice.start, 1e-5);
2596    assert.closeTo(20 / 1000, slice.duration, 1e-5);
2597    assert.equal(slice.subSlices.length, 1);
2598
2599    slice = t.sliceGroup.slices[2];
2600    assert.equal(slice.title, 'c');
2601    assert.isTrue(slice.didNotFinish);
2602    assert.closeTo(10 / 1000, slice.duration, 1e-5);
2603  });
2604
2605  test('importFlowEventsWithStackFrame', function() {
2606    var eventData = {
2607      traceEvents: [
2608        { name: 'aSlice', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 547, ph: 'B', args: {} },  // @suppress longLineCheck
2609        { name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 548, ph: 's', args: {}, sf: 1 },  // @suppress longLineCheck
2610        { id: 72, pid: 52, tid: 53, ts: 549, ph: 'E', args: {} },  // @suppress longLineCheck
2611
2612        { name: 'bSlice', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 559, ph: 'B', args: {} },  // @suppress longLineCheck
2613        { name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 560, ph: 't', args: {}, sf: 2 },  // @suppress longLineCheck
2614        { id: 72, pid: 52, tid: 53, ts: 561, ph: 'E', args: {} },  // @suppress longLineCheck
2615
2616        { name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 580, ph: 'f', args: {}, sf: 3 },   // @suppress longLineCheck
2617        { name: 'cSlice', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 581, ph: 'B', args: {} },  // @suppress longLineCheck
2618        { id: 72, pid: 52, tid: 53, ts: 582, ph: 'E', args: {} }  // @suppress longLineCheck
2619      ],
2620      stackFrames: {
2621        '1': {
2622          category: 'm1',
2623          name: 'fn1'
2624        },
2625        '2': {
2626          category: 'm2',
2627          name: 'fn2'
2628        },
2629        '3': {
2630          category: 'm3',
2631          name: 'fn3'
2632        }
2633      }
2634    };
2635
2636    var m = makeModel(eventData);
2637
2638    assert.equal(m.flowEvents.length, 2);
2639
2640    var f0 = m.flowEvents[0];
2641    assert.equal(f0.startStackFrame.title, 'fn1');
2642    assert.equal(f0.endStackFrame.title, 'fn2');
2643
2644    var f1 = m.flowEvents[1];
2645    assert.equal(f1.startStackFrame.title, 'fn2');
2646    assert.equal(f1.endStackFrame.title, 'fn3');
2647  });
2648
2649  test('importCompleteEventWithCpuDuration', function() {
2650    var events = [
2651      { name: 'a', args: {}, pid: 52, ts: 629, dur: 1, cat: 'baz', tid: 53, ph: 'X', tts: 12, tdur: 1 },  // @suppress longLineCheck
2652      { name: 'b', args: {}, pid: 52, ts: 730, dur: 20, cat: 'foo', tid: 53, ph: 'X', tts: 110, tdur: 16 },  // @suppress longLineCheck
2653      { name: 'c', args: {}, pid: 52, ts: 740, cat: 'baz', tid: 53, ph: 'X', tts: 115 }  // @suppress longLineCheck
2654    ];
2655
2656    var m = makeModel(events);
2657    assert.equal(m.numProcesses, 1);
2658    var p = m.processes[52];
2659    assert.isDefined(p);
2660
2661    assert.equal(p.numThreads, 1);
2662    var t = p.threads[53];
2663    assert.isDefined(t);
2664    assert.equal(t.sliceGroup.slices.length, 3);
2665    assert.equal(t.tid, 53);
2666
2667    var slice = t.sliceGroup.slices[0];
2668    assert.equal(slice.title, 'a');
2669    assert.equal(slice.category, 'baz');
2670    assert.closeTo(0, slice.start, 1e-5);
2671    assert.closeTo(1 / 1000, slice.duration, 1e-5);
2672    assert.closeTo(12 / 1000, slice.cpuStart, 1e-5);
2673    assert.closeTo(1 / 1000, slice.cpuDuration, 1e-5);
2674    assert.equal(slice.subSlices.length, 0);
2675
2676    slice = t.sliceGroup.slices[1];
2677    assert.equal(slice.title, 'b');
2678    assert.equal(slice.category, 'foo');
2679    assert.closeTo((730 - 629) / 1000, slice.start, 1e-5);
2680    assert.closeTo(20 / 1000, slice.duration, 1e-5);
2681    assert.closeTo(110 / 1000, slice.cpuStart, 1e-5);
2682    assert.closeTo(16 / 1000, slice.cpuDuration, 1e-5);
2683    assert.equal(slice.subSlices.length, 1);
2684
2685    slice = t.sliceGroup.slices[2];
2686    assert.equal(slice.title, 'c');
2687    assert.isTrue(slice.didNotFinish);
2688    assert.closeTo(10 / 1000, slice.duration, 1e-5);
2689  });
2690
2691  test('importNestedCompleteEventWithTightBounds', function() {
2692    var events = [
2693      { name: 'a', args: {}, pid: 52, ts: 244654227065, dur: 36075, cat: 'baz', tid: 53, ph: 'X' },  // @suppress longLineCheck
2694      { name: 'b', args: {}, pid: 52, ts: 244654227095, dur: 36045, cat: 'foo', tid: 53, ph: 'X' }  // @suppress longLineCheck
2695    ];
2696
2697    var m = makeModel(events, false);
2698    var t = m.processes[52].threads[53];
2699
2700    var sA = findSliceNamed(t.sliceGroup, 'a');
2701    var sB = findSliceNamed(t.sliceGroup, 'b');
2702
2703    assert.equal(sA.title, 'a');
2704    assert.equal(sA.category, 'baz');
2705    assert.equal(sA.start, 244654227.065);
2706    assert.equal(sA.duration, 36.075);
2707    assert.closeTo(0.03, sA.selfTime, 1e-5);
2708
2709    assert.equal(sB.title, 'b');
2710    assert.equal(sB.category, 'foo');
2711    assert.equal(sB.start, 244654227.095);
2712    assert.equal(sB.duration, 36.045);
2713
2714    assert.equal(sA.subSlices.length, 1);
2715    assert.equal(sA.subSlices[0], sB);
2716    assert.equal(sB.parentSlice, sA);
2717  });
2718
2719
2720  test('importCompleteEventWithStackFrame', function() {
2721    var eventData = {
2722      traceEvents: [
2723        { name: 'a', args: {}, pid: 1, ts: 0, dur: 1, cat: 'baz', tid: 2, ph: 'X', sf: 7 }, // @suppress longLineCheck
2724        { name: 'b', args: {}, pid: 1, ts: 5, dur: 1, cat: 'baz', tid: 2, ph: 'X', sf: 8, esf: 9 } // @suppress longLineCheck
2725      ],
2726      stackFrames: {
2727        '1': {
2728          category: 'm1',
2729          name: 'main'
2730        },
2731        '7': {
2732          category: 'm2',
2733          name: 'frame7',
2734          parent: '1'
2735        },
2736        '8': {
2737          category: 'm2',
2738          name: 'frame8',
2739          parent: '1'
2740        },
2741        '9': {
2742          category: 'm2',
2743          name: 'frame9',
2744          parent: '1'
2745        }
2746      }
2747    };
2748
2749    var m = makeModel(eventData);
2750
2751    var p = m.processes[1];
2752    var t = p.threads[2];
2753    assert.isDefined(t);
2754    assert.equal(t.sliceGroup.slices.length, 2);
2755
2756    var s0 = t.sliceGroup.slices[0];
2757    assert.equal(s0.startStackFrame.title, 'frame7');
2758    assert.isUndefined(s0.endStackFrame);
2759
2760    var s1 = t.sliceGroup.slices[1];
2761    assert.equal(s1.startStackFrame.title, 'frame8');
2762    assert.equal(s1.endStackFrame.title, 'frame9');
2763  });
2764
2765  test('importAsyncEventWithSameTimestamp', function() {
2766    var events = [];
2767    // Events are added with ts 0, 1, 1, 2, 2, 3, 3 ...500, 500, 1000
2768    // and use 'seq' to track the order of when the event is recorded.
2769    events.push({name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 0, ph: 'S', args: {'seq': 0}});  // @suppress longLineCheck
2770
2771    for (var i = 1; i <= 1000; i++)
2772      events.push({name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: Math.round(i / 2) , ph: 'T', args: {'seq': i}});  // @suppress longLineCheck
2773
2774    events.push({name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 1000, ph: 'F', args: {'seq': 1001}});  // @suppress longLineCheck
2775
2776    var m = makeModel(events);
2777    var t = m.processes[52].threads[53];
2778
2779    assert.equal(t.asyncSliceGroup.slices.length, 1);
2780    var parentSlice = t.asyncSliceGroup.slices[0];
2781    assert.equal(parentSlice.title, 'a');
2782    assert.equal(parentSlice.category, 'foo');
2783    assert.isTrue(parentSlice.isTopLevel);
2784
2785    assert.isDefined(parentSlice.subSlices);
2786    var subSlices = parentSlice.subSlices;
2787    assert.equal(subSlices.length, 1000);
2788    // Slices should be sorted according to 'ts'. And if 'ts' is the same,
2789    // slices should keep the order that they were recorded.
2790    for (var i = 0; i < 1000; i++) {
2791      assert.equal(i + 1, subSlices[i].args['seq']);
2792      assert.isFalse(subSlices[i].isTopLevel);
2793    }
2794  });
2795
2796  test('sampleDataSimple', function() {
2797    var events = {
2798      'traceEvents': [],
2799      'stackFrames': {
2800        '1': {
2801          'category': 'mod',
2802          'name': 'main'
2803        },
2804        '2': {
2805          'category': 'mod',
2806          'name': 'a',
2807          'parent': 1
2808        },
2809        '3': {
2810          'category': 'mod',
2811          'name': 'a_sub',
2812          'parent': 2
2813        },
2814        '4': {
2815          'category': 'mod',
2816          'name': 'b',
2817          'parent': 1
2818        }
2819      },
2820      'samples': [
2821        {
2822          'cpu': 0, 'tid': 1, 'ts': 1000.0,
2823          'name': 'cycles:HG', 'sf': 3, 'weight': 1
2824        },
2825        {
2826          'cpu': 0, 'tid': 1, 'ts': 2000.0,
2827          'name': 'cycles:HG', 'sf': 2, 'weight': 1
2828        },
2829        {
2830          'cpu': 1, 'tid': 1, 'ts': 3000.0,
2831          'name': 'cycles:HG', 'sf': 3, 'weight': 1
2832        }
2833      ]
2834    };
2835    var m = makeModel(events, false);
2836    assert.isDefined(m.kernel.cpus[0]);
2837    assert.equal(m.getAllThreads().length, 1);
2838
2839    assert.equal(tr.b.dictionaryKeys(m.stackFrames).length, 4);
2840    assert.equal(m.samples.length, 3);
2841
2842    var t1 = m.processes[1].threads[1];
2843    assert.equal(t1.samples.length, 3);
2844
2845    var c0 = m.kernel.cpus[0];
2846    var c1 = m.kernel.cpus[1];
2847    assert.equal(c0.samples.length, 2);
2848    assert.equal(c1.samples.length, 1);
2849
2850    assert.equal(m.samples[0].cpu, c0);
2851    assert.equal(m.samples[0].thread, t1);
2852    assert.equal(m.samples[0].title, 'cycles:HG');
2853    assert.equal(m.samples[0].start, 1);
2854    assert.deepEqual(
2855        ['a_sub', 'a', 'main'],
2856        m.samples[0].stackTrace.map(function(x) { return x.title; }));
2857    assert.equal(m.samples[0].weight, 1);
2858  });
2859
2860  test('importMemoryDumps_verifyProcessAndGlobalMemoryDumpLinks', function() {
2861    var events = [
2862      // 2 process memory dump events.
2863      {
2864        name: 'a',
2865        pid: 42,
2866        ts: 10000,
2867        cat: 'test',
2868        tid: 53,
2869        ph: 'v',
2870        id: '0x0001',
2871        args: {
2872          dumps: {
2873            process_totals: {
2874              resident_set_bytes: '100'
2875            }
2876          }
2877        }
2878      },
2879      {
2880        name: 'b',
2881        pid: 43,
2882        ts: 11000,
2883        cat: 'test',
2884        tid: 54,
2885        ph: 'v',
2886        id: '0x0001',
2887        args: {
2888          dumps: {
2889            process_totals: {
2890              resident_set_bytes: '200'
2891            }
2892          }
2893        }
2894      },
2895      // 1 process memory dump event.
2896      {
2897        name: 'd',
2898        pid: 42,
2899        ts: 13000,
2900        cat: 'test',
2901        tid: 56,
2902        ph: 'v',
2903        id: '0xfffffff12345678',
2904        args: {
2905          dumps: {
2906            process_totals: {
2907              resident_set_bytes: '300'
2908            }
2909          }
2910        }
2911      }
2912    ];
2913    var m = makeModel(events, false);
2914    var p1 = m.getProcess(42);
2915    var p2 = m.getProcess(43);
2916    assert.isDefined(p1);
2917    assert.isDefined(p2);
2918
2919    // Check that Model and Process objects contain the right dumps.
2920    assert.equal(m.globalMemoryDumps.length, 2);
2921    assert.equal(p1.memoryDumps.length, 2);
2922    assert.equal(p2.memoryDumps.length, 1);
2923
2924    assert.equal(m.globalMemoryDumps[0].start, 10);
2925    assert.equal(p1.memoryDumps[0].start, 10);
2926    assert.equal(p2.memoryDumps[0].start, 11);
2927    assert.equal(m.globalMemoryDumps[0].duration, 1);
2928    assert.equal(p1.memoryDumps[0].duration, 0);
2929    assert.equal(p2.memoryDumps[0].duration, 0);
2930
2931    assert.equal(m.globalMemoryDumps[1].start, 13);
2932    assert.equal(p1.memoryDumps[1].start, 13);
2933    assert.equal(m.globalMemoryDumps[1].duration, 0);
2934    assert.equal(p1.memoryDumps[1].duration, 0);
2935
2936    // Check that GlobalMemoryDump and ProcessMemoryDump objects are
2937    // interconnected correctly.
2938    assert.equal(p1.memoryDumps[0],
2939        m.globalMemoryDumps[0].processMemoryDumps[42]);
2940    assert.equal(p2.memoryDumps[0],
2941        m.globalMemoryDumps[0].processMemoryDumps[43]);
2942    assert.equal(p1.memoryDumps[0].globalMemoryDump, m.globalMemoryDumps[0]);
2943    assert.equal(p2.memoryDumps[0].globalMemoryDump, m.globalMemoryDumps[0]);
2944
2945    assert.equal(p1.memoryDumps[1],
2946        m.globalMemoryDumps[1].processMemoryDumps[42]);
2947    assert.equal(p1.memoryDumps[1].globalMemoryDump, m.globalMemoryDumps[1]);
2948  });
2949
2950  test('importMemoryDumps_totalResidentBytesOnly', function() {
2951    var events = [
2952      {
2953        pid: 42,
2954        ts: 10,
2955        ph: 'v',
2956        id: '0x01',
2957        args: {
2958          dumps: {
2959            process_totals: {
2960              resident_set_bytes: '1fffffffffffff'
2961            }
2962          }
2963        }
2964      }
2965    ];
2966    var m = makeModel(events);
2967    var p = m.getProcess(42);
2968    var d = p.memoryDumps[0];
2969
2970    assert.equal(d.totals.residentBytes, 9007199254740991);
2971    assert.isUndefined(d.totals.peakResidentBytes);
2972    assert.isUndefined(d.totals.arePeakResidentBytesResettable);
2973    assert.isUndefined(d.totals.platformSpecific);
2974    assert.isUndefined(d.mostRecentVmRegions);
2975    assert.lengthOf(d.memoryAllocatorDumps, 0);
2976  });
2977
2978  test('importMemoryDumps_withPeakResidentBytes', function() {
2979    var events = [
2980      {
2981        pid: 42,
2982        ts: 10,
2983        ph: 'v',
2984        id: '0x01',
2985        args: {
2986          dumps: {
2987            process_totals: {
2988              resident_set_bytes: '1fffffffffffff',
2989              peak_resident_set_bytes: '2fffffffffffff',
2990              is_peak_rss_resetable: true
2991            }
2992          }
2993        }
2994      }
2995    ];
2996    var m = makeModel(events);
2997    var p = m.getProcess(42);
2998    var d = p.memoryDumps[0];
2999
3000    assert.equal(d.totals.residentBytes, 9007199254740991);
3001    assert.equal(d.totals.peakResidentBytes, 13510798882111488);
3002    assert.isTrue(d.totals.arePeakResidentBytesResettable);
3003    assert.isUndefined(d.totals.platformSpecific);
3004    assert.isUndefined(d.mostRecentVmRegions);
3005    assert.lengthOf(d.memoryAllocatorDumps, 0);
3006  });
3007
3008  test('importMemoryDumps_platformSpecificTotals', function() {
3009    var events = [
3010      {
3011        pid: 42,
3012        ts: 10,
3013        ph: 'v',
3014        id: '0x01',
3015        args: {
3016          dumps: {
3017            process_totals: {
3018              resident_set_bytes: '1fffffffffffff',
3019              private_bytes: 'fffffffffffff',
3020              shared_bytes: '10000000000000'
3021            }
3022          }
3023        }
3024      }
3025    ];
3026    var m = makeModel(events);
3027    var p = m.getProcess(42);
3028    var d = p.memoryDumps[0];
3029
3030    assert.equal(d.totals.residentBytes, 9007199254740991);
3031    assert.isUndefined(d.totals.peakResidentBytes);
3032    assert.isUndefined(d.totals.arePeakResidentBytesResettable);
3033    assert.deepEqual(d.totals.platformSpecific,
3034        {private_bytes: 4503599627370495, shared_bytes: 4503599627370496});
3035    assert.isUndefined(d.mostRecentVmRegions);
3036    assert.lengthOf(d.memoryAllocatorDumps, 0);
3037  });
3038
3039  test('importMemoryDumps_vmRegions', function() {
3040    var events = [
3041      {
3042        name: 'some_dump_name',
3043        pid: 42,
3044        ts: 10,
3045        cat: 'test',
3046        tid: 53,
3047        ph: 'v',
3048        id: '000',
3049        args: {
3050          dumps: {
3051            process_totals: {
3052              resident_set_bytes: '0'
3053            },
3054            process_mmaps: {
3055              vm_regions: [
3056                {
3057                  sa: 'f0',
3058                  sz: '150',
3059                  pf: 6,
3060                  mf: '[stack:20310]',
3061                  bs: {
3062                    pss: '9e',
3063                    pc: '40',
3064                    pd: '20',
3065                    sc: '100',
3066                    sd: '0',
3067                    sw: '50'
3068                  }
3069                },
3070                {
3071                  sa: '350',
3072                  sz: '250',
3073                  pf: 5,
3074                  mf: '/dev/ashmem/dalvik',
3075                  bs: {
3076                    pss: 'cd',
3077                    pd: 'cd',
3078                    sc: undefined,
3079                    sw: '0'
3080                  }
3081                },
3082                {
3083                  sa: '7ff10ff4b000',
3084                  sz: '40000',
3085                  pf: 134,
3086                  mf: '/run/shm/.org.chromium.Chromium.sqqN11 (deleted)',
3087                  bs: {
3088                    pss: '40000',
3089                    pc: '0',
3090                    pd: '40000',
3091                    sc: '0',
3092                    sd: '0',
3093                    sw: '0'
3094                  }
3095                }
3096              ]
3097            }
3098          }
3099        }
3100      }
3101    ];
3102    var m = makeModel(events);
3103    var p = m.getProcess(42);
3104    var d = p.memoryDumps[0];
3105
3106    checkVMRegions(d.vmRegions, [
3107      {
3108        startAddress: 240,
3109        sizeInBytes: 336,
3110        protectionFlags: VMRegion.PROTECTION_FLAG_READ |
3111            VMRegion.PROTECTION_FLAG_WRITE,
3112        mappedFile: '[stack:20310]',
3113        byteStats: {
3114          privateCleanResident: 64,
3115          privateDirtyResident: 32,
3116          sharedCleanResident: 256,
3117          sharedDirtyResident: 0,
3118          proportionalResident: 158,
3119          swapped: 80
3120        }
3121      },
3122      {
3123        startAddress: 848,
3124        sizeInBytes: 592,
3125        protectionFlags: VMRegion.PROTECTION_FLAG_READ |
3126            VMRegion.PROTECTION_FLAG_EXECUTE,
3127        mappedFile: '/dev/ashmem/dalvik',
3128        byteStats: {
3129          proportionalResident: 205,
3130          privateDirtyResident: 205,
3131          swapped: 0
3132        }
3133      },
3134      {
3135        startAddress: 140673331539968,
3136        sizeInBytes: 262144,
3137        protectionFlags: VMRegion.PROTECTION_FLAG_READ |
3138            VMRegion.PROTECTION_FLAG_WRITE | VMRegion.PROTECTION_FLAG_MAYSHARE,
3139        mappedFile: '/run/shm/.org.chromium.Chromium.sqqN11 (deleted)',
3140        byteStats: {
3141          privateCleanResident: 0,
3142          privateDirtyResident: 262144,
3143          sharedCleanResident: 0,
3144          sharedDirtyResident: 0,
3145          proportionalResident: 262144,
3146          swapped: 0
3147        }
3148      }
3149    ]);
3150
3151    assert.equal(d.totals.residentBytes, 0);
3152    assert.isUndefined(d.totals.peakResidentBytes);
3153    assert.isUndefined(d.totals.arePeakResidentBytesResettable);
3154    assert.isUndefined(d.totals.platformSpecific);
3155    assert.lengthOf(d.memoryAllocatorDumps, 0);
3156  });
3157
3158  test('importMemoryDumps_explicitMemoryAllocatorDumps', function() {
3159    var events = [
3160      {
3161        name: 'a',
3162        pid: 42,
3163        ts: 10,
3164        cat: 'test',
3165        tid: 53,
3166        ph: 'v',
3167        id: '0x0001',
3168        args: {
3169          dumps: {
3170            process_totals: {
3171              resident_set_bytes: '100'
3172            },
3173            allocators: {
3174              'oilpan': {
3175                guid: '1a',
3176                attrs: {
3177                  objects_count: {
3178                    type: 'scalar', units: 'objects', value: '2f'
3179                  },
3180                  inner_size: {type: 'scalar', units: 'bytes', value: '1000'},
3181                  size: {type: 'scalar', units: 'bytes', value: '8000'}
3182                }
3183              },
3184              'oilpan/heap1': {
3185                guid: '2b',
3186                attrs: {
3187                  objects_count: {
3188                    type: 'scalar', units: 'objects', value: '3f'
3189                  },
3190                  inner_size: {type: 'scalar', units: 'bytes', value: '3000'},
3191                  size: {type: 'scalar', units: 'bytes', value: '4000'}
3192                }
3193              },
3194              'oilpan/heap2': {
3195                guid: '3c',
3196                attrs: {
3197                  objects_count: {
3198                    type: 'scalar', units: 'objects', value: '4f'
3199                  },
3200                  inner_size: {type: 'scalar', units: 'bytes', value: '4000'},
3201                  size: {type: 'scalar', units: 'bytes', value: '4000'}
3202                }
3203              },
3204              'oilpan/heap2/bucket1': {
3205                // Deliberately missing GUID (to check that the importer does
3206                // not skip memory allocator dump without GUID).
3207                attrs: {
3208                  objects_count: {
3209                    type: 'scalar', units: 'objects', value: '1f'
3210                  },
3211                  inner_size: {type: 'scalar', units: 'bytes', value: '2000'},
3212                  size: {type: 'scalar', units: 'bytes', value: '2000'}
3213                }
3214              },
3215              'v8': {
3216                guid: '5e',
3217                attrs: {
3218                  objects_count: {
3219                    type: 'scalar', units: 'objects', value: '5f'
3220                  },
3221                  inner_size: {type: 'scalar', units: 'bytes', value: '5000'},
3222                  size: {type: 'scalar', units: 'bytes', value: '6000'}
3223                }
3224              }
3225            }
3226          }
3227        }
3228      }
3229    ];
3230    var m = makeModel(events);
3231    var p = m.getProcess(42);
3232    var d = p.memoryDumps[0];
3233
3234    assert.equal(d.memoryAllocatorDumps.length, 2);
3235
3236    var oilpanRoot = d.getMemoryAllocatorDumpByFullName('oilpan');
3237    var v8Root = d.getMemoryAllocatorDumpByFullName('v8');
3238    assert.isDefined(oilpanRoot);
3239    assert.isDefined(v8Root);
3240    assert.include(d.memoryAllocatorDumps, oilpanRoot);
3241    assert.include(d.memoryAllocatorDumps, v8Root);
3242
3243    checkDumpNumericsAndDiagnostics(oilpanRoot, {
3244      'objects_count': new ScalarNumeric(unitlessNumber_smallerIsBetter, 47),
3245      'size': 32768,
3246      'effective_size': 32768,
3247      'inner_size': 4096
3248    }, {});
3249    assert.equal(oilpanRoot.children.length, 2);
3250
3251    var oilpanBucket1 = d.getMemoryAllocatorDumpByFullName(
3252        'oilpan/heap2/bucket1');
3253    assert.isDefined(oilpanBucket1);
3254    assert.equal(oilpanBucket1.fullName, 'oilpan/heap2/bucket1');
3255    assert.equal(oilpanBucket1.name, 'bucket1');
3256    checkDumpNumericsAndDiagnostics(oilpanBucket1, {
3257      'objects_count': new ScalarNumeric(unitlessNumber_smallerIsBetter, 31),
3258      'size': 8192,
3259      'effective_size': 8192,
3260      'inner_size': 8192
3261    }, {});
3262    assert.equal(oilpanBucket1.children.length, 0);
3263
3264    assert.isDefined(oilpanBucket1.parent);
3265    assert.equal(oilpanBucket1.parent.fullName, 'oilpan/heap2');
3266    assert.equal(oilpanBucket1.parent.name, 'heap2');
3267    assert.include(oilpanBucket1.parent.children, oilpanBucket1);
3268
3269    assert.isDefined(oilpanBucket1.parent.parent);
3270    assert.strictEqual(oilpanBucket1.parent.parent, oilpanRoot);
3271
3272    assert.equal(d.totals.residentBytes, 256);
3273    assert.isUndefined(d.totals.peakResidentBytes);
3274    assert.isUndefined(d.totals.arePeakResidentBytesResettable);
3275    assert.isUndefined(d.totals.platformSpecific);
3276    assert.isUndefined(d.mostRecentVmRegions);
3277  });
3278
3279  test('importMemoryDumps_implicitMemoryAllocatorDumps', function() {
3280    var events = [
3281      {
3282        name: 'a',
3283        pid: 42,
3284        ts: 10,
3285        cat: 'test',
3286        tid: 53,
3287        ph: 'v',
3288        id: '0x0001',
3289        args: {
3290          dumps: {
3291            process_totals: {
3292              resident_set_bytes: '100'
3293            },
3294            allocators: {
3295              'oilpan/heap1': {
3296                guid: '999',
3297                attrs: {
3298                  objects_count: {
3299                    type: 'scalar', units: 'objects', value: '3f'
3300                  },
3301                  inner_size: {type: 'scalar', units: 'bytes', value: '3000'},
3302                  size: {type: 'scalar', units: 'bytes', value: '4000'}
3303                }
3304              },
3305              'oilpan/heap2/bucket1': {
3306                guid: '888',
3307                attrs: {
3308                  objects_count: {
3309                    type: 'scalar', units: 'objects', value: '1f'
3310                  },
3311                  inner_size: {type: 'scalar', units: 'bytes', value: '2000'},
3312                  size: {type: 'scalar', units: 'bytes', value: '2000'}
3313                }
3314              },
3315              'v8': {
3316                guid: '777',
3317                attrs: {
3318                  objects_count: {
3319                    type: 'scalar', units: 'objects', value: '5f'
3320                  },
3321                  inner_size: {type: 'scalar', units: 'bytes', value: '5000'},
3322                  size: {type: 'scalar', units: 'bytes', value: '6000'}
3323                }
3324              }
3325            }
3326          }
3327        }
3328      }
3329    ];
3330    var m = makeModel(events);
3331    var p = m.getProcess(42);
3332    var d = p.memoryDumps[0];
3333
3334    assert.equal(d.memoryAllocatorDumps.length, 2);
3335
3336    var oilpanRoot = d.getMemoryAllocatorDumpByFullName('oilpan');
3337    var v8Root = d.getMemoryAllocatorDumpByFullName('v8');
3338    assert.isDefined(oilpanRoot);
3339    assert.isDefined(v8Root);
3340    assert.include(d.memoryAllocatorDumps, oilpanRoot);
3341    assert.include(d.memoryAllocatorDumps, v8Root);
3342
3343    checkDumpNumericsAndDiagnostics(oilpanRoot, {
3344      'objects_count': new ScalarNumeric(unitlessNumber_smallerIsBetter, 94),
3345      'size': 24576,
3346      'effective_size': 24576,
3347      'inner_size': 20480
3348    }, {});
3349    assert.equal(oilpanRoot.children.length, 2);
3350
3351    var oilpanBucket1 = d.getMemoryAllocatorDumpByFullName(
3352        'oilpan/heap2/bucket1');
3353    assert.isDefined(oilpanBucket1);
3354    assert.equal(oilpanBucket1.fullName, 'oilpan/heap2/bucket1');
3355    assert.equal(oilpanBucket1.name, 'bucket1');
3356    checkDumpNumericsAndDiagnostics(oilpanBucket1, {
3357      'objects_count': new ScalarNumeric(unitlessNumber_smallerIsBetter, 31),
3358      'size': 8192,
3359      'effective_size': 8192,
3360      'inner_size': 8192
3361    }, {});
3362    assert.equal(oilpanBucket1.children.length, 0);
3363
3364    assert.isDefined(oilpanBucket1.parent);
3365    assert.equal(oilpanBucket1.parent.fullName, 'oilpan/heap2');
3366    assert.equal(oilpanBucket1.parent.name, 'heap2');
3367    assert.include(oilpanBucket1.parent.children, oilpanBucket1);
3368
3369    assert.isDefined(oilpanBucket1.parent.parent);
3370    assert.strictEqual(oilpanBucket1.parent.parent, oilpanRoot);
3371
3372    assert.equal(d.totals.residentBytes, 256);
3373    assert.isUndefined(d.totals.peakResidentBytes);
3374    assert.isUndefined(d.totals.arePeakResidentBytesResettable);
3375    assert.isUndefined(d.totals.platformSpecific);
3376    assert.isUndefined(d.mostRecentVmRegions);
3377  });
3378
3379  test('importMemoryDumps_globalMemoryAllocatorDumps', function() {
3380    var events = [
3381      {
3382        name: 'a',
3383        pid: 42,
3384        ts: 10,
3385        cat: 'test',
3386        tid: 53,
3387        ph: 'v',
3388        id: '0x0001',
3389        args: {
3390          dumps: {
3391            process_totals: {
3392              resident_set_bytes: '100'
3393            },
3394            allocators: {
3395              'tile_manager/tile1': {
3396                guid: '21',
3397                attrs: {
3398                  objects_count: {
3399                    type: 'scalar', units: 'objects', value: '3f'
3400                  },
3401                  inner_size: {type: 'scalar', units: 'bytes', value: '3000'},
3402                  size: {type: 'scalar', units: 'bytes', value: '4000'},
3403                  weather: {type: 'string', units: '', value: 'rainy'}
3404                }
3405              },
3406              'global/shared_bitmap_manager/bitmap2': {
3407                guid: '42',
3408                attrs: {
3409                  objects_count: {
3410                    type: 'scalar', units: 'objects', value: '1f'
3411                  },
3412                  inner_size: {type: 'scalar', units: 'bytes', value: '2000'},
3413                  size: {type: 'scalar', units: 'bytes', value: '2000'},
3414                  weather: {type: 'string', units: '', value: 'sunny'}
3415                }
3416              }
3417            }
3418          }
3419        }
3420      }
3421    ];
3422    var m = makeModel(events);
3423    var p = m.getProcess(42);
3424    var gmd = m.globalMemoryDumps[0];
3425    var pmd = p.memoryDumps[0];
3426
3427    assert.isUndefined(gmd.totals);
3428    assert.equal(pmd.totals.residentBytes, 256);
3429    assert.isUndefined(pmd.totals.peakResidentBytes);
3430    assert.isUndefined(pmd.totals.arePeakResidentBytesResettable);
3431    assert.isUndefined(pmd.totals.platformSpecific);
3432
3433    assert.isUndefined(gmd.mostRecentVmRegions);
3434    assert.isUndefined(pmd.mostRecentVmRegions);
3435
3436    assert.equal(gmd.memoryAllocatorDumps.length, 1);
3437    assert.equal(pmd.memoryAllocatorDumps.length, 1);
3438
3439    // Global memory allocator dumps.
3440    var sharedBitmapManager = gmd.getMemoryAllocatorDumpByFullName(
3441        'shared_bitmap_manager');
3442    assert.isDefined(sharedBitmapManager);
3443    assert.include(gmd.memoryAllocatorDumps, sharedBitmapManager);
3444
3445    checkDumpNumericsAndDiagnostics(sharedBitmapManager, {
3446      'objects_count': new ScalarNumeric(unitlessNumber_smallerIsBetter, 31),
3447      'size': 8192,
3448      'effective_size': 8192,
3449      'inner_size': 8192
3450    }, {});
3451    assert.lengthOf(sharedBitmapManager.children, 1);
3452
3453    var bitmap2 = gmd.getMemoryAllocatorDumpByFullName(
3454        'shared_bitmap_manager/bitmap2');
3455    assert.isDefined(bitmap2);
3456    assert.include(sharedBitmapManager.children, bitmap2);
3457    assert.strictEqual(bitmap2.parent, sharedBitmapManager);
3458
3459    checkDumpNumericsAndDiagnostics(bitmap2, {
3460      'objects_count': new ScalarNumeric(unitlessNumber_smallerIsBetter, 31),
3461      'size': 8192,
3462      'effective_size': 8192,
3463      'inner_size': 8192
3464    }, { 'weather': 'sunny' });
3465    assert.lengthOf(bitmap2.children, 0);
3466
3467    assert.isUndefined(gmd.getMemoryAllocatorDumpByFullName('tile_manager'));
3468    assert.isUndefined(
3469        gmd.getMemoryAllocatorDumpByFullName('tile_manager/tile1'));
3470
3471    // Process memory allocator dumps.
3472    var tileManagerRoot = pmd.getMemoryAllocatorDumpByFullName('tile_manager');
3473    assert.isDefined(tileManagerRoot);
3474    assert.include(pmd.memoryAllocatorDumps, tileManagerRoot);
3475    assert.isUndefined(tileManagerRoot.parent);
3476
3477    checkDumpNumericsAndDiagnostics(tileManagerRoot, {
3478      'objects_count': new ScalarNumeric(unitlessNumber_smallerIsBetter, 63),
3479      'size': 16384,
3480      'effective_size': 16384,
3481      'inner_size': 12288
3482    }, {});
3483    assert.lengthOf(tileManagerRoot.children, 1);
3484
3485    var tile1 = pmd.getMemoryAllocatorDumpByFullName(
3486        'tile_manager/tile1');
3487    assert.isDefined(tile1);
3488    assert.include(tileManagerRoot.children, tile1);
3489    assert.strictEqual(tile1.parent, tileManagerRoot);
3490
3491    checkDumpNumericsAndDiagnostics(tile1, {
3492      'objects_count': new ScalarNumeric(unitlessNumber_smallerIsBetter, 63),
3493      'size': 16384,
3494      'effective_size': 16384,
3495      'inner_size': 12288
3496    }, { 'weather': 'rainy' });
3497    assert.lengthOf(tile1.children, 0);
3498
3499    assert.isUndefined(
3500        pmd.getMemoryAllocatorDumpByFullName('shared_bitmap_manager'));
3501    assert.isUndefined(
3502        pmd.getMemoryAllocatorDumpByFullName('shared_bitmap_manager/bitmap2'));
3503  });
3504
3505  test('importMemoryDumps_memoryAllocatorDumpEdges', function() {
3506    var events = [
3507      {
3508        name: 'browser',
3509        pid: 42,
3510        ts: 10,
3511        cat: 'test',
3512        tid: 53,
3513        ph: 'v',
3514        id: '0x0001',
3515        args: {
3516          dumps: {
3517            process_totals: {
3518              resident_set_bytes: '100'
3519            },
3520            allocators: {
3521              'local': {
3522                guid: '3',
3523                attrs: {
3524                  mood: {type: 'string', units: '', value: 'very good'}
3525                }
3526              },
3527              'global/shared': {
3528                guid: '7',
3529                attrs: {
3530                  color: {type: 'string', units: '', value: 'blue'}
3531                }
3532              }
3533            },
3534            allocators_graph: [
3535              {
3536                source: '3',
3537                target: '7',
3538                type: 'ownership',
3539                importance: 0
3540              }
3541            ]
3542          }
3543        }
3544      },
3545      {
3546        name: 'renderer',
3547        pid: 43,
3548        ts: 11,
3549        cat: 'test',
3550        tid: 53,
3551        ph: 'v',
3552        id: '0x0001',
3553        args: {
3554          dumps: {
3555            process_totals: {
3556              resident_set_bytes: '200'
3557            },
3558            allocators: {
3559              'local': {
3560                guid: '4',
3561                attrs: {
3562                  length: {type: 'scalar', units: 'bytes', value: '3'}
3563                }
3564              },
3565              'global/shared': {
3566                guid: '7',
3567                attrs: {
3568                  area: {type: 'scalar', units: 'sq ft', value: '9'}
3569                }
3570              }
3571            },
3572            allocators_graph: [
3573              {
3574                source: '4',
3575                target: '7',
3576                type: 'ownership',
3577                importance: 1
3578              }
3579            ]
3580          }
3581        }
3582      },
3583      {
3584        name: 'gpu',
3585        pid: 44,
3586        ts: 10.5,
3587        cat: 'test',
3588        tid: 53,
3589        ph: 'v',
3590        id: '0x0001',
3591        args: {
3592          dumps: {
3593            process_totals: {
3594              resident_set_bytes: '300'
3595            },
3596            allocators: {
3597              'local1': {
3598                guid: '5',
3599                attrs: {
3600                  state: {type: 'string', units: '', value: 'ON'}
3601                }
3602              },
3603              'local2': {
3604                guid: '6',
3605                attrs: {
3606                  temperature: {type: 'scalar', units: 'C', value: '64'}
3607                }
3608              }
3609            },
3610            allocators_graph: [
3611              {
3612                source: '5',
3613                target: '7',
3614                type: 'ownership',
3615                importance: -1
3616              },
3617              {
3618                source: '6',
3619                target: '5',
3620                type: 'ownership',
3621                importance: 1
3622              },
3623              {
3624                source: '5',
3625                target: '4',
3626                type: 'retention'
3627              }
3628            ]
3629          }
3630        }
3631      }
3632    ];
3633    var model = makeModel(events);
3634    var browserProcess = model.getProcess(42);
3635    var rendererProcess = model.getProcess(43);
3636    var gpuProcess = model.getProcess(44);
3637
3638    assert.lengthOf(model.globalMemoryDumps, 1);
3639    assert.lengthOf(browserProcess.memoryDumps, 1);
3640    assert.lengthOf(rendererProcess.memoryDumps, 1);
3641    assert.lengthOf(gpuProcess.memoryDumps, 1);
3642
3643    var globalDump = model.globalMemoryDumps[0];
3644    var browserDump = browserProcess.memoryDumps[0];
3645    var rendererDump = rendererProcess.memoryDumps[0];
3646    var gpuDump = gpuProcess.memoryDumps[0];
3647
3648    // Global memory allocator dump.
3649    assert.lengthOf(globalDump.memoryAllocatorDumps, 1);
3650
3651    var globalDumpShared = globalDump.getMemoryAllocatorDumpByFullName(
3652        'shared');
3653    assert.isDefined(globalDumpShared);
3654    assert.include(globalDump.memoryAllocatorDumps, globalDumpShared);
3655    checkDumpNumericsAndDiagnostics(globalDumpShared, {
3656      'area': new ScalarNumeric(unitlessNumber_smallerIsBetter, 9)
3657    }, { 'color': 'blue' });
3658    assert.lengthOf(globalDumpShared.children, 0);
3659    assert.isUndefined(globalDumpShared.parent);
3660
3661    assert.isUndefined(globalDumpShared.owns);
3662    assert.lengthOf(globalDumpShared.ownedBy, 3);
3663    assert.lengthOf(globalDumpShared.retains, 0);
3664    assert.lengthOf(globalDumpShared.retainedBy, 0);
3665
3666    // Browser memory allocator dump.
3667    assert.lengthOf(browserDump.memoryAllocatorDumps, 1);
3668
3669    var browserDumpLocal = browserDump.getMemoryAllocatorDumpByFullName(
3670        'local');
3671    assert.isDefined(browserDumpLocal);
3672    assert.include(browserDump.memoryAllocatorDumps, browserDumpLocal);
3673    checkDumpNumericsAndDiagnostics(browserDumpLocal, {
3674      'area': new ScalarNumeric(unitlessNumber_smallerIsBetter, 9)
3675    }, { 'color': 'blue', 'mood': 'very good' });
3676    assert.lengthOf(browserDumpLocal.children, 0);
3677    assert.isUndefined(browserDumpLocal.parent);
3678
3679    assert.isDefined(browserDumpLocal.owns);
3680    assert.lengthOf(browserDumpLocal.ownedBy, 0);
3681    assert.lengthOf(browserDumpLocal.retains, 0);
3682    assert.lengthOf(browserDumpLocal.retainedBy, 0);
3683
3684    var browserDumpLocalOwnsLink = browserDumpLocal.owns;
3685    assert.include(globalDumpShared.ownedBy, browserDumpLocalOwnsLink);
3686    assert.strictEqual(browserDumpLocalOwnsLink.source, browserDumpLocal);
3687    assert.strictEqual(browserDumpLocalOwnsLink.target, globalDumpShared);
3688    assert.equal(browserDumpLocalOwnsLink.importance, 0);
3689
3690    // Renderer memory allocator dump.
3691    assert.lengthOf(rendererDump.memoryAllocatorDumps, 1);
3692
3693    var rendererDumpLocal = rendererDump.getMemoryAllocatorDumpByFullName(
3694        'local');
3695    assert.isDefined(rendererDumpLocal);
3696    assert.include(rendererDump.memoryAllocatorDumps, rendererDumpLocal);
3697    checkDumpNumericsAndDiagnostics(rendererDumpLocal, {
3698      'area': new ScalarNumeric(unitlessNumber_smallerIsBetter, 9),
3699      'length': 3
3700    }, { 'color': 'blue' });
3701    assert.lengthOf(rendererDumpLocal.children, 0);
3702    assert.isUndefined(rendererDumpLocal.parent);
3703
3704    assert.isDefined(rendererDumpLocal.owns);
3705    assert.lengthOf(rendererDumpLocal.ownedBy, 0);
3706    assert.lengthOf(rendererDumpLocal.retains, 0);
3707    assert.lengthOf(rendererDumpLocal.retainedBy, 1);
3708
3709    var rendererDumpLocalOwnsLink = rendererDumpLocal.owns;
3710    assert.include(globalDumpShared.ownedBy, rendererDumpLocalOwnsLink);
3711    assert.strictEqual(rendererDumpLocalOwnsLink.source, rendererDumpLocal);
3712    assert.strictEqual(rendererDumpLocalOwnsLink.target, globalDumpShared);
3713    assert.equal(rendererDumpLocalOwnsLink.importance, 1);
3714
3715    // GPU memory allocator dumps.
3716    assert.lengthOf(gpuDump.memoryAllocatorDumps, 2);
3717
3718    var gpuDumpLocal1 = gpuDump.getMemoryAllocatorDumpByFullName('local1');
3719    assert.isDefined(gpuDumpLocal1);
3720    assert.include(gpuDump.memoryAllocatorDumps, gpuDumpLocal1);
3721    checkDumpNumericsAndDiagnostics(gpuDumpLocal1, {
3722      'area': new ScalarNumeric(unitlessNumber_smallerIsBetter, 9)
3723    }, { 'state': 'ON', 'color': 'blue' });
3724    assert.lengthOf(gpuDumpLocal1.children, 0);
3725    assert.isUndefined(gpuDumpLocal1.parent);
3726
3727    assert.isDefined(gpuDumpLocal1.owns);
3728    assert.lengthOf(gpuDumpLocal1.ownedBy, 1);
3729    assert.lengthOf(gpuDumpLocal1.retains, 1);
3730    assert.lengthOf(gpuDumpLocal1.retainedBy, 0);
3731
3732    var gpuDumpLocal1OwnsLink = gpuDumpLocal1.owns;
3733    assert.include(globalDumpShared.ownedBy, gpuDumpLocal1OwnsLink);
3734    assert.strictEqual(gpuDumpLocal1OwnsLink.source, gpuDumpLocal1);
3735    assert.strictEqual(gpuDumpLocal1OwnsLink.target, globalDumpShared);
3736    assert.equal(gpuDumpLocal1OwnsLink.importance, -1);
3737
3738    var gpuDumpLocal1RetainsLink = gpuDumpLocal1.retains[0];
3739    assert.include(rendererDumpLocal.retainedBy, gpuDumpLocal1RetainsLink);
3740    assert.strictEqual(gpuDumpLocal1RetainsLink.source, gpuDumpLocal1);
3741    assert.strictEqual(gpuDumpLocal1RetainsLink.target, rendererDumpLocal);
3742    assert.isUndefined(gpuDumpLocal1RetainsLink.importance);
3743
3744    var gpuDumpLocal2 = gpuDump.getMemoryAllocatorDumpByFullName('local2');
3745    assert.isDefined(gpuDumpLocal2);
3746    assert.include(gpuDump.memoryAllocatorDumps, gpuDumpLocal2);
3747    checkDumpNumericsAndDiagnostics(gpuDumpLocal2, {
3748      'temperature': new ScalarNumeric(unitlessNumber_smallerIsBetter, 100)
3749    }, {});
3750    assert.lengthOf(gpuDumpLocal2.children, 0);
3751    assert.isUndefined(gpuDumpLocal2.parent);
3752
3753    assert.isDefined(gpuDumpLocal2.owns);
3754    assert.lengthOf(gpuDumpLocal2.ownedBy, 0);
3755    assert.lengthOf(gpuDumpLocal2.retains, 0);
3756    assert.lengthOf(gpuDumpLocal2.retainedBy, 0);
3757
3758    var gpuDumpLocal2OwnsLink = gpuDumpLocal2.owns;
3759    assert.include(gpuDumpLocal1.ownedBy, gpuDumpLocal2OwnsLink);
3760    assert.strictEqual(gpuDumpLocal2OwnsLink.source, gpuDumpLocal2);
3761    assert.strictEqual(gpuDumpLocal2OwnsLink.target, gpuDumpLocal1);
3762    assert.equal(gpuDumpLocal2OwnsLink.importance, 1);
3763  });
3764
3765  test('importMemoryDumps_memoryAllocatorDumpsMissingFields', function() {
3766    var events = [
3767      {
3768        name: 'a',
3769        pid: 42,
3770        ts: 10,
3771        ph: 'v',
3772        id: '0x0001',
3773        args: {
3774          dumps: {
3775            allocators: {
3776              'no_crash': {
3777                /* Missing GUID and attributes. */
3778              }
3779            }
3780          }
3781        }
3782      }
3783    ];
3784    var m = makeModel(events);
3785    var p = m.getProcess(42);
3786    var d = p.memoryDumps[0];
3787
3788    assert.equal(d.memoryAllocatorDumps.length, 1);
3789    var noCrashRoot = d.getMemoryAllocatorDumpByFullName('no_crash');
3790    assert.lengthOf(noCrashRoot.children, 0);
3791    checkDumpNumericsAndDiagnostics(noCrashRoot, {}, {});
3792    assert.isUndefined(noCrashRoot.parent);
3793    assert.isUndefined(noCrashRoot.guid);
3794  });
3795
3796  test('importMemoryDumps_weakMemoryAllocatorDumps', function() {
3797    var events = [
3798      {
3799        pid: 42,
3800        ts: 10,
3801        ph: 'v',
3802        id: '0x0001',
3803        args: {
3804          dumps: {
3805            allocators: {
3806              // Sinks for ownership edges (to check that the correct ownership
3807              // edges are removed).
3808              'root_sink': { guid: '100', attrs: {} },
3809              'root_sink/child_sink': { guid: '200', attrs: {} },
3810              'root_sink/child_sink/descendant_sink': {
3811                guid: '300', attrs: {}
3812              },
3813
3814              // Note: 'removed' in the name of a dump means that the dump will
3815              // be removed despite being non-weak (strong), e.g. due to one of
3816              // its ancestors being weak.
3817
3818              // All descendants of a weak root dump should be removed.
3819              'weak_root': { guid: '1', attrs: {}, flags: 1 },
3820              'weak_root/removed_child': { guid: '2', attrs: {} },
3821              'weak_root/inferred_removed_child/removed_descendant': {
3822                guid: '3', attrs: {}, flags: 0
3823              },
3824
3825              // A strong root should be kept even if all its descendants are
3826              // weak.
3827              'strong_root': { guid: '4', attrs: {}, flags: 0 },
3828              'strong_root/weak_child': { guid: '5', attrs: {}, flags: 1 },
3829              'strong_root/inferred_weak_child/weak_descendant': {
3830                guid: '6', attrs: {}, flags: 1
3831              },
3832
3833              // All inferred ancestors of a weak descendant should be marked
3834              // weak and, consequently, removed (provided that they don't have
3835              // any non-weak descendants).
3836              'inferred_weak_root/inferred_weak_child/weak_descendant': {
3837                guid: '7', attrs: {}, flags: 1
3838              },
3839
3840              // An inferred dump should be marked non-weak if it has at least
3841              // one strong descendant.
3842              'inferred_strong_root/child1_weak': {
3843                guid: '8', attrs: {}, flags: 1
3844              },
3845              'inferred_strong_root/child2_strong': {
3846                guid: '9', attrs: {}
3847              },
3848              'inferred_strong_root/child3_weak': {
3849                guid: '10', attrs: {}, flags: 1
3850              },
3851              'inferred_strong_root2/inferred_strong_child/desc1_strong': {
3852                guid: '11', attrs: {}
3853              },
3854              'inferred_strong_root2/inferred_strong_child/desc2_weak': {
3855                guid: '12', attrs: {}, flags: 1
3856              },
3857              'inferred_strong_root2/inferred_strong_child/desc3_strong': {
3858                guid: '13', attrs: {}
3859              },
3860              'inferred_strong_root2/weak_child': {
3861                guid: '14', attrs: {}, flags: 1
3862              },
3863
3864              // A desdendant dump should be removed if it has a weak ancestor.
3865              'strong_root2': { guid: '15', attrs: {} },
3866              'strong_root2/weak_child': { guid: '16', attrs: {}, flags: 1 },
3867              'strong_root2/weak_child/removed_descendant': {
3868                guid: '17', attrs: {}
3869              },
3870
3871              // Check that "weakness" also propagates across ownership edges.
3872              'removed_root': { guid: '18', attrs: {} },
3873              'removed_root/removed_child': {
3874                guid: '19', attrs: {}
3875              },
3876              'inferred_strong_root3/removed_child': {
3877                guid: '20', attrs: {}
3878              },
3879            },
3880            allocators_graph: [
3881              { source: '1', target: '100', type: 'ownership' },
3882              { source: '2', target: '200', type: 'ownership' },
3883              { source: '3', target: '300', type: 'ownership' },
3884
3885              { source: '4', target: '100', type: 'ownership' },  // Kept.
3886              { source: '5', target: '200', type: 'ownership' },
3887              { source: '6', target: '300', type: 'ownership' },
3888
3889              { source: '7', target: '300', type: 'ownership' },
3890
3891              { source: '8', target: '200', type: 'ownership' },
3892              { source: '9', target: '200', type: 'ownership' },  // Kept.
3893              { source: '10', target: '200', type: 'ownership' },
3894              { source: '11', target: '300', type: 'ownership' },  // Kept.
3895              { source: '12', target: '300', type: 'ownership' },
3896              { source: '13', target: '300', type: 'ownership' },  // Kept.
3897              { source: '14', target: '200', type: 'ownership' },
3898
3899              { source: '15', target: '100', type: 'ownership' },  // Kept.
3900              { source: '16', target: '200', type: 'ownership' },
3901              { source: '17', target: '300', type: 'ownership' },
3902
3903              { source: '18', target: '3' /* not a sink */, type: 'ownership' },
3904              { source: '19', target: '200', type: 'ownership' },
3905              { source: '20', target: '19' /* not a sink */, type: 'ownership' }
3906            ]
3907          }
3908        }
3909      }
3910    ];
3911    var m = makeModel(events);
3912    var p = m.getProcess(42);
3913    var d = p.memoryDumps[0];
3914    var memoryAllocatorDumps = d.memoryAllocatorDumps;
3915    assert.lengthOf(memoryAllocatorDumps, 6);
3916
3917    function checkDump(dump, expectedFullName, expectedGuid, expectedParent,
3918        expectedChildCount, expectedOwnsLink, expectedOwnedByLinkCount) {
3919      assert.strictEqual(dump.fullName, expectedFullName);
3920      assert.strictEqual(dump.guid, expectedGuid);
3921      assert.strictEqual(dump.parent, expectedParent);
3922      assert.lengthOf(dump.children, expectedChildCount);
3923      assert.strictEqual(dump.owns, expectedOwnsLink);
3924      assert.lengthOf(dump.ownedBy, expectedOwnedByLinkCount);
3925      assert.strictEqual(
3926          d.getMemoryAllocatorDumpByFullName(expectedFullName), dump);
3927    }
3928
3929    function checkOwnsLink(ownerDump, expectedTarget) {
3930      assert.strictEqual(ownerDump.owns.source, ownerDump);
3931      assert.strictEqual(ownerDump.owns.target, expectedTarget);
3932    }
3933
3934    // Check root_sink/* dumps.
3935    var rootSink = d.memoryAllocatorDumps[3];
3936    checkDump(rootSink, 'root_sink', '100', undefined, 1, undefined, 2);
3937    var childSink = rootSink.children[0];
3938    checkDump(childSink, 'root_sink/child_sink', '200', rootSink, 1, undefined,
3939        1);
3940    var descendantSink = childSink.children[0];
3941    checkDump(descendantSink, 'root_sink/child_sink/descendant_sink', '300',
3942        childSink, 0, undefined, 2);
3943
3944    // Check strong_root/* dumps.
3945    var strongRoot = d.memoryAllocatorDumps[4];
3946    checkDump(strongRoot, 'strong_root', '4', undefined, 0, rootSink.ownedBy[0],
3947        0);
3948
3949    // Check inferred_strong_root/* dumps.
3950    var inferredStrongRoot = d.memoryAllocatorDumps[0];
3951    checkDump(inferredStrongRoot, 'inferred_strong_root', undefined, undefined,
3952        1, undefined, 0);
3953    var child2Strong = inferredStrongRoot.children[0];
3954    checkDump(child2Strong, 'inferred_strong_root/child2_strong', '9',
3955        inferredStrongRoot, 0, childSink.ownedBy[0], 0);
3956
3957    // Check inferred_strong_root2/* dumps.
3958    var inferredStrongRoot2 = d.memoryAllocatorDumps[1];
3959    checkDump(inferredStrongRoot2, 'inferred_strong_root2', undefined,
3960        undefined, 1, undefined, 0);
3961    var inferredStrongChild = inferredStrongRoot2.children[0];
3962    checkDump(inferredStrongChild,
3963        'inferred_strong_root2/inferred_strong_child', undefined,
3964        inferredStrongRoot2, 2, undefined, 0);
3965    var desc1Strong = inferredStrongChild.children[0];
3966    checkDump(desc1Strong,
3967        'inferred_strong_root2/inferred_strong_child/desc1_strong', '11',
3968        inferredStrongChild, 0, descendantSink.ownedBy[0], 0);
3969    var desc3Strong = inferredStrongChild.children[1];
3970    checkDump(desc3Strong,
3971        'inferred_strong_root2/inferred_strong_child/desc3_strong', '13',
3972        inferredStrongChild, 0, descendantSink.ownedBy[1], 0);
3973
3974    // Check strong_root2/* dumps.
3975    var strongRoot2 = d.memoryAllocatorDumps[5];
3976    checkDump(strongRoot2, 'strong_root2', '15', undefined, 0,
3977        rootSink.ownedBy[1], 0);
3978
3979    // Check inferred_strong_root3/* dumps.
3980    var inferredStrongRoot3 = d.memoryAllocatorDumps[2];
3981    checkDump(inferredStrongRoot3, 'inferred_strong_root3', undefined,
3982        undefined, 0, undefined, 0);
3983
3984    // Check the links.
3985    checkOwnsLink(strongRoot, rootSink);
3986    checkOwnsLink(child2Strong, childSink);
3987    checkOwnsLink(desc1Strong, descendantSink);
3988    checkOwnsLink(desc3Strong, descendantSink);
3989    checkOwnsLink(strongRoot2, rootSink);
3990
3991    // Check that the removed weak dumps are not indexed.
3992    [
3993      'weak_root',
3994      'weak_root/removed_child',
3995      'weak_root/inferred_removed_child',
3996      'weak_root/inferred_removed_child/removed_descendant',
3997      'strong_root/weak_child',
3998      'strong_root/inferred_weak_child/weak_descendant',
3999      'inferred_weak_root',
4000      'inferred_weak_root/inferred_weak_child',
4001      'inferred_weak_root/inferred_weak_child/weak_descendant',
4002      'inferred_strong_root/child1_weak',
4003      'inferred_strong_root/child3_weak',
4004      'inferred_strong_root2/inferred_strong_child/desc2_weak',
4005      'inferred_strong_root2/weak_child',
4006      'strong_root2/weak_child',
4007      'strong_root2/removed_descendant',
4008      'removed_root',
4009      'removed_root/removed_child',
4010      'inferred_strong_root3/removed_child'
4011    ].forEach(function(fullName) {
4012      assert.isUndefined(d.getMemoryAllocatorDumpByFullName(fullName));
4013    });
4014  });
4015
4016  test('importMemoryDumps_levelsOfDetail', function() {
4017    function checkLevelsOfDetail(rawLevelsOfDetail, expectedGlobalLevelOfDetail,
4018        expectedProcessLevelsOfDetail) {
4019      var events = [];
4020      rawLevelsOfDetail.forEach(function(rawProcessLevelsOfDetail, pid) {
4021        rawProcessLevelsOfDetail.forEach(function(rawComposableLevelOfDetail) {
4022          var dumps = {};
4023          if (rawComposableLevelOfDetail !== undefined)
4024            dumps.level_of_detail = rawComposableLevelOfDetail;
4025          events.push({
4026            name: 'process_' + pid,
4027            pid: pid,
4028            ts: 10,
4029            ph: 'v',
4030            id: '0x0001',
4031            args: {
4032              dumps: dumps
4033            }
4034          });
4035        });
4036      });
4037      var model = makeModel(events);
4038
4039      // Check GlobalMemoryDump level of detail.
4040      assert.lengthOf(model.globalMemoryDumps, 1);
4041      assert.strictEqual(model.globalMemoryDumps[0].levelOfDetail,
4042          expectedGlobalLevelOfDetail);
4043
4044      // Check ProcessMemoryDumps levels of detail.
4045      assert.lengthOf(Object.keys(model.processes),
4046          expectedProcessLevelsOfDetail.length);
4047      for (var i = 0; i < expectedProcessLevelsOfDetail.length; i++) {
4048        var process = model.getProcess(i);
4049        assert.lengthOf(process.memoryDumps, 1);
4050        assert.strictEqual(process.memoryDumps[0].levelOfDetail,
4051            expectedProcessLevelsOfDetail[i]);
4052      }
4053    }
4054
4055    // Well-formed trace events.
4056    checkLevelsOfDetail([[undefined]], undefined, [undefined]);
4057    checkLevelsOfDetail([['light'], ['light']], 'light', ['light', 'light']);
4058    checkLevelsOfDetail(
4059        [
4060          ['detailed', 'detailed'],
4061          ['detailed'],
4062          ['detailed', 'detailed', 'detailed']
4063        ],
4064        'detailed',
4065        ['detailed', 'detailed', 'detailed']);
4066
4067    // Not so well-formed trace events.
4068    checkLevelsOfDetail(
4069        [['light'], [undefined], ['detailed'], ['light']],
4070        'detailed',
4071        ['light', undefined, 'detailed', 'light']);
4072    checkLevelsOfDetail(
4073        [['light', 'detailed'], [undefined], ['light', undefined]],
4074        'detailed',
4075        ['detailed', undefined, 'light']);
4076    checkLevelsOfDetail(
4077        [['invalid', 'light'], ['invalid']],
4078        'light',
4079        ['light', undefined]);
4080  });
4081
4082  test('importMemoryDumps_heapDumps_oldFormat', function() {
4083    var events = [  // Intentionally shuffled.
4084      {
4085        pid: 21,
4086        ts: 9,
4087        ph: 'v',
4088        id: '0123',
4089        args: {
4090          dumps: {
4091            heaps: {
4092              partition_alloc: {
4093                entries: [
4094                  { size: '1000' },
4095                  { bt: '0', size: 'abc' }
4096                ]
4097              }
4098            }
4099          }
4100        }
4101      },
4102      {
4103        pid: 42,
4104        ph: 'M',
4105        name: 'stackFrames',
4106        args: {
4107          stackFrames: {
4108            '0': { name: 'MessageLoop::RunTask' },
4109            '1': { name: 'TimerBase::run', parent: '0' },
4110            'TWO': { name: 'ScheduledAction::execute', 'parent': '1' },
4111            '3': { name: 'FunctionCall', parent: 'TWO' },
4112            '4': { name: 'UpdateLayoutTree', parent: '1' },
4113            '5': { name: 'MessageLoop::JogTask' }
4114          }
4115        }
4116      },
4117      {
4118        pid: 42,
4119        ph: 'M',
4120        name: 'typeNames',
4121        args: {
4122          typeNames: {
4123            // GCC.
4124            '22': '[unknown]',
4125            '23': 'testing::ManuallyAnnotatedMockClass',
4126            '24': 'const char* WTF::getStringWithTypeName() [with T = ' +
4127                'blink::Event]',
4128            '25': 'blink::ContextLifecycleObserver*',
4129            '26': 'const char* WTF::getStringWithTypeName() [with T = ' +
4130                'blink::WebFrame*]'
4131          }
4132        }
4133      },
4134      {
4135        pid: 42,
4136        ts: 10,
4137        ph: 'v',
4138        id: '0123',
4139        args: {
4140          dumps: {
4141            process_totals: {
4142              resident_set_bytes: '0'
4143            },
4144            heaps: {
4145              partition_alloc: {
4146                entries: [
4147                  { type: '24', size: '2e6fc8' },
4148                  { size: '5cdf91' },
4149                  { type: '25', size: '1737e4' },
4150                  { bt: '', size: '5b6cd6' },
4151                  { bt: '4', size: '18f0' },
4152                  { bt: '3', size: 'e3a8' }
4153                ]
4154              },
4155              malloc: {
4156                entries: [
4157                  { size: '789' },
4158                  { bt: '0', size: '123' },
4159                  { bt: '5', size: '456' },
4160                  { type: '25', size: 'cd' }
4161                ]
4162              }
4163            }
4164          }
4165        }
4166      },
4167      {
4168        pid: 21,
4169        ph: 'M',
4170        name: 'stackFrames',
4171        args: {
4172          stackFrames: {
4173            // Intentionally in reverse order.
4174            '0': { name: 'FrameView::layout', parent: '1' },
4175            '1': { name: 'MessageLoop::RunTask' }
4176          }
4177        }
4178      },
4179      {
4180        pid: 21,
4181        ts: 12,
4182        ph: 'v',
4183        id: '0987',
4184        args: {
4185          dumps: {
4186            heaps: {
4187              partition_alloc: {
4188                entries: [
4189                  { size: '2000' },
4190                  { bt: '0', size: 'def' }
4191                ]
4192              }
4193            }
4194          }
4195        }
4196      }
4197    ];
4198    var m = makeModel(events);
4199    var p1 = m.getProcess(21);
4200    var p2 = m.getProcess(42);
4201    assert.lengthOf(m.globalMemoryDumps, 2);
4202    assert.lengthOf(p1.memoryDumps, 2);
4203    assert.lengthOf(p2.memoryDumps, 1);
4204
4205    // Stack frames.
4206    assert.deepEqual(
4207        tr.b.mapItems(m.stackFrames, function(id, f) { return f.title }),
4208        {
4209          'p21:0': 'FrameView::layout',
4210          'p21:0:self': '<self>',
4211          'p21:1': 'MessageLoop::RunTask',
4212          'p42::self': '<self>',
4213          'p42:0': 'MessageLoop::RunTask',
4214          'p42:0:self': '<self>',
4215          'p42:1': 'TimerBase::run',
4216          'p42:TWO': 'ScheduledAction::execute',
4217          'p42:3': 'FunctionCall',
4218          'p42:3:self': '<self>',
4219          'p42:4': 'UpdateLayoutTree',
4220          'p42:4:self': '<self>',
4221          'p42:5': 'MessageLoop::JogTask',
4222          'p42:5:self': '<self>'
4223        });
4224
4225    // 1. Process 21, first dump.
4226    var pmd1 = p1.memoryDumps[0];
4227    var hds1 = pmd1.heapDumps;
4228    assert.sameMembers(Object.keys(hds1), ['partition_alloc']);
4229
4230    var partitionAllocDump1 = hds1['partition_alloc'];
4231    assert.strictEqual(partitionAllocDump1.processMemoryDump, pmd1);
4232    assert.equal(partitionAllocDump1.allocatorName, 'partition_alloc');
4233    var partitionAllocEntries1 = partitionAllocDump1.entries;
4234    assert.lengthOf(partitionAllocEntries1, 2);
4235    checkHeapEntry(partitionAllocEntries1[0], partitionAllocDump1, 4096,
4236        undefined /* root */, undefined /* sum over all types */);
4237    checkHeapEntry(partitionAllocEntries1[1], partitionAllocDump1, 2748,
4238        ['<self>', 'FrameView::layout', 'MessageLoop::RunTask']);
4239
4240    // 2. Process 21, second dump.
4241    var pmd2 = p1.memoryDumps[1];
4242    var hds2 = pmd2.heapDumps;
4243    assert.sameMembers(Object.keys(hds2), ['partition_alloc']);
4244
4245    var partitionAllocDump2 = hds2['partition_alloc'];
4246    assert.strictEqual(partitionAllocDump2.processMemoryDump, pmd2);
4247    assert.equal(partitionAllocDump2.allocatorName, 'partition_alloc');
4248    var partitionAllocEntries2 = partitionAllocDump2.entries;
4249    assert.lengthOf(partitionAllocEntries2, 2);
4250    checkHeapEntry(partitionAllocEntries2[0], partitionAllocDump2, 8192,
4251        undefined /* root */, undefined /* sum over all types */);
4252    checkHeapEntry(partitionAllocEntries2[1], partitionAllocDump2, 3567,
4253        ['<self>', 'FrameView::layout', 'MessageLoop::RunTask'],
4254        undefined /* sum over all types */);
4255
4256    // All heap dumps in Process 21 should use the same stack frames.
4257    assert.strictEqual(
4258        getFrame(partitionAllocEntries1[1], 0),
4259        getFrame(partitionAllocEntries2[1], 0));
4260
4261    // 3. Process 42.
4262    var pmd3 = p2.memoryDumps[0];
4263    var hds3 = pmd3.heapDumps;
4264    assert.sameMembers(Object.keys(hds3), ['partition_alloc', 'malloc']);
4265
4266    var partitionAllocDump3 = hds3['partition_alloc'];
4267    assert.strictEqual(partitionAllocDump3.processMemoryDump, pmd3);
4268    assert.equal(partitionAllocDump3.allocatorName, 'partition_alloc');
4269    var partitionAllocEntries3 = partitionAllocDump3.entries;
4270    assert.lengthOf(partitionAllocEntries3, 6);
4271    checkHeapEntry(partitionAllocEntries3[0], partitionAllocDump3, 3043272,
4272        undefined /* root */, 'blink::Event');
4273    checkHeapEntry(partitionAllocEntries3[1], partitionAllocDump3, 6086545,
4274        undefined /* root */, undefined /* sum over all types */);
4275    checkHeapEntry(partitionAllocEntries3[2], partitionAllocDump3, 1521636,
4276        undefined /* root */, 'blink::ContextLifecycleObserver*');
4277    checkHeapEntry(partitionAllocEntries3[3], partitionAllocDump3, 5991638,
4278        ['<self>'], undefined /* sum over all types */);
4279    checkHeapEntry(partitionAllocEntries3[4], partitionAllocDump3, 6384,
4280        ['<self>', 'UpdateLayoutTree', 'TimerBase::run',
4281            'MessageLoop::RunTask'], undefined /* sum over all types */);
4282    checkHeapEntry(partitionAllocEntries3[5], partitionAllocDump3, 58280,
4283        ['<self>', 'FunctionCall', 'ScheduledAction::execute', 'TimerBase::run',
4284            'MessageLoop::RunTask'], undefined /* sum over all types */);
4285
4286    var mallocDump3 = hds3['malloc'];
4287    assert.strictEqual(mallocDump3.processMemoryDump, pmd3);
4288    assert.equal(mallocDump3.allocatorName, 'malloc');
4289    var mallocEntries3 = mallocDump3.entries;
4290    assert.lengthOf(mallocEntries3, 4);
4291    checkHeapEntry(mallocEntries3[0], mallocDump3, 1929, undefined /* root */,
4292        undefined /* sum over all types */);
4293    checkHeapEntry(mallocEntries3[1], mallocDump3, 291,
4294        ['<self>', 'MessageLoop::RunTask'], undefined /* sum over all types */);
4295    checkHeapEntry(mallocEntries3[2], mallocDump3, 1110,
4296        ['<self>', 'MessageLoop::JogTask'], undefined /* sum over all types */);
4297    checkHeapEntry(mallocEntries3[3], mallocDump3, 205, undefined /* root */,
4298        'blink::ContextLifecycleObserver*');
4299
4300    // All heap dumps in Process 42 should use the same stack frames.
4301    assert.strictEqual(
4302        getFrame(partitionAllocEntries3[5], 3),
4303        getFrame(partitionAllocEntries3[4], 2));
4304    assert.strictEqual(
4305        getFrame(mallocEntries3[1], 1),
4306        getFrame(partitionAllocEntries3[4], 3));
4307  });
4308
4309  test('importMemoryDumps_heapDumps_newFormat', function() {
4310    var events = [  // Intentionally shuffled.
4311      {
4312        pid: 21,
4313        ts: 9,
4314        ph: 'v',
4315        id: '0123',
4316        args: {
4317          dumps: {
4318            heaps: {
4319              partition_alloc: {
4320                entries: [
4321                  { bt: '', type: '25', size: '1000' },
4322                  { bt: 'A', size: 'abc' }
4323                ]
4324              }
4325            }
4326          }
4327        }
4328      },
4329      {
4330        pid: 42,
4331        ph: 'M',
4332        name: 'stackFrames',
4333        args: {
4334          stackFrames: {
4335            '-1': { name: '<self>' },
4336            '0': { name: 'MessageLoop::RunTask' },
4337            '0.5': { name: '<self>', parent: '0' },
4338            '1': { name: 'TimerBase::run', parent: '0' },
4339            'TWO': { name: 'ScheduledAction::execute', 'parent': '1' },
4340            '2.72': { name: '<self>', 'parent': 'TWO' },
4341            '3': { name: 'FunctionCall', parent: 'TWO' },
4342            '\u03C0': { name: '<self>', parent: '3' },
4343            '4': { name: 'UpdateLayoutTree', parent: '1' },
4344            'FOUR-AND-A-BIT': { name: '<self>', parent: '4' },
4345            '5': { name: 'MessageLoop::JogTask' },
4346            'NaN': { name: '<self>', parent: '5' }
4347          }
4348        }
4349      },
4350      {
4351        pid: 42,
4352        ph: 'M',
4353        name: 'typeNames',
4354        args: {
4355          typeNames: {
4356            // Clang.
4357            '22': '[unknown]',
4358            '23': 'testing::ManuallyAnnotatedMockClass',
4359            '24': 'const char *WTF::getStringWithTypeName() [T = ' +
4360                'blink::Event]',
4361            '25': 'blink::ContextLifecycleObserver *',
4362            '26': 'const char *WTF::getStringWithTypeName() [T = ' +
4363                'blink::WebFrame *]'
4364          }
4365        }
4366      },
4367      {
4368        pid: 42,
4369        ts: 10,
4370        ph: 'v',
4371        id: '0123',
4372        args: {
4373          dumps: {
4374            process_totals: {
4375              resident_set_bytes: '0'
4376            },
4377            heaps: {
4378              partition_alloc: {
4379                entries: [
4380                  { bt: '' /* root */, size: '5cdf91' },
4381                  { bt: '' /* root */, type: '24', size: '2e6fc8' },
4382                  { bt: '' /* root */, type: '25', size: '1737e4' },
4383                  { bt: '-1', type: '22', size: '5b6cd6' },
4384                  { bt: 'FOUR-AND-A-BIT', size: '18f0' },
4385                  { bt: 'FOUR-AND-A-BIT', type: '26', size: 'c78' },
4386                  { bt: '\u03C0', size: 'e3a8' }
4387                ]
4388              },
4389              malloc: {
4390                entries: [
4391                  { bt: '', size: '789' },
4392                  { bt: '0.5', size: '123' },
4393                  { bt: 'NaN', size: '456' },
4394                  { bt: '3', type: '25', size: 'cd' }
4395                ]
4396              }
4397            }
4398          }
4399        }
4400      },
4401      {
4402        pid: 21,
4403        ph: 'M',
4404        name: 'stackFrames',
4405        args: {
4406          stackFrames: {
4407            // Intentionally in reverse order.
4408            'A': { name: '<self>', parent: '0' },
4409            '0': { name: 'FrameView::layout', parent: '1' },
4410            '1': { name: 'MessageLoop::RunTask' }
4411          }
4412        }
4413      },
4414      {
4415        pid: 21,
4416        ts: 12,
4417        ph: 'v',
4418        id: '0987',
4419        args: {
4420          dumps: {
4421            heaps: {
4422              winheap: {
4423                entries: []  // Intentionally empty.
4424              },
4425              partition_alloc: {
4426                entries: [
4427                  { bt: '', size: '2000' },
4428                  { bt: 'A', type: '25', size: 'def' },
4429                  { bt: '3' /* invalid */, size: 'aaa' },
4430                  { bt: 'A', type: '24' /* invalid */, size: 'bbb' },
4431                  { bt: '0', size: 'fff' }
4432                ]
4433              }
4434            }
4435          }
4436        }
4437      },
4438      {
4439        pid: 21,
4440        ph: 'M',
4441        name: 'typeNames',
4442        args: {
4443          typeNames: {
4444            // Microsoft Visual C++.
4445            '25': 'const char *__cdecl WTF::getStringWithTypeName<class ' +
4446                'v8::FunctionCallbackInfo<class v8::Value>>(void)'
4447          }
4448        }
4449      },
4450      {
4451        pid: 63,
4452        ph: 'M',
4453        name: 'stackFrames',
4454        args: {
4455          stackFrames: {}  // Intentionally empty.
4456        }
4457      },
4458      {
4459        pid: 63,
4460        ph: 'M',
4461        name: 'typeNames',
4462        args: {
4463          typeNames: {}  // Intentionally empty.
4464        }
4465      },
4466      {
4467        pid: 63,
4468        ts: 13,
4469        ph: 'v',
4470        id: '0987',
4471        args: {
4472          dumps: {
4473            heaps: {
4474              winheap: {
4475                entries: [
4476                  { bt: '', size: '10000' }
4477                ]
4478              }
4479            }
4480          }
4481        }
4482      },
4483      {
4484        pid: 84,
4485        ph: 'M',
4486        name: 'stackFrames',
4487        args: {
4488          stackFrames: {
4489            '5': { name: 'MessageLoop::WalkTask' }
4490          }
4491        }
4492      },
4493      {
4494        pid: 84,
4495        ph: 'M',
4496        name: 'typeNames',
4497        args: {
4498          typeNames: {
4499            '0': '[unknown]',
4500            '1': 'base::All',
4501            '3': 'content::Manually',
4502            '4': 'net::Annotated'
4503          }
4504        }
4505      },
4506      {
4507        pid: 84,
4508        ts: 14,
4509        ph: 'v',
4510        id: '0987',
4511        args: {
4512          dumps: {
4513            heaps: {
4514              malloc: {
4515                entries: [
4516                  { bt: '5', type: '3', size: 'abcd' }
4517                ]
4518              }
4519            }
4520          }
4521        }
4522      }
4523    ];
4524    var m = makeModel(events);
4525    var p1 = m.getProcess(21);
4526    var p2 = m.getProcess(42);
4527    var p3 = m.getProcess(63);
4528    var p4 = m.getProcess(84);
4529    assert.lengthOf(m.globalMemoryDumps, 2);
4530    assert.lengthOf(p1.memoryDumps, 2);
4531    assert.lengthOf(p2.memoryDumps, 1);
4532    assert.lengthOf(p3.memoryDumps, 1);
4533    assert.lengthOf(p4.memoryDumps, 1);
4534
4535    // Stack frames.
4536    assert.deepEqual(
4537        tr.b.mapItems(m.stackFrames, function(id, f) { return f.title }),
4538        {
4539          'p21:0': 'FrameView::layout',
4540          'p21:A': '<self>',
4541          'p21:1': 'MessageLoop::RunTask',
4542          'p42:-1': '<self>',
4543          'p42:0': 'MessageLoop::RunTask',
4544          'p42:0.5': '<self>',
4545          'p42:1': 'TimerBase::run',
4546          'p42:TWO': 'ScheduledAction::execute',
4547          'p42:2.72': '<self>',
4548          'p42:3': 'FunctionCall',
4549          'p42:\u03C0': '<self>',
4550          'p42:4': 'UpdateLayoutTree',
4551          'p42:FOUR-AND-A-BIT': '<self>',
4552          'p42:5': 'MessageLoop::JogTask',
4553          'p42:NaN': '<self>',
4554          'p84:5': 'MessageLoop::WalkTask'
4555        });
4556
4557    // 1. Process 21, first dump.
4558    var pmd1 = p1.memoryDumps[0];
4559    var hds1 = pmd1.heapDumps;
4560    assert.sameMembers(Object.keys(hds1), ['partition_alloc']);
4561
4562    var partitionAllocDump1 = hds1['partition_alloc'];
4563    assert.strictEqual(partitionAllocDump1.processMemoryDump, pmd1);
4564    assert.equal(partitionAllocDump1.allocatorName, 'partition_alloc');
4565    var partitionAllocEntries1 = partitionAllocDump1.entries;
4566    assert.lengthOf(partitionAllocEntries1, 2);
4567    checkHeapEntry(partitionAllocEntries1[0], partitionAllocDump1, 4096,
4568        undefined /* root */,
4569        'class v8::FunctionCallbackInfo<class v8::Value>');
4570    checkHeapEntry(partitionAllocEntries1[1], partitionAllocDump1, 2748,
4571        ['<self>', 'FrameView::layout', 'MessageLoop::RunTask'],
4572        undefined /* sum over all types */);
4573
4574    // 2. Process 21, second dump.
4575    var pmd2 = p1.memoryDumps[1];
4576    var hds2 = pmd2.heapDumps;
4577    assert.sameMembers(Object.keys(hds2), ['partition_alloc']);
4578
4579    var partitionAllocDump2 = hds2['partition_alloc'];
4580    assert.strictEqual(partitionAllocDump2.processMemoryDump, pmd2);
4581    assert.equal(partitionAllocDump2.allocatorName, 'partition_alloc');
4582    var partitionAllocEntries2 = partitionAllocDump2.entries;
4583    assert.lengthOf(partitionAllocEntries2, 3);
4584    checkHeapEntry(partitionAllocEntries2[0], partitionAllocDump2, 8192,
4585        undefined /* root */, undefined /* sum over all types */);
4586    checkHeapEntry(partitionAllocEntries2[1], partitionAllocDump2, 3567,
4587        ['<self>', 'FrameView::layout', 'MessageLoop::RunTask'],
4588        'class v8::FunctionCallbackInfo<class v8::Value>');
4589    checkHeapEntry(partitionAllocEntries2[2], partitionAllocDump2, 4095,
4590        ['FrameView::layout', 'MessageLoop::RunTask'],
4591        undefined /* sum over all types */);
4592
4593    // All heap dumps in Process 21 should use the same stack frames.
4594    assert.strictEqual(
4595        getFrame(partitionAllocEntries1[1], 0),
4596        getFrame(partitionAllocEntries2[1], 0));
4597    assert.strictEqual(
4598        getFrame(partitionAllocEntries2[2], 0),
4599        getFrame(partitionAllocEntries2[1], 1));
4600
4601    // 3. Process 42.
4602    var pmd3 = p2.memoryDumps[0];
4603    var hds3 = pmd3.heapDumps;
4604    assert.sameMembers(Object.keys(hds3), ['partition_alloc', 'malloc']);
4605
4606    var partitionAllocDump3 = hds3['partition_alloc'];
4607    assert.strictEqual(partitionAllocDump3.processMemoryDump, pmd3);
4608    assert.equal(partitionAllocDump3.allocatorName, 'partition_alloc');
4609    var partitionAllocEntries3 = partitionAllocDump3.entries;
4610    assert.lengthOf(partitionAllocEntries3, 7);
4611    checkHeapEntry(partitionAllocEntries3[0], partitionAllocDump3, 6086545,
4612        undefined /* root */, undefined /* sum over all types */);
4613    checkHeapEntry(partitionAllocEntries3[1], partitionAllocDump3, 3043272,
4614        undefined /* root */, 'blink::Event');
4615    checkHeapEntry(partitionAllocEntries3[2], partitionAllocDump3, 1521636,
4616        undefined /* root */, 'blink::ContextLifecycleObserver *');
4617    checkHeapEntry(partitionAllocEntries3[3], partitionAllocDump3, 5991638,
4618        ['<self>'], '[unknown]');
4619    checkHeapEntry(partitionAllocEntries3[4], partitionAllocDump3, 6384,
4620        ['<self>', 'UpdateLayoutTree', 'TimerBase::run',
4621            'MessageLoop::RunTask'], undefined /* sum over all types */);
4622    checkHeapEntry(partitionAllocEntries3[5], partitionAllocDump3, 3192,
4623        ['<self>', 'UpdateLayoutTree', 'TimerBase::run',
4624            'MessageLoop::RunTask'], 'blink::WebFrame *');
4625    checkHeapEntry(partitionAllocEntries3[6], partitionAllocDump3, 58280,
4626        ['<self>', 'FunctionCall', 'ScheduledAction::execute', 'TimerBase::run',
4627            'MessageLoop::RunTask'], undefined /* sum over all types */);
4628
4629    var mallocDump3 = hds3['malloc'];
4630    assert.strictEqual(mallocDump3.processMemoryDump, pmd3);
4631    assert.equal(mallocDump3.allocatorName, 'malloc');
4632    var mallocEntries3 = mallocDump3.entries;
4633    assert.lengthOf(mallocEntries3, 4);
4634    checkHeapEntry(mallocEntries3[0], mallocDump3, 1929, undefined /* root */,
4635        undefined /* sum over all types */);
4636    checkHeapEntry(mallocEntries3[1], mallocDump3, 291,
4637        ['<self>', 'MessageLoop::RunTask'], undefined /* sum over all types */);
4638    checkHeapEntry(mallocEntries3[2], mallocDump3, 1110,
4639        ['<self>', 'MessageLoop::JogTask'], undefined /* sum over all types */);
4640    checkHeapEntry(mallocEntries3[3], mallocDump3, 205,
4641        ['FunctionCall', 'ScheduledAction::execute', 'TimerBase::run',
4642            'MessageLoop::RunTask'], 'blink::ContextLifecycleObserver *');
4643
4644    // All heap dumps in Process 42 should use the same stack frames.
4645    assert.strictEqual(
4646        getFrame(partitionAllocEntries3[5], 0),
4647        getFrame(partitionAllocEntries3[4], 0));
4648    assert.strictEqual(
4649        getFrame(partitionAllocEntries3[6], 3),
4650        getFrame(partitionAllocEntries3[4], 2));
4651    assert.strictEqual(
4652        getFrame(mallocEntries3[1], 1),
4653        getFrame(partitionAllocEntries3[4], 3));
4654    assert.strictEqual(
4655        getFrame(mallocEntries3[3], 0),
4656        getFrame(partitionAllocEntries3[6], 1));
4657
4658    // 4. Process 63.
4659    var pmd4 = p3.memoryDumps[0];
4660    var hds4 = pmd4.heapDumps;
4661    assert.sameMembers(Object.keys(hds4), ['winheap']);
4662
4663    var winheapDump = hds4['winheap'];
4664    assert.strictEqual(winheapDump.processMemoryDump, pmd4);
4665    assert.equal(winheapDump.allocatorName, 'winheap');
4666    var winheapEntries = winheapDump.entries;
4667    assert.lengthOf(winheapEntries, 1);
4668    checkHeapEntry(winheapEntries[0], winheapDump, 65536,
4669        undefined /* root */, undefined /* sum over all types */);
4670
4671    // 5. Process 84.
4672    var pmd5 = p4.memoryDumps[0];
4673    var hds5 = pmd5.heapDumps;
4674    assert.sameMembers(Object.keys(hds5), ['malloc']);
4675
4676    var mallocDump4 = hds5['malloc'];
4677    assert.strictEqual(mallocDump4.processMemoryDump, pmd5);
4678    assert.equal(mallocDump4.allocatorName, 'malloc');
4679    var mallocEntries4 = mallocDump4.entries;
4680    assert.lengthOf(mallocEntries4, 1);
4681    checkHeapEntry(mallocEntries4[0], mallocDump4, 43981,
4682        ['MessageLoop::WalkTask'], 'content::Manually');
4683  });
4684
4685  test('importMemoryDumps_composableDumps', function() {
4686    // Split PMD1 over multiple PMD trace events, all of which should be merged
4687    // by the importer. Also inject some PMD trace events with different PIDs
4688    // or dump IDs, which should *not* be merged with the rest.
4689    var events = [
4690      {  // Heap dumps.
4691        pid: 42,
4692        ts: 10000,
4693        ph: 'v',
4694        id: '0x0001',
4695        args: {
4696          dumps: {
4697            level_of_detail: 'light',
4698            heaps: {
4699              partition_alloc: {
4700                entries: [
4701                  { bt: '99', type: '888', size: '500' }
4702                ]
4703              }
4704            }
4705          }
4706        }
4707      },
4708      {  // PMD2 with a different dump id (should not be merged).
4709        pid: 42,
4710        ts: 10003,  // Same PID, so it will end up in the same process.
4711        ph: 'v',
4712        id: '0x0002',
4713        args: {
4714          dumps: {
4715            level_of_detail: 'detailed',
4716            allocators: {
4717              'local1': {
4718                guid: '3',
4719                attrs: {
4720                  A: {type: 'scalar', units: 'bytes', value: '0xBAD'}
4721                }
4722              },
4723              'global/shared1': {
4724                guid: '4',
4725                attrs: {
4726                  A: {type: 'string', units: '', value: 'brown'}
4727                }
4728              }
4729            },
4730            allocators_graph: [
4731              {
4732                source: '4',
4733                target: '3',
4734                type: 'ownership'
4735              }
4736            ]
4737          }
4738        }
4739      },
4740      {  // Stack frames (required for heap dumps).
4741        pid: 42,
4742        ph: 'M',
4743        name: 'stackFrames',
4744        args: {
4745          stackFrames: {
4746            '99': { name: 'MessageLoop::RunTask' }
4747          }
4748        }
4749      },
4750      {  // Allocator dumps.
4751        pid: 42,
4752        ts: 10001,
4753        ph: 'v',
4754        id: '0x0001',
4755        args: {
4756          dumps: {
4757            level_of_detail: 'light',
4758            allocators: {
4759              'local1': {
4760                guid: '3',
4761                attrs: {
4762                  A: {type: 'string', units: '', value: 'blue'}
4763                }
4764              },
4765              'global/shared1': {
4766                guid: '7',
4767                attrs: {
4768                  A: {type: 'string', units: '', value: 'purple'}
4769                }
4770              },
4771              'global/shared2': {
4772                guid: '8',
4773                attrs: {
4774                  A: {type: 'string', units: '', value: 'cyan'}
4775                }
4776              }
4777            }
4778          }
4779        }
4780      },
4781      {  // PMD3 with a different PID (should not be merged).
4782        pid: 68,
4783        ts: 9999,
4784        ph: 'v',
4785        id: '0x0001',  // Same dump ID, so it will end up in the same GMD.
4786        args: {
4787          dumps: {
4788            process_mmaps: {
4789              vm_regions: [
4790                {
4791                  sa: '350',
4792                  sz: '250',
4793                  pf: 5,
4794                  mf: '/dev/ashmem/dalvik',
4795                  bs: {
4796                    pd: 'cd'
4797                  }
4798                }
4799              ]
4800            }
4801          }
4802        }
4803      },
4804      {  // VM regions.
4805        pid: 42,
4806        ts: 10005,
4807        ph: 'v',
4808        id: '0x0001',
4809        args: {
4810          dumps: {
4811            level_of_detail: 'light',
4812            process_mmaps: {
4813              vm_regions: [
4814                {
4815                  sa: 'f0',
4816                  sz: '150',
4817                  pf: 6,
4818                  mf: '[stack:20310]',
4819                  bs: {
4820                    pss: '9e'
4821                  }
4822                }
4823              ]
4824            }
4825          }
4826        }
4827      },
4828      {  // Totals.
4829        pid: 42,
4830        ts: 10003,
4831        ph: 'v',
4832        id: '0x0001',
4833        args: {
4834          dumps: {
4835            level_of_detail: 'light',
4836            process_totals: {
4837              resident_set_bytes: '100',
4838              private_bytes: '80'  // OS-specific total.
4839            }
4840          }
4841        }
4842      },
4843      {  // Allocator dump edges.
4844        pid: 42,
4845        ts: 10004,
4846        ph: 'v',
4847        id: '0x0001',
4848        args: {
4849          dumps: {
4850            level_of_detail: 'light',
4851            allocators_graph: [
4852              {
4853                source: '4',
4854                target: '8',
4855                type: 'retention'
4856              }
4857            ]
4858          }
4859        }
4860      },
4861      {  // Object type names (required for heap dumps).
4862        pid: 42,
4863        ph: 'M',
4864        name: 'typeNames',
4865        args: {
4866          typeNames: {
4867            // GCC.
4868            '888': 'const char* WTF::getStringWithTypeName() [with T = ' +
4869                'cc::SurfaceFactory]'
4870          }
4871        }
4872      },
4873      {  // Allocator dumps and dump edges (should be merged).
4874        pid: 42,
4875        ts: 10002,
4876        ph: 'v',
4877        id: '0x0001',
4878        args: {
4879          dumps: {
4880            level_of_detail: 'light',
4881            allocators: {
4882              'local1': {
4883                guid: '3',
4884                attrs: {
4885                  B: {type: 'string', units: '', value: 'red'}
4886                }
4887              },
4888              'local2': {
4889                guid: '4',
4890                attrs: {
4891                  B: {type: 'string', units: '', value: 'yellow'}
4892                }
4893              },
4894              'global/shared1': {
4895                guid: '7',
4896                attrs: {
4897                  B: {type: 'string', units: '', value: 'green'}
4898                }
4899              }
4900            },
4901            allocators_graph: [
4902              {
4903                source: '3',
4904                target: '7',
4905                type: 'ownership',
4906                importance: 1
4907              }
4908            ]
4909          }
4910        }
4911      }
4912    ];
4913    var m = makeModel(events, false);
4914
4915    function checkDumpTime(dump, expectedStart, expectedDuration) {
4916      assert.closeTo(dump.start, expectedStart / 1000, 1e-5);
4917      assert.closeTo(dump.duration, expectedDuration / 1000, 1e-5);
4918    }
4919
4920    function checkLinkCounts(allocatorDump, expectedHasOwns,
4921        expectedOwnedByCount, expectedRetainsCount, expectedRetainedByCount) {
4922      assert.strictEqual(allocatorDump.owns !== undefined, expectedHasOwns);
4923      assert.lengthOf(allocatorDump.ownedBy, expectedOwnedByCount);
4924      assert.lengthOf(allocatorDump.retains, expectedRetainsCount);
4925      assert.lengthOf(allocatorDump.retainedBy, expectedRetainedByCount);
4926    }
4927
4928    // Check the overall structure of the model and memory dumps.
4929    assert.sameMembers(Object.keys(m.processes), ['42', '68']);
4930    assert.lengthOf(m.globalMemoryDumps, 2);
4931    var gmd1 = m.globalMemoryDumps[0];
4932    assert.strictEqual(gmd1.model, m);
4933    checkDumpTime(gmd1, 9999, 6);
4934    var gmd2 = m.globalMemoryDumps[1];
4935    assert.strictEqual(gmd2.model, m);
4936    checkDumpTime(gmd2, 10003, 0);
4937
4938    var p1 = m.getProcess(42);
4939    assert.lengthOf(p1.memoryDumps, 2);
4940    var pmd1 = p1.memoryDumps[0];
4941    checkDumpTime(pmd1, 10000, 5);
4942    assert.strictEqual(pmd1.globalMemoryDump, gmd1);
4943    assert.strictEqual(pmd1.process, p1);
4944    var pmd2 = p1.memoryDumps[1];
4945    checkDumpTime(pmd2, 10003, 0);
4946    assert.strictEqual(pmd2.globalMemoryDump, gmd2);
4947    assert.strictEqual(pmd2.process, p1);
4948
4949    var p2 = m.getProcess(68);
4950    assert.lengthOf(p2.memoryDumps, 1);
4951    var pmd3 = p2.memoryDumps[0];
4952    checkDumpTime(pmd3, 9999, 0);
4953    assert.strictEqual(pmd3.globalMemoryDump, gmd1);
4954    assert.strictEqual(pmd3.process, p2);
4955
4956    assert.deepEqual(gmd1.processMemoryDumps, {42: pmd1, 68: pmd3});
4957    assert.deepEqual(gmd2.processMemoryDumps, {42: pmd2});
4958
4959    // Check the composed dump.
4960    assert.strictEqual(pmd1.levelOfDetail, 'light');
4961
4962    var totals = pmd1.totals;
4963    assert.strictEqual(totals.residentBytes, 256);
4964    assert.isUndefined(totals.peakResidentBytes);
4965    assert.isUndefined(totals.arePeakResidentBytesResettable);
4966    assert.deepEqual(totals.platformSpecific, {private_bytes: 128});
4967
4968    var vmRegions = pmd1.vmRegions;
4969    checkVMRegions(vmRegions, [
4970      {
4971        mappedFile: '[stack:20310]',
4972        startAddress: 240,
4973        sizeInBytes: 336,
4974        protectionFlags: VMRegion.PROTECTION_FLAG_READ |
4975            VMRegion.PROTECTION_FLAG_WRITE,
4976        byteStats: {
4977          proportionalResident: 158
4978        }
4979      }
4980    ]);
4981
4982    var memoryAllocatorDumps = pmd1.memoryAllocatorDumps;
4983    assert.lengthOf(memoryAllocatorDumps, 2);
4984
4985    var local1Dump = pmd1.getMemoryAllocatorDumpByFullName('local1');
4986    assert.strictEqual(memoryAllocatorDumps[0], local1Dump);
4987    assert.strictEqual(local1Dump.fullName, 'local1');
4988    assert.isUndefined(local1Dump.parent);
4989    assert.lengthOf(local1Dump.children, 0);
4990    checkDumpNumericsAndDiagnostics(local1Dump, {},
4991        { 'A': 'blue', 'B': 'red' });
4992    checkLinkCounts(local1Dump, true /* owns */, 0 /* owned by */,
4993        0 /* retains */, 0 /* retained by */);
4994
4995    var local2Dump = pmd1.getMemoryAllocatorDumpByFullName('local2');
4996    assert.strictEqual(memoryAllocatorDumps[1], local2Dump);
4997    assert.strictEqual(local2Dump.fullName, 'local2');
4998    assert.isUndefined(local2Dump.parent);
4999    assert.lengthOf(local2Dump.children, 0);
5000    checkDumpNumericsAndDiagnostics(local2Dump, {}, { 'B': 'yellow' });
5001    checkLinkCounts(local2Dump, false /* owns */, 0 /* owned by */,
5002        1 /* retains */, 0 /* retained by */);
5003
5004    var heapDumps = pmd1.heapDumps;
5005    assert.sameMembers(Object.keys(heapDumps), ['partition_alloc']);
5006    var heapDump = heapDumps['partition_alloc'];
5007    assert.strictEqual(heapDump.processMemoryDump, pmd1);
5008    assert.strictEqual(heapDump.allocatorName, 'partition_alloc');
5009    var entries = heapDump.entries;
5010    assert.lengthOf(entries, 1);
5011    assert.strictEqual(entries[0].heapDump, heapDump);
5012    assert.strictEqual(entries[0].leafStackFrame.title, 'MessageLoop::RunTask');
5013    assert.strictEqual(entries[0].objectTypeName, 'cc::SurfaceFactory');
5014    assert.strictEqual(entries[0].size, 1280);
5015
5016    // Check the other dumps.
5017    assert.strictEqual(pmd2.levelOfDetail, 'detailed');
5018    assert.isUndefined(pmd2.vmRegions);
5019    assert.lengthOf(pmd2.memoryAllocatorDumps, 1);
5020    var otherLocal1Dump = pmd2.getMemoryAllocatorDumpByFullName('local1');
5021    assert.strictEqual(otherLocal1Dump, pmd2.memoryAllocatorDumps[0]);
5022    assert.strictEqual(otherLocal1Dump.fullName, 'local1');
5023    assert.isUndefined(otherLocal1Dump.parent);
5024    checkDumpNumericsAndDiagnostics(otherLocal1Dump, { 'A': 2989 }, {});
5025    assert.isUndefined(pmd2.heapDumps);
5026    checkLinkCounts(otherLocal1Dump, false /* owns */, 1 /* owned by */,
5027        0 /* retains */, 0 /* retained by */);
5028
5029    assert.isUndefined(pmd3.levelOfDetail);
5030    var otherVmRegions = pmd3.vmRegions;
5031    checkVMRegions(otherVmRegions, [
5032      {
5033        mappedFile: '/dev/ashmem/dalvik',
5034        startAddress: 848,
5035        sizeInBytes: 592,
5036        protectionFlags: VMRegion.PROTECTION_FLAG_READ |
5037            VMRegion.PROTECTION_FLAG_EXECUTE,
5038        byteStats: {
5039          privateDirtyResident: 205
5040        }
5041      }
5042    ]);
5043    assert.lengthOf(pmd3.memoryAllocatorDumps, 0);
5044    assert.isUndefined(pmd3.heapDumps);
5045
5046    // Check the global dumps.
5047    assert.lengthOf(gmd1.memoryAllocatorDumps, 2);
5048    var shared1Dump = gmd1.getMemoryAllocatorDumpByFullName('shared1');
5049    assert.strictEqual(shared1Dump, gmd1.memoryAllocatorDumps[0]);
5050    assert.strictEqual(shared1Dump.fullName, 'shared1');
5051    assert.isUndefined(shared1Dump.parent);
5052    checkDumpNumericsAndDiagnostics(shared1Dump, {},
5053        { 'A': 'purple', 'B': 'green' });
5054    checkLinkCounts(shared1Dump, false /* owns */, 1 /* owned by */,
5055        0 /* retains */, 0 /* retained by */);
5056    var shared2Dump = gmd1.getMemoryAllocatorDumpByFullName('shared2');
5057    assert.strictEqual(shared2Dump, gmd1.memoryAllocatorDumps[1]);
5058    assert.strictEqual(shared2Dump.fullName, 'shared2');
5059    assert.isUndefined(shared2Dump.parent);
5060    checkDumpNumericsAndDiagnostics(shared2Dump, {}, { 'A': 'cyan' });
5061    checkLinkCounts(shared2Dump, false /* owns */, 0 /* owned by */,
5062        0 /* retains */, 1 /* retained by */);
5063
5064    assert.lengthOf(gmd2.memoryAllocatorDumps, 1);
5065    var otherShared1Dump = gmd2.getMemoryAllocatorDumpByFullName('shared1');
5066    assert.strictEqual(otherShared1Dump, gmd2.memoryAllocatorDumps[0]);
5067    assert.strictEqual(otherShared1Dump.fullName, 'shared1');
5068    assert.isUndefined(otherShared1Dump.parent);
5069    checkDumpNumericsAndDiagnostics(otherShared1Dump, {}, { 'A': 'brown' });
5070    checkLinkCounts(otherShared1Dump, true /* owns */, 0 /* owned by */,
5071        0 /* retains */, 0 /* retained by */);
5072
5073    // Check the edges.
5074    var ownershipLink = local1Dump.owns;
5075    assert.strictEqual(shared1Dump.ownedBy[0], ownershipLink);
5076    assert.strictEqual(ownershipLink.source, local1Dump);
5077    assert.strictEqual(ownershipLink.target, shared1Dump);
5078    assert.strictEqual(ownershipLink.importance, 1);
5079
5080    var retentionLink = local2Dump.retains[0];
5081    assert.strictEqual(shared2Dump.retainedBy[0], retentionLink);
5082    assert.strictEqual(retentionLink.source, local2Dump);
5083    assert.strictEqual(retentionLink.target, shared2Dump);
5084    assert.isUndefined(retentionLink.importance);
5085
5086    var otherOwnershipLink = otherShared1Dump.owns;
5087    assert.strictEqual(otherLocal1Dump.ownedBy[0], otherOwnershipLink);
5088    assert.strictEqual(otherOwnershipLink.source, otherShared1Dump);
5089    assert.strictEqual(otherOwnershipLink.target, otherLocal1Dump);
5090    assert.isUndefined(otherOwnershipLink.importance);
5091  });
5092
5093  test('importThreadInstantSliceWithStackFrame', function() {
5094    var eventData = {
5095      traceEvents: [
5096        { name: 'a', args: {}, pid: 1, ts: 0, cat: 'baz', tid: 2, ph: 'I', s: 't', sf: 7 } // @suppress longLineCheck
5097      ],
5098      stackFrames: {
5099        '1': {
5100          category: 'm1',
5101          name: 'main'
5102        },
5103        '7': {
5104          category: 'm2',
5105          name: 'frame7',
5106          parent: '1'
5107        }
5108      }
5109    };
5110
5111    var m = makeModel(eventData);
5112
5113    var p = m.processes[1];
5114    var t = p.threads[2];
5115    assert.isDefined(t);
5116    assert.equal(t.sliceGroup.slices.length, 1);
5117
5118    var s0 = t.sliceGroup.slices[0];
5119    assert.equal(s0.startStackFrame.title, 'frame7');
5120    assert.isUndefined(s0.endStackFrame);
5121  });
5122
5123  test('importDurationEventsWithStackFrames', function() {
5124    var eventData = {
5125      traceEvents: [
5126        { name: 'a', args: {}, pid: 1, ts: 0, cat: 'baz', tid: 2, ph: 'B', sf: 7 }, // @suppress longLineCheck
5127        { name: 'b', args: {}, pid: 1, ts: 5, cat: 'baz', tid: 2, ph: 'E', sf: 8 } // @suppress longLineCheck
5128      ],
5129      stackFrames: {
5130        '1': {
5131          category: 'm1',
5132          name: 'main'
5133        },
5134        '7': {
5135          category: 'm2',
5136          name: 'frame7',
5137          parent: '1'
5138        },
5139        '8': {
5140          category: 'm2',
5141          name: 'frame8',
5142          parent: '1'
5143        }
5144      }
5145    };
5146
5147    var m = makeModel(eventData);
5148
5149    var p = m.processes[1];
5150    var t = p.threads[2];
5151    assert.isDefined(t);
5152    assert.equal(t.sliceGroup.slices.length, 1);
5153
5154    var s0 = t.sliceGroup.slices[0];
5155    assert.equal(s0.startStackFrame.title, 'frame7');
5156    assert.equal(s0.endStackFrame.title, 'frame8');
5157  });
5158
5159  test('annotationParsing', function() {
5160    var yComponents1 = [{stableId: '52.53', yPercentOffset: 0.5},
5161      {stableId: '52', yPercentOffset: 0.3}];
5162    var yComponents2 = [{stableId: '52.53', yPercentOffset: 0.7},
5163      {stableId: '52', yPercentOffset: 0.4}];
5164    var location1 = new tr.model.Location(0.1, yComponents1);
5165    var location2 = new tr.model.Location(0.2, yComponents2);
5166
5167    var eventData = { traceEvents: [
5168      {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'}],
5169      traceAnnotations: [
5170        {typeName: 'xmarker', args: {timestamp: 12}},
5171        {typeName: 'rect', args: {
5172          start: location1.toDict(), end: location2.toDict()}},
5173        {typeName: 'comment_box', args: {text: 'test',
5174          location: location1.toDict()}}
5175      ]};
5176
5177    var m = makeModel(eventData);
5178    var annotations = m.getAllAnnotations();
5179    assert.equal(annotations.length, 3);
5180
5181    assert.isTrue(annotations[0] instanceof tr.model.XMarkerAnnotation);
5182    assert.equal(annotations[0].timestamp, 12);
5183
5184    assert.isTrue(annotations[1] instanceof tr.model.RectAnnotation);
5185    assert.deepEqual(annotations[1].startLocation, location1);
5186    assert.deepEqual(annotations[1].endLocation, location2);
5187
5188    assert.isTrue(
5189        annotations[2] instanceof tr.model.CommentBoxAnnotation);
5190    assert.equal(annotations[2].text, 'test');
5191    assert.deepEqual(annotations[2].location, location1);
5192  });
5193
5194  test('importDisplayTimeUnit', function() {
5195    var eventData = {
5196      traceEvents: [],
5197      displayTimeUnit: 'ns'
5198    };
5199    var m = makeModel(JSON.stringify(eventData));
5200    assert.equal(m.intrinsicTimeUnit, tr.v.TimeDisplayModes.ns);
5201  });
5202
5203  test('extractBattorSubTraces', function() {
5204    var battorLog = '# BattOr\n# voltage range [0.000000, 7196.484161] mV\n' +
5205        '#current range [11.898481, 2110.900916] mA\n' +
5206        '# sample_rate=10000Hz, gain=30.257143x\n' +
5207        '# filpot_pos=3, amppot_pos=35, timer_ovf=399, timer_div=4 ovs_bits=0\n' + // @suppress longLineCheck
5208        '0.0 1040.3 3984.2\n' +
5209        '0.1 1081.3 3987.8\n' +
5210        '0.2 1092.6 3987.8\n' +
5211        '0.3 1070.0 3987.8\n' +
5212        '0.4 1017.7 3994.8\n';
5213
5214    var eventData = {
5215      traceEvents: [
5216        { name: 'a', args: {}, pid: 1, ts: 0, cat: 'baz', tid: 2, ph: 'B', sf: 7 }, // @suppress longLineCheck
5217        { name: 'b', args: {}, pid: 1, ts: 5, cat: 'baz', tid: 2, ph: 'E', sf: 8 } // @suppress longLineCheck
5218      ],
5219      powerTraceAsString: battorLog
5220    };
5221
5222    var m = makeModel(eventData);
5223    var importer = new tr.e.importer.TraceEventImporter(m, eventData);
5224    var subTraces = importer.extractSubtraces();
5225    assert.isTrue(subTraces instanceof Array);
5226    assert.equal(subTraces.length, 1);
5227    assert.equal(subTraces[0], battorLog);
5228  });
5229
5230  test('metadataParsing', function() {
5231    var metadataValue = {value: {}};
5232    var eventData = {
5233      traceEvents: [
5234        { name: 'a', args: {}, pid: 1, ts: 0, cat: 'baz', tid: 2, ph: 'B', sf: 7 }, // @suppress longLineCheck
5235        { name: 'b', args: {}, pid: 1, ts: 5, cat: 'baz', tid: 2, ph: 'E', sf: 8 } // @suppress longLineCheck
5236      ],
5237      metadata: metadataValue
5238    };
5239
5240    var m = makeModel(eventData);
5241    assert.isTrue(m.metadata instanceof Array);
5242    assert.equal(m.metadata.length, 1);
5243    assert.equal(m.metadata[0].name, 'metadata');
5244    assert.equal(m.metadata[0].value, metadataValue);
5245  });
5246
5247  test('importMarks', function() {
5248    var eventData = {
5249      traceEvents: [
5250        { name: 'mark1', args: {}, pid: 1, tid: 2, ts: 0, tts: 0, cat: 'blink.user_timing', ph: 'R' }, // @suppress longLineCheck
5251        { name: 'mark2', args: {}, pid: 1, tid: 2, ts: 10, tts: 10, cat: 'blink.user_timing', ph: 'R' } // @suppress longLineCheck
5252      ]
5253    };
5254
5255    var m = makeModel(eventData);
5256
5257    var p = m.processes[1];
5258    var t = p.threads[2];
5259    assert.lengthOf(t.sliceGroup.slices, 2);
5260
5261    var s0 = t.sliceGroup.slices[0];
5262    assert.equal(s0.title, 'mark1');
5263    assert.equal(s0.start, 0);
5264
5265    var s1 = t.sliceGroup.slices[1];
5266    assert.equal(s1.title, 'mark2');
5267    assert.equal(s1.start, .01);
5268  });
5269
5270  test('createNestableAsyncSlicesForUserTimingWithoutArgs', function() {
5271    /**
5272     * Structure of this async slices
5273     *
5274     * Group A:
5275     *
5276     * |__________|
5277     *      a1
5278     *
5279     * Group B:
5280     *
5281     *                                |______________________________|
5282     *                                               b1
5283     *                                           |__________||_|
5284     *                                                b2      b4
5285     *                                            |_|
5286     *                                             b3
5287     **/
5288    var events = [
5289      {
5290        name: 'A:a1',
5291        args: {params: ''},
5292        pid: 1,
5293        ts: 100,
5294        cat: 'blink.user_timing',
5295        tid: 2,
5296        id: 3,
5297        ph: 'b'
5298      },
5299      {
5300        name: 'A:a1',
5301        args: {params: ''},
5302        pid: 1,
5303        ts: 110,
5304        cat: 'blink.user_timing',
5305        tid: 2,
5306        id: 3,
5307        ph: 'e'
5308      },
5309      {
5310        name: 'B:b1',
5311        args: {params: ''},
5312        pid: 1,
5313        ts: 120,
5314        cat: 'blink.user_timing',
5315        tid: 2,
5316        id: 4,
5317        ph: 'b'
5318      },
5319      {
5320        name: 'B:b2',
5321        args: {params: ''},
5322        pid: 1,
5323        ts: 130,
5324        cat: 'blink.user_timing',
5325        tid: 2,
5326        id: 5,
5327        ph: 'b'
5328      },
5329      {
5330        name: 'B:b3',
5331        args: {params: ''},
5332        pid: 1,
5333        ts: 131,
5334        cat: 'blink.user_timing',
5335        tid: 2,
5336        id: 5,
5337        ph: 'b'
5338      },
5339      {
5340        name: 'B:b3',
5341        args: {params: ''},
5342        pid: 1,
5343        ts: 132,
5344        cat: 'blink.user_timing',
5345        tid: 2,
5346        id: 5,
5347        ph: 'e'
5348      },
5349      {
5350        name: 'B:b2',
5351        args: {params: ''},
5352        pid: 1,
5353        ts: 140,
5354        cat: 'blink.user_timing',
5355        tid: 2,
5356        id: 5,
5357        ph: 'e'
5358      },
5359      {
5360        name: 'B:b4',
5361        args: {params: ''},
5362        pid: 1,
5363        ts: 141,
5364        cat: 'blink.user_timing',
5365        tid: 2,
5366        id: 5,
5367        ph: 'b'
5368      },
5369      {
5370        name: 'B:b4',
5371        args: {params: ''},
5372        pid: 1,
5373        ts: 142,
5374        cat: 'blink.user_timing',
5375        tid: 2,
5376        id: 5,
5377        ph: 'e'
5378      },
5379      {
5380        name: 'B:b1',
5381        args: {params: ''},
5382        pid: 1,
5383        ts: 150,
5384        cat: 'blink.user_timing',
5385        tid: 2,
5386        id: 4,
5387        ph: 'e'
5388      }
5389    ];
5390
5391    var m = makeModel(events);
5392    assert(m.numProcesses, 1);
5393    var p = m.processes[1];
5394    assert.isDefined(p);
5395    assert.equal(p.numThreads, 1);
5396    var t = p.threads[2];
5397    var asyncSliceGroup = t.asyncSliceGroup;
5398    assert.equal(asyncSliceGroup.length, 2);
5399    for (var i = 0; i < asyncSliceGroup.length; ++i) {
5400      assert.isTrue(asyncSliceGroup.slices[i] instanceof MeasureAsyncSlice);
5401    }
5402
5403    var groupA = asyncSliceGroup.slices[0];
5404    assert.equal(groupA.viewSubGroupTitle, 'A');
5405    assert.equal(groupA.title, 'a1');
5406    assert.equal(groupA.subSlices.length, 0);
5407    var groupB = asyncSliceGroup.slices[1];
5408    assert.equal(groupB.viewSubGroupTitle, 'B');
5409    assert.equal(groupB.title, 'b1');
5410    assert.equal(groupB.subSlices.length, 2);
5411    var groupBSubSlice1 = groupB.subSlices[0];
5412    assert.equal(groupBSubSlice1.viewSubGroupTitle, 'B');
5413    assert.equal(groupBSubSlice1.title, 'b2');
5414    assert.equal(groupBSubSlice1.subSlices.length, 1);
5415    assert.equal(groupBSubSlice1.subSlices[0].viewSubGroupTitle, 'B');
5416    assert.equal(groupBSubSlice1.subSlices[0].title, 'b3');
5417    assert.equal(groupBSubSlice1.subSlices[0].subSlices.length, 0);
5418    var groupBSubSlice2 = groupB.subSlices[1];
5419    assert.equal(groupBSubSlice2.viewSubGroupTitle, 'B');
5420    assert.equal(groupBSubSlice2.title, 'b4');
5421    assert.equal(groupBSubSlice2.subSlices.length, 0);
5422  });
5423
5424  test('createNestableAsyncSlicesForUserTimingWithArgs', function() {
5425    /**
5426     * Structure of this async slices
5427     *
5428     * Group A:
5429     *
5430     * |__________|          |__________|
5431     *      a1                    a2
5432     *
5433     * a1.args = {a: 1}
5434     * a2.args = {a: 2, b: 2}
5435     **/
5436    var events = [
5437      {
5438        name: 'A:a1/eyJhIjoxfQ==',
5439        args: {params: ''},
5440        pid: 1,
5441        ts: 100,
5442        cat: 'blink.user_timing',
5443        tid: 2,
5444        id: 3,
5445        ph: 'b'
5446      },
5447      {
5448        name: 'A:a1/eyJhIjoxfQ==',
5449        args: {params: ''},
5450        pid: 1,
5451        ts: 110,
5452        cat: 'blink.user_timing',
5453        tid: 2,
5454        id: 3,
5455        ph: 'e'
5456      },
5457      {
5458        name: 'A:a2/eyJhIjoyLCJiIjoyfQ==',
5459        args: {params: ''},
5460        pid: 1,
5461        ts: 120,
5462        cat: 'blink.user_timing',
5463        tid: 2,
5464        id: 4,
5465        ph: 'b'
5466      },
5467      {
5468        name: 'A:a2/eyJhIjoyLCJiIjoyfQ==',
5469        args: {params: ''},
5470        pid: 1,
5471        ts: 130,
5472        cat: 'blink.user_timing',
5473        tid: 2,
5474        id: 4,
5475        ph: 'e'
5476      }
5477    ];
5478
5479    var m = makeModel(events);
5480    assert(m.numProcesses, 1);
5481    var p = m.processes[1];
5482    assert.isDefined(p);
5483    assert.equal(p.numThreads, 1);
5484    var t = p.threads[2];
5485    var asyncSliceGroup = t.asyncSliceGroup;
5486    assert.equal(asyncSliceGroup.length, 2);
5487    for (var i = 0; i < asyncSliceGroup.length; ++i) {
5488      assert.isTrue(asyncSliceGroup.slices[i] instanceof MeasureAsyncSlice);
5489    }
5490
5491    var a1 = asyncSliceGroup.slices[0];
5492    assert.equal(a1.viewSubGroupTitle, 'A');
5493    assert.equal(a1.title, 'a1');
5494    assert.equal(a1.subSlices.length, 0);
5495    assert.deepEqual(a1.args, {a: 1});
5496    var a2 = asyncSliceGroup.slices[1];
5497    assert.equal(a2.viewSubGroupTitle, 'A');
5498    assert.equal(a2.title, 'a2');
5499    assert.equal(a2.subSlices.length, 0);
5500    assert.deepEqual(a2.args, {a: 2, b: 2});
5501  });
5502
5503  test('UserTimingAsyncSlicesWithNormalAsyncSlices', function() {
5504    /**
5505     * Structure of user timing async slices
5506     *
5507     * Group A:
5508     *
5509     * |__________|
5510     *      a1
5511     *  |__|
5512     *   a2
5513     *
5514     * B
5515     *         |__|
5516     *          B
5517     * C
5518     *             |_|
5519     *              C
5520     **/
5521    var events = [
5522      {
5523        name: 'A:a1', args: {params: ''}, pid: 1, ts: 1,
5524        cat: 'blink.user_timing', tid: 2, id: 3, ph: 'b'
5525      },
5526      {
5527        name: 'A:a1', args: {params: ''}, pid: 1, ts: 11,
5528        cat: 'blink.user_timing', tid: 2, id: 3, ph: 'e'
5529      },
5530      {
5531        name: 'A:a2', args: {params: ''}, pid: 1, ts: 2,
5532        cat: 'blink.user_timing', tid: 2, id: 4, ph: 'b'
5533      },
5534      {
5535        name: 'A:a2', args: {params: ''}, pid: 1, ts: 4,
5536        cat: 'blink.user_timing', tid: 2, id: 4, ph: 'e'
5537      },
5538      {
5539        name: 'B', args: {}, pid: 1, ts: 9, cat: 'foo',
5540        tid: 2, ph: 'b', id: 5
5541      },
5542      {
5543        name: 'B', args: {}, pid: 1, ts: 11, cat: 'foo',
5544        tid: 2, ph: 'e', id: 5
5545      },
5546      {
5547        name: 'C', args: {}, pid: 1, ts: 12, cat: 'foo',
5548        tid: 2, ph: 'b', id: 6
5549      },
5550      {
5551        name: 'C', args: {}, pid: 1, ts: 13, cat: 'foo',
5552        tid: 2, ph: 'e', id: 6
5553      }
5554    ];
5555
5556    var m = makeModel(events);
5557    assert(m.numProcesses, 1);
5558    var p = m.processes[1];
5559    assert.isDefined(p);
5560    assert.equal(p.numThreads, 1);
5561    var t = p.threads[2];
5562    var asyncSliceGroup = t.asyncSliceGroup;
5563    assert.equal(asyncSliceGroup.length, 3);
5564    assert.isTrue(asyncSliceGroup.slices[0] instanceof MeasureAsyncSlice);
5565    assert.isFalse(asyncSliceGroup.slices[1] instanceof MeasureAsyncSlice);
5566    assert.isFalse(asyncSliceGroup.slices[2] instanceof MeasureAsyncSlice);
5567
5568    var a1 = asyncSliceGroup.slices[0];
5569    assert.equal(a1.viewSubGroupTitle, 'A');
5570    assert.equal(a1.title, 'a1');
5571    assert.equal(a1.subSlices.length, 1);
5572    var a2 = a1.subSlices[0];
5573    assert.equal(a2.viewSubGroupTitle, 'A');
5574    assert.equal(a2.title, 'a2');
5575    assert.equal(a2.subSlices.length, 0);
5576    var B = asyncSliceGroup.slices[1];
5577    assert.equal(B.viewSubGroupTitle, 'B');
5578    assert.equal(B.title, 'B');
5579    assert.equal(B.subSlices.length, 0);
5580    var C = asyncSliceGroup.slices[2];
5581    assert.equal(C.viewSubGroupTitle, 'C');
5582    assert.equal(C.title, 'C');
5583    assert.equal(C.subSlices.length, 0);
5584  });
5585
5586  test('clockSync', function() {
5587    var events = [{
5588      name: 'clock_sync', args: {sync_id: 'abc', issue_ts: 5},
5589      pid: 1, ts: 15, cat: '__metadata', tid: 2, ph: 'c'
5590    }];
5591
5592    var m = makeModel(events);
5593
5594    var foundClockSync = false;
5595    for (var i = 0; i < m.clockSyncRecords.length; i++) {
5596      var clockSync = m.clockSyncRecords[i];
5597      if (clockSync.syncId !== 'abc')
5598        continue;
5599
5600      foundClockSync = true;
5601      assert.equal(clockSync.start, .005);
5602      assert.equal(clockSync.duration, .010);
5603    }
5604
5605    assert.isTrue(foundClockSync);
5606    assert.isFalse(m.hasImportWarnings);
5607  });
5608
5609  test('clockSync_missingSyncId', function() {
5610    var events = [{
5611      name: 'clock_sync', args: {issue_ts: 5},
5612      pid: 1, ts: 15, cat: '__metadata', tid: 2, ph: 'c'
5613    }];
5614
5615    var m = makeModel(events);
5616    assert.isTrue(m.hasImportWarnings);
5617  });
5618
5619  test('clockSync_missingStartTs', function() {
5620    var events = [{
5621      name: 'clock_sync', args: {sync_id: 'abc'},
5622      pid: 1, ts: 15, cat: '__metadata', tid: 2, ph: 'c'
5623    }];
5624
5625    var m = makeModel(events);
5626
5627    var foundClockSync = false;
5628    for (var i = 0; i < m.clockSyncRecords.length; i++) {
5629      var clockSync = m.clockSyncRecords[i];
5630      if (clockSync.syncId !== 'abc')
5631        continue;
5632
5633      foundClockSync = true;
5634    }
5635
5636    assert.isFalse(foundClockSync);
5637    assert.isTrue(m.hasImportWarnings);
5638  });
5639
5640  // TODO(nduca): one slice, two threads
5641  // TODO(nduca): one slice, two pids
5642
5643});
5644</script>
5645