1#    Copyright 2015-2017 ARM Limited
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14#
15
16
17import matplotlib
18import os
19import pandas as pd
20import re
21import shutil
22import subprocess
23import tempfile
24import time
25import unittest
26
27from test_thermal import BaseTestThermal
28import trappy
29import utils_tests
30
31class TestFTrace(BaseTestThermal):
32    def __init__(self, *args, **kwargs):
33        super(TestFTrace, self).__init__(*args, **kwargs)
34        self.map_label = {"00000000,00000006": "A57", "00000000,00000039": "A53"}
35
36    def test_ftrace_has_all_classes(self):
37        """The FTrace() class has members for all classes"""
38
39        trace = trappy.FTrace()
40
41        for attr in trace.class_definitions.iterkeys():
42            self.assertTrue(hasattr(trace, attr))
43
44    def test_ftrace_has_all_classes_scope_all(self):
45        """The FTrace() class has members for all classes with scope=all"""
46
47        trace = trappy.FTrace(scope="all")
48
49        for attr in trace.class_definitions.iterkeys():
50            self.assertTrue(hasattr(trace, attr))
51
52    def test_ftrace_has_all_classes_scope_thermal(self):
53        """The FTrace() class has only members for thermal classes with scope=thermal"""
54
55        trace = trappy.FTrace(scope="thermal")
56
57        for attr in trace.thermal_classes.iterkeys():
58            self.assertTrue(hasattr(trace, attr))
59
60        for attr in trace.sched_classes.iterkeys():
61            self.assertFalse(hasattr(trace, attr))
62
63    def test_ftrace_has_all_classes_scope_sched(self):
64        """The FTrace() class has only members for sched classes with scope=sched"""
65
66        trace = trappy.FTrace(scope="sched")
67
68        for attr in trace.thermal_classes.iterkeys():
69            self.assertFalse(hasattr(trace, attr))
70
71        for attr in trace.sched_classes.iterkeys():
72            self.assertTrue(hasattr(trace, attr))
73
74    def test_ftrace_has_no_classes_scope_dynamic(self):
75        """The FTrace() class has only dynamically registered classes with scope=custom"""
76
77        trace = trappy.FTrace(scope="custom")
78
79        for attr in trace.thermal_classes.iterkeys():
80            self.assertFalse(hasattr(trace, attr))
81
82        for attr in trace.sched_classes.iterkeys():
83            self.assertFalse(hasattr(trace, attr))
84
85        ftrace_parser = trappy.register_dynamic_ftrace("ADynamicEvent",
86                                                       "a_dynamic_event")
87        trace = trappy.FTrace(scope="custom")
88
89        self.assertTrue(hasattr(trace, "a_dynamic_event"))
90
91        trappy.unregister_dynamic_ftrace(ftrace_parser)
92
93
94    def test_ftrace_doesnt_overwrite_parsed_event(self):
95        """FTrace().add_parsed_event() should not override an event that's already present"""
96        trace = trappy.FTrace()
97        dfr = pd.DataFrame({"temp": [45000, 46724, 45520]},
98                           index=pd.Series([1.020, 1.342, 1.451], name="Time"))
99
100        with self.assertRaises(ValueError):
101            trace.add_parsed_event("sched_switch", dfr)
102
103    def test_fail_if_no_trace_dat(self):
104        """Raise an IOError with the path if there's no trace.dat and trace.txt"""
105        os.remove("trace.txt")
106        self.assertRaises(IOError, trappy.FTrace)
107
108        cwd = os.getcwd()
109
110        try:
111            trappy.FTrace(cwd)
112        except IOError as exception:
113            pass
114
115        self.assertTrue(cwd in str(exception))
116
117    def test_other_directory(self):
118        """FTrace() can grab the trace.dat from other directories"""
119
120        other_random_dir = tempfile.mkdtemp()
121        os.chdir(other_random_dir)
122
123        dfr = trappy.FTrace(self.out_dir).thermal.data_frame
124
125        self.assertTrue(len(dfr) > 0)
126        self.assertEquals(os.getcwd(), other_random_dir)
127
128    def test_ftrace_arbitrary_trace_txt(self):
129        """FTrace() works if the trace is called something other than trace.txt"""
130        arbitrary_trace_name = "my_trace.txt"
131        shutil.move("trace.txt", arbitrary_trace_name)
132
133        dfr = trappy.FTrace(arbitrary_trace_name).thermal.data_frame
134
135        self.assertTrue(len(dfr) > 0)
136        self.assertFalse(os.path.exists("trace.txt"))
137        # As there is no raw trace requested. The mytrace.raw.txt
138        # Should not have been generated
139        self.assertFalse(os.path.exists("mytrace.raw.txt"))
140
141    def test_ftrace_autonormalize_time(self):
142        """FTrace() normalizes by default"""
143
144        trace = trappy.FTrace()
145
146        self.assertEquals(round(trace.thermal.data_frame.index[0], 7), 0)
147
148    def test_ftrace_dont_normalize_time(self):
149        """FTrace() doesn't normalize if asked not to"""
150
151        trace = trappy.FTrace(normalize_time=False)
152
153        self.assertNotEquals(round(trace.thermal.data_frame.index[0], 7), 0)
154
155    def test_ftrace_basetime(self):
156        """Test that basetime calculation is correct"""
157
158        trace = trappy.FTrace(normalize_time=False)
159
160        basetime = trace.thermal.data_frame.index[0]
161
162        self.assertEqual(trace.basetime, basetime)
163
164    def test_ftrace_duration(self):
165        """Test get_duration: normalize_time=False"""
166
167        trace = trappy.FTrace(normalize_time=True)
168
169        duration = trace.thermal_governor.data_frame.index[-1] - trace.thermal.data_frame.index[0]
170
171        self.assertEqual(trace.get_duration(), duration)
172
173    def test_ftrace_duration_window(self):
174        """Test that duration is correct with time window (normalize_time=True)"""
175        trace = trappy.FTrace(normalize_time=True, window=[1, 5])
176
177        duration = trace.thermal_governor.data_frame.index[-1] - trace.thermal.data_frame.index[0]
178
179        self.assertEqual(trace.get_duration(), duration)
180
181    def test_ftrace_duration_window_not_normalized(self):
182        """Test that duration is correct with time window (normalize_time=False)"""
183        trace = trappy.FTrace(normalize_time=False, window=[1, 5])
184
185        duration = trace.thermal_governor.data_frame.index[-1] - trace.thermal.data_frame.index[0]
186
187        self.assertEqual(trace.get_duration(), duration)
188
189    def test_ftrace_duration_not_normalized(self):
190        """Test get_duration: normalize_time=True"""
191
192        trace = trappy.FTrace(normalize_time=False)
193
194        duration = trace.thermal_governor.data_frame.index[-1] - trace.thermal.data_frame.index[0]
195
196        self.assertEqual(trace.get_duration(), duration)
197
198
199    def test_ftrace_normalize_time(self):
200        """FTrace()._normalize_time() works accross all classes"""
201
202        trace = trappy.FTrace(normalize_time=False)
203
204        prev_inpower_basetime = trace.cpu_in_power.data_frame.index[0]
205        prev_inpower_last = trace.cpu_in_power.data_frame.index[-1]
206
207        trace._normalize_time()
208
209        self.assertEquals(round(trace.thermal.data_frame.index[0], 7), 0)
210
211        exp_inpower_first = prev_inpower_basetime - trace.basetime
212        self.assertEquals(round(trace.cpu_in_power.data_frame.index[0] - exp_inpower_first, 7), 0)
213
214        exp_inpower_last = prev_inpower_last - trace.basetime
215        self.assertEquals(round(trace.cpu_in_power.data_frame.index[-1] - exp_inpower_last, 7), 0)
216
217    def test_ftrace_accepts_events(self):
218        """The FTrace class accepts an events parameter with only the parameters interesting for a trace"""
219
220        trace = trappy.FTrace(scope="custom", events=["cdev_update"])
221
222        self.assertGreater(len(trace.cdev_update.data_frame), 1)
223
224        # If you specify events as a string by mistake, trappy does the right thing
225        trace = trappy.FTrace(scope="custom", events="foo")
226        self.assertTrue(hasattr(trace, "foo"))
227
228    def test_ftrace_already_registered_events_are_not_registered_again(self):
229        """FTrace(events="foo") uses class for foo if it is a known class for trappy"""
230        events = ["sched_switch", "sched_load_avg_sg"]
231        trace = trappy.FTrace(scope="custom", events=events)
232
233        self.assertTrue(trace.sched_switch.parse_raw)
234        self.assertEquals(trace.sched_load_avg_sg.pivot, "cpus")
235
236    def test_get_all_freqs_data(self):
237        """Test get_all_freqs_data()"""
238
239        allfreqs = trappy.FTrace().get_all_freqs_data(self.map_label)
240
241        self.assertEquals(allfreqs[1][1]["A53_freq_out"].iloc[3], 850)
242        self.assertEquals(allfreqs[1][1]["A53_freq_in"].iloc[1], 850)
243        self.assertEquals(allfreqs[0][1]["A57_freq_out"].iloc[2], 1100)
244        self.assertTrue("gpu_freq_in" in allfreqs[2][1].columns)
245
246        # Make sure there are no NaNs in the middle of the array
247        self.assertTrue(allfreqs[0][1]["A57_freq_in"].notnull().all())
248
249    def test_apply_callbacks(self):
250        """Test apply_callbacks()"""
251
252        counts = {
253            "cpu_in_power": 0,
254            "cpu_out_power": 0
255        }
256
257        def cpu_in_power_fn(data):
258            counts["cpu_in_power"] += 1
259
260        def cpu_out_power_fn(data):
261            counts["cpu_out_power"] += 1
262
263        fn_map = {
264            "cpu_in_power": cpu_in_power_fn,
265            "cpu_out_power": cpu_out_power_fn
266        }
267        trace = trappy.FTrace()
268
269        trace.apply_callbacks(fn_map)
270
271        self.assertEqual(counts["cpu_in_power"], 134)
272        self.assertEqual(counts["cpu_out_power"], 134)
273
274    def test_plot_freq_hists(self):
275        """Test that plot_freq_hists() doesn't bomb"""
276
277        trace = trappy.FTrace()
278
279        _, axis = matplotlib.pyplot.subplots(nrows=2)
280        trace.plot_freq_hists(self.map_label, axis)
281        matplotlib.pyplot.close('all')
282
283    def test_plot_load(self):
284        """Test that plot_load() doesn't explode"""
285        trace = trappy.FTrace()
286        trace.plot_load(self.map_label, title="Util")
287
288        _, ax = matplotlib.pyplot.subplots()
289        trace.plot_load(self.map_label, ax=ax)
290
291    def test_plot_normalized_load(self):
292        """Test that plot_normalized_load() doesn't explode"""
293
294        trace = trappy.FTrace()
295
296        _, ax = matplotlib.pyplot.subplots()
297        trace.plot_normalized_load(self.map_label, ax=ax)
298
299    def test_plot_allfreqs(self):
300        """Test that plot_allfreqs() doesn't bomb"""
301
302        trace = trappy.FTrace()
303
304        trace.plot_allfreqs(self.map_label)
305        matplotlib.pyplot.close('all')
306
307        _, axis = matplotlib.pyplot.subplots(nrows=2)
308
309        trace.plot_allfreqs(self.map_label, ax=axis)
310        matplotlib.pyplot.close('all')
311
312    def test_plot_allfreqs_with_one_actor(self):
313        """Check that plot_allfreqs() works with one actor"""
314
315        in_data = """     kworker/4:1-397   [004]   720.741349: thermal_power_cpu_get: cpus=00000000,00000006 freq=1400000 raw_cpu_power=189 load={23, 12} power=14
316     kworker/4:1-397   [004]   720.741679: thermal_power_cpu_limit: cpus=00000000,00000006 freq=1400000 cdev_state=1 power=14"""
317
318        with open("trace.txt", "w") as fout:
319            fout.write(in_data)
320
321        trace = trappy.FTrace()
322        map_label = {"00000000,00000006": "A57"}
323        _, axis = matplotlib.pyplot.subplots(nrows=1)
324
325        trace.plot_allfreqs(map_label, ax=[axis])
326        matplotlib.pyplot.close('all')
327
328    def test_trace_metadata(self):
329        """Test if metadata gets populated correctly"""
330
331        expected_metadata = {}
332        expected_metadata["version"] = "6"
333        expected_metadata["cpus"] = "6"
334
335        trace = trappy.FTrace()
336        for key, value in expected_metadata.items():
337            self.assertTrue(hasattr(trace, "_" + key))
338            self.assertEquals(getattr(trace, "_" + key), value)
339
340    def test_missing_metadata(self):
341        """Test if trappy.FTrace() works with a trace missing metadata info"""
342        lines = []
343
344        with open("trace.txt", "r") as fil:
345            lines += fil.readlines()
346            lines = lines[7:]
347            fil.close()
348
349        with open("trace.txt", "w") as fil:
350            fil.write("".join(lines))
351            fil.close()
352
353        trace = trappy.FTrace()
354        self.assertEquals(trace._cpus, None)
355        self.assertEquals(trace._version, None)
356        self.assertTrue(len(trace.thermal.data_frame) > 0)
357
358    def test_ftrace_accepts_window(self):
359        """FTrace class accepts a window parameter"""
360        trace = trappy.FTrace(window=(1.234726, 5.334726))
361        self.assertEquals(trace.thermal.data_frame.iloc[0]["temp"], 68989)
362        self.assertEquals(trace.thermal.data_frame.iloc[-1]["temp"], 69530)
363
364    def test_ftrace_accepts_abs_window(self):
365        """FTrace class accepts an abs_window parameter"""
366        trace = trappy.FTrace(abs_window=(1585, 1589.1))
367        self.assertEquals(trace.thermal.data_frame.iloc[0]["temp"], 68989)
368        self.assertEquals(trace.thermal.data_frame.iloc[-1]["temp"], 69530)
369
370    def test_parse_tracing_mark_write_events(self):
371        """Check that tracing_mark_write events are parsed without errors"""
372
373        in_data = """     sh-1379  [002]   353.397813: print:                tracing_mark_write: TRACE_MARKER_START
374     shutils-1381  [001]   353.680439: print:                tracing_mark_write: cpu_frequency:        state=450000 cpu_id=5"""
375
376        with open("trace.txt", "w") as fout:
377            fout.write(in_data)
378
379        try:
380            trace = trappy.FTrace()
381        except TypeError as e:
382            self.fail("tracing_mark_write parsing failed with {} exception"\
383                      .format(e.message))
384        # The second event is recognised as a cpu_frequency event and therefore
385        # put under trace.cpu_frequency
386        self.assertEquals(trace.tracing_mark_write.data_frame.iloc[-1]["string"],
387                          "TRACE_MARKER_START")
388        self.assertEquals(len(trace.tracing_mark_write.data_frame), 1)
389
390
391@unittest.skipUnless(utils_tests.trace_cmd_installed(),
392                     "trace-cmd not installed")
393class TestFTraceRawDat(utils_tests.SetupDirectory):
394
395    def __init__(self, *args, **kwargs):
396        super(TestFTraceRawDat, self).__init__(
397             [("raw_trace.dat", "trace.dat")],
398             *args,
399             **kwargs)
400
401    def test_raw_dat(self):
402        """Tests an event that relies on raw parsing"""
403
404        trace = trappy.FTrace()
405        self.assertTrue(hasattr(trace, "sched_switch"))
406        self.assertTrue(len(trace.sched_switch.data_frame) > 0)
407        self.assertTrue("prev_comm" in trace.sched_switch.data_frame.columns)
408
409    def test_raw_dat_arb_name(self):
410        """Tests an event that relies on raw parsing with arbitrary .dat file name"""
411
412        arbitrary_name = "my_trace.dat"
413        shutil.move("trace.dat", arbitrary_name)
414
415        trace = trappy.FTrace(arbitrary_name)
416        self.assertTrue(hasattr(trace, "sched_switch"))
417        self.assertTrue(len(trace.sched_switch.data_frame) > 0)
418
419class TestFTraceRawBothTxt(utils_tests.SetupDirectory):
420
421    def __init__(self, *args, **kwargs):
422        super(TestFTraceRawBothTxt, self).__init__(
423             [("raw_trace.txt", "trace.txt"),],
424             *args,
425             **kwargs)
426
427    def test_both_txt_files(self):
428        """test raw parsing for txt files"""
429
430        self.assertFalse(os.path.isfile("trace.dat"))
431        trace = trappy.FTrace()
432        self.assertTrue(hasattr(trace, "sched_switch"))
433        self.assertTrue(len(trace.sched_switch.data_frame) > 0)
434
435    def test_both_txt_arb_name(self):
436        """Test raw parsing for txt files arbitrary name"""
437
438        arbitrary_name = "my_trace.txt"
439
440        shutil.move("trace.txt", arbitrary_name)
441
442        trace = trappy.FTrace(arbitrary_name)
443        self.assertTrue(hasattr(trace, "sched_switch"))
444        self.assertTrue(len(trace.sched_switch.data_frame) > 0)
445
446class TestFTraceSched(utils_tests.SetupDirectory):
447    """Tests using a trace with only sched info and no (or partial) thermal"""
448
449    def __init__(self, *args, **kwargs):
450        super(TestFTraceSched, self).__init__(
451             [("trace_empty.txt", "trace.txt")],
452             *args,
453             **kwargs)
454
455    def test_ftrace_basetime_empty(self):
456        """Test that basetime is 0 if data frame of all data objects is empty"""
457
458        trace = trappy.FTrace(normalize_time=False)
459
460        self.assertEqual(trace.basetime, 0)
461
462    def test_ftrace_unique_but_no_fields(self):
463        """Test with a matching unique but no special fields"""
464        version_parser = trappy.register_dynamic_ftrace("Version", "version")
465
466        # Append invalid line to file
467        with open("trace.txt", "a") as fil:
468            fil.write("version = 6")
469
470        with self.assertRaises(ValueError):
471            trappy.FTrace(scope="custom")
472
473        trappy.unregister_dynamic_ftrace(version_parser)
474
475    def test_ftrace_normalize_some_tracepoints(self):
476        """Test that normalizing time works if not all the tracepoints are in the trace"""
477
478        with open("trace.txt", "a") as fil:
479            fil.write("     kworker/4:1-1219  [004]   508.424826: thermal_temperature:  thermal_zone=exynos-therm id=0 temp_prev=24000 temp=24000")
480
481        trace = trappy.FTrace()
482
483        self.assertEqual(trace.thermal.data_frame.index[0], 0)
484
485@unittest.skipUnless(utils_tests.trace_cmd_installed(),
486                     "trace-cmd not installed")
487class TestTraceDat(utils_tests.SetupDirectory):
488    """Test that trace.dat handling work"""
489    def __init__(self, *args, **kwargs):
490        super(TestTraceDat, self).__init__(
491            [("trace.dat", "trace.dat")],
492            *args, **kwargs)
493
494    def assert_thermal_in_trace(self, fname):
495        """Assert that the thermal event is in the trace
496
497        fname is the trace file, usually "trace.txt" or "trace.raw.txt"
498        """
499
500        found = False
501        with open(fname) as fin:
502            for line in fin:
503                if re.search("thermal", line):
504                    found = True
505                    break
506
507        self.assertTrue(found)
508
509    def test_do_txt_if_not_there(self):
510        """Create trace.txt if it's not there"""
511        self.assertFalse(os.path.isfile("trace.txt"))
512
513        trappy.FTrace()
514
515        self.assert_thermal_in_trace("trace.txt")
516
517    def test_ftrace_arbitrary_trace_dat(self):
518        """FTrace() works if asked to parse a binary trace with a filename other than trace.dat"""
519        arbitrary_trace_name = "my_trace.dat"
520        shutil.move("trace.dat", arbitrary_trace_name)
521
522        dfr = trappy.FTrace(arbitrary_trace_name).thermal.data_frame
523
524        self.assertTrue(os.path.exists("my_trace.txt"))
525        self.assertTrue(len(dfr) > 0)
526        self.assertFalse(os.path.exists("trace.dat"))
527        self.assertFalse(os.path.exists("trace.txt"))
528
529    def test_regenerate_txt_if_outdated(self):
530        """Regenerate the trace.txt if it's older than the trace.dat"""
531
532        trappy.FTrace()
533
534        # Empty the trace.txt
535        with open("trace.txt", "w") as fout:
536            fout.write("")
537
538        # Set access and modified time of trace.txt to 10 seconds ago
539        now = time.time()
540        os.utime("trace.txt", (now - 10, now - 10))
541
542        # touch trace.dat
543        os.utime("trace.dat", None)
544
545        trappy.FTrace()
546
547        self.assert_thermal_in_trace("trace.txt")
548