• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3# Copyright (C) 2024 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18import unittest
19
20from report_fuchsia import Stacker
21from etm_types import InstrSubtype
22from . test_utils import TestBase
23
24
25class Result:
26    def __init__(self):
27        self.events = []
28
29    def duration(self, begin, thread, category, name, timestamp):
30        self.events.append(('call' if begin else 'ret', name, timestamp))
31
32
33class TestEtmStacker(TestBase):
34    def setUp(self):
35        super().setUp()
36        self.r = Result()
37        self.s = Stacker(self.r, 0)
38
39    def assertResult(self, sequence):
40        self.s.flush()
41        self.assertEqual(self.r.events, sequence)
42
43    # Primitives.
44    def test_primitives_call(self):
45        self.s.call('main', 1)
46        self.assertResult([('call', 'main', 1)])
47
48    def test_primitives_ret(self):
49        self.s.call('main', 1)
50        self.s.ret('main', 2)
51        self.assertResult([('call', 'main', 1), ('ret', 'main', 2)])
52
53    def test_primitives_no_timestamp(self):
54        self.s.call('main', None)
55        self.s.ret('main', None)
56        self.assertResult([])
57
58    # Simple stacks.
59    def test_simple_start(self):
60        self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
61        self.assertResult([('call', 'main', 1), ('ret', 'main', 1)])
62
63    def test_simple_return(self):
64        self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
65        self.assertResult([('call', 'main', 1), ('ret', 'main', 1)])
66
67    def test_simple_inner(self):
68        self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK)
69        self.s.instr_range(2, 'inner', 0, 'inner', 0, InstrSubtype.V8_RET)
70        self.assertResult([('call', 'main', 1), ('call', 'inner', 2), ('ret', 'inner', 2)])
71
72    def test_simple_recursion(self):
73        self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK)
74        self.s.instr_range(2, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
75        self.s.instr_range(3, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
76        self.assertResult([('call', 'main', 1), ('call', 'main', 2),
77                           ('ret', 'main', 2), ('ret', 'main', 3)])
78
79    def test_simple_tailcall(self):
80        # If not a return or a call, the jump is considered a tail call.
81        self.s.instr_range(1, 'main', 0, 'main', 0, None)
82        self.s.instr_range(2, 'next', 0, 'next', 0, None)
83        self.assertResult([('call', 'main', 1), ('ret', 'main', 2), ('call', 'next', 2)])
84
85    def test_simple_tailcall_single(self):
86        # If the symbol is different between the start and the end, it changed without a jump.
87        # Pretend that it is a tail-call.
88        self.s.instr_range(1, 'main', 0, 'next', 0, None)
89        self.assertResult([('call', 'main', 1), ('ret', 'main', 1), ('call', 'next', 1)])
90
91    def test_simple_no_call(self):
92        # If execution returns below the known stack, pretend that we have known about the function
93        # running since the first known timestamp.
94        self.s.instr_range(1, 'main', 0, 'main', 0, None)
95        self.s.instr_range(2, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
96        self.s.instr_range(3, 'next', 0, 'next', 0, None)
97        self.assertResult([('call', 'next', 1),
98                           ('call', 'main', 1), ('ret', 'main', 2)])
99
100    # Delayed events (i.e., no timestamps).
101    def test_delay_later(self):
102        self.s.instr_range(None, 'main', 0, 'main', 0, None)
103        self.s.timestamp(1)
104        self.assertResult([('call', 'main', 1)])
105
106    def test_delay_later_ret(self):
107        self.s.instr_range(None, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
108        self.s.timestamp(1)
109        self.assertResult([('call', 'main', 1), ('ret', 'main', 1)])
110
111    def test_delay_inner(self):
112        self.s.instr_range(None, 'main', 0, 'main', 0, InstrSubtype.BR_LINK)
113        self.s.instr_range(2, 'inner', 0, 'inner', 0, InstrSubtype.V8_RET)
114        self.assertResult([('call', 'main', 2), ('call', 'inner', 2), ('ret', 'inner', 2)])
115
116    def test_delay_no_call(self):
117        self.s.instr_range(None, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
118        self.s.instr_range(2, 'next', 0, 'next', 0, None)
119        self.assertResult([('call', 'next', 2),
120                           ('call', 'main', 2), ('ret', 'main', 2)])
121
122    # Gaps.
123    def test_gaps_gap(self):
124        self.s.instr_range(1, 'main', 0, 'main', 0, None)
125        self.s.gap(2)
126        self.s.instr_range(3, 'next', 0, 'next', 0, None)
127        self.assertResult([('call', 'main', 1), ('ret', 'main', 2),
128                           ('call', 'next', 3)])
129
130    def test_gaps_exception_start(self):
131        self.s.exception(1, 'exception', 0)
132        self.assertResult([('call', 'exception', 1)])
133
134    def test_gaps_exception_twice(self):
135        self.s.exception(1, 'exception', 0)
136        self.s.gap(2)
137        self.s.exception(2, 'another', 0)
138        self.assertResult([('call', 'exception', 1), ('ret', 'exception', 2),
139                           ('call', 'another', 2)])
140
141    def test_gaps_exception_ret(self):
142        self.s.instr_range(1, 'main', 0, 'main', 0, None)
143        self.s.exception(2, 'exception', 1)
144        self.s.gap(2)
145        self.s.instr_range(3, 'main', 1, 'main', 1, InstrSubtype.V8_RET)
146        self.assertResult([('call', 'main', 1), ('call', 'exception', 2),
147                           ('ret', 'exception', 3), ('ret', 'main', 3)])
148
149    def test_gaps_exception_ret_twice(self):
150        self.s.instr_range(1, 'main', 0, 'main', 0, None)
151        self.s.exception(2, 'exception', 1)
152        self.s.gap(2)
153        self.s.exception(3, 'another', 1)
154        self.s.gap(3)
155        self.s.instr_range(4, 'main', 1, 'main', 1, InstrSubtype.V8_RET)
156        self.assertResult([('call', 'main', 1), ('call', 'exception', 2),
157                           ('ret', 'exception', 3), ('call', 'another', 3),
158                           ('ret', 'another', 4), ('ret', 'main', 4)])
159
160    def test_gaps_exception_ret_bad(self):
161        self.s.instr_range(1, 'main', 0, 'main', 0, None)
162        self.s.exception(2, 'exception', 100)
163        self.s.gap(2)
164        self.s.instr_range(3, 'next', 1, 'next', 1, None)
165        self.assertResult([('call', 'main', 1), ('call', 'exception', 2),
166                           ('ret', 'exception', 3), ('ret', 'main', 3),
167                           ('call', 'next', 3)])
168
169    def test_gaps_exception_ret_twice_bad(self):
170        self.s.instr_range(1, 'main', 0, 'main', 0, None)
171        self.s.exception(2, 'exception', 1)
172        self.s.gap(2)
173        self.s.exception(3, 'another', 2)
174        self.s.gap(3)
175        self.s.instr_range(4, 'next', 3, 'next', 3, None)
176        self.assertResult([('call', 'main', 1), ('call', 'exception', 2),
177                           ('ret', 'exception', 3), ('ret', 'main', 3),
178                           ('call', 'another', 3), ('ret', 'another', 4), ('call', 'next', 4)])
179
180    def test_gaps_exception_call_preserved(self):
181        self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK)
182        self.s.exception(2, 'exception', 1)
183        self.s.gap(2)
184        self.s.instr_range(3, 'next', 1, 'next', 2, None)
185        self.assertResult([('call', 'main', 1), ('call', 'exception', 2),
186                           ('ret', 'exception', 3), ('call', 'next', 3)])
187
188    def test_gaps_exception_ret_preserved(self):
189        # The actual ret is not preserved, but the starting point of elements below the known stack
190        # should be.
191        self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
192        self.s.exception(2, 'exception', 1)
193        self.s.gap(2)
194        self.s.instr_range(3, 'next', 1, 'next', 2, None)
195        self.assertResult([('call', 'next', 1),
196                           ('call', 'main', 1), ('ret', 'main', 1),
197                           ('call', 'exception', 2), ('ret', 'exception', 3)])
198
199    def test_gaps_plt(self):
200        self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK)
201        self.s.instr_range(2, 'func@plt', 0, 'func@plt', 0, None)
202        self.s.gap(3)
203        self.s.instr_range(4, 'main', 0, 'main', 0, None)
204        self.s.instr_range(5, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
205        self.assertResult([('call', 'main', 1), ('call', 'func@plt', 2),
206                           ('ret', 'func@plt', 4), ('ret', 'main', 5)])
207
208    def test_gaps_plt_wrong(self):
209        self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK)
210        self.s.instr_range(2, 'func@plt', 0, 'func@plt', 0, None)
211        self.s.gap(3)
212        self.s.instr_range(4, 'next', 0, 'next', 0, None)
213        self.assertResult([('call', 'main', 1), ('call', 'func@plt', 2),
214                           ('ret', 'func@plt', 4), ('ret', 'main', 4),
215                           ('call', 'next', 4)])
216
217    def test_gaps_danger(self):
218        self.s.instr_range(1, 'main', 0, 'main', 0, None)
219        self.s.exception(2, 'exception', 100)
220        self.s.gap(2)
221        self.s.instr_range(3, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
222        self.assertResult([('call', 'main', 1), ('call', 'exception', 2),
223                           ('ret', 'exception', 3), ('ret', 'main', 3)])
224
225    # Stack was lost.
226    def test_lost_lost(self):
227        self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK)
228        self.s.instr_range(2, 'next', 0, 'next', 0, None)
229        self.s.lost_stack(3)
230        self.assertResult([('call', 'main', 1), ('call', 'next', 2),
231                           ('ret', 'next', 3), ('ret', 'main', 3)])
232
233    def test_lost_ordering(self):
234        self.s.exception(1, 'exception', 0)
235        self.s.gap(None)
236        self.s.lost_stack(None)
237        self.s.timestamp(2)
238        self.assertResult([('call', 'exception', 1), ('ret', 'exception', 2)])
239
240    def test_lost_assumption(self):
241        self.s.instr_range(1, 'main', 0, 'main', 0, None)
242        self.s.exception(2, 'exception', 0)
243        self.s.gap(2)
244        self.s.instr_range(3, 'main', 0, 'main', 0, None)
245        self.assertResult([('call', 'main', 1), ('call', 'exception', 2),
246                           ('ret', 'exception', 3)])
247
248    def test_lost_late(self):
249        self.s.instr_range(None, 'main', 0, 'main', 0, InstrSubtype.BR_LINK)
250        self.s.exception(None, 'exception', 0)
251        self.s.gap(None)
252        self.s.instr_range(1, 'next', 0, 'next', 0, None)
253        self.assertResult([('call', 'main', 1), ('call', 'exception', 1), ('ret', 'exception', 1),
254                           ('call', 'next', 1)])
255
256    # Timestamps.
257    def test_timestamps_updates(self):
258        self.s.timestamp(10)
259        self.assertEqual(self.s.last_timestamp, 10)
260
261    def test_timestamps_gap_removes(self):
262        self.s.timestamp(10)
263        self.s.gap(11)
264        self.assertEqual(self.s.last_timestamp, None)
265
266    def test_timestamps_gap_removes_exception(self):
267        self.s.timestamp(10)
268        self.s.exception(10, "exception", 0)
269        self.s.gap(10)
270        self.assertEqual(self.s.last_timestamp, None)
271
272    def test_timestamps_gap_removes_plt(self):
273        self.s.instr_range(1, 'foo@plt', 0, 'foo@plt', 0, None)
274        self.s.gap(10)
275        self.assertEqual(self.s.last_timestamp, None)
276
277    def test_timestamps_gap_again(self):
278        self.s.timestamp(10)
279        self.s.gap(11)
280        self.s.timestamp(11)
281        self.assertEqual(self.s.last_timestamp, 11)
282
283    def test_timestamps_first(self):
284        self.s.instr_range(1, 'main', 0, 'main', 0, None)
285        self.s.instr_range(2, 'main', 0, 'main', 0, None)
286        self.assertEqual(self.s.first_timestamp, 1)
287        self.assertEqual(self.s.last_timestamp, 2)
288
289    def test_timestamps_first_gap(self):
290        self.s.instr_range(1, 'main', 0, 'main', 0, None)
291        self.s.gap(10)
292        self.s.instr_range(11, 'main', 0, 'main', 0, None)
293        self.assertEqual(self.s.first_timestamp, 11)
294
295    def test_timestamps_first_exception_good(self):
296        self.s.instr_range(1, 'main', 0, 'main', 0, None)
297        self.s.exception(2, 'exception', 100)
298        self.s.gap(10)
299        self.s.instr_range(11, 'main', 100, 'main', 100, None)
300        self.assertEqual(self.s.first_timestamp, 1)
301
302    def test_timestamps_first_exception_bad(self):
303        self.s.instr_range(1, 'main', 0, 'main', 0, None)
304        self.s.exception(2, 'exception', 100)
305        self.s.gap(10)
306        self.s.instr_range(11, 'next', 200, 'next', 200, None)
307        self.assertEqual(self.s.first_timestamp, 11)
308
309    def test_timestamps_first_plt_good(self):
310        self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK)
311        self.s.instr_range(2, 'foo@plt', 0, 'foo@plt', 0, None)
312        self.s.gap(10)
313        self.s.instr_range(11, 'main', 200, 'main', 200, None)
314        self.assertEqual(self.s.first_timestamp, 1)
315
316    def test_timestamps_first_plt_bad(self):
317        self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK)
318        self.s.instr_range(2, 'foo@plt', 0, 'foo@plt', 0, None)
319        self.s.gap(10)
320        self.s.instr_range(11, 'next', 200, 'next', 200, None)
321        self.assertEqual(self.s.first_timestamp, 11)
322
323    def test_timestamps_exception_fake_start(self):
324        self.s.exception(1, 'exception', 100)
325        self.s.lost_stack(None)
326        self.s.instr_range(None, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
327        self.s.instr_range(2, 'deep', 0, 'deep', 0, None)
328        self.assertResult([('call', 'exception', 1), ('ret', 'exception', 2),
329                           ('call', 'deep', 2), ('call', 'main', 2), ('ret', 'main', 2)])
330