blob: ef430149e701e62ad331a9a014bb6cd6afb61ec6 [file] [log] [blame]
# 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()