Skip to content

Commit

Permalink
add trace tool to support libva trace process
Browse files Browse the repository at this point in the history
Signed-off-by: Lindong Wu <lindong.wu@intel.com>
  • Loading branch information
lindongw committed May 25, 2022
1 parent 11bc130 commit 5d4e977
Show file tree
Hide file tree
Showing 28 changed files with 6,325 additions and 0 deletions.
87 changes: 87 additions & 0 deletions tracetool/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Linux trace tool


## Introduction

This is python tool helps to analysis media stack trace logs combining ftrace events from libva, media driver and Linux kernel mode driver (e.g. i915).


## Linux trace capture

1. Install trace-cmd:

sudo apt-get install trace-cmd

2. Grant write access to trace node for application:

sudo chmod 777 /sys/kernel/debug/
sudo chmod 777 /sys/kernel/debug/tracing/
sudo chmod 777 /sys/kernel/debug/tracing/trace_marker_raw

3. Enable libva trace:

export LIBVA_TRACE = FTRACE

to enable libva buffer data capture

export LIBVA_TRACE_BUFDATA = 1

4. Run application under trace-cmd in a proxy mode:

trace-cmd record -e i915 <workflow-cmd-line>

5. Output is "trace.dat"

Alternatively you can collect trace data in separate terminal.
It is useful if you want to profile daemon or a service:

1. Start trace capture:

sudo trace-cmd record -e i915

2. Run test app in another terminal
3. Stop capturing in the first terminal
4. Output is "trace.dat"


## Trace post-processing and analysis

python3 main.py [-raw] file.dat|file.etl [file.dat|file.etl ...]

Options:

* `-raw` - Parse trace events and dump into <trace-file>.csv file.

Output:

* `<trace-file>.json.gz` - visualized driver activity, open in `<chrome://tracing/>` or `<edge://tracing/>`
* `<trace-file>_stat.csv` - statistic of driver activity, open in Excel
* `<trace-file>_surface.csv` - driver activity in surface point of view, open in Excel


## Trace tool manifests and modules

Manifests are for decoding trace data. Each trace system supported by trace tool need have a manifest file in manifests folder.
The manifest file could be in MSFT ETW XML manifest format or json format. Current supported traces:
* libva_trace.man - libva trace manifest in MSFT ETW XML
* Intel-Media-Open.json - iHD open source media driver trace manifest in json.

Trace tool dynamic load functional module in modules folders. Each module should have its own standalone function.
If information exchange required among modules, should share through shared context, should not assume counter part module is available.
current modules:
* ftrace.py - Linux ftace file parser, trace file from trace-cmd.
* i915.py - i915 trace handler to extract GPU workload submit&execution timing.
* libva.py - libva trace handler.
* media.py - Intel iHD open source media driver trace handler.
* mediaRTLog.py - Intel iHD open source media driver runtime log trace handler.
* surface.py - surface object & attributes track across iHD and i915 traces.

## Making changes in the tool

Make sure to run unit tests before creating PR:

cd tracetool
python3 -m unittest

Make sure trace event and event data are backward compatible.

70 changes: 70 additions & 0 deletions tracetool/callStack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#
# Copyright (C) Intel Corporation. All rights reserved.
# Licensed under the MIT License.
#

# build call stack from events with the same process and thread id
class callStack:

def __init__(self):
self.context = {} # maintain call stack

# get latest pushed call event in stack
def current(self, pid, tid):
if pid not in self.context or tid not in self.context[pid]:
return None
if self.context[pid][tid]:
return self.context[pid][tid][0]
return None

# get full call stack record
def get(self, pid, tid):
if pid not in self.context:
self.context[pid] = {}
if tid not in self.context[pid]:
self.context[pid][tid] = []
return self.context[pid][tid]

# push event into stack
def push(self, evt):
if evt['pid'] not in self.context:
self.context[evt['pid']] = {}
if evt['tid'] not in self.context[evt['pid']]:
self.context[evt['pid']][evt['tid']] = []
self.context[evt['pid']][evt['tid']].insert(0, evt)

# pop event from stack
def pop(self, evt):
if evt['pid'] not in self.context:
return None
if evt['tid'] not in self.context[evt['pid']] or not self.context[evt['pid']][evt['tid']]:
thrds = self.context[evt['pid']]
for t in thrds.values():
if t and t[0]['name'] == evt['name']:
return t.pop(0)
return None
ctx = self.context[evt['pid']][evt['tid']]
name = evt['name']
idx = 0
ret = None
# find target in the stack
for i in range(len(ctx)):
if ctx[i]['name'] == name:
idx = i+1
ret = ctx[i]
break
# remove target from stack
del ctx[0:idx]
return ret

