Joel Fernandes | a19ff25 | 2017-05-15 03:02:31 -0700 | [diff] [blame] | 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 |
Brendan Jackman | 878b9ac | 2017-06-02 14:05:04 +0100 | [diff] [blame] | 17 | import shutil |
Joel Fernandes | a19ff25 | 2017-05-15 03:02:31 -0700 | [diff] [blame] | 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()]) |
Brendan Jackman | 878b9ac | 2017-06-02 14:05:04 +0100 | [diff] [blame] | 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) |
Joel Fernandes | 96f1148 | 2017-07-08 19:22:22 -0700 | [diff] [blame] | 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' |
Kevin DuBois | a74ef6f | 2017-10-23 11:07:15 -0700 | [diff] [blame] | 203 | number_of_trace_categories = 29 |
Kevin DuBois | b5108a1 | 2017-10-04 14:28:52 -0700 | [diff] [blame] | 204 | self.assertEquals(len(os.listdir(cache_dir)), number_of_trace_categories) |
Joel Fernandes | 96f1148 | 2017-07-08 19:22:22 -0700 | [diff] [blame] | 205 | |
| 206 | os.remove(os.path.join(cache_dir, 'SchedWakeup.csv')) |
Kevin DuBois | b5108a1 | 2017-10-04 14:28:52 -0700 | [diff] [blame] | 207 | self.assertEquals(len(os.listdir(cache_dir)), number_of_trace_categories - 1) |
Joel Fernandes | 96f1148 | 2017-07-08 19:22:22 -0700 | [diff] [blame] | 208 | |
| 209 | # Generate trace again, should regenerate only the missing item |
| 210 | trace = trappy.FTrace() |
Kevin DuBois | b5108a1 | 2017-10-04 14:28:52 -0700 | [diff] [blame] | 211 | self.assertEquals(len(os.listdir(cache_dir)), number_of_trace_categories) |
Joel Fernandes | 96f1148 | 2017-07-08 19:22:22 -0700 | [diff] [blame] | 212 | for c in trace.trace_classes: |
| 213 | if isinstance(c, trace.class_definitions['sched_wakeup']): |
| 214 | self.assertEquals(c.cached, False) |
| 215 | continue |
| 216 | self.assertEquals(c.cached, True) |