12da489cd246702bee5938545b18a6f710ed214bcJamie Gennis// Copyright (c) 2012 The Chromium Authors. All rights reserved. 22da489cd246702bee5938545b18a6f710ed214bcJamie Gennis// Use of this source code is governed by a BSD-style license that can be 32da489cd246702bee5938545b18a6f710ed214bcJamie Gennis// found in the LICENSE file. 42da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 52da489cd246702bee5938545b18a6f710ed214bcJamie Gennis'use strict'; 62da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 72da489cd246702bee5938545b18a6f710ed214bcJamie Gennis/** 82da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * @fileoverview Implements a WebSocket client that receives 92da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * a stream of slices from a server. 102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis */ 122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1366a37686207944273ced825e0e8b6b6375f8c3deJamie Gennisbase.require('base.events'); 1466a37686207944273ced825e0e8b6b6375f8c3deJamie Gennisbase.require('tracing.trace_model'); 1566a37686207944273ced825e0e8b6b6375f8c3deJamie Gennisbase.require('tracing.trace_model.slice'); 162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1788448d9ae4dfff1805045790ef5f32495d62abccJeff Brownbase.exportTo('tracing.importer', function() { 182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var STATE_PAUSED = 0x1; 202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var STATE_CAPTURING = 0x2; 212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis /** 2388448d9ae4dfff1805045790ef5f32495d62abccJeff Brown * Converts a stream of trace data from a websocket into a model. 242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * Events consumed by this importer have the following JSON structure: 262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * { 282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 'cmd': 'commandName', 292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * ... command specific data 302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * } 312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * The importer understands 2 commands: 332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 'ptd' (Process Thread Data) 342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 'pcd' (Process Counter Data) 352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * The command specific data is as follows: 372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * { 392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 'pid': 'Remote Process Id', 402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 'td': { 412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 'n': 'Thread Name Here', 422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 's: [ { 432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 'l': 'Slice Label', 442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 's': startTime, 452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 'e': endTime 462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * }, ... ] 472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * } 482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * } 492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * { 512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 'pid' 'Remote Process Id', 522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 'cd': { 532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 'n': 'Counter Name', 542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 'sn': ['Series Name',...] 552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 'sc': [seriesColor, ...] 562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 'c': [ 572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * { 582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 't': timestamp, 592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * 'v': [value0, value1, ...] 602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * }, 612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * .... 622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * ] 632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * } 642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * } 6588448d9ae4dfff1805045790ef5f32495d62abccJeff Brown * @param {Model} model that will be updated 662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * when events are received. 672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * @constructor 682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis */ 692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis function TimelineStreamImporter(model) { 702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var self = this; 712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.model_ = model; 722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.connection_ = undefined; 732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.state_ = STATE_CAPTURING; 742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.connectionOpenHandler_ = 7566a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis this.connectionOpenHandler_.bind(this); 762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.connectionCloseHandler_ = 7766a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis this.connectionCloseHandler_.bind(this); 782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.connectionErrorHandler_ = 7966a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis this.connectionErrorHandler_.bind(this); 802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.connectionMessageHandler_ = 8166a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis this.connectionMessageHandler_.bind(this); 822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis } 832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis TimelineStreamImporter.prototype = { 852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis __proto__: base.EventTarget.prototype, 862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis cleanup_: function() { 882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if (!this.connection_) 892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return; 902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.connection_.removeEventListener('open', 9166a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis this.connectionOpenHandler_); 922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.connection_.removeEventListener('close', 9366a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis this.connectionCloseHandler_); 942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.connection_.removeEventListener('error', 9566a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis this.connectionErrorHandler_); 962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.connection_.removeEventListener('message', 9766a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis this.connectionMessageHandler_); 982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis }, 992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis connectionOpenHandler_: function() { 1012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.dispatchEvent({'type': 'connect'}); 1022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis }, 1032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis connectionCloseHandler_: function() { 1052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.dispatchEvent({'type': 'disconnect'}); 1062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.cleanup_(); 1072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis }, 1082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis connectionErrorHandler_: function() { 1102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.dispatchEvent({'type': 'connectionerror'}); 1112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.cleanup_(); 1122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis }, 1132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis connectionMessageHandler_: function(event) { 1152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var packet = JSON.parse(event.data); 1162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var command = packet['cmd']; 1172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var pid = packet['pid']; 1182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var modelDirty = false; 1192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if (command == 'ptd') { 1202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var process = this.model_.getOrCreateProcess(pid); 1212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var threadData = packet['td']; 1222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var threadName = threadData['n']; 1232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var threadSlices = threadData['s']; 1242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var thread = process.getOrCreateThread(threadName); 1252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis for (var s = 0; s < threadSlices.length; s++) { 1262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var slice = threadSlices[s]; 12766a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis thread.sliceGroup.pushSlice(new tracing.trace_model.ThreadSlice( 12866a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis 'streamed', 12966a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis slice['l'], 13066a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis 0, 13166a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis slice['s'], 13266a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis {}, 13366a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis slice['e'] - slice['s'])); 1342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis } 1352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis modelDirty = true; 1362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis } else if (command == 'pcd') { 1372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var process = this.model_.getOrCreateProcess(pid); 1382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var counterData = packet['cd']; 1392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var counterName = counterData['n']; 1402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var counterSeriesNames = counterData['sn']; 1412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var counterSeriesColors = counterData['sc']; 1422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var counterValues = counterData['c']; 1432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var counter = process.getOrCreateCounter('streamed', counterName); 1442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if (counterSeriesNames.length != counterSeriesColors.length) { 1452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var importError = 'Streamed counter name length does not match' + 1462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'counter color length' + counterSeriesNames.length + 1472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis ' vs ' + counterSeriesColors.length; 1482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.model_.importErrors.push(importError); 1492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return; 1502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis } 1516833e18b1d4077bf3a727b4422cc2acdbeee35a7Jamie Gennis if (counter.series.length === 0) { 1526833e18b1d4077bf3a727b4422cc2acdbeee35a7Jamie Gennis for (var i = 0; i < counterSeriesNames.length; ++i) { 1536833e18b1d4077bf3a727b4422cc2acdbeee35a7Jamie Gennis counter.addSeries(new tracing.trace_model.CounterSeries( 1546833e18b1d4077bf3a727b4422cc2acdbeee35a7Jamie Gennis counterSeriesNames[i], counterSeriesColors[i])); 1556833e18b1d4077bf3a727b4422cc2acdbeee35a7Jamie Gennis } 1562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis } else { 1576833e18b1d4077bf3a727b4422cc2acdbeee35a7Jamie Gennis if (counter.series.length != counterSeriesNames.length) { 1582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var importError = 'Streamed counter ' + counterName + 15966a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis 'changed number of seriesNames'; 1602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.model_.importErrors.push(importError); 1612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return; 1622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis } else { 1636833e18b1d4077bf3a727b4422cc2acdbeee35a7Jamie Gennis for (var i = 0; i < counter.series.length; i++) { 1646833e18b1d4077bf3a727b4422cc2acdbeee35a7Jamie Gennis var oldSeriesName = counter.series[i].name; 1652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var newSeriesName = counterSeriesNames[i]; 1666833e18b1d4077bf3a727b4422cc2acdbeee35a7Jamie Gennis 1672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if (oldSeriesName != newSeriesName) { 1682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var importError = 'Streamed counter ' + counterName + 16966a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis 'series name changed from ' + 17066a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis oldSeriesName + ' to ' + 17166a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis newSeriesName; 1722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.model_.importErrors.push(importError); 1732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return; 1742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis } 1752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis } 1762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis } 1772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis } 1782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis for (var c = 0; c < counterValues.length; c++) { 1792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis var count = counterValues[c]; 1806833e18b1d4077bf3a727b4422cc2acdbeee35a7Jamie Gennis var ts = count['t']; 1816833e18b1d4077bf3a727b4422cc2acdbeee35a7Jamie Gennis var values = count['v']; 1826833e18b1d4077bf3a727b4422cc2acdbeee35a7Jamie Gennis for (var i = 0; i < values.length; ++i) { 1836833e18b1d4077bf3a727b4422cc2acdbeee35a7Jamie Gennis counter.series[i].addSample(ts, values[i]); 1846833e18b1d4077bf3a727b4422cc2acdbeee35a7Jamie Gennis } 1852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis } 1862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis modelDirty = true; 1872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis } 1882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if (modelDirty == true) { 1892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.model_.updateBounds(); 1902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.dispatchEvent({'type': 'modelchange', 1912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'model': this.model_}); 1922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis } 1932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis }, 1942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis get connected() { 1962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if (this.connection_ !== undefined && 19766a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis this.connection_.readyState == WebSocket.OPEN) { 1982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return true; 1992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis } 2002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return false; 2012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis }, 2022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis get paused() { 2042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return this.state_ == STATE_PAUSED; 2052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis }, 2062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis /** 2082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis * Connects the stream to a websocket. 20966a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis * @param {WebSocket} wsConnection The websocket to use for the stream. 2102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis */ 2112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis connect: function(wsConnection) { 2122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.connection_ = wsConnection; 2132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.connection_.addEventListener('open', 21466a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis this.connectionOpenHandler_); 2152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.connection_.addEventListener('close', 21666a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis this.connectionCloseHandler_); 2172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.connection_.addEventListener('error', 21866a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis this.connectionErrorHandler_); 2192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.connection_.addEventListener('message', 22066a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis this.connectionMessageHandler_); 2212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis }, 2222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis pause: function() { 2242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if (this.state_ == STATE_PAUSED) 2252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis throw new Error('Already paused.'); 2262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if (!this.connection_) 2272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis throw new Error('Not connected.'); 2282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.connection_.send(JSON.stringify({'cmd': 'pause'})); 2292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.state_ = STATE_PAUSED; 2302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis }, 2312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis resume: function() { 2332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if (this.state_ == STATE_CAPTURING) 2342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis throw new Error('Already capturing.'); 2352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if (!this.connection_) 2362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis throw new Error('Not connected.'); 2372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.connection_.send(JSON.stringify({'cmd': 'resume'})); 2382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this.state_ = STATE_CAPTURING; 2392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis } 2402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis }; 2412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return { 2432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis TimelineStreamImporter: TimelineStreamImporter 2442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis }; 2452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis}); 246