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.
8 * @fileoverview Implements a WebSocket client that receives
9 * a stream of slices from a server.
13 tvcm.require('tvcm.events');
14 tvcm.require('tracing.trace_model');
15 tvcm.require('tracing.trace_model.slice');
17 tvcm.exportTo('tracing.importer', function() {
19 var STATE_PAUSED = 0x1;
20 var STATE_CAPTURING = 0x2;
23 * Converts a stream of trace data from a websocket into a model.
25 * Events consumed by this importer have the following JSON structure:
28 * 'cmd': 'commandName',
29 * ... command specific data
32 * The importer understands 2 commands:
33 * 'ptd' (Process Thread Data)
34 * 'pcd' (Process Counter Data)
36 * The command specific data is as follows:
39 * 'pid': 'Remote Process Id',
41 * 'n': 'Thread Name Here',
51 * 'pid' 'Remote Process Id',
53 * 'n': 'Counter Name',
54 * 'sn': ['Series Name',...]
55 * 'sc': [seriesColor, ...]
59 * 'v': [value0, value1, ...]
65 * @param {Model} model that will be updated
66 * when events are received.
69 function TimelineStreamImporter(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);
84 TimelineStreamImporter.prototype = {
85 __proto__: tvcm.EventTarget.prototype,
87 cleanup_: function() {
88 if (!this.connection_)
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_);
100 connectionOpenHandler_: function() {
101 this.dispatchEvent({'type': 'connect'});
104 connectionCloseHandler_: function() {
105 this.dispatchEvent({'type': 'disconnect'});
109 connectionErrorHandler_: function() {
110 this.dispatchEvent({'type': 'connectionerror'});
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(
133 slice['e'] - slice['s']));
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 this.model_.importWarning({
147 message: 'Streamed counter name length does not match' +
148 ' counter color length' + counterSeriesNames.length +
149 ' vs ' + counterSeriesColors.length
153 if (counter.series.length === 0) {
154 for (var i = 0; i < counterSeriesNames.length; ++i) {
155 counter.addSeries(new tracing.trace_model.CounterSeries(
156 counterSeriesNames[i], counterSeriesColors[i]));
159 if (counter.series.length != counterSeriesNames.length) {
160 this.model_.importWarning({
162 message: 'Streamed counter ' + counterName +
163 ' changed number of seriesNames'
167 for (var i = 0; i < counter.series.length; i++) {
168 var oldSeriesName = counter.series[i].name;
169 var newSeriesName = counterSeriesNames[i];
171 if (oldSeriesName != newSeriesName) {
172 this.model_.importWarning({
174 message: 'Streamed counter ' + counterName +
175 ' series name changed from ' + oldSeriesName + ' to ' +
183 for (var c = 0; c < counterValues.length; c++) {
184 var count = counterValues[c];
186 var values = count['v'];
187 for (var i = 0; i < values.length; ++i) {
188 counter.series[i].addCounterSample(ts, values[i]);
193 if (modelDirty == true) {
194 this.model_.updateBounds();
195 this.dispatchEvent({'type': 'modelchange',
196 'model': this.model_});
201 if (this.connection_ !== undefined &&
202 this.connection_.readyState == WebSocket.OPEN) {
209 return this.state_ == STATE_PAUSED;
213 * Connects the stream to a websocket.
214 * @param {WebSocket} wsConnection The websocket to use for the stream.
216 connect: function(wsConnection) {
217 this.connection_ = wsConnection;
218 this.connection_.addEventListener('open',
219 this.connectionOpenHandler_);
220 this.connection_.addEventListener('close',
221 this.connectionCloseHandler_);
222 this.connection_.addEventListener('error',
223 this.connectionErrorHandler_);
224 this.connection_.addEventListener('message',
225 this.connectionMessageHandler_);
229 if (this.state_ == STATE_PAUSED)
230 throw new Error('Already paused.');
231 if (!this.connection_)
232 throw new Error('Not connected.');
233 this.connection_.send(JSON.stringify({'cmd': 'pause'}));
234 this.state_ = STATE_PAUSED;
238 if (this.state_ == STATE_CAPTURING)
239 throw new Error('Already capturing.');
240 if (!this.connection_)
241 throw new Error('Not connected.');
242 this.connection_.send(JSON.stringify({'cmd': 'resume'}));
243 this.state_ = STATE_CAPTURING;
248 TimelineStreamImporter: TimelineStreamImporter