1 # Copyright 2015-2017 ARM Limited, Google and contributors 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 import os 17 import shutil 18 import sys 19 import unittest 20 import utils_tests 21 import trappy 22 from trappy.ftrace import GenericFTrace 23 24 class TestCaching(utils_tests.SetupDirectory): 25 def __init__(self, *args, **kwargs): 26 super(TestCaching, self).__init__( 27 [("trace_sched.txt", "trace.txt"), 28 ("trace_sched.txt", "trace.raw.txt")], 29 *args, 30 **kwargs) 31 32 def test_cache_created(self): 33 """Test cache creation when enabled""" 34 GenericFTrace.disable_cache = False 35 trace = trappy.FTrace() 36 37 trace_path = os.path.abspath(trace.trace_path) 38 trace_dir = os.path.dirname(trace_path) 39 trace_file = os.path.basename(trace_path) 40 cache_dir = '.' + trace_file + '.cache' 41 42 self.assertTrue(cache_dir in os.listdir(trace_dir)) 43 44 def test_cache_not_created(self): 45 """Test that cache should not be created when disabled """ 46 GenericFTrace.disable_cache = True 47 trace = trappy.FTrace() 48 49 trace_path = os.path.abspath(trace.trace_path) 50 trace_dir = os.path.dirname(trace_path) 51 trace_file = os.path.basename(trace_path) 52 cache_dir = '.' + trace_file + '.cache' 53 54 self.assertFalse(cache_dir in os.listdir(trace_dir)) 55 56 def test_compare_cached_vs_uncached(self): 57 """ Test that the cached and uncached traces are same """ 58 # Build the cache, but the actual trace will be parsed 59 # fresh since this is a first time parse 60 GenericFTrace.disable_cache = False 61 uncached_trace = trappy.FTrace() 62 uncached_dfr = uncached_trace.sched_wakeup.data_frame 63 64 # Now read from previously parsed cache by reusing the path 65 cached_trace = trappy.FTrace(uncached_trace.trace_path) 66 cached_dfr = cached_trace.sched_wakeup.data_frame 67 68 # Test whether timestamps are the same: 69 # The cached/uncached versions of the timestamps are slightly 70 # different due to floating point precision errors due to converting 71 # back and forth CSV and DataFrame. For all purposes this is not relevant 72 # since such rounding doesn't effect the end result. 73 # Here's an example of the error, the actual normalized time when 74 # calculated by hand is 0.081489, however following is what's stored 75 # in the CSV for sched_wakeup events in this trace. 76 # When converting the index to strings (and also what's in the CSV) 77 # cached: ['0.0814890000001', '1.981491'] 78 # uncached: ['0.0814890000001', '1.981491'] 79 # 80 # Keeping index as numpy.float64 81 # cached: [0.081489000000100009, 1.9814909999999999] 82 # uncached: [0.081489000000146916, 1.9814909999995507] 83 # 84 # To make it possible to test, lets just convert the timestamps to strings 85 # and compare them below. 86 87 cached_times = [str(r[0]) for r in cached_dfr.iterrows()] 88 uncached_times = [str(r[0]) for r in uncached_dfr.iterrows()] 89 90 self.assertTrue(cached_times == uncached_times) 91 92 # compare other columns as well 93 self.assertTrue([r[1].pid for r in cached_dfr.iterrows()] == 94 [r[1].pid for r in uncached_dfr.iterrows()]) 95 96 self.assertTrue([r[1].comm for r in cached_dfr.iterrows()] == 97 [r[1].comm for r in uncached_dfr.iterrows()]) 98 99 self.assertTrue([r[1].prio for r in cached_dfr.iterrows()] == 100 [r[1].prio for r in uncached_dfr.iterrows()]) 101 102 def test_invalid_cache_overwritten(self): 103 """Test a cache with a bad checksum is overwritten""" 104 # This is a directory so we can't use the files_to_copy arg of 105 # SetUpDirectory, just do it ourselves. 106 cache_path = ".trace.txt.cache" 107 src = os.path.join(utils_tests.TESTS_DIRECTORY, "trace_sched.txt.cache") 108 shutil.copytree(src, cache_path) 109 110 md5_path = os.path.join(cache_path, "md5sum") 111 def read_md5sum(): 112 with open(md5_path) as f: 113 return f.read() 114 115 # Change 1 character of the stored checksum 116 md5sum = read_md5sum() 117 # Sorry, I guess modifying strings in Python is kind of awkward? 118 md5sum_inc = "".join(list(md5sum[:-1]) + [chr(ord(md5sum[-1]) + 1)]) 119 with open(md5_path, "w") as f: 120 f.write(md5sum_inc) 121 122 # Parse a trace, this should delete and overwrite the invalidated cache 123 GenericFTrace.disable_cache = False 124 trace = trappy.FTrace() 125 126 # Check that the modified md5sum was overwritten 127 self.assertNotEqual(read_md5sum(), md5sum_inc, 128 "The invalid ftrace cache wasn't overwritten") 129 130 def test_cache_dynamic_events(self): 131 """Test that caching works if new event parsers have been registered""" 132 133 # Parse the trace to create a cache 134 GenericFTrace.disable_cache = False 135 trace1 = trappy.FTrace() 136 137 # Check we're actually testing what we think we are 138 if hasattr(trace1, 'dynamic_event'): 139 raise RuntimeError('Test bug: found unexpected event in trace') 140 141 # Now register a new event type, call the constructor again, and check 142 # that the newly added event (which is not present in the cache) is 143 # parsed. 144 145 parse_class = trappy.register_dynamic_ftrace("DynamicEvent", "dynamic_test_key") 146 147 trace2 = trappy.FTrace() 148 self.assertTrue(len(trace2.dynamic_event.data_frame) == 1) 149 150 trappy.unregister_dynamic_ftrace(parse_class) 151 152 def test_cache_normalize_time(self): 153 """Test that caching doesn't break normalize_time""" 154 GenericFTrace.disable_cache = False 155 156 # Times in trace_sched.txt 157 start_time = 6550.018511 158 first_freq_event_time = 6550.056870 159 160 # Parse without normalizing time 161 trace1 = trappy.FTrace(events=['cpu_frequency', 'sched_wakeup'], 162 normalize_time=False) 163 164 self.assertEqual(trace1.cpu_frequency.data_frame.index[0], 165 first_freq_event_time) 166 167 # Parse with normalized time 168 trace2 = trappy.FTrace(events=['cpu_frequency', 'sched_wakeup'], 169 normalize_time=True) 170 171 self.assertEqual(trace2.cpu_frequency.data_frame.index[0], 172 first_freq_event_time - start_time) 173 174 def test_cache_window(self): 175 """Test that caching doesn't break the 'window' parameter""" 176 GenericFTrace.disable_cache = False 177 178 trace1 = trappy.FTrace( 179 events=['sched_wakeup'], 180 window=(0, 1)) 181 182 # Check that we're testing what we think we're testing The trace 183 # contains 2 sched_wakeup events; this window should get rid of one of 184 # them. 185 if len(trace1.sched_wakeup.data_frame) != 1: 186 raise RuntimeError('Test bug: bad sched_wakeup event count') 187 188 # Parse again without the window 189 trace1 = trappy.FTrace( 190 events=['sched_wakeup'], 191 window=(0, None)) 192 193 self.assertEqual(len(trace1.sched_wakeup.data_frame), 2) 194 195 def test_cache_delete_single(self): 196 GenericFTrace.disable_cache = False 197 trace = trappy.FTrace() 198 199 trace_path = os.path.abspath(trace.trace_path) 200 trace_dir = os.path.dirname(trace_path) 201 trace_file = os.path.basename(trace_path) 202 cache_dir = '.' + trace_file + '.cache' 203 self.assertEquals(len(os.listdir(cache_dir)), 22) 204 205 os.remove(os.path.join(cache_dir, 'SchedWakeup.csv')) 206 self.assertEquals(len(os.listdir(cache_dir)), 21) 207 208 # Generate trace again, should regenerate only the missing item 209 trace = trappy.FTrace() 210 self.assertEquals(len(os.listdir(cache_dir)), 22) 211 for c in trace.trace_classes: 212 if isinstance(c, trace.class_definitions['sched_wakeup']): 213 self.assertEquals(c.cached, False) 214 continue 215 self.assertEquals(c.cached, True) 216