| # Copyright 2015-2017 ARM Limited, Google and contributors |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| import os |
| import shutil |
| import sys |
| import unittest |
| import utils_tests |
| import trappy |
| from trappy.ftrace import GenericFTrace |
| |
| class TestCaching(utils_tests.SetupDirectory): |
| def __init__(self, *args, **kwargs): |
| super(TestCaching, self).__init__( |
| [("trace_sched.txt", "trace.txt"), |
| ("trace_sched.txt", "trace.raw.txt")], |
| *args, |
| **kwargs) |
| |
| def test_cache_created(self): |
| """Test cache creation when enabled""" |
| GenericFTrace.disable_cache = False |
| trace = trappy.FTrace() |
| |
| trace_path = os.path.abspath(trace.trace_path) |
| trace_dir = os.path.dirname(trace_path) |
| trace_file = os.path.basename(trace_path) |
| cache_dir = '.' + trace_file + '.cache' |
| |
| self.assertTrue(cache_dir in os.listdir(trace_dir)) |
| |
| def test_cache_not_created(self): |
| """Test that cache should not be created when disabled """ |
| GenericFTrace.disable_cache = True |
| trace = trappy.FTrace() |
| |
| trace_path = os.path.abspath(trace.trace_path) |
| trace_dir = os.path.dirname(trace_path) |
| trace_file = os.path.basename(trace_path) |
| cache_dir = '.' + trace_file + '.cache' |
| |
| self.assertFalse(cache_dir in os.listdir(trace_dir)) |
| |
| def test_compare_cached_vs_uncached(self): |
| """ Test that the cached and uncached traces are same """ |
| # Build the cache, but the actual trace will be parsed |
| # fresh since this is a first time parse |
| GenericFTrace.disable_cache = False |
| uncached_trace = trappy.FTrace() |
| uncached_dfr = uncached_trace.sched_wakeup.data_frame |
| |
| # Now read from previously parsed cache by reusing the path |
| cached_trace = trappy.FTrace(uncached_trace.trace_path) |
| cached_dfr = cached_trace.sched_wakeup.data_frame |
| |
| # Test whether timestamps are the same: |
| # The cached/uncached versions of the timestamps are slightly |
| # different due to floating point precision errors due to converting |
| # back and forth CSV and DataFrame. For all purposes this is not relevant |
| # since such rounding doesn't effect the end result. |
| # Here's an example of the error, the actual normalized time when |
| # calculated by hand is 0.081489, however following is what's stored |
| # in the CSV for sched_wakeup events in this trace. |
| # When converting the index to strings (and also what's in the CSV) |
| # cached: ['0.0814890000001', '1.981491'] |
| # uncached: ['0.0814890000001', '1.981491'] |
| # |
| # Keeping index as numpy.float64 |
| # cached: [0.081489000000100009, 1.9814909999999999] |
| # uncached: [0.081489000000146916, 1.9814909999995507] |
| # |
| # To make it possible to test, lets just convert the timestamps to strings |
| # and compare them below. |
| |
| cached_times = [str(r[0]) for r in cached_dfr.iterrows()] |
| uncached_times = [str(r[0]) for r in uncached_dfr.iterrows()] |
| |
| self.assertTrue(cached_times == uncached_times) |
| |
| # compare other columns as well |
| self.assertTrue([r[1].pid for r in cached_dfr.iterrows()] == |
| [r[1].pid for r in uncached_dfr.iterrows()]) |
| |
| self.assertTrue([r[1].comm for r in cached_dfr.iterrows()] == |
| [r[1].comm for r in uncached_dfr.iterrows()]) |
| |
| self.assertTrue([r[1].prio for r in cached_dfr.iterrows()] == |
| [r[1].prio for r in uncached_dfr.iterrows()]) |
| |
| def test_invalid_cache_overwritten(self): |
| """Test a cache with a bad checksum is overwritten""" |
| # This is a directory so we can't use the files_to_copy arg of |
| # SetUpDirectory, just do it ourselves. |
| cache_path = ".trace.txt.cache" |
| src = os.path.join(utils_tests.TESTS_DIRECTORY, "trace_sched.txt.cache") |
| shutil.copytree(src, cache_path) |
| |
| md5_path = os.path.join(cache_path, "md5sum") |
| def read_md5sum(): |
| with open(md5_path) as f: |
| return f.read() |
| |
| # Change 1 character of the stored checksum |
| md5sum = read_md5sum() |
| # Sorry, I guess modifying strings in Python is kind of awkward? |
| md5sum_inc = "".join(list(md5sum[:-1]) + [chr(ord(md5sum[-1]) + 1)]) |
| with open(md5_path, "w") as f: |
| f.write(md5sum_inc) |
| |
| # Parse a trace, this should delete and overwrite the invalidated cache |
| GenericFTrace.disable_cache = False |
| trace = trappy.FTrace() |
| |
| # Check that the modified md5sum was overwritten |
| self.assertNotEqual(read_md5sum(), md5sum_inc, |
| "The invalid ftrace cache wasn't overwritten") |
| |
| def test_cache_dynamic_events(self): |
| """Test that caching works if new event parsers have been registered""" |
| |
| # Parse the trace to create a cache |
| GenericFTrace.disable_cache = False |
| trace1 = trappy.FTrace() |
| |
| # Check we're actually testing what we think we are |
| if hasattr(trace1, 'dynamic_event'): |
| raise RuntimeError('Test bug: found unexpected event in trace') |
| |
| # Now register a new event type, call the constructor again, and check |
| # that the newly added event (which is not present in the cache) is |
| # parsed. |
| |
| parse_class = trappy.register_dynamic_ftrace("DynamicEvent", "dynamic_test_key") |
| |
| trace2 = trappy.FTrace() |
| self.assertTrue(len(trace2.dynamic_event.data_frame) == 1) |
| |
| trappy.unregister_dynamic_ftrace(parse_class) |
| |
| def test_cache_normalize_time(self): |
| """Test that caching doesn't break normalize_time""" |
| GenericFTrace.disable_cache = False |
| |
| # Times in trace_sched.txt |
| start_time = 6550.018511 |
| first_freq_event_time = 6550.056870 |
| |
| # Parse without normalizing time |
| trace1 = trappy.FTrace(events=['cpu_frequency', 'sched_wakeup'], |
| normalize_time=False) |
| |
| self.assertEqual(trace1.cpu_frequency.data_frame.index[0], |
| first_freq_event_time) |
| |
| # Parse with normalized time |
| trace2 = trappy.FTrace(events=['cpu_frequency', 'sched_wakeup'], |
| normalize_time=True) |
| |
| self.assertEqual(trace2.cpu_frequency.data_frame.index[0], |
| first_freq_event_time - start_time) |
| |
| def test_cache_window(self): |
| """Test that caching doesn't break the 'window' parameter""" |
| GenericFTrace.disable_cache = False |
| |
| trace1 = trappy.FTrace( |
| events=['sched_wakeup'], |
| window=(0, 1)) |
| |
| # Check that we're testing what we think we're testing The trace |
| # contains 2 sched_wakeup events; this window should get rid of one of |
| # them. |
| if len(trace1.sched_wakeup.data_frame) != 1: |
| raise RuntimeError('Test bug: bad sched_wakeup event count') |
| |
| # Parse again without the window |
| trace1 = trappy.FTrace( |
| events=['sched_wakeup'], |
| window=(0, None)) |
| |
| self.assertEqual(len(trace1.sched_wakeup.data_frame), 2) |
| |
| def test_cache_delete_single(self): |
| GenericFTrace.disable_cache = False |
| trace = trappy.FTrace() |
| |
| trace_path = os.path.abspath(trace.trace_path) |
| trace_dir = os.path.dirname(trace_path) |
| trace_file = os.path.basename(trace_path) |
| cache_dir = '.' + trace_file + '.cache' |
| number_of_trace_categories = 29 |
| self.assertEquals(len(os.listdir(cache_dir)), number_of_trace_categories) |
| |
| os.remove(os.path.join(cache_dir, 'SchedWakeup.csv')) |
| self.assertEquals(len(os.listdir(cache_dir)), number_of_trace_categories - 1) |
| |
| # Generate trace again, should regenerate only the missing item |
| trace = trappy.FTrace() |
| self.assertEquals(len(os.listdir(cache_dir)), number_of_trace_categories) |
| for c in trace.trace_classes: |
| if isinstance(c, trace.class_definitions['sched_wakeup']): |
| self.assertEquals(c.cached, False) |
| continue |
| self.assertEquals(c.cached, True) |