1# Copyright 2014 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. 4import itertools 5 6import telemetry.timeline.event_container as event_container 7import telemetry.timeline.sample as tracing_sample 8import telemetry.timeline.slice as tracing_slice 9 10class Thread(event_container.TimelineEventContainer): 11 ''' A Thread stores all the trace events collected for a particular 12 thread. We organize the synchronous slices on a thread by "subrows," where 13 subrow 0 has all the root slices, subrow 1 those nested 1 deep, and so on. 14 The asynchronous slices are stored in an AsyncSliceGroup object. 15 ''' 16 def __init__(self, process, tid): 17 super(Thread, self).__init__('thread %s' % tid, parent=process) 18 self.tid = tid 19 self._async_slices = [] 20 self._flow_events = [] 21 self._samples = [] 22 self._toplevel_slices = [] 23 24 # State only valid during import. 25 self._open_slices = [] 26 self._newly_added_slices = [] 27 28 @property 29 def toplevel_slices(self): 30 return self._toplevel_slices 31 32 @property 33 def all_slices(self): 34 return list(self.IterAllSlices()) 35 36 @property 37 def samples(self): 38 return self._samples 39 40 @property 41 def async_slices(self): 42 return self._async_slices 43 44 @property 45 def open_slice_count(self): 46 return len(self._open_slices) 47 48 def IterChildContainers(self): 49 return iter([]) 50 51 def IterAllSlices(self): 52 for s in self._toplevel_slices: 53 yield s 54 for sub_slice in s.IterEventsInThisContainerRecrusively(): 55 yield sub_slice 56 57 def IterAllSlicesInRange(self, start, end): 58 for s in self.IterAllSlices(): 59 if s.start >= start and s.end <= end: 60 yield s 61 62 def IterAllSlicesOfName(self, name): 63 for s in self.IterAllSlices(): 64 if s.name == name: 65 yield s 66 67 def IterAllAsyncSlices(self): 68 for async_slice in self._async_slices: 69 yield async_slice 70 for sub_slice in async_slice.IterEventsInThisContainerRecrusively(): 71 yield sub_slice 72 73 def IterAllAsyncSlicesOfName(self, name): 74 for s in self.IterAllAsyncSlices(): 75 if s.name == name: 76 yield s 77 78 def IterAllFlowEvents(self): 79 for flow_event in self._flow_events: 80 yield flow_event 81 82 def IterEventsInThisContainer(self): 83 return itertools.chain( 84 iter(self._newly_added_slices), 85 self.IterAllAsyncSlices(), 86 self.IterAllFlowEvents(), 87 self.IterAllSlices(), 88 iter(self._samples) 89 ) 90 91 def AddSample(self, category, name, timestamp, args=None): 92 if len(self._samples) and timestamp < self._samples[-1].start: 93 raise ValueError( 94 'Samples must be added in increasing timestamp order') 95 sample = tracing_sample.Sample(self, 96 category, name, timestamp, args=args) 97 self._samples.append(sample) 98 99 def AddAsyncSlice(self, async_slice): 100 self._async_slices.append(async_slice) 101 102 def AddFlowEvent(self, flow_event): 103 self._flow_events.append(flow_event) 104 105 def BeginSlice(self, category, name, timestamp, thread_timestamp=None, 106 args=None): 107 """Opens a new slice for the thread. 108 Calls to beginSlice and endSlice must be made with 109 non-monotonically-decreasing timestamps. 110 111 * category: Category to which the slice belongs. 112 * name: Name of the slice to add. 113 * timestamp: The timetsamp of the slice, in milliseconds. 114 * thread_timestamp: Thread specific clock (scheduled) timestamp of the 115 slice, in milliseconds. 116 * args: Arguments associated with 117 118 Returns newly opened slice 119 """ 120 if len(self._open_slices) > 0 and timestamp < self._open_slices[-1].start: 121 raise ValueError( 122 'Slices must be added in increasing timestamp order') 123 new_slice = tracing_slice.Slice(self, category, name, timestamp, 124 thread_timestamp=thread_timestamp, 125 args=args) 126 self._open_slices.append(new_slice) 127 new_slice.did_not_finish = True 128 self.PushSlice(new_slice) 129 return new_slice 130 131 def EndSlice(self, end_timestamp, end_thread_timestamp=None): 132 """ Ends the last begun slice in this group and pushes it onto the slice 133 array. 134 135 * end_timestamp: Timestamp when the slice ended in milliseconds 136 * end_thread_timestamp: Timestamp when the scheduled time of the slice ended 137 in milliseconds 138 139 returns completed slice. 140 """ 141 if not len(self._open_slices): 142 raise ValueError( 143 'EndSlice called without an open slice') 144 curr_slice = self._open_slices.pop() 145 if end_timestamp < curr_slice.start: 146 raise ValueError( 147 'Slice %s end time is before its start.' % curr_slice.name) 148 curr_slice.duration = end_timestamp - curr_slice.start 149 if end_thread_timestamp != None: 150 if curr_slice.thread_start == None: 151 raise ValueError( 152 'EndSlice with thread_timestamp called on open slice without ' + 153 'thread_timestamp') 154 curr_slice.thread_duration = (end_thread_timestamp - 155 curr_slice.thread_start) 156 curr_slice.did_not_finish = False 157 return curr_slice 158 159 def PushCompleteSlice(self, category, name, timestamp, duration, 160 thread_timestamp, thread_duration, args=None): 161 new_slice = tracing_slice.Slice(self, category, name, timestamp, 162 thread_timestamp=thread_timestamp, 163 args=args) 164 if duration == None: 165 new_slice.did_not_finish = True 166 else: 167 new_slice.duration = duration 168 new_slice.thread_duration = thread_duration 169 self.PushSlice(new_slice) 170 return new_slice 171 172 def PushSlice(self, new_slice): 173 self._newly_added_slices.append(new_slice) 174 return new_slice 175 176 def AutoCloseOpenSlices(self, max_timestamp, max_thread_timestamp): 177 for s in self._newly_added_slices: 178 if s.did_not_finish: 179 s.duration = max_timestamp - s.start 180 assert s.duration >= 0 181 if s.thread_start != None: 182 s.thread_duration = max_thread_timestamp - s.thread_start 183 assert s.thread_duration >= 0 184 self._open_slices = [] 185 186 def IsTimestampValidForBeginOrEnd(self, timestamp): 187 if not len(self._open_slices): 188 return True 189 return timestamp >= self._open_slices[-1].start 190 191 def FinalizeImport(self): 192 self._BuildSliceSubRows() 193 194 def _BuildSliceSubRows(self): 195 '''This function works by walking through slices by start time. 196 197 The basic idea here is to insert each slice as deep into the subrow 198 list as it can go such that every subslice is fully contained by its 199 parent slice. 200 201 Visually, if we start with this: 202 0: [ a ] 203 1: [ b ] 204 2: [c][d] 205 206 To place this slice: 207 [e] 208 We first check row 2's last item, [d]. [e] wont fit into [d] (they dont 209 even intersect). So we go to row 1. That gives us [b], and [d] wont fit 210 into that either. So, we go to row 0 and its last slice, [a]. That can 211 completely contain [e], so that means we should add [e] as a subslice 212 of [a]. That puts it on row 1, yielding: 213 0: [ a ] 214 1: [ b ][e] 215 2: [c][d] 216 217 If we then get this slice: 218 [f] 219 We do the same deepest-to-shallowest walk of the subrows trying to fit 220 it. This time, it doesn't fit in any open slice. So, we simply append 221 it to row 0 (a root slice): 222 0: [ a ] [f] 223 1: [ b ][e] 224 ''' 225 def CompareSlices(s1, s2): 226 if s1.start == s2.start: 227 # Break ties by having the slice with the greatest 228 # end timestamp come first. 229 return cmp(s2.end, s1.end) 230 return cmp(s1.start, s2.start) 231 232 assert len(self._toplevel_slices) == 0 233 if not len(self._newly_added_slices): 234 return 235 236 sorted_slices = sorted(self._newly_added_slices, cmp=CompareSlices) 237 root_slice = sorted_slices[0] 238 self._toplevel_slices.append(root_slice) 239 for s in sorted_slices[1:]: 240 if not self._AddSliceIfBounds(root_slice, s): 241 root_slice = s 242 self._toplevel_slices.append(root_slice) 243 self._newly_added_slices = [] 244 245 def _AddSliceIfBounds(self, root, child): 246 ''' Adds a child slice to a root slice its proper row. 247 Return False if the child slice is not in the bounds 248 of the root slice. 249 250 Because we know that the start time of child is >= the start time 251 of all other slices seen so far, we can just check the last slice 252 of each row for bounding. 253 ''' 254 # The source trace data is in microseconds but we store it as milliseconds 255 # in floating-point. Since we can't represent micros as millis perfectly, 256 # two end=start+duration combos that should be the same will be slightly 257 # different. Round back to micros to ensure equality below. 258 child_end_micros = round(child.end * 1000) 259 root_end_micros = round(root.end * 1000) 260 if child.start >= root.start and child_end_micros <= root_end_micros: 261 if len(root.sub_slices) > 0: 262 if self._AddSliceIfBounds(root.sub_slices[-1], child): 263 return True 264 child.parent_slice = root 265 root.AddSubSlice(child) 266 return True 267 return False 268