profiler.js revision 0529e5d033099cbfc42635f6f6183833b09dff6e
1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// This module handles the UI for the profiling. A memory profile is obtained
6// asking the webservice to profile some data sources (through /profile/create)
7// as, for instance, some mmaps dumps or some heap traces.
8// Regardless of the data source, a profile consists of three main concepts:
9// 1. A tree of buckets, each one having a name and a set of 1+ values (see
10//    /classification/rules.py,results.py), one value per metric (see below).
11// 2. A set of snapshots, identifying the times when the dump was taken. All the
12//    snapshots have the same shape and the same nodes (but their values can
13//    obviously differ).
14// 3. A set of metrics, identifying the cardinality (how many) and the semantic
15//    (what do they mean) of the values of the nodes in the results tree.
16//
17// From a graphical viewpoint a profile is displayed using two charts:
18// - A tree (organizational) chart which shows, for a given snapshot and metric,
19//   the taxonomy of the buckets and their corresponding value.
20// - A time series (scattered area) chart which shows, for a given metric and a
21//   given bucket, its evolution over time (and of its direct children).
22
23profiler = new (function() {
24
25this.treeData_ = null;
26this.treeChart_ = null;
27this.timeSeriesData_ = null;
28this.timeSeriesChart_ = null;
29this.isRedrawing_ = false;
30this.profileId_ = null;  // The profile id retrieved on /ajax/profile/create.
31this.times_ = [];  // Snapshot times: [0,4,8] -> 3 snapshots x 4 sec.
32this.metrics_ = [];  // Keys in the result tree, e.g., ['RSS', 'PSS'].
33this.curTime_ = null;  // Time of the snapshot currently displayed.
34this.curMetric_ = null;  // Index (rel. to |metrics_|) currently displayed.
35this.curBucket_ = null;  // Index (rel. to the tree) currently displayed.
36
37this.onDomReady_ = function() {
38  this.treeChart_ = new google.visualization.OrgChart($('#prof-tree_chart')[0]);
39  this.timeSeriesChart_ = new google.visualization.SteppedAreaChart(
40      $('#prof-time_chart')[0]);
41
42  // TODO(primiano): De-hardcodify the ruleset and list/load from the server.
43  $('#prof-ruleset').append(
44      $('<option>').val('default/mmap-android.py').text('mmap-Android'));
45
46  // Setup the UI event listeners to trigger the onUiParamsChange_ event.
47  google.visualization.events.addListener(this.treeChart_, 'select',
48                                          this.onUiParamsChange_.bind(this));
49  $('#prof-metric').on('change', this.onUiParamsChange_.bind(this));
50  $('#prof-time').slider({range: 'max', min: 0, max: 0, value: 0,
51                          change: this.onUiParamsChange_.bind(this)});
52};
53
54this.profileCachedMmapDump = function(mmapDumpId) {
55  // Creates a profile using the data grabbed during a recent mmap dump.
56  // This is used to get a quick overview (only one snapshot), of the memory
57  // without doing a full periodic trace first.
58  webservice.ajaxRequest('/profile/create',  // This is a POST request.
59                         this.onProfileAjaxResponse_.bind(this),
60                         null,  // use the default error handler.
61                         {type: 'mmap',
62                          source: 'cache',
63                          id: mmapDumpId,
64                          ruleset: $('#prof-ruleset').val()});
65};
66
67this.profileArchivedMmaps = function(archiveName, snapshots) {
68  // Creates a mmap profile using the data from the storage.
69  webservice.ajaxRequest('/profile/create',  // This is a POST request.
70                         this.onProfileAjaxResponse_.bind(this),
71                         null,  // use the default error handler.
72                         {type: 'mmap',
73                          source: 'archive',
74                          archive: archiveName,
75                          snapshots: snapshots,
76                          ruleset: $('#prof-ruleset').val()});
77};
78
79this.profileArchivedNHeaps = function(archiveName, snapshots) {
80  // Creates a native-heap profile using the data from the storage.
81  webservice.ajaxRequest('/profile/create',  // This is a POST request.
82                         this.onProfileAjaxResponse_.bind(this),
83                         null,  // use the default error handler.
84                         {type: 'nheap',
85                          source: 'archive',
86                          archive: archiveName,
87                          snapshots: snapshots,
88                          ruleset: 'heuristic'});
89  // TODO(primiano): Next CLs: support custom rules, not just the heuristic one.
90};
91
92this.onProfileAjaxResponse_ = function(data) {
93  // This AJAX response contains a summary of the profile requested via the
94  // /profile endpoint, which consists of:
95  // - The number of snapshots (and their corresponding time) in an array.
96  //   e.g., [0, 3 ,6] indicates that the profile contains three snapshots taken
97  //   respectively at T=0, T=3 and T=6 sec.
98  // - A list of profile metrics, e.g., ['RSS', 'P. Dirty'] indicates that every
99  //   node in the result tree is a 2-tuple.
100  // After this response, the concrete data for the charts can be fetched using
101  // the /ajax/profile/{ID}/tree and /ajax/profile/{ID}/time_serie endpoints.
102  this.profileId_ = data.id;
103  this.times_ = data.times;  // An array of integers.
104  this.metrics_ = data.metrics;  // An array of strings.
105  this.curBucket_ = data.rootBucket;  // URI of the bucket, e.g., Total/Libs/.
106  this.curTime_ = data.times[0];
107  this.curMetric_ = 0;
108
109  // Populate the "metrics" select box.
110  $('#prof-metric').empty();
111  this.metrics_.forEach(function(metric) {
112    $('#prof-metric').append($('<option/>').text(metric));
113  }, this);
114
115  // Setup the bounds of the snapshots slider.
116  $('#prof-time').slider('option', 'max', this.times_.length - 1);
117
118  // Fetch the actual chart data (via /profile/{ID}/...) and redraw the charts.
119  this.updateCharts();
120};
121
122this.onUiParamsChange_ = function() {
123  // Triggered whenever any of the UI params (the metric select, the snapshot
124  // slider or the selected bucket in the tree) changes.
125  this.curMetric_ = $('#prof-metric').prop('selectedIndex');
126  this.curTime_ = this.times_[$('#prof-time').slider('value')];
127  $('#prof-time_label').text(this.curTime_);
128  var selBucket = this.treeChart_.getSelection();
129  if (selBucket.length)
130    this.curBucket_ = this.treeData_.getValue(selBucket[0].row, 0);
131  this.updateCharts();
132};
133
134this.updateCharts = function() {
135  if (!this.profileId_)
136    return;
137
138  var profileUri = '/profile/' + this.profileId_;
139  webservice.ajaxRequest(
140      profileUri +'/tree/' + this.curMetric_ + '/' + this.curTime_,
141      this.onTreeAjaxResponse_.bind(this));
142  webservice.ajaxRequest(
143      profileUri +'/time_serie/' + this.curMetric_ + '/' + this.curBucket_,
144      this.onTimeSerieAjaxResponse_.bind(this));
145};
146
147this.onTreeAjaxResponse_ = function(data) {
148  this.treeData_ = new google.visualization.DataTable(data);
149  this.redrawTree_();
150};
151
152this.onTimeSerieAjaxResponse_ = function(data) {
153  this.timeSeriesData_ = new google.visualization.DataTable(data);
154  this.redrawTimeSerie_();
155};
156
157this.redrawTree_ = function() {
158  // isRedrawing_ is used here to break the avalanche chain that would be caused
159  // by redraw changing the node selection, triggering in turn another redraw.
160  if (!this.treeData_ || this.isRedrawing_)
161    return;
162
163  this.isRedrawing_ = true;
164  var savedSelection = this.treeChart_.getSelection();
165  this.treeChart_.draw(this.treeData_, {allowHtml: true});
166
167  // "If we want things to stay as they are, things will have to change."
168  // (work around GChart bug, as if we didn't have enough problems on our own).
169  this.treeChart_.setSelection([{row: null, column: null}]);
170  this.treeChart_.setSelection(savedSelection);
171  this.isRedrawing_ = false;
172};
173
174this.redrawTimeSerie_ = function() {
175  if (!this.timeSeriesData_)
176    return;
177
178  var metric = this.metrics_[this.curMetric_];
179  this.timeSeriesChart_.draw(this.timeSeriesData_, {
180      title: metric + ' over time for ' + this.curBucket_,
181      isStacked: true,
182      hAxis: {title: 'Time [sec.]'},
183      vAxis: {title: this.metrics_[this.curMetric_] + ' [KB]'}});
184};
185
186this.redraw = function() {
187  this.redrawTree_();
188  this.redrawTimeSerie_();
189};
190
191$(document).ready(this.onDomReady_.bind(this));
192
193})();