Home | History | Annotate | Download | only in net_internals
      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 /**
      6  * TimelineView displays a zoomable and scrollable graph of a number of values
      7  * over time.  The TimelineView class itself is responsible primarily for
      8  * updating the TimelineDataSeries its GraphView displays.
      9  */
     10 var TimelineView = (function() {
     11   'use strict';
     12 
     13   // We inherit from HorizontalSplitView.
     14   var superClass = HorizontalSplitView;
     15 
     16   /**
     17    * @constructor
     18    */
     19   function TimelineView() {
     20     assertFirstConstructorCall(TimelineView);
     21 
     22     this.graphView_ = new TimelineGraphView(
     23         TimelineView.GRAPH_DIV_ID,
     24         TimelineView.GRAPH_CANVAS_ID,
     25         TimelineView.SCROLLBAR_DIV_ID,
     26         TimelineView.SCROLLBAR_INNER_DIV_ID);
     27 
     28     // Call superclass's constructor.
     29 
     30     var selectionView = new DivView(TimelineView.SELECTION_DIV_ID);
     31     superClass.call(this, selectionView, this.graphView_);
     32 
     33     this.selectionDivFullWidth_ = selectionView.getWidth();
     34     $(TimelineView.SELECTION_TOGGLE_ID).onclick =
     35         this.toggleSelectionDiv_.bind(this);
     36 
     37     // Interval id returned by window.setInterval for update timer.
     38     this.updateIntervalId_ = null;
     39 
     40     // List of DataSeries.  These are shared with the TimelineGraphView.  The
     41     // TimelineView updates their state, the TimelineGraphView reads their
     42     // state and draws them.
     43     this.dataSeries_ = [];
     44 
     45     // DataSeries depend on some of the global constants, so they're only
     46     // created once constants have been received.  We also use this message to
     47     // recreate DataSeries when log files are being loaded.
     48     g_browser.addConstantsObserver(this);
     49 
     50     // We observe new log entries to determine the range of the graph, and pass
     51     // them on to each DataSource.  We initialize the graph range to initially
     52     // include all events, but after that, we only update it to be the current
     53     // time on a timer.
     54     EventsTracker.getInstance().addLogEntryObserver(this);
     55     this.graphRangeInitialized_ = false;
     56   }
     57 
     58   TimelineView.TAB_ID = 'tab-handle-timeline';
     59   TimelineView.TAB_NAME = 'Timeline';
     60   TimelineView.TAB_HASH = '#timeline';
     61 
     62   // IDs for special HTML elements in timeline_view.html
     63   TimelineView.GRAPH_DIV_ID = 'timeline-view-graph-div';
     64   TimelineView.GRAPH_CANVAS_ID = 'timeline-view-graph-canvas';
     65   TimelineView.SELECTION_DIV_ID = 'timeline-view-selection-div';
     66   TimelineView.SELECTION_TOGGLE_ID = 'timeline-view-selection-toggle';
     67   TimelineView.SELECTION_UL_ID = 'timeline-view-selection-ul';
     68   TimelineView.SCROLLBAR_DIV_ID = 'timeline-view-scrollbar-div';
     69   TimelineView.SCROLLBAR_INNER_DIV_ID = 'timeline-view-scrollbar-inner-div';
     70 
     71   TimelineView.OPEN_SOCKETS_ID = 'timeline-view-open-sockets';
     72   TimelineView.IN_USE_SOCKETS_ID = 'timeline-view-in-use-sockets';
     73   TimelineView.URL_REQUESTS_ID = 'timeline-view-url-requests';
     74   TimelineView.DNS_REQUESTS_ID = 'timeline-view-dns-requests';
     75   TimelineView.DNS_JOBS_ID = 'timeline-view-dns-jobs';
     76   TimelineView.BYTES_RECEIVED_ID = 'timeline-view-bytes-received';
     77   TimelineView.BYTES_SENT_ID = 'timeline-view-bytes-sent';
     78   TimelineView.DISK_CACHE_BYTES_READ_ID =
     79       'timeline-view-disk-cache-bytes-read';
     80   TimelineView.DISK_CACHE_BYTES_WRITTEN_ID =
     81       'timeline-view-disk-cache-bytes-written';
     82 
     83   // Class used for hiding the colored squares next to the labels for the
     84   // lines.
     85   TimelineView.HIDDEN_CLASS = 'timeline-view-hidden';
     86 
     87   cr.addSingletonGetter(TimelineView);
     88 
     89   // Frequency with which we increase update the end date to be the current
     90   // time, when actively capturing events.
     91   var UPDATE_INTERVAL_MS = 2000;
     92 
     93   TimelineView.prototype = {
     94     // Inherit the superclass's methods.
     95     __proto__: superClass.prototype,
     96 
     97     setGeometry: function(left, top, width, height) {
     98       superClass.prototype.setGeometry.call(this, left, top, width, height);
     99     },
    100 
    101     show: function(isVisible) {
    102       superClass.prototype.show.call(this, isVisible);
    103       // If we're hidden or not capturing events, we don't want to update the
    104       // graph's range.
    105       if (!isVisible || g_browser.isDisabled()) {
    106         this.setUpdateEndDateInterval_(0);
    107         return;
    108       }
    109 
    110       // Otherwise, update the visible range on a timer.
    111       this.setUpdateEndDateInterval_(UPDATE_INTERVAL_MS);
    112       this.updateEndDate_();
    113     },
    114 
    115     /**
    116      * Starts calling the GraphView's updateEndDate function every |intervalMs|
    117      * milliseconds.  If |intervalMs| is 0, stops calling the function.
    118      */
    119     setUpdateEndDateInterval_: function(intervalMs) {
    120       if (this.updateIntervalId_ !== null) {
    121         window.clearInterval(this.updateIntervalId_);
    122         this.updateIntervalId_ = null;
    123       }
    124       if (intervalMs > 0) {
    125         this.updateIntervalId_ =
    126             window.setInterval(this.updateEndDate_.bind(this), intervalMs);
    127       }
    128     },
    129 
    130     /**
    131      * Updates the end date of graph to be the current time, unless the
    132      * BrowserBridge is disabled.
    133      */
    134     updateEndDate_: function() {
    135       // If we loaded a log file or capturing data was stopped, stop the timer.
    136       if (g_browser.isDisabled()) {
    137         this.setUpdateEndDateInterval_(0);
    138         return;
    139       }
    140       this.graphView_.updateEndDate();
    141     },
    142 
    143     onLoadLogFinish: function(data) {
    144       this.setUpdateEndDateInterval_(0);
    145       return true;
    146     },
    147 
    148     /**
    149      * Updates the visibility state of |dataSeries| to correspond to the
    150      * current checked state of |checkBox|.  Also updates the class of
    151      * |listItem| based on the new visibility state.
    152      */
    153     updateDataSeriesVisibility_: function(dataSeries, listItem, checkBox) {
    154       dataSeries.show(checkBox.checked);
    155       if (checkBox.checked)
    156         listItem.classList.remove(TimelineView.HIDDEN_CLASS);
    157       else
    158         listItem.classList.add(TimelineView.HIDDEN_CLASS);
    159     },
    160 
    161     dataSeriesClicked_: function(dataSeries, listItem, checkBox) {
    162       this.updateDataSeriesVisibility_(dataSeries, listItem, checkBox);
    163       this.graphView_.repaint();
    164     },
    165 
    166     /**
    167      * Adds the specified DataSeries to |dataSeries_|, and hooks up
    168      * |listItemId|'s checkbox and color to correspond to the current state
    169      * of the given DataSeries.
    170      */
    171     addDataSeries_: function(dataSeries, listItemId) {
    172       this.dataSeries_.push(dataSeries);
    173       var listItem = $(listItemId);
    174       var checkBox = $(listItemId).querySelector('input');
    175 
    176       // Make sure |listItem| is visible, and then use its color for the
    177       // DataSource.
    178       listItem.classList.remove(TimelineView.HIDDEN_CLASS);
    179       dataSeries.setColor(getComputedStyle(listItem).color);
    180 
    181       this.updateDataSeriesVisibility_(dataSeries, listItem, checkBox);
    182       checkBox.onclick = this.dataSeriesClicked_.bind(this, dataSeries,
    183                                                       listItem, checkBox);
    184     },
    185 
    186     /**
    187      * Recreate all DataSeries.  Global constants must have been set before
    188      * this is called.
    189      */
    190     createDataSeries_: function() {
    191       this.graphRangeInitialized_ = false;
    192       this.dataSeries_ = [];
    193 
    194       this.addDataSeries_(new SourceCountDataSeries(
    195                               EventSourceType.SOCKET,
    196                               EventType.SOCKET_ALIVE),
    197                           TimelineView.OPEN_SOCKETS_ID);
    198 
    199       this.addDataSeries_(new SocketsInUseDataSeries(),
    200                           TimelineView.IN_USE_SOCKETS_ID);
    201 
    202       this.addDataSeries_(new SourceCountDataSeries(
    203                               EventSourceType.URL_REQUEST,
    204                               EventType.REQUEST_ALIVE),
    205                           TimelineView.URL_REQUESTS_ID);
    206 
    207       this.addDataSeries_(new SourceCountDataSeries(
    208                               EventSourceType.HOST_RESOLVER_IMPL_REQUEST,
    209                               EventType.HOST_RESOLVER_IMPL_REQUEST),
    210                           TimelineView.DNS_REQUESTS_ID);
    211 
    212       this.addDataSeries_(new SourceCountDataSeries(
    213                               EventSourceType.HOST_RESOLVER_IMPL_JOB,
    214                               EventType.HOST_RESOLVER_IMPL_JOB),
    215                           TimelineView.DNS_JOBS_ID);
    216 
    217       this.addDataSeries_(new NetworkTransferRateDataSeries(
    218                               EventType.SOCKET_BYTES_RECEIVED,
    219                               EventType.UDP_BYTES_RECEIVED),
    220                           TimelineView.BYTES_RECEIVED_ID);
    221 
    222       this.addDataSeries_(new NetworkTransferRateDataSeries(
    223                               EventType.SOCKET_BYTES_SENT,
    224                               EventType.UDP_BYTES_SENT),
    225                           TimelineView.BYTES_SENT_ID);
    226 
    227       this.addDataSeries_(new DiskCacheTransferRateDataSeries(
    228                               EventType.ENTRY_READ_DATA),
    229                           TimelineView.DISK_CACHE_BYTES_READ_ID);
    230 
    231       this.addDataSeries_(new DiskCacheTransferRateDataSeries(
    232                               EventType.ENTRY_WRITE_DATA),
    233                           TimelineView.DISK_CACHE_BYTES_WRITTEN_ID);
    234 
    235       this.graphView_.setDataSeries(this.dataSeries_);
    236     },
    237 
    238     /**
    239      * When we receive the constants, create or recreate the DataSeries.
    240      */
    241     onReceivedConstants: function(constants) {
    242       this.createDataSeries_();
    243     },
    244 
    245     /**
    246      * When all log entries are deleted, recreate the DataSeries.
    247      */
    248     onAllLogEntriesDeleted: function() {
    249       this.graphRangeInitialized_ = false;
    250       this.createDataSeries_();
    251     },
    252 
    253     onReceivedLogEntries: function(entries) {
    254       // Pass each entry to every DataSeries, one at a time.  Not having each
    255       // data series get data directly from the EventsTracker saves us from
    256       // having very un-Javascript-like destructors for when we load new,
    257       // constants and slightly simplifies DataSeries objects.
    258       for (var entry = 0; entry < entries.length; ++entry) {
    259         for (var i = 0; i < this.dataSeries_.length; ++i)
    260           this.dataSeries_[i].onReceivedLogEntry(entries[entry]);
    261       }
    262 
    263       // If this is the first non-empty set of entries we've received, or we're
    264       // viewing a loaded log file, we will need to update the date range.
    265       if (this.graphRangeInitialized_ && !MainView.isViewingLoadedLog())
    266         return;
    267       if (entries.length == 0)
    268         return;
    269 
    270       // Update the date range.
    271       var startDate;
    272       if (!this.graphRangeInitialized_) {
    273         startDate = timeutil.convertTimeTicksToDate(entries[0].time);
    274       } else {
    275         startDate = this.graphView_.getStartDate();
    276       }
    277       var endDate =
    278           timeutil.convertTimeTicksToDate(entries[entries.length - 1].time);
    279       this.graphView_.setDateRange(startDate, endDate);
    280       this.graphRangeInitialized_ = true;
    281     },
    282 
    283     toggleSelectionDiv_: function() {
    284       var toggle = $(TimelineView.SELECTION_TOGGLE_ID);
    285       var shouldCollapse = toggle.className == 'timeline-view-rotateleft';
    286 
    287       setNodeDisplay($(TimelineView.SELECTION_UL_ID), !shouldCollapse);
    288       toggle.className = shouldCollapse ?
    289           'timeline-view-rotateright' : 'timeline-view-rotateleft';
    290 
    291       // Figure out the appropriate width for the selection div.
    292       var newWidth;
    293       if (shouldCollapse) {
    294         newWidth = toggle.offsetWidth;
    295       } else {
    296         newWidth = this.selectionDivFullWidth_;
    297       }
    298 
    299       // Change the width on the selection view (doesn't matter what we
    300       // set the other values to, since we will re-layout in the next line).
    301       this.leftView_.setGeometry(0, 0, newWidth, 100);
    302 
    303       // Force a re-layout now that the left view has changed width.
    304       this.setGeometry(this.getLeft(), this.getTop(), this.getWidth(),
    305                        this.getHeight());
    306     }
    307   };
    308 
    309   return TimelineView;
    310 })();
    311