| # Copyright 2016 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import unittest |
| |
| import metrics |
| import report |
| from queuing_lens import QueuingLens |
| import test_utils |
| import user_satisfied_lens_unittest |
| |
| |
| class LoadingReportTestCase(unittest.TestCase): |
| MILLI_TO_MICRO = 1000 |
| _NAVIGATION_START_TIME = 12 |
| _FIRST_REQUEST_TIME = 15 |
| _CONTENTFUL_PAINT = 120 |
| _TEXT_PAINT = 30 |
| _SIGNIFICANT_PAINT = 50 |
| _DURATION = 400 |
| _REQUEST_OFFSET = 5 |
| _LOAD_END_TIME = 1280 |
| _MAIN_FRAME_ID = 1 |
| _FIRST_REQUEST_DATA_LENGTH = 128 |
| _SECOND_REQUEST_DATA_LENGTH = 1024 |
| _TOPLEVEL_EVENT_OFFSET = 10 |
| _TOPLEVEL_EVENT_DURATION = 100 |
| _SCRIPT_EVENT_DURATION = 50 |
| _PARSING_EVENT_DURATION = 60 |
| |
| def setUp(self): |
| self.trace_creator = test_utils.TraceCreator() |
| self.requests = [ |
| self.trace_creator.RequestAt(self._FIRST_REQUEST_TIME, frame_id=1), |
| self.trace_creator.RequestAt( |
| self._NAVIGATION_START_TIME + self._REQUEST_OFFSET, self._DURATION)] |
| self.requests[0].timing.receive_headers_end = 0 |
| self.requests[1].timing.receive_headers_end = 0 |
| self.requests[0].encoded_data_length = self._FIRST_REQUEST_DATA_LENGTH |
| self.requests[1].encoded_data_length = self._SECOND_REQUEST_DATA_LENGTH |
| |
| self.ad_domain = 'i-ve-got-the-best-ads.com' |
| self.ad_url = 'http://www.' + self.ad_domain + '/i-m-really-rich.js' |
| self.requests[0].url = self.ad_url |
| |
| self.trace_events = [ |
| {'args': {'name': 'CrRendererMain'}, 'cat': '__metadata', |
| 'name': 'thread_name', 'ph': 'M', 'pid': 1, 'tid': 1, 'ts': 0}, |
| {'ts': self._NAVIGATION_START_TIME * self.MILLI_TO_MICRO, 'ph': 'R', |
| 'cat': 'blink.user_timing', 'pid': 1, 'tid': 1, |
| 'name': 'navigationStart', |
| 'args': {'frame': 1}}, |
| {'ts': self._LOAD_END_TIME * self.MILLI_TO_MICRO, 'ph': 'I', |
| 'cat': 'devtools.timeline', 'pid': 1, 'tid': 1, |
| 'name': 'MarkLoad', |
| 'args': {'data': {'isMainFrame': True}}}, |
| {'ts': self._CONTENTFUL_PAINT * self.MILLI_TO_MICRO, 'ph': 'I', |
| 'cat': 'blink.user_timing', 'pid': 1, 'tid': 1, |
| 'name': 'firstContentfulPaint', |
| 'args': {'frame': self._MAIN_FRAME_ID}}, |
| {'ts': self._TEXT_PAINT * self.MILLI_TO_MICRO, 'ph': 'I', |
| 'cat': 'blink.user_timing', 'pid': 1, 'tid': 1, |
| 'name': 'firstPaint', |
| 'args': {'frame': self._MAIN_FRAME_ID}}, |
| {'ts': 90 * self.MILLI_TO_MICRO, 'ph': 'I', |
| 'cat': 'blink', 'pid': 1, 'tid': 1, |
| 'name': 'FrameView::paintTree'}, |
| {'ts': self._SIGNIFICANT_PAINT * self.MILLI_TO_MICRO, 'ph': 'I', |
| 'cat': 'foobar', 'name': 'biz', 'pid': 1, 'tid': 1, |
| 'args': {'counters': { |
| 'LayoutObjectsThatHadNeverHadLayout': 10}}}, |
| {'ts': (self._NAVIGATION_START_TIME - self._TOPLEVEL_EVENT_OFFSET) |
| * self.MILLI_TO_MICRO, |
| 'pid': 1, 'tid': 1, 'ph': 'X', |
| 'dur': self._TOPLEVEL_EVENT_DURATION * self.MILLI_TO_MICRO, |
| 'cat': 'toplevel', 'name': 'MessageLoop::RunTask'}, |
| {'ts': self._NAVIGATION_START_TIME * self.MILLI_TO_MICRO, |
| 'pid': 1, 'tid': 1, 'ph': 'X', |
| 'dur': self._PARSING_EVENT_DURATION * self.MILLI_TO_MICRO, |
| 'cat': 'devtools.timeline', 'name': 'ParseHTML', |
| 'args': {'beginData': {'url': ''}}}, |
| {'ts': self._NAVIGATION_START_TIME * self.MILLI_TO_MICRO, |
| 'pid': 1, 'tid': 1, 'ph': 'X', |
| 'dur': self._SCRIPT_EVENT_DURATION * self.MILLI_TO_MICRO, |
| 'cat': 'devtools.timeline', 'name': 'EvaluateScript', |
| 'args': {'data': {'scriptName': ''}}}] |
| |
| def _MakeTrace(self): |
| trace = self.trace_creator.CreateTrace( |
| self.requests, self.trace_events, self._MAIN_FRAME_ID) |
| return trace |
| |
| def _AddQueuingEvents(self, source_id, url, start_msec, ready_msec, end_msec): |
| self.trace_events.extend([ |
| {'args': { |
| 'data': { |
| 'request_url': url, |
| 'source_id': source_id |
| } |
| }, |
| 'cat': QueuingLens.QUEUING_CATEGORY, |
| 'id': source_id, |
| 'pid': 1, 'tid': 10, |
| 'name': QueuingLens.ASYNC_NAME, |
| 'ph': 'b', |
| 'ts': start_msec * self.MILLI_TO_MICRO |
| }, |
| {'args': { |
| 'data': { |
| 'source_id': source_id |
| } |
| }, |
| 'cat': QueuingLens.QUEUING_CATEGORY, |
| 'id': source_id, |
| 'pid': 1, 'tid': 10, |
| 'name': QueuingLens.READY_NAME, |
| 'ph': 'n', |
| 'ts': ready_msec * self.MILLI_TO_MICRO |
| }, |
| {'args': { |
| 'data': { |
| 'source_id': source_id |
| } |
| }, |
| 'cat': QueuingLens.QUEUING_CATEGORY, |
| 'id': source_id, |
| 'pid': 1, 'tid': 10, |
| 'name': QueuingLens.ASYNC_NAME, |
| 'ph': 'e', |
| 'ts': end_msec * self.MILLI_TO_MICRO |
| }]) |
| |
| def testGenerateReport(self): |
| trace = self._MakeTrace() |
| loading_report = report.LoadingReport(trace).GenerateReport() |
| self.assertEqual(trace.url, loading_report['url']) |
| self.assertEqual(self._TEXT_PAINT - self._NAVIGATION_START_TIME, |
| loading_report['first_text_ms']) |
| self.assertEqual(self._SIGNIFICANT_PAINT - self._NAVIGATION_START_TIME, |
| loading_report['significant_ms']) |
| self.assertEqual(self._CONTENTFUL_PAINT - self._NAVIGATION_START_TIME, |
| loading_report['contentful_ms']) |
| self.assertAlmostEqual(self._LOAD_END_TIME - self._NAVIGATION_START_TIME, |
| loading_report['plt_ms']) |
| self.assertEqual(2, loading_report['total_requests']) |
| self.assertAlmostEqual(0.34, loading_report['contentful_byte_frac'], 2) |
| self.assertAlmostEqual(0.1844, loading_report['significant_byte_frac'], 2) |
| self.assertEqual(2, loading_report['plt_requests']) |
| self.assertEqual(1, loading_report['first_text_requests']) |
| self.assertEqual(1, loading_report['contentful_requests']) |
| self.assertEqual(1, loading_report['significant_requests']) |
| self.assertEqual(1, loading_report['plt_preloaded_requests']) |
| self.assertEqual(1, loading_report['first_text_preloaded_requests']) |
| self.assertEqual(1, loading_report['contentful_preloaded_requests']) |
| self.assertEqual(1, loading_report['significant_preloaded_requests']) |
| self.assertEqual(401, loading_report['plt_requests_cost']) |
| self.assertEqual(1, loading_report['first_text_requests_cost']) |
| self.assertEqual(1, loading_report['contentful_requests_cost']) |
| self.assertEqual(1, loading_report['significant_requests_cost']) |
| self.assertEqual(1, loading_report['plt_preloaded_requests_cost']) |
| self.assertEqual(1, loading_report['first_text_preloaded_requests_cost']) |
| self.assertEqual(1, loading_report['contentful_preloaded_requests_cost']) |
| self.assertEqual(1, loading_report['significant_preloaded_requests_cost']) |
| self.assertEqual(400, loading_report['plt_predicted_no_state_prefetch_ms']) |
| self.assertEqual(14, |
| loading_report['first_text_predicted_no_state_prefetch_ms']) |
| self.assertEqual(104, |
| loading_report['contentful_predicted_no_state_prefetch_ms']) |
| self.assertEqual(74, |
| loading_report['significant_predicted_no_state_prefetch_ms']) |
| self.assertEqual('', loading_report['contentful_inversion']) |
| self.assertEqual('', loading_report['significant_inversion']) |
| self.assertIsNone(loading_report['ad_requests']) |
| self.assertIsNone(loading_report['ad_or_tracking_requests']) |
| self.assertIsNone(loading_report['ad_or_tracking_initiated_requests']) |
| self.assertIsNone(loading_report['ad_or_tracking_initiated_transfer_size']) |
| self.assertIsNone(loading_report['ad_or_tracking_script_frac']) |
| self.assertIsNone(loading_report['ad_or_tracking_parsing_frac']) |
| self.assertEqual( |
| self._FIRST_REQUEST_DATA_LENGTH + self._SECOND_REQUEST_DATA_LENGTH |
| + metrics.HTTP_OK_LENGTH * 2, |
| loading_report['transfer_size']) |
| self.assertEqual(0, loading_report['total_queuing_blocked_msec']) |
| self.assertEqual(0, loading_report['total_queuing_load_msec']) |
| self.assertEqual(0, loading_report['average_blocking_request_count']) |
| self.assertEqual(0, loading_report['median_blocking_request_count']) |
| |
| def testInversion(self): |
| self.requests[0].timing.loading_finished = 4 * ( |
| self._REQUEST_OFFSET + self._DURATION) |
| self.requests[1].initiator['type'] = 'parser' |
| self.requests[1].initiator['url'] = self.requests[0].url |
| for e in self.trace_events: |
| if e['name'] == 'firstContentfulPaint': |
| e['ts'] = self.MILLI_TO_MICRO * ( |
| self._FIRST_REQUEST_TIME + self._REQUEST_OFFSET + |
| self._DURATION + 1) |
| break |
| loading_report = report.LoadingReport(self._MakeTrace()).GenerateReport() |
| self.assertEqual(self.requests[0].url, |
| loading_report['contentful_inversion']) |
| self.assertEqual('', loading_report['significant_inversion']) |
| |
| def testPltNoLoadEvents(self): |
| trace = self._MakeTrace() |
| # Change the MarkLoad events. |
| for e in trace.tracing_track.GetEvents(): |
| if e.name == 'MarkLoad': |
| e.tracing_event['name'] = 'dummy' |
| loading_report = report.LoadingReport(trace).GenerateReport() |
| self.assertAlmostEqual(self._REQUEST_OFFSET + self._DURATION, |
| loading_report['plt_ms']) |
| |
| def testAdTrackingRules(self): |
| trace = self._MakeTrace() |
| loading_report = report.LoadingReport( |
| trace, [self.ad_domain], []).GenerateReport() |
| self.assertEqual(1, loading_report['ad_requests']) |
| self.assertEqual(1, loading_report['ad_or_tracking_requests']) |
| self.assertEqual(1, loading_report['ad_or_tracking_initiated_requests']) |
| self.assertIsNone(loading_report['tracking_requests']) |
| self.assertEqual( |
| self._FIRST_REQUEST_DATA_LENGTH + metrics.HTTP_OK_LENGTH, |
| loading_report['ad_or_tracking_initiated_transfer_size']) |
| |
| def testThreadBusyness(self): |
| loading_report = report.LoadingReport(self._MakeTrace()).GenerateReport() |
| self.assertAlmostEqual( |
| 1., loading_report['significant_activity_frac']) |
| self.assertAlmostEqual( |
| float(self._TOPLEVEL_EVENT_DURATION - self._TOPLEVEL_EVENT_OFFSET) |
| / (self._CONTENTFUL_PAINT - self._NAVIGATION_START_TIME), |
| loading_report['contentful_activity_frac']) |
| self.assertAlmostEqual( |
| float(self._TOPLEVEL_EVENT_DURATION - self._TOPLEVEL_EVENT_OFFSET) |
| / (self._LOAD_END_TIME - self._NAVIGATION_START_TIME), |
| loading_report['plt_activity_frac']) |
| |
| def testActivityBreakdown(self): |
| loading_report = report.LoadingReport(self._MakeTrace()).GenerateReport() |
| load_time = float(self._LOAD_END_TIME - self._NAVIGATION_START_TIME) |
| contentful_time = float( |
| self._CONTENTFUL_PAINT - self._NAVIGATION_START_TIME) |
| |
| self.assertAlmostEqual(self._SCRIPT_EVENT_DURATION / load_time, |
| loading_report['plt_script_frac']) |
| self.assertAlmostEqual( |
| (self._PARSING_EVENT_DURATION - self._SCRIPT_EVENT_DURATION) |
| / load_time, |
| loading_report['plt_parsing_frac']) |
| |
| self.assertAlmostEqual(1., loading_report['significant_script_frac']) |
| self.assertAlmostEqual(0., loading_report['significant_parsing_frac']) |
| |
| self.assertAlmostEqual(self._SCRIPT_EVENT_DURATION / contentful_time, |
| loading_report['contentful_script_frac']) |
| self.assertAlmostEqual( |
| (self._PARSING_EVENT_DURATION - self._SCRIPT_EVENT_DURATION) |
| / contentful_time, loading_report['contentful_parsing_frac']) |
| |
| def testAdsAndTrackingCost(self): |
| load_time = float(self._LOAD_END_TIME - self._NAVIGATION_START_TIME) |
| self.trace_events.append( |
| {'ts': load_time / 3. * self.MILLI_TO_MICRO, |
| 'pid': 1, 'tid': 1, 'ph': 'X', |
| 'dur': load_time / 2. * self.MILLI_TO_MICRO, |
| 'cat': 'devtools.timeline', 'name': 'EvaluateScript', |
| 'args': {'data': {'scriptName': self.ad_url}}}) |
| loading_report = report.LoadingReport( |
| self._MakeTrace(), [self.ad_domain]).GenerateReport() |
| self.assertAlmostEqual(.5, loading_report['ad_or_tracking_script_frac'], 2) |
| self.assertAlmostEqual(0., loading_report['ad_or_tracking_parsing_frac']) |
| |
| def testQueueStats(self): |
| # We use three requests, A, B and C. A is not blocked, B is blocked by A, |
| # and C blocked by A and B. |
| BASE_MSEC = self._FIRST_REQUEST_TIME + 4 * self._DURATION |
| self.requests = [] |
| request_A = self.trace_creator.RequestAt(BASE_MSEC, 5) |
| request_B = self.trace_creator.RequestAt(BASE_MSEC + 6, 5) |
| request_C = self.trace_creator.RequestAt(BASE_MSEC + 12, 10) |
| self.requests.extend([request_A, request_B, request_C]) |
| self._AddQueuingEvents(10, request_A.url, |
| BASE_MSEC, BASE_MSEC, BASE_MSEC + 5) |
| self._AddQueuingEvents(20, request_B.url, |
| BASE_MSEC + 1, BASE_MSEC + 6, BASE_MSEC + 11) |
| self._AddQueuingEvents(30, request_C.url, |
| BASE_MSEC + 2, BASE_MSEC + 12, BASE_MSEC + 22) |
| loading_report = report.LoadingReport(self._MakeTrace()).GenerateReport() |
| self.assertEqual(15, loading_report['total_queuing_blocked_msec']) |
| self.assertEqual(35, loading_report['total_queuing_load_msec']) |
| self.assertAlmostEqual(1, loading_report['average_blocking_request_count']) |
| self.assertEqual(1, loading_report['median_blocking_request_count']) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |