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