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