# find top event with the same sys id + pid + tid
def find(self, evt):
if evt['pid'] not in self.context or evt['tid'] not in self.context[evt['pid']]:
return None
for e in self.context[evt['pid']][evt['tid']]:
if e['sys'] == evt['sys']:
return e
return None

def __del__(self):
del self.context
160 changes: 160 additions & 0 deletions tracetool/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
#
# Copyright (C) Intel Corporation. All rights reserved.
# Licensed under the MIT License.
#

import os
import sys
import importlib
from writeUI import writeUI
from statistic import statistic
from callStack import callStack
from util import *

class core:

def __init__(self):
self.source = None
self.sharedCtx = {} # shared context for all handlers, dict for flexible usage
self.handlers = {}
self.instances = []
self.readers = []
self.parsers = {}
self.dumpRaw = False

cur = os.path.abspath(os.path.dirname(__file__))
sys.path.append(cur+os.sep+'modules')
for py in os.listdir('modules'):
name = os.path.splitext(py)[0]
m = importlib.import_module(name)
if hasattr(m, 'traceHandler'):
cls = getattr(m, 'traceHandler')
# create handler instace, the class init should call register of this instance
instance = cls(self)
# just for keep instance ref
self.instances.append(instance)
elif hasattr(m, 'traceReader'):
cls = getattr(m, 'traceReader')
self.readers.append(cls)

# open trace file
def open(self, input, options) -> int:
ret = -1
if isinstance(input, list) and len(input) == 1:
input = input[0]
if isinstance(input, list):
# enumerate and open trace files
names = []
readers = []
for i in input:
for cls in self.readers:
reader = cls()
sts = reader.open(i, options)
if sts == 0:
names.append(i)
readers.append(reader)
break
if len(input) == len(readers):
# sync time stamp across multi trace files, need find single source reader
print('Multi trace input files, sync time line ...')
for i in readers:
for j in readers:
if i != j and i.syncSource(j) == 0:
self.source = i
self.sharedCtx['sourceFile'] = names[readers.index(i)]
break
if self.source != None:
break
if self.source != None:
print('done')
ret = 0
else:
print('Error! could not syn time line')
else:
for cls in self.readers:
reader = cls()
sts = reader.open(input, options)
if sts == 0:
self.source = reader
self.sharedCtx['sourceFile'] = input
ret = 0
break
# setup handlers and output if success
if ret == 0:
self.source.setParser(self.parsers)

baseName = self.sharedCtx['sourceFile']
baseName = os.path.splitext(baseName)[0]
self.sharedCtx['Output'] = baseName
self.sharedCtx['UI'] = writeUI(baseName)
self.sharedCtx['Stat'] = statistic(baseName)
self.sharedCtx['Stack'] = callStack()
self.sharedCtx['Opt'] = options
return ret

# start process event from trace file
def process(self) -> None:
self.source.process(self.filter, self.callback)

# close
def __del__(self):
del self.source
del self.readers
del self.instances
del self.handlers
del self.sharedCtx

# test if event handler is set for this event
def filter(self, evt) -> bool:
if 'raw' in self.sharedCtx['Opt']:
return True
if 'sys' not in evt or 'name' not in evt:
return False
if evt['sys'] not in self.handlers:
return False
handler = self.handlers[evt['sys']]
if evt['name'] not in handler and 'allEvent' not in handler:
return False
return True

# call back function to process event with handler
def callback(self, evt) -> None:
if evt['sys'] not in self.handlers:
return
# get handler, could be a list, multi handler for single event
hnd = self.handlers[evt['sys']]
flag = 0
if evt['name'] in hnd:
for h in hnd[evt['name']]:
sts = h(evt)
if sts != None and sts < 0:
flag = 1
# call all event handler at last step, skip if any handler has returned -1
if 'allEvent' in hnd and flag == 0:
for h in hnd['allEvent']:
h(evt)

# register event handler
def regHandler(self, sys, name, handler) -> None:
if name == None:
name = 'allEvent' # name = None means handler for all events of this trace system
if sys not in self.handlers:
self.handlers[sys] = {}
# add handler to list
hnd = self.handlers[sys]
if name in hnd:
hnd[name].append(handler)
else:
hnd[name] = [handler]

# register event head parser from raw message
def regParser(self, id, parser) -> int:
if id in self.parsers:
print('Warning! duplicated event header id')
return -1
self.parsers[id] = parser
return 0

# get shared context
def getContext(self) -> dict:
return self.sharedCtx
Loading

0 comments on commit 5d4e977

Please sign in to comment.