1<!DOCTYPE html>
2<!--
3Copyright (c) 2015 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/model/memory_allocator_dump.html">
9<link rel="import" href="/tracing/ui/tracks/chart_axis.html">
10<link rel="import" href="/tracing/ui/tracks/chart_point.html">
11<link rel="import" href="/tracing/ui/tracks/chart_series.html">
12<link rel="import" href="/tracing/ui/tracks/chart_track.html">
13<link rel="import" href="/tracing/ui/tracks/container_track.html">
14<link rel="import" href="/tracing/ui/tracks/letter_dot_track.html">
15
16<script>
17'use strict';
18
19tr.exportTo('tr.ui.tracks', function() {
20  var ColorScheme = tr.b.ColorScheme;
21
22  var DISPLAYED_SIZE_NUMERIC_NAME =
23      tr.model.MemoryAllocatorDump.DISPLAYED_SIZE_NUMERIC_NAME;
24
25  /**
26   * Add numeric values from a source dictionary to the numeric values in
27   * a destination dictionary. Undefined values are treated as zeros. Note that
28   * this method modifies the destination dictionary in place.
29   *
30   * Example: addDictionary({a: 1, b: 2}, {b: 3, c: 4}) will update the
31   * destination dictionary (first argument) to {a: 1, b: 5, c: 4}.
32   */
33  function addDictionary(dstDict, srcDict) {
34    tr.b.iterItems(srcDict, function(key, value) {
35      var existingValue = dstDict[key];
36      if (existingValue === undefined)
37        existingValue = 0;
38      dstDict[key] = existingValue + value;
39    });
40  }
41
42  /**
43   * Get a dictionary mapping root allocator names (e.g. 'v8') to the
44   * corresponding sizes (e.g. 1024) in a process memory dump.
45   */
46  function getProcessMemoryDumpAllocatorSizes(processMemoryDump) {
47    var allocatorDumps = processMemoryDump.memoryAllocatorDumps;
48    if (allocatorDumps === undefined)
49      return {};
50    var allocatorSizes = {};
51    allocatorDumps.forEach(function(allocatorDump) {
52      // Don't show tracing overhead in the charts.
53      // TODO(petrcermak): Find a less hacky way to do this.
54      if (allocatorDump.fullName === 'tracing')
55        return;
56      var allocatorSize = allocatorDump.numerics[DISPLAYED_SIZE_NUMERIC_NAME];
57      if (allocatorSize === undefined)
58        return;
59      var allocatorSizeValue = allocatorSize.value;
60      if (allocatorSizeValue === undefined)
61        return;
62      allocatorSizes[allocatorDump.fullName] = allocatorSizeValue;
63    });
64    return allocatorSizes;
65  };
66
67  /**
68   * Get a dictionary mapping root allocator names (e.g. 'v8') to the
69   * corresponding sizes (e.g. 1024) in a global memory dump (i.e. summed over
70   * all simultaneous process memory dumps).
71   */
72  function getGlobalMemoryDumpAllocatorSizes(globalMemoryDump) {
73    var globalAllocatorSizes = {};
74    tr.b.iterItems(globalMemoryDump.processMemoryDumps,
75        function(pid, processMemoryDump) {
76      addDictionary(globalAllocatorSizes,
77          getProcessMemoryDumpAllocatorSizes(processMemoryDump));
78    });
79    return globalAllocatorSizes;
80  }
81
82  /**
83   * A generic function which converts a list of memory dumps to a list of chart
84   * series (one per root allocator). Each series represents the evolution of
85   * the size of a the corresponding root allocator (e.g. 'v8') over time.
86   */
87  function buildAllocatedMemoryChartSeries(memoryDumps,
88      memoryDumpToAllocatorSizesFn) {
89    var allocatorNameToPoints = {};
90    var dumpsData = memoryDumps.map(function(memoryDump) {
91      var allocatorSizes = memoryDumpToAllocatorSizesFn(memoryDump);
92      tr.b.iterItems(allocatorSizes, function(allocatorName) {
93        allocatorNameToPoints[allocatorName] = [];
94      });
95      return {dump: memoryDump, sizes: allocatorSizes};
96    });
97
98    // Do not generate any chart series if no process memory dump contains any
99    // allocator dumps.
100    if (Object.keys(allocatorNameToPoints).length === 0)
101      return undefined;
102
103    dumpsData.forEach(function(dumpData) {
104      var memoryDump = dumpData.dump;
105      var allocatorSizes = dumpData.sizes;
106      tr.b.iterItems(allocatorNameToPoints, function(allocatorName, points) {
107        var allocatorSize = allocatorSizes[allocatorName] || 0;
108        points.push(new tr.ui.tracks.ChartPoint(
109            memoryDump, memoryDump.start, allocatorSize));
110      });
111    });
112
113    // Create one common axis for all allocated memory chart series.
114    var axis = new tr.ui.tracks.ChartAxis(0);
115
116    // Build a chart series for each allocator.
117    var series = [];
118    tr.b.iterItems(allocatorNameToPoints, function(allocatorName, points) {
119      var colorId = ColorScheme.getColorIdForGeneralPurposeString(
120          allocatorName);
121      var renderingConfig = {
122        chartType: tr.ui.tracks.ChartSeriesType.LINE,
123        colorId: colorId
124      };
125      series.push(new tr.ui.tracks.ChartSeries(points, axis, renderingConfig));
126    });
127
128    return series;
129  }
130
131  /**
132   * Transform a list of memory dumps to a list of letter dots (with letter 'M'
133   * inside).
134   */
135  function buildMemoryLetterDots(memoryDumps) {
136    var lightMemoryColorId =
137        ColorScheme.getColorIdForReservedName('light_memory_dump');
138    var detailedMemoryColorId =
139        ColorScheme.getColorIdForReservedName('detailed_memory_dump');
140    return memoryDumps.map(function(memoryDump) {
141      var memoryColorId;
142      switch (memoryDump.levelOfDetail) {
143        case 'detailed':
144          memoryColorId = detailedMemoryColorId;
145          break;
146        case 'light':
147        default:
148          memoryColorId = lightMemoryColorId;
149      }
150      return new tr.ui.tracks.LetterDot(
151          memoryDump, 'M', memoryColorId, memoryDump.start);
152    });
153  }
154
155  /**
156   * Convert a list of global memory dumps to a list of chart series (one per
157   * process). Each series represents the evolution of the memory used by the
158   * process over time.
159   */
160  function buildGlobalUsedMemoryChartSeries(globalMemoryDumps) {
161    // Do not generate the chart if no process memory dump contains VM regions.
162    var containsVmRegions = globalMemoryDumps.some(function(globalDump) {
163      for (var pid in globalDump.processMemoryDumps)
164        if (globalDump.processMemoryDumps[pid].mostRecentVmRegions)
165          return true;
166      return false;
167    });
168    if (!containsVmRegions)
169      return undefined;
170
171    // Find all processes that dump memory at least once.
172    var pidToProcess = {};
173    globalMemoryDumps.forEach(function(globalDump) {
174      tr.b.iterItems(globalDump.processMemoryDumps, function(pid, processDump) {
175        pidToProcess[pid] = processDump.process;
176      });
177    });
178
179    // Build one list of points for each instrumented process.
180    var pidToPoints = {};
181    tr.b.iterItems(pidToProcess, function(pid, process) {
182      pidToPoints[pid] = [];
183    });
184
185    // For every timestamp, calculate the total PSS (proportional set size) of
186    // each process and append it to the corresponding list of points.
187    globalMemoryDumps.forEach(function(globalDump) {
188      var pssBase = 0;
189      tr.b.iterItems(pidToPoints, function(pid, points) {
190        var processMemoryDump = globalDump.processMemoryDumps[pid];
191        var cumulativePss = pssBase;
192        // If no dump was found (probably dead) or it does not provide the
193        // necessary information (namely most recent VM regions), assume zero.
194        if (processMemoryDump !== undefined) {
195          var vmRegions = processMemoryDump.mostRecentVmRegions;
196          if (vmRegions !== undefined)
197            cumulativePss += vmRegions.byteStats.proportionalResident || 0;
198        }
199        points.push(new tr.ui.tracks.ChartPoint(
200            globalDump, globalDump.start, cumulativePss, pssBase));
201        pssBase = cumulativePss;
202      });
203    });
204
205    // Create one common axis for all used memory chart series.
206    var axis = new tr.ui.tracks.ChartAxis(0);
207
208    // Build a chart series for each instrumented process.
209    var series = [];
210    tr.b.iterItems(pidToPoints, function(pid, points) {
211      var process = pidToProcess[pid];
212      var colorId = ColorScheme.getColorIdForGeneralPurposeString(
213          process.userFriendlyName);
214      var renderingConfig = {
215        chartType: tr.ui.tracks.ChartSeriesType.AREA,
216        colorId: colorId,
217        backgroundOpacity: 0.8
218      };
219      series.push(new tr.ui.tracks.ChartSeries(points, axis, renderingConfig));
220    });
221
222    // Show the first series (with the smallest cumulative value) at the top.
223    series.reverse();
224
225    return series;
226  }
227
228  /**
229   * Convert a list of process memory dumps to a list of chart series (one per
230   * root allocator). Each series represents the evolution of the size of a the
231   * corresponding root allocator (e.g. 'v8') over time.
232   */
233  function buildProcessAllocatedMemoryChartSeries(processMemoryDumps) {
234    return buildAllocatedMemoryChartSeries(processMemoryDumps,
235        getProcessMemoryDumpAllocatorSizes);
236  }
237
238  /**
239   * Convert a list of global memory dumps to a list of chart series (one per
240   * root allocator). Each series represents the evolution of the size of a the
241   * corresponding root allocator (e.g. 'v8') over time.
242   */
243  function buildGlobalAllocatedMemoryChartSeries(globalMemoryDumps) {
244    return buildAllocatedMemoryChartSeries(globalMemoryDumps,
245        getGlobalMemoryDumpAllocatorSizes);
246  }
247
248  return {
249    buildMemoryLetterDots:
250        buildMemoryLetterDots,
251    buildGlobalUsedMemoryChartSeries:
252        buildGlobalUsedMemoryChartSeries,
253    buildProcessAllocatedMemoryChartSeries:
254        buildProcessAllocatedMemoryChartSeries,
255    buildGlobalAllocatedMemoryChartSeries:
256        buildGlobalAllocatedMemoryChartSeries
257  };
258});
259</script>
260