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_not_normalized(self): 174 """Test get_duration: normalize_time=True""" 175 176 trace = trappy.FTrace(normalize_time=False) 177 178 duration = trace.thermal_governor.data_frame.index[-1] - trace.thermal.data_frame.index[0] 179 180 self.assertEqual(trace.get_duration(), duration) 181 182 183 def test_ftrace_normalize_time(self): 184 """FTrace()._normalize_time() works accross all classes""" 185 186 trace = trappy.FTrace(normalize_time=False) 187 188 prev_inpower_basetime = trace.cpu_in_power.data_frame.index[0] 189 prev_inpower_last = trace.cpu_in_power.data_frame.index[-1] 190 191 trace._normalize_time() 192 193 self.assertEquals(round(trace.thermal.data_frame.index[0], 7), 0) 194 195 exp_inpower_first = prev_inpower_basetime - trace.basetime 196 self.assertEquals(round(trace.cpu_in_power.data_frame.index[0] - exp_inpower_first, 7), 0) 197 198 exp_inpower_last = prev_inpower_last - trace.basetime 199 self.assertEquals(round(trace.cpu_in_power.data_frame.index[-1] - exp_inpower_last, 7), 0) 200 201 def test_ftrace_accepts_events(self): 202 """The FTrace class accepts an events parameter with only the parameters interesting for a trace""" 203 204 trace = trappy.FTrace(scope="custom", events=["cdev_update"]) 205 206 self.assertGreater(len(trace.cdev_update.data_frame), 1) 207 208 # If you specify events as a string by mistake, trappy does the right thing 209 trace = trappy.FTrace(scope="custom", events="foo") 210 self.assertTrue(hasattr(trace, "foo")) 211 212 def test_ftrace_already_registered_events_are_not_registered_again(self): 213 """FTrace(events="foo") uses class for foo if it is a known class for trappy""" 214 events = ["sched_switch", "sched_load_avg_sg"] 215 trace = trappy.FTrace(scope="custom", events=events) 216 217 self.assertTrue(trace.sched_switch.parse_raw) 218 self.assertEquals(trace.sched_load_avg_sg.pivot, "cpus") 219 220 def test_get_all_freqs_data(self): 221 """Test get_all_freqs_data()""" 222 223 allfreqs = trappy.FTrace().get_all_freqs_data(self.map_label) 224 225 self.assertEquals(allfreqs[1][1]["A53_freq_out"].iloc[3], 850) 226 self.assertEquals(allfreqs[1][1]["A53_freq_in"].iloc[1], 850) 227 self.assertEquals(allfreqs[0][1]["A57_freq_out"].iloc[2], 1100) 228 self.assertTrue("gpu_freq_in" in allfreqs[2][1].columns) 229 230 # Make sure there are no NaNs in the middle of the array 231 self.assertTrue(allfreqs[0][1]["A57_freq_in"].notnull().all()) 232 233 def test_apply_callbacks(self): 234 """Test apply_callbacks()""" 235 236 counts = { 237 "cpu_in_power": 0, 238 "cpu_out_power": 0 239 } 240 241 def cpu_in_power_fn(data): 242 counts["cpu_in_power"] += 1 243 244 def cpu_out_power_fn(data): 245 counts["cpu_out_power"] += 1 246 247 fn_map = { 248 "cpu_in_power": cpu_in_power_fn, 249 "cpu_out_power": cpu_out_power_fn 250 } 251 trace = trappy.FTrace() 252 253 trace.apply_callbacks(fn_map) 254 255 self.assertEqual(counts["cpu_in_power"], 134) 256 self.assertEqual(counts["cpu_out_power"], 134) 257 258 def test_plot_freq_hists(self): 259 """Test that plot_freq_hists() doesn't bomb""" 260 261 trace = trappy.FTrace() 262 263 _, axis = matplotlib.pyplot.subplots(nrows=2) 264 trace.plot_freq_hists(self.map_label, axis) 265 matplotlib.pyplot.close('all') 266 267 def test_plot_load(self): 268 """Test that plot_load() doesn't explode""" 269 trace = trappy.FTrace() 270 trace.plot_load(self.map_label, title="Util") 271 272 _, ax = matplotlib.pyplot.subplots() 273 trace.plot_load(self.map_label, ax=ax) 274 275 def test_plot_normalized_load(self): 276 """Test that plot_normalized_load() doesn't explode""" 277 278 trace = trappy.FTrace() 279 280 _, ax = matplotlib.pyplot.subplots() 281 trace.plot_normalized_load(self.map_label, ax=ax) 282 283 def test_plot_allfreqs(self): 284 """Test that plot_allfreqs() doesn't bomb""" 285 286 trace = trappy.FTrace() 287 288 trace.plot_allfreqs(self.map_label) 289 matplotlib.pyplot.close('all') 290 291 _, axis = matplotlib.pyplot.subplots(nrows=2) 292 293 trace.plot_allfreqs(self.map_label, ax=axis) 294 matplotlib.pyplot.close('all') 295 296 def test_plot_allfreqs_with_one_actor(self): 297 """Check that plot_allfreqs() works with one actor""" 298 299 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 300 kworker/4:1-397 [004] 720.741679: thermal_power_cpu_limit: cpus=00000000,00000006 freq=1400000 cdev_state=1 power=14""" 301 302 with open("trace.txt", "w") as fout: 303 fout.write(in_data) 304 305 trace = trappy.FTrace() 306 map_label = {"00000000,00000006": "A57"} 307 _, axis = matplotlib.pyplot.subplots(nrows=1) 308 309 trace.plot_allfreqs(map_label, ax=[axis]) 310 matplotlib.pyplot.close('all') 311 312 def test_trace_metadata(self): 313 """Test if metadata gets populated correctly""" 314 315 expected_metadata = {} 316 expected_metadata["version"] = "6" 317 expected_metadata["cpus"] = "6" 318 319 trace = trappy.FTrace() 320 for key, value in expected_metadata.items(): 321 self.assertTrue(hasattr(trace, "_" + key)) 322 self.assertEquals(getattr(trace, "_" + key), value) 323 324 def test_missing_metadata(self): 325 """Test if trappy.FTrace() works with a trace missing metadata info""" 326 lines = [] 327 328 with open("trace.txt", "r") as fil: 329 lines += fil.readlines() 330 lines = lines[7:] 331 fil.close() 332 333 with open("trace.txt", "w") as fil: 334 fil.write("".join(lines)) 335 fil.close() 336 337 trace = trappy.FTrace() 338 self.assertEquals(trace._cpus, None) 339 self.assertEquals(trace._version, None) 340 self.assertTrue(len(trace.thermal.data_frame) > 0) 341 342 def test_ftrace_accepts_window(self): 343 """FTrace class accepts a window parameter""" 344 trace = trappy.FTrace(window=(1.234726, 5.334726)) 345 self.assertEquals(trace.thermal.data_frame.iloc[0]["temp"], 68989) 346 self.assertEquals(trace.thermal.data_frame.iloc[-1]["temp"], 69530) 347 348 def test_ftrace_accepts_abs_window(self): 349 """FTrace class accepts an abs_window parameter""" 350 trace = trappy.FTrace(abs_window=(1585, 1589.1)) 351 self.assertEquals(trace.thermal.data_frame.iloc[0]["temp"], 68989) 352 self.assertEquals(trace.thermal.data_frame.iloc[-1]["temp"], 69530) 353 354 355@unittest.skipUnless(utils_tests.trace_cmd_installed(), 356 "trace-cmd not installed") 357class TestFTraceRawDat(utils_tests.SetupDirectory): 358 359 def __init__(self, *args, **kwargs): 360 super(TestFTraceRawDat, self).__init__( 361 [("raw_trace.dat", "trace.dat")], 362 *args, 363 **kwargs) 364 365 def test_raw_dat(self): 366 """Tests an event that relies on raw parsing""" 367 368 trace = trappy.FTrace() 369 self.assertTrue(hasattr(trace, "sched_switch")) 370 self.assertTrue(len(trace.sched_switch.data_frame) > 0) 371 self.assertTrue("prev_comm" in trace.sched_switch.data_frame.columns) 372 373 def test_raw_dat_arb_name(self): 374 """Tests an event that relies on raw parsing with arbitrary .dat file name""" 375 376 arbitrary_name = "my_trace.dat" 377 shutil.move("trace.dat", arbitrary_name) 378 379 trace = trappy.FTrace(arbitrary_name) 380 self.assertTrue(hasattr(trace, "sched_switch")) 381 self.assertTrue(len(trace.sched_switch.data_frame) > 0) 382 383class TestFTraceRawBothTxt(utils_tests.SetupDirectory): 384 385 def __init__(self, *args, **kwargs): 386 super(TestFTraceRawBothTxt, self).__init__( 387 [("raw_trace.txt", "trace.txt"),], 388 *args, 389 **kwargs) 390 391 def test_both_txt_files(self): 392 """test raw parsing for txt files""" 393 394 self.assertFalse(os.path.isfile("trace.dat")) 395 trace = trappy.FTrace() 396 self.assertTrue(hasattr(trace, "sched_switch")) 397 self.assertTrue(len(trace.sched_switch.data_frame) > 0) 398 399 def test_both_txt_arb_name(self): 400 """Test raw parsing for txt files arbitrary name""" 401 402 arbitrary_name = "my_trace.txt" 403 404 shutil.move("trace.txt", arbitrary_name) 405 406 trace = trappy.FTrace(arbitrary_name) 407 self.assertTrue(hasattr(trace, "sched_switch")) 408 self.assertTrue(len(trace.sched_switch.data_frame) > 0) 409 410class TestFTraceSched(utils_tests.SetupDirectory): 411 """Tests using a trace with only sched info and no (or partial) thermal""" 412 413 def __init__(self, *args, **kwargs): 414 super(TestFTraceSched, self).__init__( 415 [("trace_empty.txt", "trace.txt")], 416 *args, 417 **kwargs) 418 419 def test_ftrace_basetime_empty(self): 420 """Test that basetime is 0 if data frame of all data objects is empty""" 421 422 trace = trappy.FTrace(normalize_time=False) 423 424 self.assertEqual(trace.basetime, 0) 425 426 def test_ftrace_unique_but_no_fields(self): 427 """Test with a matching unique but no special fields""" 428 version_parser = trappy.register_dynamic_ftrace("Version", "version") 429 430 # Append invalid line to file 431 with open("trace.txt", "a") as fil: 432 fil.write("version = 6") 433 434 with self.assertRaises(ValueError): 435 trappy.FTrace(scope="custom") 436 437 trappy.unregister_dynamic_ftrace(version_parser) 438 439 def test_ftrace_normalize_some_tracepoints(self): 440 """Test that normalizing time works if not all the tracepoints are in the trace""" 441 442 with open("trace.txt", "a") as fil: 443 fil.write(" kworker/4:1-1219 [004] 508.424826: thermal_temperature: thermal_zone=exynos-therm id=0 temp_prev=24000 temp=24000") 444 445 trace = trappy.FTrace() 446 447 self.assertEqual(trace.thermal.data_frame.index[0], 0) 448 449@unittest.skipUnless(utils_tests.trace_cmd_installed(), 450 "trace-cmd not installed") 451class TestTraceDat(utils_tests.SetupDirectory): 452 """Test that trace.dat handling work""" 453 def __init__(self, *args, **kwargs): 454 super(TestTraceDat, self).__init__( 455 [("trace.dat", "trace.dat")], 456 *args, **kwargs) 457 458 def assert_thermal_in_trace(self, fname): 459 """Assert that the thermal event is in the trace 460 461 fname is the trace file, usually "trace.txt" or "trace.raw.txt" 462 """ 463 464 found = False 465 with open(fname) as fin: 466 for line in fin: 467 if re.search("thermal", line): 468 found = True 469 break 470 471 self.assertTrue(found) 472 473 def test_do_txt_if_not_there(self): 474 """Create trace.txt if it's not there""" 475 self.assertFalse(os.path.isfile("trace.txt")) 476 477 trappy.FTrace() 478 479 self.assert_thermal_in_trace("trace.txt") 480 481 def test_ftrace_arbitrary_trace_dat(self): 482 """FTrace() works if asked to parse a binary trace with a filename other than trace.dat""" 483 arbitrary_trace_name = "my_trace.dat" 484 shutil.move("trace.dat", arbitrary_trace_name) 485 486 dfr = trappy.FTrace(arbitrary_trace_name).thermal.data_frame 487 488 self.assertTrue(os.path.exists("my_trace.txt")) 489 self.assertTrue(len(dfr) > 0) 490 self.assertFalse(os.path.exists("trace.dat")) 491 self.assertFalse(os.path.exists("trace.txt")) 492 493 def test_regenerate_txt_if_outdated(self): 494 """Regenerate the trace.txt if it's older than the trace.dat""" 495 496 trappy.FTrace() 497 498 # Empty the trace.txt 499 with open("trace.txt", "w") as fout: 500 fout.write("") 501 502 # Set access and modified time of trace.txt to 10 seconds ago 503 now = time.time() 504 os.utime("trace.txt", (now - 10, now - 10)) 505 506 # touch trace.dat 507 os.utime("trace.dat", None) 508 509 trappy.FTrace() 510 511 self.assert_thermal_in_trace("trace.txt") 512