• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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'use strict';
6
7/**
8 * @fileoverview Implements a WebSocket client that receives
9 * a stream of slices from a server.
10 *
11 */
12
13base.require('base.events');
14base.require('tracing.trace_model');
15base.require('tracing.trace_model.slice');
16
17base.exportTo('tracing.importer', function() {
18
19  var STATE_PAUSED = 0x1;
20  var STATE_CAPTURING = 0x2;
21
22  /**
23   * Converts a stream of trace data from a websocket into a model.
24   *
25   * Events consumed by this importer have the following JSON structure:
26   *
27   * {
28   *   'cmd': 'commandName',
29   *   ... command specific data
30   * }
31   *
32   * The importer understands 2 commands:
33   *      'ptd' (Process Thread Data)
34   *      'pcd' (Process Counter Data)
35   *
36   * The command specific data is as follows:
37   *
38   * {
39   *   'pid': 'Remote Process Id',
40   *   'td': {
41   *                  'n': 'Thread Name Here',
42   *                  's: [ {
43   *                              'l': 'Slice Label',
44   *                              's': startTime,
45   *                              'e': endTime
46   *                              }, ... ]
47   *         }
48   * }
49   *
50   * {
51   *  'pid' 'Remote Process Id',
52   *  'cd': {
53   *      'n': 'Counter Name',
54   *      'sn': ['Series Name',...]
55   *      'sc': [seriesColor, ...]
56   *      'c': [
57   *            {
58   *              't': timestamp,
59   *              'v': [value0, value1, ...]
60   *            },
61   *            ....
62   *           ]
63   *       }
64   * }
65   * @param {Model} model that will be updated
66   * when events are received.
67   * @constructor
68   */
69  function TimelineStreamImporter(model) {
70    var self = this;
71    this.model_ = model;
72    this.connection_ = undefined;
73    this.state_ = STATE_CAPTURING;
74    this.connectionOpenHandler_ =
75        this.connectionOpenHandler_.bind(this);
76    this.connectionCloseHandler_ =
77        this.connectionCloseHandler_.bind(this);
78    this.connectionErrorHandler_ =
79        this.connectionErrorHandler_.bind(this);
80    this.connectionMessageHandler_ =
81        this.connectionMessageHandler_.bind(this);
82  }
83
84  TimelineStreamImporter.prototype = {
85    __proto__: base.EventTarget.prototype,
86
87    cleanup_: function() {
88      if (!this.connection_)
89        return;
90      this.connection_.removeEventListener('open',
91          this.connectionOpenHandler_);
92      this.connection_.removeEventListener('close',
93          this.connectionCloseHandler_);
94      this.connection_.removeEventListener('error',
95          this.connectionErrorHandler_);
96      this.connection_.removeEventListener('message',
97          this.connectionMessageHandler_);
98    },
99
100    connectionOpenHandler_: function() {
101      this.dispatchEvent({'type': 'connect'});
102    },
103
104    connectionCloseHandler_: function() {
105      this.dispatchEvent({'type': 'disconnect'});
106      this.cleanup_();
107    },
108
109    connectionErrorHandler_: function() {
110      this.dispatchEvent({'type': 'connectionerror'});
111      this.cleanup_();
112    },
113
114    connectionMessageHandler_: function(event) {
115      var packet = JSON.parse(event.data);
116      var command = packet['cmd'];
117      var pid = packet['pid'];
118      var modelDirty = false;
119      if (command == 'ptd') {
120        var process = this.model_.getOrCreateProcess(pid);
121        var threadData = packet['td'];
122        var threadName = threadData['n'];
123        var threadSlices = threadData['s'];
124        var thread = process.getOrCreateThread(threadName);
125        for (var s = 0; s < threadSlices.length; s++) {
126          var slice = threadSlices[s];
127          thread.sliceGroup.pushSlice(new tracing.trace_model.ThreadSlice(
128              'streamed',
129              slice['l'],
130              0,
131              slice['s'],
132              {},
133              slice['e'] - slice['s']));
134        }
135        modelDirty = true;
136      } else if (command == 'pcd') {
137        var process = this.model_.getOrCreateProcess(pid);
138        var counterData = packet['cd'];
139        var counterName = counterData['n'];
140        var counterSeriesNames = counterData['sn'];
141        var counterSeriesColors = counterData['sc'];
142        var counterValues = counterData['c'];
143        var counter = process.getOrCreateCounter('streamed', counterName);
144        if (counterSeriesNames.length != counterSeriesColors.length) {
145          var importError = 'Streamed counter name length does not match' +
146                            'counter color length' + counterSeriesNames.length +
147                            ' vs ' + counterSeriesColors.length;
148          this.model_.importErrors.push(importError);
149          return;
150        }
151        if (counter.series.length === 0) {
152          for (var i = 0; i < counterSeriesNames.length; ++i) {
153            counter.addSeries(new tracing.trace_model.CounterSeries(
154                counterSeriesNames[i], counterSeriesColors[i]));
155          }
156        } else {
157          if (counter.series.length != counterSeriesNames.length) {
158            var importError = 'Streamed counter ' + counterName +
159                'changed number of seriesNames';
160            this.model_.importErrors.push(importError);
161            return;
162          } else {
163            for (var i = 0; i < counter.series.length; i++) {
164              var oldSeriesName = counter.series[i].name;
165              var newSeriesName = counterSeriesNames[i];
166
167              if (oldSeriesName != newSeriesName) {
168                var importError = 'Streamed counter ' + counterName +
169                    'series name changed from ' +
170                    oldSeriesName + ' to ' +
171                    newSeriesName;
172                this.model_.importErrors.push(importError);
173                return;
174              }
175            }
176          }
177        }
178        for (var c = 0; c < counterValues.length; c++) {
179          var count = counterValues[c];
180          var ts = count['t'];
181          var values = count['v'];
182          for (var i = 0; i < values.length; ++i) {
183            counter.series[i].addSample(ts, values[i]);
184          }
185        }
186        modelDirty = true;
187      }
188      if (modelDirty == true) {
189        this.model_.updateBounds();
190        this.dispatchEvent({'type': 'modelchange',
191          'model': this.model_});
192      }
193    },
194
195    get connected() {
196      if (this.connection_ !== undefined &&
197          this.connection_.readyState == WebSocket.OPEN) {
198        return true;
199      }
200      return false;
201    },
202
203    get paused() {
204      return this.state_ == STATE_PAUSED;
205    },
206
207    /**
208     * Connects the stream to a websocket.
209     * @param {WebSocket} wsConnection The websocket to use for the stream.
210     */
211    connect: function(wsConnection) {
212      this.connection_ = wsConnection;
213      this.connection_.addEventListener('open',
214          this.connectionOpenHandler_);
215      this.connection_.addEventListener('close',
216          this.connectionCloseHandler_);
217      this.connection_.addEventListener('error',
218          this.connectionErrorHandler_);
219      this.connection_.addEventListener('message',
220          this.connectionMessageHandler_);
221    },
222
223    pause: function() {
224      if (this.state_ == STATE_PAUSED)
225        throw new Error('Already paused.');
226      if (!this.connection_)
227        throw new Error('Not connected.');
228      this.connection_.send(JSON.stringify({'cmd': 'pause'}));
229      this.state_ = STATE_PAUSED;
230    },
231
232    resume: function() {
233      if (this.state_ == STATE_CAPTURING)
234        throw new Error('Already capturing.');
235      if (!this.connection_)
236        throw new Error('Not connected.');
237      this.connection_.send(JSON.stringify({'cmd': 'resume'}));
238      this.state_ = STATE_CAPTURING;
239    }
240  };
241
242  return {
243    TimelineStreamImporter: TimelineStreamImporter
244  };
245});
246