# 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::synchronizedPaint'},
        {'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()

# 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::synchronizedPaint'},
        {'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()

# 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. # Change the MarkLoad events. # We use three requests, A, B and C. A is not blocked, B is blocked by A, # and C blocked by A and B.