home *** CD-ROM | disk | FTP | other *** search
/ Freelog 115 / FreelogNo115-MaiJuin2013.iso / Internet / AvantBrowser / asetup.exe / _data / webkit / chrome.dll / 0 / BINDATA / 553 < prev    next >
Encoding:
Text File  |  2013-04-03  |  49.2 KB  |  1,392 lines

  1. /* Copyright (c) 2012 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. cr.define('performance_monitor', function() {
  6.   'use strict';
  7.  
  8.   /**
  9.    * Map of available time resolutions.
  10.    * @type {Object.<string, PerformanceMonitor.TimeResolution>}
  11.    * @private
  12.    */
  13.   var TimeResolutions_ = {
  14.     // Prior 15 min, resolution of 15 seconds.
  15.     minutes: {id: 0, i18nKey: 'timeLastFifteenMinutes', timeSpan: 900 * 1000,
  16.               pointResolution: 1000 * 15},
  17.  
  18.     // Prior hour, resolution of 1 minute.
  19.     // Labels at 5 point (5 min) intervals.
  20.     hour: {id: 1, i18nKey: 'timeLastHour', timeSpan: 3600 * 1000,
  21.            pointResolution: 1000 * 60},
  22.  
  23.     // Prior day, resolution of 24 min.
  24.     // Labels at 5 point (2 hour) intervals.
  25.     day: {id: 2, i18nKey: 'timeLastDay', timeSpan: 24 * 3600 * 1000,
  26.           pointResolution: 1000 * 60 * 24},
  27.  
  28.     // Prior week, resolution of 2.8 hours (168 min).
  29.     // Labels at ~8.5 point (daily) intervals.
  30.     week: {id: 3, i18nKey: 'timeLastWeek', timeSpan: 7 * 24 * 3600 * 1000,
  31.            pointResolution: 1000 * 60 * 168},
  32.  
  33.     // Prior month (30 days), resolution of 12 hours.
  34.     // Labels at 14 point (weekly) intervals.
  35.     month: {id: 4, i18nKey: 'timeLastMonth', timeSpan: 30 * 24 * 3600 * 1000,
  36.             pointResolution: 1000 * 3600 * 12},
  37.  
  38.     // Prior quarter (90 days), resolution of 36 hours.
  39.     // Labels at ~9.3 point (fortnightly) intervals.
  40.     quarter: {id: 5, i18nKey: 'timeLastQuarter',
  41.               timeSpan: 90 * 24 * 3600 * 1000,
  42.               pointResolution: 1000 * 3600 * 36},
  43.   };
  44.  
  45.   /**
  46.    * Map of available date formats in Flot-style format strings.
  47.    * @type {Object.<string, string>}
  48.    * @private
  49.    */
  50.   var TimeFormats_ = {
  51.     time: '%h:%M %p',
  52.     monthDayTime: '%b %d<br/>%h:%M %p',
  53.     monthDay: '%b %d',
  54.     yearMonthDay: '%y %b %d',
  55.   };
  56.  
  57.   /*
  58.    * Table of colors to use for metrics and events. Basically boxing the
  59.    * colorwheel, but leaving out yellows and fully saturated colors.
  60.    * @type {Array.<string>}
  61.    * @private
  62.    */
  63.   var ColorTable_ = [
  64.     'rgb(255, 128, 128)', 'rgb(128, 255, 128)', 'rgb(128, 128, 255)',
  65.     'rgb(128, 255, 255)', 'rgb(255, 128, 255)', // No bright yellow
  66.     'rgb(255,  64,  64)', 'rgb( 64, 255,  64)', 'rgb( 64,  64, 255)',
  67.     'rgb( 64, 255, 255)', 'rgb(255,  64, 255)', // No medium yellow either
  68.     'rgb(128,  64,  64)', 'rgb( 64, 128,  64)', 'rgb( 64,  64, 128)',
  69.     'rgb( 64, 128, 128)', 'rgb(128,  64, 128)', 'rgb(128, 128,  64)'
  70.   ];
  71.  
  72.   /*
  73.    * Offset, in ms, by which to subtract to convert GMT to local time.
  74.    * @type {number}
  75.    * @private
  76.    */
  77.   var timezoneOffset_ = new Date().getTimezoneOffset() * 60000;
  78.  
  79.   /*
  80.    * Additional range multiplier to ensure that points don't hit the top of
  81.    * the graph.
  82.    * @type {number}
  83.    * @private
  84.    */
  85.   var yAxisMargin_ = 1.05;
  86.  
  87.   /*
  88.    * Number of time resolution periods to wait between automated update of
  89.    * graphs.
  90.    * @type {number}
  91.    * @private
  92.    */
  93.   var intervalMultiple_ = 2;
  94.  
  95.   /*
  96.    * Number of milliseconds to wait before deciding that the most recent
  97.    * resize event is not going to be followed immediately by another, and
  98.    * thus needs handling.
  99.    * @type {number}
  100.    * @private
  101.    */
  102.   var resizeDelay_ = 500;
  103.  
  104.   /*
  105.    * The value of the 'No Aggregation' option enum (AGGREGATION_METHOD_NONE) on
  106.    * the C++ side. We use this to warn the user that selecting this aggregation
  107.    * option will be slow.
  108.    */
  109.   var aggregationMethodNone = 0;
  110.  
  111.   /*
  112.    * The value of the default aggregation option, 'Median Aggregation'
  113.    * (AGGREGATION_METHOD_MEDIAN), on the C++ side.
  114.    */
  115.   var aggregationMethodMedian = 1;
  116.  
  117.   /** @constructor */
  118.   function PerformanceMonitor() {
  119.     this.__proto__ = PerformanceMonitor.prototype;
  120.  
  121.     /** Information regarding a certain time resolution option, including an
  122.      *  enumerative id, a readable name, the timespan in milliseconds prior to
  123.      *  |now|, data point resolution in milliseconds, and time-label frequency
  124.      *  in data points per label.
  125.      *  @typedef {{
  126.      *    id: number,
  127.      *    name: string,
  128.      *    timeSpan: number,
  129.      *    pointResolution: number,
  130.      *    labelEvery: number,
  131.      *  }}
  132.      */
  133.     PerformanceMonitor.TimeResolution;
  134.  
  135.     /**
  136.      * Detailed information on a metric in the UI. |metricId| is a unique
  137.      * identifying number for the metric, provided by the webui, and assumed to
  138.      * be densely populated. |description| is a localized string description
  139.      * suitable for mouseover on the metric. |category| corresponds to a
  140.      * category object to which the metric belongs (see |metricCategoryMap_|).
  141.      * |color| is the color in which the metric is displayed on the graphs.
  142.      * |maxValue| is a value by which to scale the y-axis, in order to avoid
  143.      * constant resizing to fit the present data. |checkbox| is the HTML element
  144.      * for the checkbox which toggles the metric's display. |enabled| indicates
  145.      * whether or not the metric is being actively displayed. |data| is the
  146.      * collection of data for the metric.
  147.      *
  148.      * For |data|, the inner-most array represents a point in a pair of numbers,
  149.      * representing time and value (this will always be of length 2). The
  150.      * array above is the collection of points within a series, which is an
  151.      * interval for which PerformanceMonitor was active. The outer-most array
  152.      * is the collection of these series.
  153.      *
  154.      * @typedef {{
  155.      *   metricId: number,
  156.      *   description: string,
  157.      *   category: !Object,
  158.      *   color: string,
  159.      *   maxValue: number,
  160.      *   checkbox: HTMLElement,
  161.      *   enabled: boolean,
  162.      *   data: ?Array.<Array<Array<number> > >
  163.      * }}
  164.      */
  165.     PerformanceMonitor.MetricDetails;
  166.  
  167.     /**
  168.      * Similar data for events as for metrics, though no y-axis info is needed
  169.      * since events are simply labeled markers at X locations.
  170.      *
  171.      * The |data| field follows a special rule not describable in
  172.      * JSDoc: Aside from the |time| key, each event type has varying other
  173.      * properties, with unknown key names, which properties must still be
  174.      * displayed. Such properties always have value of form
  175.      * {label: 'some label', value: 'some value'}, with label and value
  176.      * internationalized.
  177.      *
  178.      * @typedef {{
  179.      *   eventId: number,
  180.      *   name: string,
  181.      *   popupTitle: string,
  182.      *   description: string,
  183.      *   color: string,
  184.      *   checkbox: HTMLElement,
  185.      *   enabled: boolean
  186.      *   data: ?Array.<{time: number}>
  187.      * }}
  188.      */
  189.     PerformanceMonitor.EventDetails;
  190.  
  191.     /**
  192.      * The collection of divs that compose a chart on the UI, plus the metricIds
  193.      * of any metric which should be shown on the chart (whether the metric is
  194.      * enabled or not). The |mainDiv| is the full element, under which all other
  195.      * divs are nested. The |grid| is the div into which the |plot| (which is
  196.      * the core of the graph, including the axis, gridlines, dataseries, etc)
  197.      * goes. The |yaxisLabel| is nested under the mainDiv, and shows the units
  198.      * for the chart.
  199.      *
  200.      * @typedef {{
  201.      *   mainDiv: HTMLDivElement,
  202.      *   grid: HTMLDivElement,
  203.      *   plot: HTMLDivElement,
  204.      *   yaxisLabel: HTMLDivElement,
  205.      *   metricIds: ?Array.<number>
  206.      */
  207.     PerformanceMonitor.Chart;
  208.  
  209.     /**
  210.      * The time range which we are currently viewing, with the start and end of
  211.      * the range, the TimeResolution, and an appropriate for display (this
  212.      * format is the string structure which Flot expects for its setting).
  213.      * @typedef {{
  214.      * @type {{
  215.      *   start: number,
  216.      *   end: number,
  217.      *   resolution: PerformanceMonitor.TimeResolution
  218.      *   format: string
  219.      * }}
  220.      * @private
  221.      */
  222.     this.range_ = { 'start': 0, 'end': 0, 'resolution': undefined };
  223.  
  224.     /**
  225.      * The map containing the available TimeResolutions and the radio button to
  226.      * which each corresponds. The key is the id field from the TimeResolution
  227.      * object.
  228.      * @type {Object.<string, {
  229.      *   option: PerformanceMonitor.TimeResolution,
  230.      *   element: HTMLElement
  231.      * }>}
  232.      * @private
  233.      */
  234.     this.timeResolutionRadioMap_ = {};
  235.  
  236.     /**
  237.      * The map containing the available Aggregation Methods and the radio button
  238.      * to which each corresponds. The different methods are retrieved from the
  239.      * WebUI, and the information about the method is stored in the 'option'
  240.      * field. The key to the map is the id of the aggregation method.
  241.      *
  242.      * @type {Object.<string, {
  243.      *   option: {
  244.      *     id: number,
  245.      *     name: string,
  246.      *     description: string,
  247.      *   },
  248.      *   element: HTMLElement
  249.      * }>}
  250.      * @private
  251.      */
  252.     this.aggregationRadioMap_ = {};
  253.  
  254.     /**
  255.      * Metrics fall into categories that have common units and thus may
  256.      * share a common graph, or share y-axes within a multi-y-axis graph.
  257.      * Each category has a unique identifying metricCategoryId; a localized
  258.      * name, mouseover description, and unit; and an array of all the metrics
  259.      * which are in this category. The key is |metricCategoryId|.
  260.      *
  261.      * @type {Object.<string, {
  262.      *   metricCategoryId: number,
  263.      *   name: string,
  264.      *   description: string,
  265.      *   unit: string,
  266.      *   details: Array.<{!PerformanceMonitor.MetricDetails}>,
  267.      * }>}
  268.      * @private
  269.      */
  270.     this.metricCategoryMap_ = {};
  271.  
  272.     /**
  273.      * Comprehensive map from metricId to MetricDetails.
  274.      * @type {Object.<string, {PerformanceMonitor.MetricDetails}>}
  275.      * @private
  276.      */
  277.     this.metricDetailsMap_ = {};
  278.  
  279.     /**
  280.      * Events fall into categories just like metrics, above. This category
  281.      * grouping is not as important as that for metrics, since events
  282.      * needn't share maxima, y-axes, nor units, and since events appear on
  283.      * all charts. But grouping of event categories in the event-selection
  284.      * UI is still useful. The key is the id of the event category.
  285.      *
  286.      * @type {Object.<string, {
  287.      *   eventCategoryId: number,
  288.      *   name: string,
  289.      *   description: string,
  290.      *   details: !Array.<!PerformanceMonitor.EventDetails>,
  291.      * }>}
  292.      * @private
  293.      */
  294.     this.eventCategoryMap_ = {};
  295.  
  296.     /**
  297.      * Comprehensive map from eventId to EventDetails.
  298.      * @type {Object.<string, {PerformanceMonitor.EventDetails}>}
  299.      * @private
  300.      */
  301.     this.eventDetailsMap_ = {};
  302.  
  303.     /**
  304.      * Time periods in which the browser was active and collecting metrics
  305.      * and events.
  306.      * @type {!Array.<{start: number, end: number}>}
  307.      * @private
  308.      */
  309.     this.intervals_ = [];
  310.  
  311.     /**
  312.      * The record of all the warnings which are currently active (or empty if no
  313.      * warnings are being displayed).
  314.      * @type {!Array.<string>}
  315.      * @private
  316.      */
  317.     this.activeWarnings_ = [];
  318.  
  319.     /**
  320.      * Handle of timer interval function used to update charts
  321.      * @type {Object}
  322.      * @private
  323.      */
  324.     this.updateTimer_ = null;
  325.  
  326.     /**
  327.      * Handle of timer interval function used to check for resizes. Nonnull
  328.      * only when resize events are coming steadily.
  329.      * @type {Object}
  330.      * @private
  331.      */
  332.     this.resizeTimer_ = null;
  333.  
  334.     /**
  335.      * The status of all calls for data, stored in order to keep track of the
  336.      * internal state. This stores an attribute for each type of repeated data
  337.      * call (for now, only metrics and events), which will be true if we are
  338.      * awaiting data and false otherwise.
  339.      * @type {Object.<string, boolean>}
  340.      * @private
  341.      */
  342.     this.awaitingDataCalls_ = {};
  343.  
  344.     /**
  345.      * The progress into the initialization process. This must be stored, since
  346.      * certain tasks must be performed in a specific order which cannot be
  347.      * statically determined. Mainly, we must not request any data until the
  348.      * metrics, events, aggregation method, and time range have all been set.
  349.      * This object contains an attribute for each stage of the initialization
  350.      * process, which is set to true if the stage has been completed.
  351.      * @type {Object.<string, boolean>}
  352.      * @private
  353.      */
  354.     this.initProgress_ = { 'aggregation': false,
  355.                            'events': false,
  356.                            'metrics': false,
  357.                            'timeRange': false };
  358.  
  359.     /**
  360.      * All PerformanceMonitor.Chart objects available in the display, whether
  361.      * hidden or visible.
  362.      * @type {Array.<PerformanceMonitor.Chart>}
  363.      * @private
  364.      */
  365.     this.charts_ = [];
  366.  
  367.     this.setupStaticControlPanelFeatures_();
  368.     chrome.send('getFlagEnabled');
  369.     chrome.send('getAggregationTypes');
  370.     chrome.send('getEventTypes');
  371.     chrome.send('getMetricTypes');
  372.   }
  373.  
  374.   PerformanceMonitor.prototype = {
  375.     /**
  376.      * Display the appropriate warning at the top of the page.
  377.      * @param {string} warningId the id of the HTML element with the warning
  378.      *     to display; this does not include the '#'.
  379.      */
  380.     showWarning: function(warningId) {
  381.       if (this.activeWarnings_.indexOf(warningId) != -1)
  382.         return;
  383.  
  384.       if (this.activeWarnings_.length == 0)
  385.         $('#warnings-box')[0].style.display = 'block';
  386.       $('#' + warningId)[0].style.display = 'block';
  387.       this.activeWarnings_.push(warningId);
  388.     },
  389.  
  390.     /**
  391.      * Hide the warning, and, if that was the only warning showing, the entire
  392.      * warnings box.
  393.      * @param {string} warningId the id of the HTML element with the warning
  394.      *     to display; this does not include the '#'.
  395.      */
  396.     hideWarning: function(warningId) {
  397.       var index = this.activeWarnings_.indexOf(warningId);
  398.       if (index == -1)
  399.         return;
  400.       $('#' + warningId)[0].style.display = 'none';
  401.       this.activeWarnings_.splice(index, 1);
  402.  
  403.       if (this.activeWarnings_.length == 0)
  404.         $('#warnings-box')[0].style.display = 'none';
  405.     },
  406.  
  407.     /**
  408.      * Receive an indication of whether or not the kPerformanceMonitorGathering
  409.      * flag has been enabled and, if not, warn the user of such.
  410.      * @param {boolean} flagEnabled indicates whether or not the flag has been
  411.      *     enabled.
  412.      */
  413.     getFlagEnabledCallback: function(flagEnabled) {
  414.       if (!flagEnabled)
  415.         this.showWarning('flag-not-enabled-warning');
  416.     },
  417.  
  418.     /**
  419.      * Return true if we are not awaiting any returning data calls, and false
  420.      * otherwise.
  421.      * @return {boolean} The value indicating whether or not we are actively
  422.      *     fetching data.
  423.      */
  424.     fetchingData_: function() {
  425.       return this.awaitingDataCalls_.metrics == true ||
  426.              this.awaitingDataCalls_.events == true;
  427.     },
  428.  
  429.     /**
  430.      * Return true if the main steps of initialization prior to the first draw
  431.      * are complete, and false otherwise.
  432.      * @return {boolean} The value indicating whether or not the initialization
  433.      *     process has finished.
  434.      */
  435.     isInitialized_: function() {
  436.       return this.initProgress_.aggregation == true &&
  437.              this.initProgress_.events == true &&
  438.              this.initProgress_.metrics == true &&
  439.              this.initProgress_.timeRange == true;
  440.     },
  441.  
  442.     /**
  443.      * Refresh all data areas.
  444.      */
  445.     refreshAll: function() {
  446.       this.refreshMetrics();
  447.       this.refreshEvents();
  448.     },
  449.  
  450.     /**
  451.      * Receive a list of all the aggregation methods. Populate
  452.      * |this.aggregationRadioMap_| to reflect said list. Create the section of
  453.      * radio buttons for the aggregation methods, and choose the first method
  454.      * by default.
  455.      * @param {Array<{
  456.      *   id: number,
  457.      *   name: string,
  458.      *   description: string
  459.      * }>} methods All aggregation methods needing radio buttons.
  460.      */
  461.     getAggregationTypesCallback: function(methods) {
  462.       methods.forEach(function(method) {
  463.         this.aggregationRadioMap_[method.id] = { 'option': method };
  464.       }, this);
  465.  
  466.       this.setupRadioButtons_($('#choose-aggregation')[0],
  467.                               this.aggregationRadioMap_,
  468.                               this.setAggregationMethod,
  469.                               aggregationMethodMedian,
  470.                               'aggregation-methods');
  471.       this.setAggregationMethod(aggregationMethodMedian);
  472.       this.initProgress_.aggregation = true;
  473.       if (this.isInitialized_())
  474.         this.refreshAll();
  475.     },
  476.  
  477.     /**
  478.      * Receive a list of all metric categories, each with its corresponding
  479.      * list of metric details. Populate |this.metricCategoryMap_| and
  480.      * |this.metricDetailsMap_| to reflect said list. Reconfigure the
  481.      * checkbox set for metric selection.
  482.      * @param {Array.<{
  483.      *   metricCategoryId: number,
  484.      *   name: string,
  485.      *   unit: string,
  486.      *   description: string,
  487.      *   details: Array.<{
  488.      *     metricId: number,
  489.      *     name: string,
  490.      *     description: string
  491.      *   }>
  492.      * }>} categories All metric categories needing charts and checkboxes.
  493.      */
  494.     getMetricTypesCallback: function(categories) {
  495.       categories.forEach(function(category) {
  496.         this.addCategoryChart_(category);
  497.         this.metricCategoryMap_[category.metricCategoryId] = category;
  498.  
  499.         category.details.forEach(function(metric) {
  500.           metric.color = ColorTable_[metric.metricId % ColorTable_.length];
  501.           metric.maxValue = 1;
  502.           metric.divs = [];
  503.           metric.data = null;
  504.           metric.category = category;
  505.           this.metricDetailsMap_[metric.metricId] = metric;
  506.         }, this);
  507.       }, this);
  508.  
  509.       this.setupCheckboxes_($('#choose-metrics')[0],
  510.           this.metricCategoryMap_, 'metricId', this.addMetric, this.dropMetric);
  511.  
  512.       for (var metric in this.metricDetailsMap_) {
  513.         this.metricDetailsMap_[metric].checkbox.checked = true;
  514.         this.metricDetailsMap_[metric].enabled = true;
  515.       }
  516.  
  517.       this.initProgress_.metrics = true;
  518.       if (this.isInitialized_())
  519.         this.refreshAll();
  520.     },
  521.  
  522.     /**
  523.      * Receive a list of all event categories, each with its correspoinding
  524.      * list of event details. Populate |this.eventCategoryMap_| and
  525.      * |this.eventDetailsMap| to reflect said list. Reconfigure the
  526.      * checkbox set for event selection.
  527.      * @param {Array.<{
  528.      *   eventCategoryId: number,
  529.      *   name: string,
  530.      *   description: string,
  531.      *   details: Array.<{
  532.      *     eventId: number,
  533.      *     name: string,
  534.      *     description: string
  535.      *   }>
  536.      * }>} categories All event categories needing charts and checkboxes.
  537.      */
  538.     getEventTypesCallback: function(categories) {
  539.       categories.forEach(function(category) {
  540.         this.eventCategoryMap_[category.eventCategoryId] = category;
  541.  
  542.         category.details.forEach(function(event) {
  543.           event.color = ColorTable_[event.eventId % ColorTable_.length];
  544.           event.divs = [];
  545.           event.data = null;
  546.           this.eventDetailsMap_[event.eventId] = event;
  547.         }, this);
  548.       }, this);
  549.  
  550.       this.setupCheckboxes_($('#choose-events')[0], this.eventCategoryMap_,
  551.           'eventId', this.addEventType, this.dropEventType);
  552.  
  553.       this.initProgress_.events = true;
  554.       if (this.isInitialized_())
  555.         this.refreshAll();
  556.     },
  557.  
  558.     /**
  559.      * Set up the aspects of the control panel which are not dependent upon the
  560.      * information retrieved from PerformanceMonitor's database; this includes
  561.      * the Time Resolutions and Aggregation Methods radio sections.
  562.      * @private
  563.      */
  564.     setupStaticControlPanelFeatures_: function() {
  565.       // Initialize the options in the |timeResolutionRadioMap_| and set the
  566.       // localized names for the time resolutions.
  567.       for (var key in TimeResolutions_) {
  568.         var resolution = TimeResolutions_[key];
  569.         this.timeResolutionRadioMap_[resolution.id] = { 'option': resolution };
  570.         resolution.name = loadTimeData.getString(resolution.i18nKey);
  571.       }
  572.  
  573.       // Setup the Time Resolution radio buttons, and select the default option
  574.       // of minutes (finer resolution in order to ensure that the user sees
  575.       // something at startup).
  576.       this.setupRadioButtons_($('#choose-time-range')[0],
  577.                               this.timeResolutionRadioMap_,
  578.                               this.changeTimeResolution_,
  579.                               TimeResolutions_.minutes.id,
  580.                               'time-resolutions');
  581.  
  582.       // Set the default selection to 'Minutes' and set the time range.
  583.       this.setTimeRange(TimeResolutions_.minutes,
  584.                         Date.now(),
  585.                         true);  // Auto-refresh the chart.
  586.  
  587.       var forwardButton = $('#forward-time')[0];
  588.       forwardButton.addEventListener('click', this.forwardTime.bind(this));
  589.       var backButton = $('#back-time')[0];
  590.       backButton.addEventListener('click', this.backTime.bind(this));
  591.  
  592.       this.initProgress_.timeRange = true;
  593.       if (this.isInitialized_())
  594.         this.refreshAll();
  595.     },
  596.  
  597.     /**
  598.      * Change the current time resolution. The visible range will stay centered
  599.      * around the current center unless the latest edge crosses now(), in which
  600.      * case it will be pinned there and start auto-updating.
  601.      * @param {number} mapId the index into the |timeResolutionRadioMap_| of the
  602.      *     selected resolution.
  603.      */
  604.     changeTimeResolution_: function(mapId) {
  605.       var newEnd;
  606.       var now = Date.now();
  607.       var newResolution = this.timeResolutionRadioMap_[mapId].option;
  608.  
  609.       // If we are updating the timer, then we know that we are already ending
  610.       // at the perceived current time (which may be different than the actual
  611.       // current time, since we don't update continuously).
  612.       newEnd = this.updateTimer_ ? now :
  613.           Math.min(now, this.range_.end + (newResolution.timeSpan -
  614.               this.range_.resolution.timeSpan) / 2);
  615.  
  616.       this.setTimeRange(newResolution, newEnd, newEnd == now);
  617.     },
  618.  
  619.     /**
  620.      * Generalized function to create checkboxes for either events
  621.      * or metrics, given a |div| into which to put the checkboxes, and a
  622.      * |optionCategoryMap| describing the checkbox structure.
  623.      *
  624.      * For instance, |optionCategoryMap| might be metricCategoryMap_, with
  625.      * contents thus:
  626.      *
  627.      * optionCategoryMap : {
  628.      *   1: {
  629.      *     name: 'CPU',
  630.      *     details: [
  631.      *       {
  632.      *         metricId: 1,
  633.      *         name: 'CPU Usage',
  634.      *         description:
  635.      *             'The combined CPU usage of all processes related to Chrome',
  636.      *         color: 'rgb(255, 128, 128)'
  637.      *       }
  638.      *     ],
  639.      *   2: {
  640.      *     name : 'Memory',
  641.      *     details: [
  642.      *       {
  643.      *         metricId: 2,
  644.      *         name: 'Private Memory Usage',
  645.      *         description:
  646.      *             'The combined private memory usage of all processes related
  647.      *             to Chrome',
  648.      *         color: 'rgb(128, 255, 128)'
  649.      *       },
  650.      *       {
  651.      *         metricId: 3,
  652.      *         name: 'Shared Memory Usage',
  653.      *         description:
  654.      *             'The combined shared memory usage of all processes related
  655.      *             to Chrome',
  656.      *         color: 'rgb(128, 128, 255)'
  657.      *       }
  658.      *     ]
  659.      *  }
  660.      *
  661.      * and we would call setupCheckboxes_ thus:
  662.      *
  663.      * this.setupCheckboxes_(<parent div>, this.metricCategoryMap_, 'metricId',
  664.      *     this.addMetric, this.dropMetric);
  665.      *
  666.      * MetricCategoryMap_'s values each have a |name| and |details| property.
  667.      * SetupCheckboxes_ creates one major header for each such value, with title
  668.      * given by the |name| field. Under each major header are checkboxes,
  669.      * one for each element in the |details| property. The checkbox titles
  670.      * come from the |name| property of each |details| object,
  671.      * and they each have an associated colored icon matching the |color|
  672.      * property of the details object.
  673.      *
  674.      * So, for the example given, the generated HTML looks thus:
  675.      *
  676.      * <div>
  677.      *   <h3 class="category-heading">CPU</h3>
  678.      *   <div class="checkbox-group">
  679.      *     <div>
  680.      *       <label class="input-label" title=
  681.      *           "The combined CPU usage of all processes related to Chrome">
  682.      *         <input type="checkbox">
  683.      *         <span>CPU</span>
  684.      *       </label>
  685.      *     </div>
  686.      *   </div>
  687.      * </div>
  688.      * <div>
  689.      *   <h3 class="category-heading">Memory</h3>
  690.      *   <div class="checkbox-group">
  691.      *     <div>
  692.      *       <label class="input-label" title= "The combined private memory \
  693.      *           usage of all processes related to Chrome">
  694.      *         <input type="checkbox">
  695.      *         <span>Private Memory</span>
  696.      *       </label>
  697.      *     </div>
  698.      *     <div>
  699.      *       <label class="input-label" title= "The combined shared memory \
  700.      *           usage of all processes related to Chrome">
  701.      *         <input type="checkbox">
  702.      *         <span>Shared Memory</span>
  703.      *       </label>
  704.      *     </div>
  705.      *   </div>
  706.      * </div>
  707.      *
  708.      * The checkboxes for each details object call addMetric or
  709.      * dropMetric as they are checked and unchecked, passing the relevant
  710.      * |metricId| value. Parameter 'metricId' identifies key |metricId| as the
  711.      * identifying property to pass to the methods. So, for instance, checking
  712.      * the CPU Usage box results in a call to this.addMetric(1), since
  713.      * metricCategoryMap_[1].details[0].metricId == 1.
  714.      *
  715.      * In general, |optionCategoryMap| must have values that each include
  716.      * a property |name|, and a property |details|. The |details| value must
  717.      * be an array of objects that in turn each have an identifying property
  718.      * with key given by parameter |idKey|, plus a property |name| and a
  719.      * property |color|.
  720.      *
  721.      * @param {!HTMLDivElement} div A <div> into which to put checkboxes.
  722.      * @param {!Object} optionCategoryMap A map of metric/event categories.
  723.      * @param {string} idKey The key of the id property.
  724.      * @param {!function(this:Controller, Object)} check
  725.      *     The function to select an entry (metric or event).
  726.      * @param {!function(this:Controller, Object)} uncheck
  727.      *     The function to deselect an entry (metric or event).
  728.      * @private
  729.      */
  730.     setupCheckboxes_: function(div, optionCategoryMap, idKey, check, uncheck) {
  731.       var categoryTemplate = $('#category-template')[0];
  732.       var checkboxTemplate = $('#checkbox-template')[0];
  733.  
  734.       for (var c in optionCategoryMap) {
  735.         var category = optionCategoryMap[c];
  736.         var template = categoryTemplate.cloneNode(true);
  737.         template.id = '';
  738.  
  739.         var heading = template.querySelector('.category-heading');
  740.         heading.innerText = category.name;
  741.         heading.title = category.description;
  742.  
  743.         var checkboxGroup = template.querySelector('.checkbox-group');
  744.         category.details.forEach(function(details) {
  745.           var checkbox = checkboxTemplate.cloneNode(true);
  746.           checkbox.id = '';
  747.           var input = checkbox.querySelector('input');
  748.  
  749.           details.checkbox = input;
  750.           input.checked = false;
  751.           input.option = details[idKey];
  752.           input.addEventListener('change', function(e) {
  753.             (e.target.checked ? check : uncheck).call(this, e.target.option);
  754.           }.bind(this));
  755.  
  756.           checkbox.querySelector('span').innerText = details.name;
  757.           checkbox.querySelector('.input-label').title = details.description;
  758.  
  759.           checkboxGroup.appendChild(checkbox);
  760.         }, this);
  761.  
  762.         div.appendChild(template);
  763.       }
  764.     },
  765.  
  766.     /**
  767.      * Generalized function to create radio buttons in a collection of
  768.      * |collectionName|, given a |div| into which the radio buttons are placed
  769.      * and a |optionMap| describing the radio buttons' options.
  770.      *
  771.      * optionMaps have two guaranteed fields - 'option' and 'element'. The
  772.      * 'option' field corresponds to the item which the radio button will be
  773.      * representing (e.g., a particular aggregation method).
  774.      *   - Each 'option' is guaranteed to have a 'value', a 'name', and a
  775.      *     'description'. 'Value' holds the id of the option, while 'name' and
  776.      *     'description' are internationalized strings for the radio button's
  777.      *     content.
  778.      *   - 'Element' is the field devoted to the HTMLElement for the radio
  779.      *     button corresponding to that entry; this will be set in this
  780.      *     function.
  781.      *
  782.      * Assume that |optionMap| is |aggregationRadioMap_|, as follows:
  783.      * optionMap: {
  784.      *   0: {
  785.      *     option: {
  786.      *       id: 0
  787.      *       name: 'Median'
  788.      *       description: 'Aggregate using median calculations to reduce
  789.      *           noisiness in reporting'
  790.      *     },
  791.      *     element: null
  792.      *   },
  793.      *   1: {
  794.      *     option: {
  795.      *       id: 1
  796.      *       name: 'Mean'
  797.      *       description: 'Aggregate using mean calculations for the most
  798.      *           accurate average in reporting'
  799.      *     },
  800.      *     element: null
  801.      *   }
  802.      * }
  803.      *
  804.      * and we would call setupRadioButtons_ with:
  805.      * this.setupRadioButtons_(<parent_div>, this.aggregationRadioMap_,
  806.      *     this.setAggregationMethod, 0, 'aggregation-methods');
  807.      *
  808.      * The resultant HTML would be:
  809.      * <div class="radio">
  810.      *   <label class="input-label" title="Aggregate using median \
  811.      *       calculations to reduce noisiness in reporting">
  812.      *     <input type="radio" name="aggregation-methods" value=0>
  813.      *     <span>Median</span>
  814.      *   </label>
  815.      * </div>
  816.      * <div class="radio">
  817.      *   <label class="input-label" title="Aggregate using mean \
  818.      *       calculations for the most accurate average in reporting">
  819.      *     <input type="radio" name="aggregation-methods" value=1>
  820.      *     <span>Mean</span>
  821.      *   </label>
  822.      * </div>
  823.      *
  824.      * If a radio button is selected, |onSelect| is called with the radio
  825.      * button's value. The |defaultKey| is used to choose which radio button
  826.      * to select at startup; the |onSelect| method is not called on this
  827.      * selection.
  828.      *
  829.      * @param {!HTMLDivElement} div A <div> into which we place the radios.
  830.      * @param {!Object} optionMap A map containing the radio button information.
  831.      * @param {!function(this:Controller, Object)} onSelect
  832.      *     The function called when a radio is selected.
  833.      * @param {string} defaultKey The key to the radio which should be selected
  834.      *     initially.
  835.      * @param {string} collectionName The name of the radio button collection.
  836.      * @private
  837.      */
  838.     setupRadioButtons_: function(div,
  839.                                  optionMap,
  840.                                  onSelect,
  841.                                  defaultKey,
  842.                                  collectionName) {
  843.       var radioTemplate = $('#radio-template')[0];
  844.       for (var key in optionMap) {
  845.         var entry = optionMap[key];
  846.         var radio = radioTemplate.cloneNode(true);
  847.         radio.id = '';
  848.         var input = radio.querySelector('input');
  849.  
  850.         input.name = collectionName;
  851.         input.enumerator = entry.option.id;
  852.         input.option = entry;
  853.         radio.querySelector('span').innerText = entry.option.name;
  854.         if (entry.option.description != undefined)
  855.           radio.querySelector('.input-label').title = entry.option.description;
  856.         div.appendChild(radio);
  857.         entry.element = input;
  858.       }
  859.  
  860.       optionMap[defaultKey].element.click();
  861.  
  862.       div.addEventListener('click', function(e) {
  863.         if (!e.target.webkitMatchesSelector('input[type="radio"]'))
  864.           return;
  865.  
  866.         onSelect.call(this, e.target.enumerator);
  867.       }.bind(this));
  868.     },
  869.  
  870.     /**
  871.      * Add a new chart for |category|, making it initially hidden,
  872.      * with no metrics displayed in it.
  873.      * @param {!Object} category The metric category for which to create
  874.      *     the chart. Category is a value from metricCategoryMap_.
  875.      * @private
  876.      */
  877.     addCategoryChart_: function(category) {
  878.       var chartParent = $('#charts')[0];
  879.       var mainDiv = $('#chart-template')[0].cloneNode(true);
  880.       mainDiv.id = '';
  881.  
  882.       var yaxisLabel = mainDiv.querySelector('h4');
  883.       yaxisLabel.innerText = category.unit;
  884.  
  885.       // Rotation is weird in html. The length of the text affects the x-axis
  886.       // placement of the label. We shift it back appropriately.
  887.       var width = -1 * (yaxisLabel.offsetWidth / 2) + 20;
  888.       var widthString = width.toString() + 'px';
  889.       yaxisLabel.style.webkitMarginStart = widthString;
  890.  
  891.       var grid = mainDiv.querySelector('.grid');
  892.  
  893.       mainDiv.hidden = true;
  894.       chartParent.appendChild(mainDiv);
  895.  
  896.       grid.hovers = [];
  897.  
  898.       // Set the various fields for the PerformanceMonitor.Chart object, and
  899.       // add the new object to |charts_|.
  900.       var chart = {};
  901.       chart.mainDiv = mainDiv;
  902.       chart.yaxisLabel = yaxisLabel;
  903.       chart.grid = grid;
  904.       chart.metricIds = [];
  905.  
  906.       category.details.forEach(function(details) {
  907.         chart.metricIds.push(details.metricId);
  908.       });
  909.  
  910.       this.charts_.push(chart);
  911.  
  912.       // Receive hover events from Flot.
  913.       // Attached to chart will be properties 'hovers', a list of {x, div}
  914.       // pairs. As pos events arrive, check each hover to see if it should
  915.       // be hidden or made visible.
  916.       $(grid).bind('plothover', function(event, pos, item) {
  917.         var tolerance = this.range_.resolution.pointResolution;
  918.  
  919.         grid.hovers.forEach(function(hover) {
  920.           hover.div.hidden = hover.x < pos.x - tolerance ||
  921.               hover.x > pos.x + tolerance;
  922.         });
  923.  
  924.       }.bind(this));
  925.  
  926.       $(window).resize(function() {
  927.         if (this.resizeTimer_ != null)
  928.           clearTimeout(this.resizeTimer_);
  929.         this.resizeTimer_ = setTimeout(this.checkResize_.bind(this),
  930.             resizeDelay_);
  931.       }.bind(this));
  932.     },
  933.  
  934.     /**
  935.      * |resizeDelay_| ms have elapsed since the last resize event, and the timer
  936.      * for redrawing has triggered. Clear it, and redraw all the charts.
  937.      * @private
  938.      */
  939.     checkResize_: function() {
  940.       clearTimeout(this.resizeTimer_);
  941.       this.resizeTimer_ = null;
  942.  
  943.       this.drawCharts();
  944.     },
  945.  
  946.     /**
  947.      * Set the time range for which to display metrics and events. For
  948.      * now, the time range always ends at 'now', but future implementations
  949.      * may allow time ranges not so anchored. Also set the format string for
  950.      * Flot.
  951.      *
  952.      * @param {TimeResolution} resolution
  953.      *     The time resolution at which to display the data.
  954.      * @param {number} end Ending time, in ms since epoch, to which to
  955.      *     set the new time range.
  956.      * @param {boolean} autoRefresh Indicates whether we should restart the
  957.      *     range-update timer.
  958.      */
  959.     setTimeRange: function(resolution, end, autoRefresh) {
  960.       // If we have a timer and we are no longer updating, or if we need a timer
  961.       // for a different resolution, disable the current timer.
  962.       if (this.updateTimer_ &&
  963.               (this.range_.resolution != resolution || !autoRefresh)) {
  964.         clearInterval(this.updateTimer_);
  965.         this.updateTimer_ = null;
  966.       }
  967.  
  968.       if (autoRefresh && !this.updateTimer_) {
  969.         this.updateTimer_ = setInterval(
  970.             this.forwardTime.bind(this),
  971.             intervalMultiple_ * resolution.pointResolution);
  972.       }
  973.  
  974.       this.range_.resolution = resolution;
  975.       this.range_.end = Math.floor(end / resolution.pointResolution) *
  976.           resolution.pointResolution;
  977.       this.range_.start = this.range_.end - resolution.timeSpan;
  978.       this.setTimeFormat_();
  979.  
  980.       if (this.isInitialized_())
  981.         this.refreshAll();
  982.     },
  983.  
  984.     /**
  985.      * Set the format string for Flot. For time formats, we display the time
  986.      * if we are showing data only for the current day; we display the month,
  987.      * day, and time if we are showing data for multiple days at a fine
  988.      * resolution; we display the month and day if we are showing data for
  989.      * multiple days within the same year at course resolution; and we display
  990.      * the year, month, and day if we are showing data for multiple years.
  991.      * @private
  992.      */
  993.     setTimeFormat_: function() {
  994.       // If the range is set to a week or less, then we will need to show times.
  995.       if (this.range_.resolution.id <= TimeResolutions_['week'].id) {
  996.         var dayStart = new Date();
  997.         dayStart.setHours(0);
  998.         dayStart.setMinutes(0);
  999.  
  1000.         if (this.range_.start >= dayStart.getTime())
  1001.           this.range_.format = TimeFormats_['time'];
  1002.         else
  1003.           this.range_.format = TimeFormats_['monthDayTime'];
  1004.       } else {
  1005.         var yearStart = new Date();
  1006.         yearStart.setMonth(0);
  1007.         yearStart.setDate(0);
  1008.  
  1009.         if (this.range_.start >= yearStart.getTime())
  1010.           this.range_.format = TimeFormats_['monthDay'];
  1011.         else
  1012.           this.range_.format = TimeFormats_['yearMonthDay'];
  1013.       }
  1014.     },
  1015.  
  1016.     /**
  1017.      * Back up the time range by 1/2 of its current span, and cause chart
  1018.      * redraws.
  1019.      */
  1020.     backTime: function() {
  1021.       this.setTimeRange(this.range_.resolution,
  1022.                         this.range_.end - this.range_.resolution.timeSpan / 2,
  1023.                         false);
  1024.     },
  1025.  
  1026.     /**
  1027.      * Advance the time range by 1/2 of its current span, or up to the point
  1028.      * where it ends at the present time, whichever is less.
  1029.      */
  1030.     forwardTime: function() {
  1031.       var now = Date.now();
  1032.       var newEnd =
  1033.           Math.min(now, this.range_.end + this.range_.resolution.timeSpan / 2);
  1034.  
  1035.       this.setTimeRange(this.range_.resolution, newEnd, newEnd == now);
  1036.     },
  1037.  
  1038.     /**
  1039.      * Set the aggregation method.
  1040.      * @param {number} methodId The id of the aggregation method.
  1041.      */
  1042.     setAggregationMethod: function(methodId) {
  1043.       if (methodId != aggregationMethodNone)
  1044.         this.hideWarning('no-aggregation-warning');
  1045.       else
  1046.         this.showWarning('no-aggregation-warning');
  1047.  
  1048.       this.aggregationMethod = methodId;
  1049.       if (this.isInitialized_())
  1050.         this.refreshMetrics();
  1051.     },
  1052.  
  1053.     /**
  1054.      * Add a new metric to the display, fetching its data and triggering a
  1055.      * chart redraw.
  1056.      * @param {number} metricId The id of the metric to start displaying.
  1057.      */
  1058.     addMetric: function(metricId) {
  1059.       var metric = this.metricDetailsMap_[metricId];
  1060.       metric.enabled = true;
  1061.       this.refreshMetrics();
  1062.     },
  1063.  
  1064.     /**
  1065.      * Remove a metric from its homechart, triggering a chart redraw.
  1066.      * @param {number} metricId The metric to stop displaying.
  1067.      */
  1068.     dropMetric: function(metricId) {
  1069.       var metric = this.metricDetailsMap_[metricId];
  1070.       metric.enabled = false;
  1071.       this.drawCharts();
  1072.     },
  1073.  
  1074.     /**
  1075.      * Refresh all metrics which are active on the graph in one call to the
  1076.      * webui. Results will be returned in getMetricsCallback().
  1077.      */
  1078.     refreshMetrics: function() {
  1079.       var metrics = [];
  1080.  
  1081.       for (var metric in this.metricDetailsMap_) {
  1082.         if (this.metricDetailsMap_[metric].enabled)
  1083.           metrics.push(this.metricDetailsMap_[metric].metricId);
  1084.       }
  1085.  
  1086.       if (!metrics.length)
  1087.         return;
  1088.  
  1089.       this.awaitingDataCalls_.metrics = true;
  1090.       chrome.send('getMetrics',
  1091.                   [metrics,
  1092.                    this.range_.start, this.range_.end,
  1093.                    this.range_.resolution.pointResolution,
  1094.                    this.aggregationMethod]);
  1095.     },
  1096.  
  1097.     /**
  1098.      * The callback from refreshing the metrics. The resulting metrics will be
  1099.      * returned in a list, containing for each active metric a list of data
  1100.      * point series, representing the time periods for which PerformanceMonitor
  1101.      * was active. These data will be in sorted order, and will be aggregated
  1102.      * according to |aggregationMethod_|. These data are put into a Flot-style
  1103.      * series, with each point stored in an array of length 2, comprised of the
  1104.      * time and the value of the point.
  1105.      * @param Array<{
  1106.      *   metricId: number,
  1107.      *   data: Array<{time: number, value: number}>,
  1108.      *   maxValue: number
  1109.      * }> results The data for the requested metrics.
  1110.      */
  1111.     getMetricsCallback: function(results) {
  1112.       results.forEach(function(metric) {
  1113.         var metricDetails = this.metricDetailsMap_[metric.metricId];
  1114.  
  1115.         metricDetails.data = [];
  1116.  
  1117.         // Each data series sent back represents a interval for which
  1118.         // PerformanceMonitor was active. Iterate through the points of each
  1119.         // series, converting them to Flot standard (an array of time, value
  1120.         // pairs).
  1121.         metric.metrics.forEach(function(series) {
  1122.           var seriesData = [];
  1123.           series.forEach(function(point) {
  1124.             seriesData.push([point.time - timezoneOffset_, point.value]);
  1125.           });
  1126.           metricDetails.data.push(seriesData);
  1127.         });
  1128.  
  1129.         metricDetails.maxValue = Math.max(metricDetails.maxValue,
  1130.                                           metric.maxValue);
  1131.       }, this);
  1132.  
  1133.       this.awaitingDataCalls_.metrics = false;
  1134.       this.drawCharts();
  1135.     },
  1136.  
  1137.     /**
  1138.      * Add a new event to the display, fetching its data and triggering a
  1139.      * redraw.
  1140.      * @param {number} eventType The type of event to start displaying.
  1141.      */
  1142.     addEventType: function(eventId) {
  1143.       this.eventDetailsMap_[eventId].enabled = true;
  1144.       this.refreshEvents();
  1145.     },
  1146.  
  1147.     /*
  1148.      * Remove an event from the display, triggering a redraw.
  1149.      * @param {number} eventId The type of event to stop displaying.
  1150.      */
  1151.     dropEventType: function(eventId) {
  1152.       this.eventDetailsMap_[eventId].enabled = false;
  1153.       this.drawCharts();
  1154.     },
  1155.  
  1156.     /**
  1157.      * Refresh all events which are active on the graph in one call to the
  1158.      * webui. Results will be returned in getEventsCallback().
  1159.      */
  1160.     refreshEvents: function() {
  1161.       var events = [];
  1162.       for (var eventType in this.eventDetailsMap_) {
  1163.         if (this.eventDetailsMap_[eventType].enabled)
  1164.           events.push(this.eventDetailsMap_[eventType].eventId);
  1165.       }
  1166.       if (!events.length)
  1167.         return;
  1168.  
  1169.       this.awaitingDataCalls_.events = true;
  1170.       chrome.send('getEvents', [events, this.range_.start, this.range_.end]);
  1171.     },
  1172.  
  1173.     /**
  1174.      * The callback from refreshing events. Resulting events are stored in a
  1175.      * list object, which contains for each event type requested a series
  1176.      * of event points. Each event point contains a time and an arbitrary list
  1177.      * of additional properties to be displayed as a tooltip message for the
  1178.      * event.
  1179.      * @param Array.<{
  1180.      *   eventId: number,
  1181.      *   Array.<{time: number}>
  1182.      * }> results The collection of events for the requested types.
  1183.      */
  1184.     getEventsCallback: function(results) {
  1185.       results.forEach(function(eventSet) {
  1186.         var eventType = this.eventDetailsMap_[eventSet.eventId];
  1187.  
  1188.         eventSet.events.forEach(function(eventData) {
  1189.           eventData.time -= timezoneOffset_;
  1190.         });
  1191.         eventType.data = eventSet.events;
  1192.       }, this);
  1193.  
  1194.       this.awaitingDataCalls_.events = false;
  1195.       this.drawCharts();
  1196.     },
  1197.  
  1198.     /**
  1199.      * Create and return an array of 'markings' (per Flot), representing
  1200.      * vertical lines at the event time, in the event's color. Also add
  1201.      * (not per Flot) a |popupTitle| property to each, to be used for
  1202.      * labeling description popups.
  1203.      * @return {!Array.<{
  1204.      *   color: string,
  1205.      *   popupContent: string,
  1206.      *   xaxis: {from: number, to: number}
  1207.      * }>} A marks data structure for Flot to use.
  1208.      * @private
  1209.      */
  1210.     getEventMarks_: function() {
  1211.       var enabledEvents = [];
  1212.       var markings = [];
  1213.       var explanation;
  1214.       var date;
  1215.  
  1216.       for (var eventType in this.eventDetailsMap_) {
  1217.         if (this.eventDetailsMap_[eventType].enabled)
  1218.           enabledEvents.push(this.eventDetailsMap_[eventType]);
  1219.       }
  1220.  
  1221.       enabledEvents.forEach(function(eventValue) {
  1222.         eventValue.data.forEach(function(point) {
  1223.           if (point.time >= this.range_.start - timezoneOffset_ &&
  1224.               point.time <= this.range_.end - timezoneOffset_) {
  1225.             date = new Date(point.time + timezoneOffset_);
  1226.             explanation = '<b>' + eventValue.popupTitle + '<br/>' +
  1227.                 date.toLocaleString() + '</b><br/>';
  1228.  
  1229.             for (var key in point) {
  1230.               if (key != 'time') {
  1231.                 var datum = point[key];
  1232.  
  1233.                 // We display all fields with a label-value pair.
  1234.                 if ('label' in datum && 'value' in datum) {
  1235.                   explanation = explanation + '<b>' + datum.label + ': </b>' +
  1236.                       datum.value + ' <br/>';
  1237.                 }
  1238.               }
  1239.             }
  1240.             markings.push({
  1241.               color: eventValue.color,
  1242.               popupContent: explanation,
  1243.               xaxis: { from: point.time, to: point.time }
  1244.             });
  1245.           } else {
  1246.             console.log('Event out of time range ' + this.range_.start +
  1247.                 ' -> ' + this.range_.end + ' at: ' + point.time);
  1248.           }
  1249.         }, this);
  1250.       }, this);
  1251.  
  1252.       return markings;
  1253.     },
  1254.  
  1255.     /**
  1256.      * Return an object containing an array of series for Flot to chart, as well
  1257.      * as a series of axes (currently this will only be one axis).
  1258.      * @param {Array.<PerformanceMonitor.MetricDetails>} activeMetrics
  1259.      *     The metrics for which we are generating series.
  1260.      * @return {!{
  1261.      *   series: !Array.<{
  1262.      *     color: string,
  1263.      *     data: !Array<{time: number, value: number},
  1264.      *     yaxis: {min: number, max: number, labelWidth: number}
  1265.      *   },
  1266.      *   yaxes: !Array.<{min: number, max: number, labelWidth: number}>
  1267.      * }}
  1268.      * @private
  1269.      */
  1270.     getChartSeriesAndAxes_: function(activeMetrics) {
  1271.       var seriesList = [];
  1272.       var axisList = [];
  1273.       var axisMap = {};
  1274.       activeMetrics.forEach(function(metric) {
  1275.         var categoryId = metric.category.metricCategoryId;
  1276.         var yaxisNumber = axisMap[categoryId];
  1277.  
  1278.         // Add a new y-axis if we are encountering this category of metric
  1279.         // for the first time. Otherwise, update the existing y-axis with
  1280.         // a new max value if needed. (Presently, we expect only one category
  1281.         // of metric per chart, but this design permits more in the future.)
  1282.         if (yaxisNumber === undefined) {
  1283.           axisList.push({min: 0,
  1284.                          max: metric.maxValue * yAxisMargin_,
  1285.                          labelWidth: 60});
  1286.           axisMap[categoryId] = yaxisNumber = axisList.length;
  1287.         } else {
  1288.           axisList[yaxisNumber - 1].max =
  1289.               Math.max(axisList[yaxisNumber - 1].max,
  1290.                        metric.maxValue * yAxisMargin_);
  1291.         }
  1292.  
  1293.         // Create a Flot-style series for each data series in the metric.
  1294.         for (var i = 0; i < metric.data.length; ++i) {
  1295.           seriesList.push({
  1296.             color: metric.color,
  1297.             data: metric.data[i],
  1298.             label: i == 0 ? metric.name : null,
  1299.             yaxis: yaxisNumber
  1300.           });
  1301.         }
  1302.       }, this);
  1303.  
  1304.       return { series: seriesList, yaxes: axisList };
  1305.     },
  1306.  
  1307.     /**
  1308.      * Draw each chart which has at least one enabled metric, along with all
  1309.      * the event markers, if and only if we do not have outstanding calls for
  1310.      * data.
  1311.      */
  1312.     drawCharts: function() {
  1313.       // If we are currently waiting for data, do nothing - the callbacks will
  1314.       // re-call drawCharts when they are done. This way, we can avoid any
  1315.       // conflicts.
  1316.       if (this.fetchingData_())
  1317.         return;
  1318.  
  1319.       // All charts will share the same xaxis and events.
  1320.       var eventMarks = this.getEventMarks_();
  1321.       var xaxis = {
  1322.         mode: 'time',
  1323.         timeformat: this.range_.format,
  1324.         min: this.range_.start - timezoneOffset_,
  1325.         max: this.range_.end - timezoneOffset_
  1326.       };
  1327.  
  1328.       this.charts_.forEach(function(chart) {
  1329.         var activeMetrics = [];
  1330.         chart.metricIds.forEach(function(id) {
  1331.           if (this.metricDetailsMap_[id].enabled)
  1332.             activeMetrics.push(this.metricDetailsMap_[id]);
  1333.         }, this);
  1334.  
  1335.         if (!activeMetrics.length) {
  1336.           chart.hidden = true;
  1337.           return;
  1338.         }
  1339.  
  1340.         chart.mainDiv.hidden = false;
  1341.  
  1342.         var chartData = this.getChartSeriesAndAxes_(activeMetrics);
  1343.  
  1344.         // There is the possibility that we have no data for this particular
  1345.         // time window and metric, but Flot will not draw the grid without at
  1346.         // least one data point (regardless of whether that datapoint is
  1347.         // displayed). Thus, we will add the point (-1, -1) (which is guaranteed
  1348.         // not to show with our axis bounds), and force Flot to show the chart.
  1349.         if (chartData.series.length == 0)
  1350.           chartData.series = [[-1, -1]];
  1351.  
  1352.         chart.plot = $.plot(chart.grid, chartData.series, {
  1353.           yaxes: chartData.yaxes,
  1354.           xaxis: xaxis,
  1355.           points: { show: true, radius: 1},
  1356.           lines: { show: true},
  1357.           grid: {
  1358.             markings: eventMarks,
  1359.             hoverable: true,
  1360.             autoHighlight: true,
  1361.             backgroundColor: { colors: ['#fff', '#f0f6fc'] },
  1362.           },
  1363.         });
  1364.  
  1365.         // For each event in |eventMarks|, create also a label div, with left
  1366.         // edge colinear with the event vertical line. Top of label is
  1367.         // presently a hack-in, putting labels in three tiers of 25px height
  1368.         // each to avoid overlap. Will need something better.
  1369.         var labelTemplate = $('#label-template')[0];
  1370.         for (var i = 0; i < eventMarks.length; i++) {
  1371.           var mark = eventMarks[i];
  1372.           var point = chart.plot.pointOffset(
  1373.               {x: mark.xaxis.to, y: chartData.yaxes[0].max, yaxis: 1});
  1374.           var labelDiv = labelTemplate.cloneNode(true);
  1375.           labelDiv.innerHTML = mark.popupContent;
  1376.           labelDiv.style.left = point.left + 'px';
  1377.           labelDiv.style.top = (point.top + 100 * (i % 3)) + 'px';
  1378.  
  1379.           chart.grid.appendChild(labelDiv);
  1380.           labelDiv.hidden = true;
  1381.           chart.grid.hovers.push({x: mark.xaxis.to, div: labelDiv});
  1382.         }
  1383.       }, this);
  1384.     },
  1385.   };
  1386.   return {
  1387.     PerformanceMonitor: PerformanceMonitor
  1388.   };
  1389. });
  1390.  
  1391. var PerformanceMonitor = new performance_monitor.PerformanceMonitor();
  1392.