1# Copyright 2013 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 5from telemetry.core.backends.chrome import timeline_recorder 6from telemetry.timeline import inspector_timeline_data 7 8 9class TabBackendException(Exception): 10 """An exception which indicates an error response from devtools inspector.""" 11 pass 12 13 14class InspectorTimeline(timeline_recorder.TimelineRecorder): 15 """Implementation of dev tools timeline.""" 16 17 class Recorder(object): 18 """Utility class to Start and Stop recording timeline. 19 20 Example usage: 21 22 with inspector_timeline.InspectorTimeline.Recorder(tab): 23 # Something to run while the timeline is recording. 24 25 This is an alternative to directly calling the Start and Stop methods below. 26 """ 27 def __init__(self, tab): 28 self._tab = tab 29 30 def __enter__(self): 31 self._tab.StartTimelineRecording() 32 33 def __exit__(self, *args): 34 self._tab.StopTimelineRecording() 35 36 def __init__(self, inspector_backend): 37 super(InspectorTimeline, self).__init__() 38 self._inspector_backend = inspector_backend 39 self._is_recording = False 40 self._raw_events = None 41 42 @property 43 def is_timeline_recording_running(self): 44 return self._is_recording 45 46 def Start(self): 47 """Starts recording.""" 48 assert not self._is_recording, 'Start should only be called once.' 49 self._raw_events = None 50 self._is_recording = True 51 self._inspector_backend.RegisterDomain( 52 'Timeline', self._OnNotification, self._OnClose) 53 # The 'bufferEvents' parameter below means that events should not be sent 54 # individually as messages, but instead all at once when a Timeline.stop 55 # request is sent. 56 request = { 57 'method': 'Timeline.start', 58 'params': {'bufferEvents': True}, 59 } 60 self._SendSyncRequest(request) 61 62 def Stop(self): 63 """Stops recording and returns timeline event data.""" 64 if not self._is_recording: 65 return None 66 request = {'method': 'Timeline.stop'} 67 result = self._SendSyncRequest(request) 68 self._inspector_backend.UnregisterDomain('Timeline') 69 self._is_recording = False 70 71 # TODO: Backward compatibility. Needs to be removed when 72 # M38 becomes stable. 73 if 'events' in result: 74 raw_events = result['events'] 75 else: # In M38 events will arrive via Timeline.stopped event. 76 raw_events = self._raw_events 77 self._raw_events = None 78 return inspector_timeline_data.InspectorTimelineData(raw_events) 79 80 def _SendSyncRequest(self, request, timeout=60): 81 """Sends a devtools remote debugging protocol request. 82 83 The types of request that are valid is determined by protocol.json: 84 https://src.chromium.org/viewvc/blink/trunk/Source/devtools/protocol.json 85 86 Args: 87 request: Request dict, may contain the keys 'method' and 'params'. 88 timeout: Number of seconds to wait for a response. 89 90 Returns: 91 The result given in the response message. 92 93 Raises: 94 TabBackendException: The response indicates an error occurred. 95 """ 96 response = self._inspector_backend.SyncRequest(request, timeout) 97 if 'error' in response: 98 raise TabBackendException(response['error']['message']) 99 return response['result'] 100 101 def _OnNotification(self, msg): 102 """Handler called when a message is received.""" 103 # Since 'Timeline.start' was invoked with the 'bufferEvents' parameter, 104 # the events will arrive in Timeline.stopped event. 105 if msg['method'] == 'Timeline.stopped' and 'events' in msg['params']: 106 self._raw_events = msg['params']['events'] 107 108 def _OnClose(self): 109 """Handler called when a domain is unregistered.""" 110 pass 111