profiler.js revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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.rulesets = {'nheap': [], 'mmap': []}; 26this.treeData_ = null; 27this.treeChart_ = null; 28this.timeSeriesData_ = null; 29this.timeSeriesChart_ = null; 30this.isRedrawing_ = false; 31this.profileId_ = null; // The profile id retrieved on /ajax/profile/create. 32this.times_ = []; // Snapshot times: [0,4,8] -> 3 snapshots x 4 sec. 33this.metrics_ = []; // Keys in the result tree, e.g., ['RSS', 'PSS']. 34this.curTime_ = null; // Time of the snapshot currently displayed. 35this.curMetric_ = null; // Index (rel. to |metrics_|) currently displayed. 36this.curBucket_ = null; // Index (rel. to the tree) currently displayed. 37 38this.onDomReady_ = function() { 39 this.treeChart_ = new google.visualization.OrgChart($('#prof-tree_chart')[0]); 40 this.timeSeriesChart_ = new google.visualization.SteppedAreaChart( 41 $('#prof-time_chart')[0]); 42 43 // Setup the UI event listeners to trigger the onUiParamsChange_ event. 44 google.visualization.events.addListener(this.treeChart_, 'select', 45 this.onUiParamsChange_.bind(this)); 46 $('#prof-metric').on('change', this.onUiParamsChange_.bind(this)); 47 $('#prof-time').slider({range: 'max', min: 0, max: 0, value: 0, 48 change: this.onUiParamsChange_.bind(this)}); 49 50 // Load the available profiler rules. 51 webservice.ajaxRequest('/profile/rules', 52 this.OnRulesAjaxResponse_.bind(this)); 53}; 54 55this.profileCachedMmapDump = function(mmapDumpId, ruleset) { 56 // Creates a profile using the data grabbed during a recent mmap dump. 57 // This is used to get a quick overview (only one snapshot), of the memory 58 // without doing a full periodic trace first. 59 ruleset = ruleset || this.rulesets['mmap'][0]; 60 webservice.ajaxRequest('/profile/create', // This is a POST request. 61 this.onProfileAjaxResponse_.bind(this, ruleset), 62 null, // use the default error handler. 63 {type: 'mmap', 64 source: 'cache', 65 id: mmapDumpId, 66 ruleset: ruleset}); 67}; 68 69this.profileArchivedMmaps = function(archiveName, snapshots, ruleset) { 70 ruleset = ruleset || this.rulesets['mmap'][0]; 71 // Creates a mmap profile using the data from the storage. 72 webservice.ajaxRequest('/profile/create', // This is a POST request. 73 this.onProfileAjaxResponse_.bind(this, ruleset), 74 null, // use the default error handler. 75 {type: 'mmap', 76 source: 'archive', 77 archive: archiveName, 78 snapshots: snapshots, 79 ruleset: ruleset}); 80}; 81 82this.profileArchivedNHeaps = function(archiveName, snapshots, ruleset) { 83 // Creates a native-heap profile using the data from the storage. 84 ruleset = ruleset || this.rulesets['nheap'][0]; 85 webservice.ajaxRequest('/profile/create', // This is a POST request. 86 this.onProfileAjaxResponse_.bind(this, ruleset), 87 null, // use the default error handler. 88 {type: 'nheap', 89 source: 'archive', 90 archive: archiveName, 91 snapshots: snapshots, 92 ruleset: ruleset}); 93}; 94 95this.OnRulesAjaxResponse_ = function(data) { 96 // This AJAX response contains essentially the directory listing of the 97 // memory_inspector/classification_rules/ folder. 98 console.assert('nheap' in data && 'mmap' in data); 99 this.rulesets = data; 100}; 101 102this.onProfileAjaxResponse_ = function(ruleset, data) { 103 // This AJAX response contains a summary of the profile requested via the 104 // /profile endpoint, which consists of: 105 // - The number of snapshots (and their corresponding time) in an array. 106 // e.g., [0, 3 ,6] indicates that the profile contains three snapshots taken 107 // respectively at T=0, T=3 and T=6 sec. 108 // - A list of profile metrics, e.g., ['RSS', 'P. Dirty'] indicates that every 109 // node in the result tree is a 2-tuple. 110 // After this response, the concrete data for the charts can be fetched using 111 // the /ajax/profile/{ID}/tree and /ajax/profile/{ID}/time_serie endpoints. 112 this.profileId_ = data.id; 113 this.times_ = data.times; // An array of integers. 114 this.metrics_ = data.metrics; // An array of strings. 115 this.curBucket_ = data.rootBucket; // URI of the bucket, e.g., Total/Libs/. 116 this.curTime_ = data.times[0]; 117 this.curMetric_ = 0; 118 119 // Populate the rules label with the ruleset used for generating this profile. 120 $('#prof-ruleset').text(ruleset); 121 122 // Populate the "metrics" select box. 123 $('#prof-metric').empty(); 124 this.metrics_.forEach(function(metric) { 125 $('#prof-metric').append($('<option/>').text(metric)); 126 }, this); 127 128 // Setup the bounds of the snapshots slider. 129 $('#prof-time').slider('option', 'max', this.times_.length - 1); 130 131 // Fetch the actual chart data (via /profile/{ID}/...) and redraw the charts. 132 this.updateCharts(); 133}; 134 135this.onUiParamsChange_ = function() { 136 // Triggered whenever any of the UI params (the metric select, the snapshot 137 // slider or the selected bucket in the tree) changes. 138 this.curMetric_ = $('#prof-metric').prop('selectedIndex'); 139 this.curTime_ = this.times_[$('#prof-time').slider('value')]; 140 $('#prof-time_label').text(this.curTime_); 141 var selBucket = this.treeChart_.getSelection(); 142 if (selBucket.length) 143 this.curBucket_ = this.treeData_.getValue(selBucket[0].row, 0); 144 this.updateCharts(); 145}; 146 147this.updateCharts = function() { 148 if (!this.profileId_) 149 return; 150 151 var profileUri = '/profile/' + this.profileId_; 152 webservice.ajaxRequest( 153 profileUri +'/tree/' + this.curMetric_ + '/' + this.curTime_, 154 this.onTreeAjaxResponse_.bind(this)); 155 webservice.ajaxRequest( 156 profileUri +'/time_serie/' + this.curMetric_ + '/' + this.curBucket_, 157 this.onTimeSerieAjaxResponse_.bind(this)); 158}; 159 160this.onTreeAjaxResponse_ = function(data) { 161 this.treeData_ = new google.visualization.DataTable(data); 162 this.redrawTree_(); 163}; 164 165this.onTimeSerieAjaxResponse_ = function(data) { 166 this.timeSeriesData_ = new google.visualization.DataTable(data); 167 this.redrawTimeSerie_(); 168}; 169 170this.redrawTree_ = function() { 171 // isRedrawing_ is used here to break the avalanche chain that would be caused 172 // by redraw changing the node selection, triggering in turn another redraw. 173 if (!this.treeData_ || this.isRedrawing_) 174 return; 175 176 this.isRedrawing_ = true; 177 var savedSelection = this.treeChart_.getSelection(); 178 this.treeChart_.draw(this.treeData_, {allowHtml: true}); 179 180 // "If we want things to stay as they are, things will have to change." 181 // (work around GChart bug, as if we didn't have enough problems on our own). 182 this.treeChart_.setSelection([{row: null, column: null}]); 183 this.treeChart_.setSelection(savedSelection); 184 this.isRedrawing_ = false; 185}; 186 187this.redrawTimeSerie_ = function() { 188 if (!this.timeSeriesData_) 189 return; 190 191 var metric = this.metrics_[this.curMetric_]; 192 this.timeSeriesChart_.draw(this.timeSeriesData_, { 193 title: metric + ' over time for ' + this.curBucket_, 194 isStacked: true, 195 hAxis: {title: 'Time [sec.]'}, 196 vAxis: {title: this.metrics_[this.curMetric_] + ' [KB]'}}); 197}; 198 199this.redraw = function() { 200 this.redrawTree_(); 201 this.redrawTimeSerie_(); 202}; 203 204$(document).ready(this.onDomReady_.bind(this)); 205 206})();