1# SPDX-License-Identifier: Apache-2.0
2#
3# Copyright (C) 2017, ARM Limited and contributors.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# 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, WITHOUT
13# 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 json
19import os
20from unittest import TestCase
21
22from trace import Trace
23
24class TestTrace(TestCase):
25    """Smoke tests for LISA's Trace class"""
26
27    traces_dir = os.path.join(os.path.dirname(__file__), 'traces')
28    events = [
29        'sched_switch',
30        'sched_overutilized',
31        'cpu_idle',
32    ]
33
34    def __init__(self, *args, **kwargs):
35        super(TestTrace, self).__init__(*args, **kwargs)
36
37        self.test_trace = os.path.join(self.traces_dir, 'test_trace.txt')
38
39        self.platform = self._get_platform()
40
41        self.trace_path = os.path.join(self.traces_dir, 'trace.txt')
42        self.trace = Trace(self.platform, self.trace_path, self.events)
43
44    def make_trace(self, in_data):
45        with open(self.test_trace, "w") as fout:
46            fout.write(in_data)
47
48        return Trace(self.platform, self.test_trace, self.events,
49                     normalize_time=False)
50
51    def _get_platform(self):
52        with open(os.path.join(self.traces_dir, 'platform.json')) as f:
53            return json.load(f)
54
55    def test_getTaskByName(self):
56        """TestTrace: getTaskByName() returns the list of PIDs for all tasks with the specified name"""
57        for name, pids in [('watchdog/0', [12]),
58                           ('sh', [1642, 1702, 1717, 1718]),
59                           ('NOT_A_TASK', [])]:
60            self.assertEqual(self.trace.getTaskByName(name), pids)
61
62    def test_getTaskByPid(self):
63        """TestTrace: getTaskByPid() returns the name of the task with the specified PID"""
64        for pid, names in [(15, 'watchdog/1'),
65                           (1639, 'sshd'),
66                           (987654321, None)]:
67            self.assertEqual(self.trace.getTaskByPid(pid), names)
68
69    def test_getTasks(self):
70        """TestTrace: getTasks() returns a dictionary mapping PIDs to a single task name"""
71        tasks_dict = self.trace.getTasks()
72        for pid, name in [(1, 'init'),
73                          (9, 'rcu_sched'),
74                          (1383, 'jbd2/sda2-8')]:
75            self.assertEqual(tasks_dict[pid], name)
76
77    def test_setTaskName(self):
78        """TestTrace: getTaskBy{Pid,Name}() properly track tasks renaming"""
79        in_data = """
80          father-1234  [002] 18765.018235: sched_switch:          prev_comm=father prev_pid=1234 prev_prio=120 prev_state=0 next_comm=father next_pid=5678 next_prio=120
81           child-5678  [002] 18766.018236: sched_switch:          prev_comm=child prev_pid=5678 prev_prio=120 prev_state=1 next_comm=sh next_pid=3367 next_prio=120
82        """
83        trace = self.make_trace(in_data)
84
85        self.assertEqual(trace.getTaskByPid(1234), 'father')
86        self.assertEqual(trace.getTaskByPid(5678), 'child')
87        self.assertEqual(trace.getTaskByName('father'), [1234])
88
89        os.remove(self.test_trace)
90
91    def test_time_range(self):
92        """
93        TestTrace: time_range is the duration of the trace
94        """
95        expected_duration = 6.676497
96
97        trace = Trace(self.platform, self.trace_path,
98                      self.events, normalize_time=False
99        )
100
101        self.assertAlmostEqual(trace.time_range, expected_duration, places=6)
102
103    def test_time_range_window(self):
104        """
105        TestTrace: time_range is the duration of the trace in the given window
106        """
107        expected_duration = 4.0
108
109        trace = Trace(self.platform, self.trace_path,
110                      self.events, normalize_time=False,
111                      window=(76.402065, 80.402065)
112        )
113
114        self.assertAlmostEqual(trace.time_range, expected_duration, places=6)
115
116    def test_overutilized_time(self):
117        """
118        TestTrace: overutilized_time is the total time spent while system was overutilized
119        """
120        events = [
121            76.402065,
122            80.402065,
123            82.001337
124        ]
125
126        trace_end = self.trace.ftrace.basetime + self.trace.ftrace.get_duration()
127        # Last event should be extended to the trace's end
128        expected_time = (events[1] - events[0]) + (trace_end - events[2])
129
130        self.assertAlmostEqual(self.trace.overutilized_time, expected_time, places=6)
131
132    def test_plotCPUIdleStateResidency(self):
133        """
134        Test that plotCPUIdleStateResidency doesn't crash
135        """
136        in_data = """
137            foo-1  [000] 0.01: cpu_idle: state=0 cpu_id=0
138            foo-1  [000] 0.02: cpu_idle: state=-1 cpu_id=0
139            bar-2  [000] 0.03: cpu_idle: state=0 cpu_id=1
140            bar-2  [000] 0.04: cpu_idle: state=-1 cpu_id=1
141            baz-3  [000] 0.05: cpu_idle: state=0 cpu_id=2
142            baz-3  [000] 0.06: cpu_idle: state=-1 cpu_id=2
143            bam-4  [000] 0.07: cpu_idle: state=0 cpu_id=3
144            bam-4  [000] 0.08: cpu_idle: state=-1 cpu_id=3
145            child-5678  [002] 18765.018235: sched_switch: prev_comm=child prev_pid=5678 prev_prio=120 prev_state=1 next_comm=father next_pid=5678 next_prio=120
146        """
147        trace = self.make_trace(in_data)
148
149        trace.analysis.idle.plotCPUIdleStateResidency()
150
151    def test_deriving_cpus_count(self):
152        """Test that Trace derives cpus_count if it isn't provided"""
153        if self.platform:
154            del self.platform['cpus_count']
155
156        in_data = """
157            father-1234  [000] 18765.018235: sched_switch: prev_comm=father prev_pid=1234 prev_prio=120 prev_state=0 next_comm=father next_pid=5678 next_prio=120
158             child-5678  [002] 18765.018235: sched_switch: prev_comm=child prev_pid=5678 prev_prio=120 prev_state=1 next_comm=father next_pid=5678 next_prio=120
159        """
160
161        trace = self.make_trace(in_data)
162
163        self.assertEqual(trace.platform['cpus_count'], 3)
164
165    def test_dfg_cpu_wakeups(self):
166        """
167        Test the cpu_wakeups DataFrame getter
168        """
169        trace = self.make_trace("""
170          <idle>-0     [004]   519.021928: cpu_idle:             state=4294967295 cpu_id=4
171          <idle>-0     [004]   519.022147: cpu_idle:             state=0 cpu_id=4
172          <idle>-0     [004]   519.022641: cpu_idle:             state=4294967295 cpu_id=4
173          <idle>-0     [001]   519.022642: cpu_idle:             state=4294967295 cpu_id=1
174          <idle>-0     [002]   519.022643: cpu_idle:             state=4294967295 cpu_id=2
175          <idle>-0     [001]   519.022788: cpu_idle:             state=0 cpu_id=1
176          <idle>-0     [002]   519.022831: cpu_idle:             state=2 cpu_id=2
177          <idle>-0     [003]   519.022867: cpu_idle:             state=4294967295 cpu_id=3
178          <idle>-0     [003]   519.023045: cpu_idle:             state=2 cpu_id=3
179          <idle>-0     [004]   519.023080: cpu_idle:             state=1 cpu_id=4
180        """)
181
182        df = trace.data_frame.cpu_wakeups()
183
184        exp_index=[519.021928, 519.022641, 519.022642, 519.022643, 519.022867]
185        exp_cpus= [         4,          4,          1,          2,          3]
186        self.assertListEqual(df.index.tolist(), exp_index)
187        self.assertListEqual(df.cpu.tolist(), exp_cpus)
188
189        df = trace.data_frame.cpu_wakeups([2])
190
191        self.assertListEqual(df.index.tolist(), [519.022643])
192        self.assertListEqual(df.cpu.tolist(), [2])
193
194class TestTraceNoClusterData(TestTrace):
195    """
196    Test Trace without cluster data
197
198    Inherits from TestTrace, so all the tests are run again but with
199    no cluster info the platform dict.
200    """
201    def _get_platform(self):
202        platform = super(TestTraceNoClusterData, self)._get_platform()
203        del platform['clusters']
204        return platform
205
206class TestTraceNoPlatform(TestTrace):
207    """
208    Test Trace with platform=none
209
210    Inherits from TestTrace, so all the tests are run again but with
211    platform=None
212    """
213    def _get_platform(self):
214        return None
215