• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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