Extract actual function duration using sched_switch events for profiling

This commit is contained in:
Christophe Bedard 2019-08-05 15:21:31 +02:00
parent bbc122a0f2
commit 10cc2e9f5d
2 changed files with 70 additions and 22 deletions

View file

@ -20,7 +20,12 @@ from . import DataModel
class ProfileDataModel(DataModel): class ProfileDataModel(DataModel):
"""Container to model pre-processed profiling data for analysis.""" """
Container to model pre-processed profiling data for analysis.
Duration is the time difference between the function entry and the function exit.
Actual duration is the actual time spent executing the function (or a child function).
"""
def __init__(self) -> None: def __init__(self) -> None:
"""Constructor.""" """Constructor."""
@ -32,6 +37,7 @@ class ProfileDataModel(DataModel):
'parent_name', 'parent_name',
'start_timestamp', 'start_timestamp',
'duration', 'duration',
'actual_duration',
]) ])
def add_duration( def add_duration(
@ -42,6 +48,7 @@ class ProfileDataModel(DataModel):
parent_name: str, parent_name: str,
start_timestamp: int, start_timestamp: int,
duration: int, duration: int,
actual_duration: int,
) -> None: ) -> None:
data = { data = {
'tid': tid, 'tid': tid,
@ -50,6 +57,7 @@ class ProfileDataModel(DataModel):
'parent_name': parent_name, 'parent_name': parent_name,
'start_timestamp': start_timestamp, 'start_timestamp': start_timestamp,
'duration': duration, 'duration': duration,
'actual_duration': actual_duration,
} }
self.times = self.times.append(data, ignore_index=True) self.times = self.times.append(data, ignore_index=True)

View file

@ -18,7 +18,6 @@ from collections import defaultdict
from typing import Dict from typing import Dict
from typing import List from typing import List
from typing import Tuple from typing import Tuple
from typing import Type
from typing import Union from typing import Union
from tracetools_read import get_field from tracetools_read import get_field
@ -36,6 +35,7 @@ class ProfileHandler(EventHandler):
It uses the following events: It uses the following events:
* lttng_ust_cyg_profile_fast:func_entry * lttng_ust_cyg_profile_fast:func_entry
* lttng_ust_cyg_profile_fast:func_exit * lttng_ust_cyg_profile_fast:func_exit
* sched_switch
""" """
FUNCTIONS = { FUNCTIONS = {
@ -59,6 +59,8 @@ class ProfileHandler(EventHandler):
self._handle_function_entry, self._handle_function_entry,
'lttng_ust_cyg_profile_fast:func_exit': 'lttng_ust_cyg_profile_fast:func_exit':
self._handle_function_exit, self._handle_function_exit,
'sched_switch':
self._handle_sched_switch,
} }
super().__init__(handler_map=handler_map, **kwargs) super().__init__(handler_map=handler_map, **kwargs)
@ -67,30 +69,55 @@ class ProfileHandler(EventHandler):
# Temporary buffers # Temporary buffers
# tid -> # tid ->
# (list of functions currently executing (ordered by relative depth), # list:[
# start timestamp of the function) # functions currently executing (ordered by relative depth), with info:
self._current_funcs: Dict[int, List[Tuple[str, int]]] = defaultdict(list) # [
# function name,
# TODO get debug_info from babeltrace for # start timestamp,
# lttng_ust_cyg_profile_fast:func_entry events # last execution start timestamp of the function,
# (or resolve { address -> function } name another way) # total duration,
self.address_to_func = { # ]
int('0x7F6CD676CDB4', 16): 'get_next_ready_executable', # ]
int('0x7F6CD676BC54', 16): 'wait_for_work', self._current_funcs: Dict[int, List[List[Union[str, int]]]] = defaultdict(list)
int('0x7F6CD678D0F8', 16): 'collect_entities',
}
def get_data_model(self) -> ProfileDataModel: def get_data_model(self) -> ProfileDataModel:
return self._data return self._data
def _handle_sched_switch(
self, event: Dict, metadata: EventMetadata
) -> None:
timestamp = metadata.timestamp
# If function(s) currently running stop executing
prev_tid = get_field(event, 'prev_tid')
if prev_tid in self._current_funcs:
# Increment durations using last start timestamp
for info in self._current_funcs.get(prev_tid):
last_start = info[2]
total_duration = info[3]
total_duration += timestamp - last_start
info[2] = None
info[3] = total_duration
# If stopped function(s) start(s) executing again
next_tid = get_field(event, 'next_tid')
if next_tid in self._current_funcs:
# Set last start timestamp to now
for info in self._current_funcs.get(next_tid):
assert info[2] is None
info[2] = timestamp
def _handle_function_entry( def _handle_function_entry(
self, event: Dict, metadata: EventMetadata self, event: Dict, metadata: EventMetadata
) -> None: ) -> None:
function_name = self._get_function_name(event) function_name = self._get_function_name(event)
assert function_name is not None, f'cannot resolve function name for event: {event}' assert function_name is not None, f'cannot resolve function name for event: {event}'
# Push function data to stack # Push function data to stack, setting both timestamps to now
self._current_funcs[metadata.tid].append( self._current_funcs[metadata.tid].append(
(metadata.timestamp, function_name) [
function_name,
metadata.timestamp,
metadata.timestamp,
0,
]
) )
def _handle_function_exit( def _handle_function_exit(
@ -100,17 +127,23 @@ class ProfileHandler(EventHandler):
tid = metadata.tid tid = metadata.tid
tid_functions = self._current_funcs[tid] tid_functions = self._current_funcs[tid]
function_depth = len(tid_functions) - 1 function_depth = len(tid_functions) - 1
(start_timestamp, start_function_name) = tid_functions.pop() info = tid_functions.pop()
function_name = info[0]
start_timestamp = info[1]
last_start_timestamp = info[2]
total_duration = info[3]
# Add to data model # Add to data model
parent_name = tid_functions[-1][1] if function_depth > 0 else None parent_name = tid_functions[-1][0] if function_depth > 0 else None
duration = metadata.timestamp - start_timestamp duration = metadata.timestamp - start_timestamp
actual_duration = (metadata.timestamp - last_start_timestamp) + total_duration
self._data.add_duration( self._data.add_duration(
tid, tid,
function_depth, function_depth,
start_function_name, function_name,
parent_name, parent_name,
start_timestamp, start_timestamp,
duration duration,
actual_duration,
) )
def _get_function_name( def _get_function_name(
@ -123,5 +156,12 @@ class ProfileHandler(EventHandler):
def _resolve_function_address( def _resolve_function_address(
self, address: int self, address: int
) -> Union[str, None]: ) -> Union[str, None]:
# TODO get from trace/binaries # TODO get debug_info from babeltrace for
return self.address_to_func.get(address, None) # lttng_ust_cyg_profile_fast:func_entry events
# (or resolve { address -> function } name another way)
address_to_func = {
int('0x7F3418EC4DB4', 16): 'get_next_ready_executable',
int('0x7F3418EC3C54', 16): 'wait_for_work',
int('0x7F3418EE50F8', 16): 'collect_entities',
}
return address_to_func.get(address, None)