1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 from telemetry
.page
import page_test
6 from telemetry
.timeline
.model
import TimelineModel
7 from telemetry
.timeline
import tracing_category_filter
8 from telemetry
.timeline
import tracing_options
9 from telemetry
.util
import statistics
10 from telemetry
.value
import scalar
13 class TaskExecutionTime(page_test
.PageTest
):
15 IDLE_SECTION_TRIGGER
= 'SingleThreadIdleTaskRunner::RunTask'
17 NORMAL_SECTION
= 'NORMAL'
19 _TIME_OUT_IN_SECONDS
= 60
20 _NUMBER_OF_RESULTS_TO_DISPLAY
= 10
21 _BROWSER_THREADS
= ['Chrome_ChildIOThread',
23 _RENDERER_THREADS
= ['Chrome_ChildIOThread',
26 _CATEGORIES
= ['benchmark',
39 super(TaskExecutionTime
, self
).__init
__()
40 self
._renderer
_process
= None
41 self
._browser
_process
= None
44 def WillNavigateToPage(self
, page
, tab
):
45 category_filter
= tracing_category_filter
.TracingCategoryFilter()
47 for category
in self
._CATEGORIES
:
48 category_filter
.AddIncludedCategory(category
)
50 options
= tracing_options
.TracingOptions()
51 options
.enable_chrome_trace
= True
53 tab
.browser
.platform
.tracing_controller
.Start(
54 options
, category_filter
, self
._TIME
_OUT
_IN
_SECONDS
)
56 def ValidateAndMeasurePage(self
, page
, tab
, results
):
57 trace_data
= tab
.browser
.platform
.tracing_controller
.Stop()
58 timeline_model
= TimelineModel(trace_data
)
60 self
._renderer
_process
= timeline_model
.GetRendererProcessFromTabId(tab
.id)
61 self
._browser
_process
= timeline_model
.browser_process
62 self
._AddResults
(results
)
64 def _AddResults(self
, results
):
65 self
._results
= results
67 for thread
in self
._BROWSER
_THREADS
:
68 self
._AddTasksFromThreadToResults
(self
._browser
_process
, thread
)
70 for thread
in self
._RENDERER
_THREADS
:
71 self
._AddTasksFromThreadToResults
(self
._renderer
_process
, thread
)
73 def _AddTasksFromThreadToResults(self
, process
, thread_name
):
77 sections
= TaskExecutionTime
._GetSectionsForThread
(process
, thread_name
)
79 self
._ReportSectionPercentages
(sections
.values(),
80 '%s:%s' % (process
.name
, thread_name
))
82 # Create list with top |_NUMBER_OF_RESULTS_TO_DISPLAY| for each section.
83 for section
in sections
.itervalues():
84 if section
.name
== TaskExecutionTime
.IDLE_SECTION
:
85 # Skip sections we don't report.
87 self
._AddSlowestTasksToResults
(section
.tasks
.values())
89 def _AddSlowestTasksToResults(self
, tasks
):
90 sorted_tasks
= sorted(
92 key
=lambda slice: slice.median_self_duration
,
95 for task
in sorted_tasks
[:self
.GetExpectedResultCount()]:
96 self
._results
.AddValue(scalar
.ScalarValue(
97 self
._results
.current_page
,
100 task
.median_self_duration
,
101 description
='Slowest tasks'))
103 def _ReportSectionPercentages(self
, section_values
, metric_prefix
):
104 all_sectionstotal_duration
= sum(
105 section
.total_duration
for section
in section_values
)
107 if not all_sectionstotal_duration
:
108 # Nothing was recorded, so early out.
111 for section
in section_values
:
112 section_name
= section
.name
or TaskExecutionTime
.NORMAL_SECTION
113 section_percentage_of_total
= (
114 (section
.total_duration
* 100.0) / all_sectionstotal_duration
)
115 self
._results
.AddValue(scalar
.ScalarValue(
116 self
._results
.current_page
,
117 '%s:Section_%s' % (metric_prefix
, section_name
),
119 section_percentage_of_total
,
120 description
='Idle task percentage'))
123 def _GetSectionsForThread(process
, target_thread
):
126 for thread
in process
.threads
.itervalues():
127 if thread
.name
!= target_thread
:
129 for task_slice
in thread
.IterAllSlices():
130 _ProcessTasksForThread(
132 '%s:%s' % (process
.name
, thread
.name
),
138 def GetExpectedResultCount():
139 return TaskExecutionTime
._NUMBER
_OF
_RESULTS
_TO
_DISPLAY
142 def _ProcessTasksForThread(
147 if task_slice
.self_thread_time
is None:
148 # Early out if this slice is a TRACE_EVENT_INSTANT, as it has no duration.
151 # Note: By setting a different section below we split off this task into
152 # a different sorting bucket. Too add extra granularity (e.g. tasks executed
153 # during page loading) add logic to set a different section name here. The
154 # section name is set before the slice's data is recorded so the triggering
155 # event will be included in its own section (i.e. the idle trigger will be
156 # recorded as an idle event).
158 if task_slice
.name
== TaskExecutionTime
.IDLE_SECTION_TRIGGER
:
159 section_name
= TaskExecutionTime
.IDLE_SECTION
161 # Add the thread name and section (e.g. 'Idle') to the test name
162 # so it is human-readable.
163 reported_name
= thread_name
+ ':'
165 reported_name
+= section_name
+ ':'
167 if 'src_func' in task_slice
.args
:
168 # Data contains the name of the timed function, use it as the name.
169 reported_name
+= task_slice
.args
['src_func']
170 elif 'line' in task_slice
.args
:
171 # Data contains IPC class and line numbers, use these as the name.
172 reported_name
+= 'IPC_Class_' + str(task_slice
.args
['class'])
173 reported_name
+= ':Line_' + str(task_slice
.args
['line'])
175 # Fallback to use the name of the task slice.
176 reported_name
+= task_slice
.name
.lower()
178 # Replace any '.'s with '_'s as V8 uses them and it confuses the dashboard.
179 reported_name
= reported_name
.replace('.', '_')
181 # If this task is in a new section create a section object and add it to the
182 # section dictionary.
183 if section_name
not in sections
:
184 sections
[section_name
] = Section(section_name
)
186 sections
[section_name
].AddTask(reported_name
, task_slice
.self_thread_time
)
188 # Process sub slices recursively, passing the current section down.
189 for sub_slice
in task_slice
.sub_slices
:
190 _ProcessTasksForThread(
197 class NameAndDurations(object):
199 def __init__(self
, name
, self_duration
):
201 self
.self_durations
= [self_duration
]
203 def Update(self
, self_duration
):
204 self
.self_durations
.append(self_duration
)
207 def median_self_duration(self
):
208 return statistics
.Median(self
.self_durations
)
211 class Section(object):
213 def __init__(self
, name
):
214 # A section holds a dictionary, keyed on task name, of all the tasks that
215 # exist within it and the total duration of those tasks.
218 self
.total_duration
= 0
220 def AddTask(self
, name
, duration
):
221 if name
in self
.tasks
:
222 # section_tasks already contains an entry for this (e.g. from an earlier
223 # slice), add the new duration so we can calculate a median value later.
224 self
.tasks
[name
].Update(duration
)
226 # This is a new task so create a new entry for it.
227 self
.tasks
[name
] = NameAndDurations(name
, duration
)
228 # Accumulate total duration for all tasks in this section.
229 self
.total_duration
+= duration