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