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