blob: e01cc506e65b0b6065f17c125453409601b2eda3 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright (C) 2024 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import unittest
from report_fuchsia import Stacker
from etm_types import InstrSubtype
from . test_utils import TestBase
class Result:
def __init__(self):
self.events = []
def duration(self, begin, thread, category, name, timestamp):
self.events.append(('call' if begin else 'ret', name, timestamp))
class TestEtmStacker(TestBase):
def setUp(self):
super().setUp()
self.r = Result()
self.s = Stacker(self.r, 0)
def assertResult(self, sequence):
self.s.flush()
self.assertEqual(self.r.events, sequence)
# Primitives.
def test_primitives_call(self):
self.s.call('main', 1)
self.assertResult([('call', 'main', 1)])
def test_primitives_ret(self):
self.s.call('main', 1)
self.s.ret('main', 2)
self.assertResult([('call', 'main', 1), ('ret', 'main', 2)])
def test_primitives_no_timestamp(self):
self.s.call('main', None)
self.s.ret('main', None)
self.assertResult([])
# Simple stacks.
def test_simple_start(self):
self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
self.assertResult([('call', 'main', 1), ('ret', 'main', 1)])
def test_simple_return(self):
self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
self.assertResult([('call', 'main', 1), ('ret', 'main', 1)])
def test_simple_inner(self):
self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK)
self.s.instr_range(2, 'inner', 0, 'inner', 0, InstrSubtype.V8_RET)
self.assertResult([('call', 'main', 1), ('call', 'inner', 2), ('ret', 'inner', 2)])
def test_simple_recursion(self):
self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK)
self.s.instr_range(2, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
self.s.instr_range(3, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
self.assertResult([('call', 'main', 1), ('call', 'main', 2),
('ret', 'main', 2), ('ret', 'main', 3)])
def test_simple_tailcall(self):
# If not a return or a call, the jump is considered a tail call.
self.s.instr_range(1, 'main', 0, 'main', 0, None)
self.s.instr_range(2, 'next', 0, 'next', 0, None)
self.assertResult([('call', 'main', 1), ('ret', 'main', 2), ('call', 'next', 2)])
def test_simple_tailcall_single(self):
# If the symbol is different between the start and the end, it changed without a jump.
# Pretend that it is a tail-call.
self.s.instr_range(1, 'main', 0, 'next', 0, None)
self.assertResult([('call', 'main', 1), ('ret', 'main', 1), ('call', 'next', 1)])
def test_simple_no_call(self):
# If execution returns below the known stack, pretend that we have known about the function
# running since the first known timestamp.
self.s.instr_range(1, 'main', 0, 'main', 0, None)
self.s.instr_range(2, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
self.s.instr_range(3, 'next', 0, 'next', 0, None)
self.assertResult([('call', 'next', 1),
('call', 'main', 1), ('ret', 'main', 2)])
# Delayed events (i.e., no timestamps).
def test_delay_later(self):
self.s.instr_range(None, 'main', 0, 'main', 0, None)
self.s.timestamp(1)
self.assertResult([('call', 'main', 1)])
def test_delay_later_ret(self):
self.s.instr_range(None, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
self.s.timestamp(1)
self.assertResult([('call', 'main', 1), ('ret', 'main', 1)])
def test_delay_inner(self):
self.s.instr_range(None, 'main', 0, 'main', 0, InstrSubtype.BR_LINK)
self.s.instr_range(2, 'inner', 0, 'inner', 0, InstrSubtype.V8_RET)
self.assertResult([('call', 'main', 2), ('call', 'inner', 2), ('ret', 'inner', 2)])
def test_delay_no_call(self):
self.s.instr_range(None, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
self.s.instr_range(2, 'next', 0, 'next', 0, None)
self.assertResult([('call', 'next', 2),
('call', 'main', 2), ('ret', 'main', 2)])
# Gaps.
def test_gaps_gap(self):
self.s.instr_range(1, 'main', 0, 'main', 0, None)
self.s.gap(2)
self.s.instr_range(3, 'next', 0, 'next', 0, None)
self.assertResult([('call', 'main', 1), ('ret', 'main', 2),
('call', 'next', 3)])
def test_gaps_exception_start(self):
self.s.exception(1, 'exception', 0)
self.assertResult([('call', 'exception', 1)])
def test_gaps_exception_twice(self):
self.s.exception(1, 'exception', 0)
self.s.gap(2)
self.s.exception(2, 'another', 0)
self.assertResult([('call', 'exception', 1), ('ret', 'exception', 2),
('call', 'another', 2)])
def test_gaps_exception_ret(self):
self.s.instr_range(1, 'main', 0, 'main', 0, None)
self.s.exception(2, 'exception', 1)
self.s.gap(2)
self.s.instr_range(3, 'main', 1, 'main', 1, InstrSubtype.V8_RET)
self.assertResult([('call', 'main', 1), ('call', 'exception', 2),
('ret', 'exception', 3), ('ret', 'main', 3)])
def test_gaps_exception_ret_twice(self):
self.s.instr_range(1, 'main', 0, 'main', 0, None)
self.s.exception(2, 'exception', 1)
self.s.gap(2)
self.s.exception(3, 'another', 1)
self.s.gap(3)
self.s.instr_range(4, 'main', 1, 'main', 1, InstrSubtype.V8_RET)
self.assertResult([('call', 'main', 1), ('call', 'exception', 2),
('ret', 'exception', 3), ('call', 'another', 3),
('ret', 'another', 4), ('ret', 'main', 4)])
def test_gaps_exception_ret_bad(self):
self.s.instr_range(1, 'main', 0, 'main', 0, None)
self.s.exception(2, 'exception', 100)
self.s.gap(2)
self.s.instr_range(3, 'next', 1, 'next', 1, None)
self.assertResult([('call', 'main', 1), ('call', 'exception', 2),
('ret', 'exception', 3), ('ret', 'main', 3),
('call', 'next', 3)])
def test_gaps_exception_ret_twice_bad(self):
self.s.instr_range(1, 'main', 0, 'main', 0, None)
self.s.exception(2, 'exception', 1)
self.s.gap(2)
self.s.exception(3, 'another', 2)
self.s.gap(3)
self.s.instr_range(4, 'next', 3, 'next', 3, None)
self.assertResult([('call', 'main', 1), ('call', 'exception', 2),
('ret', 'exception', 3), ('ret', 'main', 3),
('call', 'another', 3), ('ret', 'another', 4), ('call', 'next', 4)])
def test_gaps_exception_call_preserved(self):
self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK)
self.s.exception(2, 'exception', 1)
self.s.gap(2)
self.s.instr_range(3, 'next', 1, 'next', 2, None)
self.assertResult([('call', 'main', 1), ('call', 'exception', 2),
('ret', 'exception', 3), ('call', 'next', 3)])
def test_gaps_exception_ret_preserved(self):
# The actual ret is not preserved, but the starting point of elements below the known stack
# should be.
self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
self.s.exception(2, 'exception', 1)
self.s.gap(2)
self.s.instr_range(3, 'next', 1, 'next', 2, None)
self.assertResult([('call', 'next', 1),
('call', 'main', 1), ('ret', 'main', 1),
('call', 'exception', 2), ('ret', 'exception', 3)])
def test_gaps_plt(self):
self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK)
self.s.instr_range(2, 'func@plt', 0, 'func@plt', 0, None)
self.s.gap(3)
self.s.instr_range(4, 'main', 0, 'main', 0, None)
self.s.instr_range(5, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
self.assertResult([('call', 'main', 1), ('call', 'func@plt', 2),
('ret', 'func@plt', 4), ('ret', 'main', 5)])
def test_gaps_plt_wrong(self):
self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK)
self.s.instr_range(2, 'func@plt', 0, 'func@plt', 0, None)
self.s.gap(3)
self.s.instr_range(4, 'next', 0, 'next', 0, None)
self.assertResult([('call', 'main', 1), ('call', 'func@plt', 2),
('ret', 'func@plt', 4), ('ret', 'main', 4),
('call', 'next', 4)])
def test_gaps_danger(self):
self.s.instr_range(1, 'main', 0, 'main', 0, None)
self.s.exception(2, 'exception', 100)
self.s.gap(2)
self.s.instr_range(3, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
self.assertResult([('call', 'main', 1), ('call', 'exception', 2),
('ret', 'exception', 3), ('ret', 'main', 3)])
# Stack was lost.
def test_lost_lost(self):
self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK)
self.s.instr_range(2, 'next', 0, 'next', 0, None)
self.s.lost_stack(3)
self.assertResult([('call', 'main', 1), ('call', 'next', 2),
('ret', 'next', 3), ('ret', 'main', 3)])
def test_lost_ordering(self):
self.s.exception(1, 'exception', 0)
self.s.gap(None)
self.s.lost_stack(None)
self.s.timestamp(2)
self.assertResult([('call', 'exception', 1), ('ret', 'exception', 2)])
def test_lost_assumption(self):
self.s.instr_range(1, 'main', 0, 'main', 0, None)
self.s.exception(2, 'exception', 0)
self.s.gap(2)
self.s.instr_range(3, 'main', 0, 'main', 0, None)
self.assertResult([('call', 'main', 1), ('call', 'exception', 2),
('ret', 'exception', 3)])
def test_lost_late(self):
self.s.instr_range(None, 'main', 0, 'main', 0, InstrSubtype.BR_LINK)
self.s.exception(None, 'exception', 0)
self.s.gap(None)
self.s.instr_range(1, 'next', 0, 'next', 0, None)
self.assertResult([('call', 'main', 1), ('call', 'exception', 1), ('ret', 'exception', 1),
('call', 'next', 1)])
# Timestamps.
def test_timestamps_updates(self):
self.s.timestamp(10)
self.assertEqual(self.s.last_timestamp, 10)
def test_timestamps_gap_removes(self):
self.s.timestamp(10)
self.s.gap(11)
self.assertEqual(self.s.last_timestamp, None)
def test_timestamps_gap_removes_exception(self):
self.s.timestamp(10)
self.s.exception(10, "exception", 0)
self.s.gap(10)
self.assertEqual(self.s.last_timestamp, None)
def test_timestamps_gap_removes_plt(self):
self.s.instr_range(1, 'foo@plt', 0, 'foo@plt', 0, None)
self.s.gap(10)
self.assertEqual(self.s.last_timestamp, None)
def test_timestamps_gap_again(self):
self.s.timestamp(10)
self.s.gap(11)
self.s.timestamp(11)
self.assertEqual(self.s.last_timestamp, 11)
def test_timestamps_first(self):
self.s.instr_range(1, 'main', 0, 'main', 0, None)
self.s.instr_range(2, 'main', 0, 'main', 0, None)
self.assertEqual(self.s.first_timestamp, 1)
self.assertEqual(self.s.last_timestamp, 2)
def test_timestamps_first_gap(self):
self.s.instr_range(1, 'main', 0, 'main', 0, None)
self.s.gap(10)
self.s.instr_range(11, 'main', 0, 'main', 0, None)
self.assertEqual(self.s.first_timestamp, 11)
def test_timestamps_first_exception_good(self):
self.s.instr_range(1, 'main', 0, 'main', 0, None)
self.s.exception(2, 'exception', 100)
self.s.gap(10)
self.s.instr_range(11, 'main', 100, 'main', 100, None)
self.assertEqual(self.s.first_timestamp, 1)
def test_timestamps_first_exception_bad(self):
self.s.instr_range(1, 'main', 0, 'main', 0, None)
self.s.exception(2, 'exception', 100)
self.s.gap(10)
self.s.instr_range(11, 'next', 200, 'next', 200, None)
self.assertEqual(self.s.first_timestamp, 11)
def test_timestamps_first_plt_good(self):
self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK)
self.s.instr_range(2, 'foo@plt', 0, 'foo@plt', 0, None)
self.s.gap(10)
self.s.instr_range(11, 'main', 200, 'main', 200, None)
self.assertEqual(self.s.first_timestamp, 1)
def test_timestamps_first_plt_bad(self):
self.s.instr_range(1, 'main', 0, 'main', 0, InstrSubtype.BR_LINK)
self.s.instr_range(2, 'foo@plt', 0, 'foo@plt', 0, None)
self.s.gap(10)
self.s.instr_range(11, 'next', 200, 'next', 200, None)
self.assertEqual(self.s.first_timestamp, 11)
def test_timestamps_exception_fake_start(self):
self.s.exception(1, 'exception', 100)
self.s.lost_stack(None)
self.s.instr_range(None, 'main', 0, 'main', 0, InstrSubtype.V8_RET)
self.s.instr_range(2, 'deep', 0, 'deep', 0, None)
self.assertResult([('call', 'exception', 1), ('ret', 'exception', 2),
('call', 'deep', 2), ('call', 'main', 2), ('ret', 'main', 2)])