Remove sea collectors (#4191)

* Removed redundant collectors

* Remove isea filter

* Remove the code from IntelSEAPI

* Removed shell=True

* remove asserts

* Refactored command line arguments
This commit is contained in:
Ilya Churaev
2021-02-08 15:26:48 +03:00
committed by GitHub
parent 42c8d1c45f
commit 0b60ecb2d3
10 changed files with 41 additions and 1625 deletions

View File

@@ -1,226 +0,0 @@
# Intel® Single Event API
#
# This file is provided under the BSD 3-Clause license.
# Copyright (c) 2021, Intel Corporation
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
# Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
# Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
# Neither the name of the Intel Corporation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
import os
import sys
import glob
import shutil
import traceback
import subprocess
# http://www.brendangregg.com/perf.html
# sudo perf probe --funcs
sys.path.append(os.path.realpath(os.path.join(os.path.dirname(__file__), '..')))
import sea
from sea_runtool import Collector, Progress, format_bytes
def time_sync():
sea.ITT('lin').time_sync()
supported_events = [
"binder_locked",
"binder_unlock",
"binder_lock",
"binder_transaction",
"binder_transaction_received",
"memory_bus_usage",
"clock_set_rate",
"cpufreq_interactive_up",
"cpufreq_interactive_down",
"cpufreq_interactive_already",
"cpufreq_interactive_notyet",
"cpufreq_interactive_setspeed",
"cpufreq_interactive_target",
"cpufreq_interactive_boost",
"cpufreq_interactive_unboost",
"f2fs_write_begin",
"f2fs_write_end",
"f2fs_sync_file_enter",
"f2fs_sync_file_exit",
"ext4_sync_file_enter",
"ext4_sync_file_exit",
"ext4_da_write_begin",
"ext4_da_write_end",
"block_rq_issue",
"block_rq_complete",
"drm_vblank_event",
"exynos_busfreq_target_int",
"exynos_busfreq_target_mif",
"exynos_page_flip_state",
"i915_gem_object_create",
"i915_gem_object_bind",
"i915_gem_object_unbind",
"i915_gem_object_change_domain",
"i915_gem_object_pread",
"i915_gem_object_pwrite",
"i915_gem_object_fault",
"i915_gem_object_clflush",
"i915_gem_object_destroy",
"i915_gem_ring_dispatch",
"i915_gem_ring_flush",
"i915_gem_request",
"i915_gem_request_add",
"i915_gem_request_complete",
"i915_gem_request_retire",
"i915_gem_request_wait_begin",
"i915_gem_request_wait_end",
"i915_gem_ring_wait_begin",
"i915_gem_ring_wait_end",
"i915_mvp_read_req",
"i915_reg_rw",
"i915_flip_request",
"i915_flip_complete",
"intel_gpu_freq_change",
"irq_handler_entry",
"irq_handler_exit",
"softirq_raise",
"softirq_entry",
"softirq_exit",
"ipi_entry",
"ipi_exit",
"graph_ent",
"graph_ret",
"mali_dvfs_event",
"mali_dvfs_set_clock",
"mali_dvfs_set_voltage",
"tracing_mark_write:mali_driver",
"mm_vmscan_kswapd_wake",
"mm_vmscan_kswapd_sleep",
"mm_vmscan_direct_reclaim_begin",
"mm_vmscan_direct_reclaim_end",
"workqueue_execute_start",
"workqueue_execute_end",
"power_start",
"power_frequency",
"cpu_frequency",
"cpu_idle",
"regulator_enable",
"regulator_enable_delay",
"regulator_enable_complete",
"regulator_disable",
"regulator_disable_complete",
"regulator_set_voltage",
"regulator_set_voltage_complete",
"sched_switch",
"sched_wakeup",
"workqueue_execute_start",
"workqueue_execute_end",
"workqueue_queue_work",
"workqueue_activate_work",
]
class FTrace(Collector):
def __init__(self, args, remote=False):
Collector.__init__(self, args)
self.remote = remote
self.event_list = []
self.file = None
self.perf_file = None
self.perf_proc = None
for event in supported_events:
for path in glob.glob('/sys/kernel/debug/tracing/events/*/%s/enable' % event):
self.event_list.append(path)
def echo(self, what, where):
self.log("echo %s > %s" % (what, where))
try:
if self.remote:
self.remote.execute('echo %s > %s' % (what, where))
else:
with open(where, "w") as file:
file.write(what)
except:
self.log("Failed: " + traceback.format_exc())
return False
return True
def start(self):
if not self.echo("nop", "/sys/kernel/debug/tracing/current_tracer"):
self.log("Warning: failed to access ftrace subsystem")
return
self.file = os.path.join(self.args.output, 'nop-%s.ftrace' % (self.args.cuts[0] if self.args.cuts else '0'))
self.echo("0", "/sys/kernel/debug/tracing/tracing_on")
self.echo("nop", "/sys/kernel/debug/tracing/current_tracer") # google chrome understands this format
self.echo("", "/sys/kernel/debug/tracing/set_event") # disabling all events
self.echo("", "/sys/kernel/debug/tracing/trace") # cleansing ring buffer (we need it's header only)
if self.args.ring:
self.echo("%d" % (self.args.ring * 1024), "/sys/kernel/debug/tracing/buffer_size_kb")
# best is to write sync markers here
self.echo("1", "/sys/kernel/debug/tracing/tracing_on") # activate tracing
time_sync()
self.echo("0", "/sys/kernel/debug/tracing/tracing_on") # deactivate tracing
# saving first part of synchronization as it will be wiped out in ring
self.copy_from_target("/sys/kernel/debug/tracing/trace", self.file)
self.echo("", "/sys/kernel/debug/tracing/trace") # cleansing ring buffer again
for event in self.event_list: # enabling only supported
self.echo("1", event)
for path in glob.glob('/sys/kernel/debug/dri/*/i915_mvp_enable'): # special case for Intel GPU events
self.echo("1", path)
self.echo("1", "/sys/kernel/debug/tracing/tracing_on")
if self.args.stacks and self.args.target:
self.perf_file = os.path.join(self.args.output, 'perf-%s.data' % (self.args.cuts[0] if self.args.cuts else '0'))
if os.path.exists(self.perf_file):
os.remove(self.perf_file)
cmd = 'perf record -a -g -o "%s" --pid=%s' % (self.perf_file, self.args.target)
self.log(cmd)
self.perf_proc = subprocess.Popen(cmd, shell=True, stdout=self.get_output(), stderr=self.get_output(), preexec_fn=os.setpgrp)
def copy_from_target(self, what, where):
self.log("copy %s > %s" % (what, where))
if self.remote:
self.remote.copy('%s:%s' % (self.args.ssh, what), where)
else:
shutil.copy(what, where)
def stop(self, wait=True):
results = []
if self.perf_proc:
self.perf_proc.wait()
if os.path.exists(self.perf_file):
results.append(self.perf_file + '.perf')
with open(results[-1], 'wb') as file:
self.execute('perf script -F comm,tid,pid,time,ip,sym,dso,symoff --show-kernel-path --demangle-kernel --full-source-path -i "%s"' % self.perf_file, stdout=file)
os.remove(self.perf_file)
if not self.file:
return results
time_sync()
self.echo("0", "/sys/kernel/debug/tracing/tracing_on")
for path in glob.glob('/sys/kernel/debug/dri/*/i915_mvp_enable'): # special case for Intel GPU events
self.echo("0", path)
file_name = os.path.join(self.args.output, "tmp.ftrace")
self.copy_from_target("/sys/kernel/debug/tracing/trace", file_name)
self.log("append %s > %s" % (file_name, self.file))
with open(file_name) as file_from, open(self.file, 'a') as file_to:
shutil.copyfileobj(file_from, file_to)
os.remove(file_name)
results.append(self.file)
self.execute('chmod -R a+rwX "%s"' % self.args.output)
return results
COLLECTOR_DESCRIPTORS = [{
'format': 'ftrace',
'available': True,
'collector': FTrace
}]

View File

@@ -1,827 +0,0 @@
# Intel® Single Event API
#
# This file is provided under the BSD 3-Clause license.
# Copyright (c) 2021, Intel Corporation
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
# Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
# Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
# Neither the name of the Intel Corporation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
from __future__ import print_function
import re
import os
import sys
import time
from datetime import datetime, timedelta
import shutil
import subprocess
import threading
sys.path.append(os.path.realpath(os.path.join(os.path.dirname(__file__), '..')))
from sea_runtool import Collector, is_domain_enabled, message, get_original_env
"""
sudo dtrace -l | perl -pe 's/^.*?\S+\s+(\S+?)([0-9]|\s).*/\1/' | sort | uniq > /tmp/dtrace_providers.txt
sudo dtrace -l > /tmp/dtrace_list.txt
dtrace -n 'fbt:::entry { @[probefunc] = count(); }' -c 'ping host'
http://www.brendangregg.com/DTrace/DTrace-cheatsheet.pdf
https://docs.oracle.com/cd/E19253-01/817-6223/chp-variables-5/index.html
TODO: printf("%s: called from %a\n", probefunc, caller);
objc_runtime$target::: { ustack(); } /*objc_exception_throw*/
pid$target::objc_msgSend:entry
sudo dtrace -qn 'fbt::*vent13k*:entry/arg3/{printf("%d\n",arg2)}' # keylogger
"""
DSCRIPT_HEADER = r"""
#pragma D option nolibs
#define GREEDY_ON ++self->greedy_enabled
#define GREEDY_OFF self->greedy_enabled = (self->greedy_enabled > 0) ? (self->greedy_enabled - 1) : self->greedy_enabled
BEGIN
{
self->greedy_enabled = 0;
}
"""
dtrace_context_switch = r"""
/*
off-cpu
Probe that fires when the current CPU is about to end execution of a thread.
The curcpu variable indicates the current CPU.
The curlwpsinfo variable indicates the thread that is ending execution.
The curpsinfo variable describes the process containing the current thread.
The lwpsinfo_t structure of the thread that the current CPU will next execute is pointed to by args[0].
The psinfo_t of the process containing the next thread is pointed to by args[1].
*/
sched:::off-cpu
{
printf(
"%x\toff\t%x\t%x\t%x\t%s\t%x\t%x\t%s\n", machtimestamp, curcpu->cpu_id,
curlwpsinfo->pr_lwpid, curlwpsinfo->pr_pri, curpsinfo->pr_fname,
args[0]->pr_lwpid, args[0]->pr_pri, args[1]->pr_fname
);
}
"""
OFF_CPU_STACKS = r"""
sched:::off-cpu
/pid == $target/
{
printf("%x\tkstack\t%x\t%x:", machtimestamp, pid, tid);
stack();
printf("\n%x\tustack\t%x\t%x:", machtimestamp, pid, tid);
ustack();
/*
printf("\n%x\tjstack\t%x\t%x:", machtimestamp, pid, tid);
jstack(); //TODO: enable better support for jstack-s
*/
printf("\n");
}
"""
dtrace_wakeup = r"""
sched:::wakeup
/curpsinfo->pr_pid == $target || args[1]->pr_pid == $target/
{
printf("%x\twkp\t%x\t%x\t%s\t%x\t%s\t%x\t%x\t%x\t%x\n", machtimestamp,
curpsinfo->pr_pid, curlwpsinfo->pr_lwpid,
execname, cpu,
stringof(args[1]->pr_fname),
args[1]->pr_pid, args[0]->pr_lwpid,
args[0]->pr_stype, args[0]->pr_wchan
);
}
"""
osxaskpass = r"""#!/bin/bash
osascript -e 'Tell application "System Events" to display dialog "Password:" default answer "" with hidden answer with title "DTrace requires root priveledges"' -e 'text returned of result' 2>/dev/null
"""
pid_dtrace_hooks = [r"""
pid$target::*dtSEAHookScope*:entry /*{UMD_STACKS}*/
{
printf(
"%x\te\t%x\t%x\t%s\t%s\n", machtimestamp, pid, tid, copyinstr(arg1), copyinstr(arg2)
);
}
""", r"""
pid$target::*dtSEAHookEndScope*:entry
{
printf(
"%x\tr\t%x\t%x\t%s\t%s\n", machtimestamp, pid, tid, copyinstr(arg0), copyinstr(arg1)
);
}
""", r"""
/*
pid$target::*dtSEAHookArgStr*:entry
{
printf(
"%x\targ\t%x\t%x\t%s\t%s\n",
machtimestamp, pid, tid, copyinstr(arg0), copyinstr(arg1)
);
}
pid$target::*dtSEAHookArgInt*:entry
{
printf(
"%x\targ\t%x\t%x\t%s\t%d\n",
machtimestamp, pid, tid, copyinstr(arg0), arg1
);
}
*/
"""
]
pid_dtrace_hooks += [ # XXX
r"""
objc$target:::entry
/*{CONDITIONS}*/
{
printf(
"%x\te\t%x\t%x\tobjc\t%s%s\n", machtimestamp, pid, tid, probemod, probefunc
);
/*{ARGUMENTS}*/
}
""", r"""
objc$target:::return
/*{CONDITIONS}*/
{
printf(
"%x\tr\t%x\t%x\tobjc\t%s%s\n", machtimestamp, pid, tid, probemod, probefunc
);
}
"""
] if 0 else []
FOLLOW_CHILD = r"""
//https://www.synack.com/2015/11/17/monitoring-process-creation-via-the-kernel-part-i/
proc:::exec-success /$target == curpsinfo->pr_ppid/{
printf("%x\tfollowchild\t%x\t%x\t%s\t%x\t%x\t%s\n", machtimestamp, pid, tid, probename, curpsinfo->pr_ppid, curpsinfo->pr_pid, curpsinfo->pr_psargs);
system("printf \"%d\n\" >> /*{FOLLOW_CHILD}*/", curpsinfo->pr_pid);
}
proc:::exec /$target == curpsinfo->pr_ppid/{
printf("%x\tfollowchild\t%x\t%x\t%s\t%x\t%x\t%s\n", machtimestamp, pid, tid, probename, curpsinfo->pr_ppid, curpsinfo->pr_pid, curpsinfo->pr_psargs);
system("printf \"%d\n\" >> /*{FOLLOW_CHILD}*/", curpsinfo->pr_pid);
}
syscall::exec*:return /$target == curpsinfo->pr_ppid/
{
printf(
"%x\tfollowchild\t%x\t%x\t%s\t%s\t%s\n", machtimestamp, pid, tid, probemod, probefunc, probename
);
}
syscall::fork:return /$target == curpsinfo->pr_ppid/
{
printf(
"%x\tfollowchild\t%x\t%x\t%s\t%s\t%s\n", machtimestamp, pid, tid, probemod, probefunc, probename
);
system("printf \"%d\n\" >> /*{FOLLOW_CHILD}*/", curpsinfo->pr_pid);
}
"""
BRACKET_FUNC = r"""
pid$target:/*{M:F}*/:entry /*{UMD_STACKS}*/
/*{CONDITIONS}*/
{
printf(
"%x\te\t%x\t%x\t%s\t%s\n", machtimestamp, pid, tid, probemod, probefunc
);
/*{ARGUMENTS}*/
GREEDY_ON;
}
""", r"""
pid$target:/*{M:F}*/:return
/*{CONDITIONS}*/
{
GREEDY_OFF;
printf(
"%x\tr\t%x\t%x\t%s\t%s\n", machtimestamp, pid, tid, probemod, probefunc
);
}
"""
def bracket_hook(module='', function=''):
res = []
for item in BRACKET_FUNC:
res.append(item.replace('/*{M:F}*/', '%s:%s' % (module, function)))
return res
for mask in ['JavaScriptCore', '*GL*:*gl*', '*GL*:*GL*', 'Metal', '*MTLDriver', '*GLDriver']: # ,'mdtest', 'libigdmd.dylib'
pid_dtrace_hooks += bracket_hook(*mask.split(':'))
# TODO: add opencl_api & opencl_cpu providers
IO_HOOKS = r"""
fsinfo:::open,fsinfo:::close
{
printf(
"%x\tio\t%x\t%x\t%s\t%s\n", machtimestamp, pid, tid, probename, stringof(args[0]->fi_pathname)
);
}
"""
OPEN_CL = r"""
opencl_api$target:::, opencl_cpu$target:::
{
printf(
"%x\tocl\t%x\t%x\t%s\t%s\t%s\n", machtimestamp, pid, tid, probemod, probefunc, probename
);
}
"""
# FIXME: extract Interrupt handling and do conditional
fbt_dtrace_hooks = [r"""
//void kernel_debug_enter(uint32_t coreid, uint32_t debugid, uint64_t timestamp, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4, uintptr_t threadid);
fbt::kernel_debug_enter:entry
{
printf(
"%x\tkd\t%x\t%x\t%s\t%x\t%x\t%x\t%x\t%x\t%x\t%x\t%x\n", machtimestamp, pid, tid, probefunc, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7
);
}
""", r"""
fbt::*debugid*enabled*:entry
{
printf(
"%x\tkd\t%x\t%x\t%s\t%x\t%x\t%x\t%x\t%x\t%x\n", machtimestamp, pid, tid, probefunc, arg0, arg1, arg2, arg3, arg4, arg5
);
}
""" if not "DISABLED XXX" else '', r"""
fbt::*dtSEAHookScope*:entry /*{KMD_STACKS}*/
{
printf(
"%x\te\t%x\t%x\t%s_%s\t%s\t%d\n",
machtimestamp, pid, tid, stringof(probemod), stringof(arg1), stringof(arg2), arg0
);
}
""", r"""
fbt::*dtSEAHookEndScope*:entry
{
printf(
"%x\tr\t%x\t%x\t%s_%s\t%s\n",
machtimestamp, pid, tid, stringof(probemod), stringof(arg0), stringof(arg1)
);
}
fbt::*dtSEAHookArgStr*:entry
{
printf(
"%x\targ\t%x\t%x\t%s\t%s\n",
machtimestamp, pid, tid, stringof(arg0), stringof(arg1)
);
}
fbt::*dtSEAHookArgInt*:entry
{
printf(
"%x\targ\t%x\t%x\t%s\t%d\n",
machtimestamp, pid, tid, stringof(arg0), arg1
);
}
fbt::*dtSEAHookArgBlobStart*:entry
{
printf(
"%x\tbs\t%x\t%x\t%x\t%s\t%d\n",
machtimestamp, pid, tid, arg1, stringof(arg1), arg0
);
trace(stringof(arg0));
tracemem(arg0, 10);
}
fbt::*dtSEAHookArgBlob1024*:entry
{
printf(
"%x\tblb\t%x\t%x\n",
machtimestamp, pid, tid
);
tracemem(arg0, 1024);
}
fbt::*dtSEAHookArgBlobEnd*:entry
{
printf(
"%x\tbe\t%x\t%x\n",
machtimestamp, pid, tid
);
}
""" if not "DISABLED XXX" else '', r"""
/*
Interrupt handling.
The list of interrupts was obtained by running 'dtrace -l | grep handleInterrupt'
*/
fbt:com.apple.driver.AppleAPIC:_ZN28AppleAPICInterruptController15handleInterruptEPvP9IOServicei:entry,
fbt:com.apple.iokit.IOPCIFamily:_ZN8AppleVTD15handleInterruptEP22IOInterruptEventSourcei:entry,
fbt:com.apple.iokit.IOPCIFamily:_ZN32IOPCIMessagedInterruptController15handleInterruptEPvP9IOServicei:entry,
fbt:com.apple.driver.AppleSMBusController:_ZN23AppleSMBusControllerMCP15handleInterruptEP22IOInterruptEventSourcei:entry,
fbt:com.apple.driver.AppleThunderboltNHI:_ZN19AppleThunderboltNHI15handleInterruptEv:entry
{
printf("%x\tie\t%x\t%x\t%s\t%x\t%s\t%s\n", machtimestamp,
curpsinfo->pr_pid, curlwpsinfo->pr_lwpid,
execname, cpu, probemod, probefunc
);
}
fbt:com.apple.driver.AppleAPIC:_ZN28AppleAPICInterruptController15handleInterruptEPvP9IOServicei:return,
fbt:com.apple.iokit.IOPCIFamily:_ZN8AppleVTD15handleInterruptEP22IOInterruptEventSourcei:return,
fbt:com.apple.iokit.IOPCIFamily:_ZN32IOPCIMessagedInterruptController15handleInterruptEPvP9IOServicei:return,
fbt:com.apple.driver.AppleSMBusController:_ZN23AppleSMBusControllerMCP15handleInterruptEP22IOInterruptEventSourcei:return,
fbt:com.apple.driver.AppleThunderboltNHI:_ZN19AppleThunderboltNHI15handleInterruptEv:return
{
printf("%x\tir\t%x\t%x\t%s\t%x\t%s\t%s\n", machtimestamp,
curpsinfo->pr_pid, curlwpsinfo->pr_lwpid,
execname, cpu, probemod, probefunc
);
}
"""]
STACKS = {
'UMD': r"""
{
printf("%x\tustack\t%x\t%x:", machtimestamp, pid, tid);
ustack();
printf("\n");
}
""",
'KMD': r"""
{
printf("%x\tkstack\t%x\t%x:", machtimestamp, pid, tid);
stack();
printf("\n");
}
"""
}
def mach_absolute_time(static={}):
if not static:
import ctypes
libc = ctypes.CDLL('libc.dylib', use_errno=True)
static['mach_absolute_time'] = libc.mach_absolute_time
static['mach_absolute_time'].restype = ctypes.c_uint64
return static['mach_absolute_time']()
class FifoReader(threading.Thread):
def __init__(self, collector, path):
threading.Thread.__init__(self)
self.collector = collector
self.pipe = path
if os.path.exists(self.pipe):
os.remove(self.pipe)
os.mkfifo(self.pipe)
self.file = os.open(self.pipe, os.O_RDWR)
def run(self):
print('Started reading', self.pipe)
while self.file:
chunks = os.read(self.file, 1024).strip()
for chunk in chunks.split('\n'):
if chunk != 'close':
self.collector.attach(int(chunk))
print('Read:', chunk)
print('Stopped reading', self.pipe)
def stop(self):
os.write(self.file, 'close\n')
os.close(self.file)
self.file = None
os.unlink(self.pipe)
class DTraceCollector(Collector):
class Subcollector:
@staticmethod
def get_hooks(args):
return None
@staticmethod
def collect(collector, on):
pass
def __init__(self, args):
Collector.__init__(self, args)
self.processes = {}
self.files = []
self.subcollectors = set()
self.attached = set()
if 'SUDO_ASKPASS' not in os.environ:
get_original_env()['SUDO_ASKPASS'] = self.create_ask_pass()
assert 'DYLD_INSERT_LIBRARIES' not in os.environ
self.sudo_execute('pkill dtrace')
self.script = None
self.prepare()
self.times = []
self.attach_by_pid = True
@staticmethod
def create_ask_pass():
path = '/tmp/osxaskpass.sh'
if os.path.exists(path):
return path
with open(path, 'w') as file:
file.write(osxaskpass)
os.chmod(path, 0o700)
return path
@staticmethod
def gen_options(options):
return '\n'.join('#pragma D option %s=%s' % (key, str(value)) for key, value in options) + '\n'
def prepare(self):
self.files = [os.path.join(self.args.output, 'data-%s.dtrace' % (self.args.cuts[0] if self.args.cuts else '0'))]
assert not os.path.exists(self.files[0]) # TODO: remove if not asserts, or return back: was if ... os.remove(self.files[0])
dtrace_script = [DSCRIPT_HEADER]
options = [
('bufresize', 'auto'),
]
if self.args.ring: # https://docs.oracle.com/cd/E19253-01/817-6223/chp-buf/index.html
options += [
('bufpolicy', 'ring'),
('bufsize', '64m') # 64 is maximum, system crashes on any bigger, even 65m
]
else:
options += [
('switchrate', '10hz'), # print at 10Hz (instead of 1Hz) - brendangregg
('bufsize', '4g')
]
dtrace_script.append(self.gen_options(options))
if is_domain_enabled('context_switches'):
dtrace_script.append(dtrace_context_switch)
if is_domain_enabled('fbt_hooks'):
dtrace_script += fbt_dtrace_hooks
for subcollector in self.subcollectors:
hooks = subcollector.get_hooks(self.args) # support multi pid for subcollectors
if hooks:
dtrace_script += hooks
subcollector.collect(self, True)
self.script = dtrace_script
def sudo_execute(self, cmd):
return self.execute('sudo -E -A ' + cmd)
def prepare_per_pid(self):
dtrace_script = []
if is_domain_enabled('pid_hooks'):
dtrace_script += pid_dtrace_hooks # TODO: add %app_name% hotspots unconditionally
if is_domain_enabled('instrument_target'):
if self.args.victim:
mod_name = os.path.basename(self.args.victim)
elif self.args.target:
(out, err) = DTraceCollector.execute('ps -p %d -o args' % self.args.target, log=False)
if not err:
lines = out.strip().split('\n')
if len(lines) > 1:
executable = lines[1].split()[0]
mod_name = executable.split('/')[-1]
print('Auto-instrumented module:', mod_name)
dtrace_script += bracket_hook(mod_name.replace(' ', '*'))
if is_domain_enabled('opencl'):
dtrace_script.append(OPEN_CL)
return dtrace_script
def patch_per_pid(self, pids, items):
result = []
for item in items:
if '$target:' in item:
for pid in pids:
result.append(item.replace('$target:', '%s:' % str(pid)))
else:
result.append(item)
return result
def get_cmd(self, out, script, pids=[]):
# -C Run the C preprocessor
# -Z Permit probe descriptions that match zero probes
# -w Permit destructive actions in D programs
cmd = 'sudo -E -A dtrace -C -Z -w -o "%s" -s "%s"' % (out, script) # FIXME: sudo_execute
if self.args.verbose != 'info':
cmd += ' -q' # Set quiet mode
else:
# -S Show D compiler intermediate code
# -v Print an interface stability report
# -V Report the highest D programming interface version
# -e Exit after compiling
# -l List probes instead of enabling them
cmd += ' '
for pid in pids:
cmd += " -p %s" % pid
return cmd
def launch_victim(self, victim, env):
proc = self.run_dtrace(victim=victim, env=env)
if not proc:
return None
class PopenWrapper:
def __init__(self, parent, victim):
self.parent = parent
cmd = 'pgrep -n "%s"' % os.path.basename(victim[0])
while True:
data, err = parent.execute(cmd)
if data:
self.pid = int(data)
break
time.sleep(1)
def send_signal(self, sig):
self.parent.sudo_execute('kill -%d %d' % (sig, self.pid))
def wait(self, sec=10):
proc['proc'].wait()
""" XXX
for x in range(0, sec):
proc['proc'].poll()
time.sleep(1)
"""
return proc['proc'].returncode
def communicate(self):
self.wait()
return None, None
return PopenWrapper(self, victim)
def run_dtrace(self, attach_by_name=False, victim=None, env=None):
self.attach_by_pid = False
# spawn dtrace tracers and exit, all means to stop it must be saved to self members:
# launch command line with dtrace script and remember pid
script = os.path.join(self.args.output, 'script.d')
cmd = self.get_cmd(self.files[0], script)
dtrace_script = self.script[:]
hooks = []
dtrace_script += hooks
# The target is known only when start is called, so doing part of preparation here
if attach_by_name:
dtrace_script += self.prepare_per_pid()
cmd += " -W %s" % os.path.basename(self.args.victim)
elif victim:
dtrace_script += self.prepare_per_pid()
cmd += ' -c "%s"' % ' '.join(victim)
else:
assert not any('$target' in item for item in dtrace_script)
pids = self.args.target if isinstance(self.args.target, list) else [self.args.target]
for pid in pids:
cmd += " -p %s" % pid
print("Attaching PIDs:", pids)
items = self.prepare_per_pid()
dtrace_script += self.patch_per_pid(pids, items)
for pid in pids:
if self.args.stacks:
dtrace_script.append(OFF_CPU_STACKS.replace('$target', str(pid)))
if is_domain_enabled('wakeups'):
dtrace_script.append(dtrace_wakeup.replace('$target', str(pid)))
for hook in hooks:
dtrace_script.append(hook.replace('/*{CONDITIONS}*/', '/pid == %s/' % str(pid)))
if self.args.stacks:
dtrace_script = self.patch_stacks(pids, dtrace_script)
# remove duplicates from the list:
dtrace_script = [item for n, item in enumerate(dtrace_script) if item not in dtrace_script[:n]]
dtrace_script = '\n'.join(dtrace_script)
with open(script, 'w') as file:
file.write(dtrace_script)
return self.run_parallel(cmd, env)
def start(self): # FIXME: see man dtrace -W option for proper attach
self.times.append(datetime.now())
if self.attach_by_pid:
self.run_dtrace()
def run_parallel(self, cmd, env=None):
self.log(cmd)
proc = self.processes.setdefault(cmd, {})
proc['proc'] = subprocess.Popen(cmd, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env = env or os.environ)
proc['pid'] = proc['proc'].pid
self.log("%s -> pid: %d" % (cmd, proc['proc'].pid))
return proc
@staticmethod
def patch_stacks(pids, items):
reg_exp = re.compile(r"""(.*)\/\*\{(.*)_STACKS\}\*\/.*""", re.IGNORECASE | re.DOTALL)
result = []
for item in items:
result.append(item)
if '_STACKS}*/' in item:
lines = item.strip().split('\n')
assert lines[-1].strip().endswith('}')
res = reg_exp.search(lines[0])
what, where = res.groups()
condition = '/$target == pid/' if pids else ''
code = '\n%s %s %s' % (what, condition, STACKS[where])
if pids:
for pid in pids:
result.append(code.replace('$target', str(pid)))
else:
result.append(code)
return result
def attach(self, pid):
if pid in self.attached:
return
pids = [pid] + list(self.get_pid_children(pid))
self.attached |= set(pids)
items = self.prepare_per_pid()
dtrace_script = [DSCRIPT_HEADER] + [item for item in self.script if '$target' in item]
dtrace_script += self.patch_per_pid(pids, items)
script = os.path.join(self.args.output, 'script_%d.d' % pid)
dtrace_script = '\n'.join(dtrace_script)
self.files.append(os.path.join(self.args.output, 'data-%d-%s.dtrace' % (pid, self.args.cuts[0] if self.args.cuts else '0')))
cmd = self.get_cmd(self.files[-1], script, pids)
with open(script, 'w') as file:
file.write(dtrace_script)
self.run_parallel(cmd)
@staticmethod
def get_pid_children(parent):
(out, err) = DTraceCollector.execute('ps -o pid,ppid -ax', log=False)
if err:
print(err)
return
for line in out.split('\n'):
if not line:
continue
parts = line.split()
if len(parts) != 2:
continue
pid, ppid = line.split()
if str(parent) == ppid:
yield int(pid)
@staticmethod
def locate(what, statics={}):
try:
if not statics:
res = subprocess.check_output(['locate', '-S']).decode("utf-8")
statics['locate'] = 'WARNING' not in res
if not statics['locate']:
print(res)
if statics['locate']:
return subprocess.check_output(['locate', what]).decode("utf-8").split('\n')
except Exception:
pass
return []
def collect_codes(self):
items = self.locate("*.codes")
files = [item.strip() for item in items if not item.startswith('/Volumes')]
items = self.locate("kdebug.h")
files += [item.strip() for item in items if not item.startswith('/Volumes')]
filtered = {}
for file in files:
if not file or not os.path.exists(file):
continue
name = os.path.basename(file)
size = os.path.getsize(file)
if size and (name not in filtered or os.path.getsize(filtered[name]) < size):
filtered[name] = file
for file in filtered.values():
shutil.copy(file, self.args.output)
items = self.locate("IntelGPUSignposts.plist")
plists = []
for line in items:
line = line.strip()
if line:
plists.append((os.path.getmtime(line), line)) # finding newest
if plists:
plist = sorted(plists)[-1][1]
shutil.copy(plist, self.args.output)
def collect_system_info(self):
with open(os.path.join(self.args.output, 'sysinfo.txt'), 'w') as file:
(probes, err) = self.execute('sysctl -a', stdout=file)
def stop(self, wait=True):
for name, data in self.processes.items():
print('Stopping:', name)
pids = [data['pid']] + list(self.get_pid_children(data['pid']))
for pid in pids:
self.sudo_execute("kill -2 %d" % pid)
for pid in pids:
try:
os.waitpid(pid, 0)
except:
pass
if not data['proc']:
continue
out, err = data['proc'].communicate()
message(None, "\n\n -= Target %s output =- {\n" % name)
if out:
self.log("Trace %s out:\n%s" % (name, out.decode()))
message(None, out.strip())
message(None, "-" * 50)
if err:
self.log("Trace %s err:\n%s" % (name, err.decode()), True)
message(None, err.strip())
message(None, "}\n\n")
if data['proc'].returncode != 0:
message('error', '%s(%d) has exited with error code %d check logs for details' % (name, data['pid'], data['proc'].returncode))
for subcollector in self.subcollectors:
print('Stopping:', subcollector)
subcollector.collect(self, False)
self.times.append(datetime.now())
sys_log = os.path.join(self.args.output, 'sys_log.json')
with open(sys_log, 'w') as file:
cmd = 'log show --source --style json --debug --signpost' # --last 1m --start, --end 'YYYY-MM-DD HH:MM:SS'
cmd += self.times[1].strftime(" --end '%Y-%m-%d %H:%M:%S'")
if self.args.ring:
cmd += (self.times[1] - timedelta(seconds=self.args.ring)).strftime(" --start '%Y-%m-%d %H:%M:%S'")
else:
cmd += self.times[0].strftime(" --start '%Y-%m-%d %H:%M:%S'")
self.execute(cmd, stdout=file) # FIXME: get time of collection or ring size
self.collect_system_info()
self.collect_codes()
res = self.files + [sys_log]
return res
@classmethod
def available(cls):
if 'darwin' not in sys.platform:
return False
(out, err) = cls.execute('csrutil status')
if 'disabled' not in out:
print('Please do: "csrutil disable" from Recovery OS terminal to be able using dtrace...')
return False
return True
COLLECTOR_DESCRIPTORS = [{
'format': 'dtrace',
'available': DTraceCollector.available(),
'collector': DTraceCollector
}]
if __name__ == "__main__":
print(mach_absolute_time())
DTraceCollector.check_graphics_firmware('com.apple.driver.AppleIntelSKLGraphics')

View File

@@ -1,313 +0,0 @@
# Intel® Single Event API
#
# This file is provided under the BSD 3-Clause license.
# Copyright (c) 2021, Intel Corporation
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
# Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
# Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
# Neither the name of the Intel Corporation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
from __future__ import print_function
import os
import sys
import time
from datetime import datetime
import shutil
import tempfile
import platform
import traceback
import subprocess
sys.path.append(os.path.realpath(os.path.join(os.path.dirname(__file__), '..')))
from sea_runtool import Collector, is_domain_enabled
import sea
def async_exec(cmd, title=None, env=None):
cmd = 'start "%s" /MIN /LOW %s' % (title if title else cmd, cmd)
subprocess.Popen(cmd, shell=True, stdin=None, stdout=None, stderr=None, creationflags=0x00000008, env=env) # DETACHED_PROCESS
class WPRCollector(Collector):
def __init__(self, args):
Collector.__init__(self, args)
self.wpr = self.detect()
self.started = False
if self.args.cuts:
self.file = os.path.join(args.output, "wpa-%s.etl" % (self.args.cuts[0] if self.args.cuts else '0'))
else:
self.file = os.path.join(args.output, "wpa.etl")
@classmethod
def detect(cls, statics={}):
if 'res' in statics:
return statics['res']
wprs = cls.detect_instances('wpr')
res = []
for wpr in wprs:
proc = subprocess.Popen('"%s" /?' % wpr, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = proc.communicate()
out = out.decode()
if err:
return None
for line in out.split('\n'):
pos = line.find('Version')
if -1 != pos:
version = line[pos + len('Version '):].strip()
if int(version.split('.')[0]) >= 10:
res.append((wpr, version.split()[0]))
break
if not res:
return None
statics['res'] = sorted(res, key=lambda __ver: [int(item) for item in __ver[1].split('.')], reverse=True)[0][0]
return statics['res']
@staticmethod
def get_options():
wpr = WPRCollector.detect()
if not wpr:
return
proc = subprocess.Popen('"%s" -profiles' % wpr, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = proc.communicate()
if err:
return
for line in out.split('\n'):
if not line.startswith('\t'):
continue
parts = line.strip().split()
yield parts[0], parts[0] in ['DiskIO', 'FileIO', 'GPU', 'GeneralProfile', 'Handle', 'Heap', 'Network', 'Power', 'Video', 'VirtualAllocation']
def start(self):
if not self.wpr:
print("Failed to start without WPR...")
return
if self.is_recording():
self.cancel()
profile = os.path.normpath(os.path.join(self.args.bindir, '..', 'ETW', 'IntelSEAPI.wprp'))
profiles = ['-start %s' % option for option, _ in WPRCollector.get_options() if is_domain_enabled('wpa.' + option)]
cmd = '"%s" -start "%s" %s %s' % (self.wpr, profile, ' '.join(profiles), ('' if self.args.ring else '-filemode'))
(out, err) = self.execute(cmd)
if err:
return
self.started = True
return self
def cancel(self):
return self.execute('"%s" -cancel' % self.wpr)
@classmethod
def is_recording(cls, statics={}):
if not statics:
statics['wpr'] = cls.detect()
statics['xperf'] = os.path.normpath(os.path.join(os.path.dirname(statics['wpr']), 'xperf.exe'))
if os.path.exists(statics['xperf']):
(out, err) = cls.execute('"%s" -Loggers | find "WPR_"' % statics['xperf'])
return any('WPR_' in line for line in out.split('\n'))
else:
(out, err) = cls.execute('"%s" -status' % statics['wpr'])
return err or not any('WPR is not recording' in line for line in out.split('\n'))
def stop(self, wait=True):
if not self.started:
return []
self.log("Stop wait=%s" % str(wait))
if not wait:
cmd = 'start "WPR stop" /MIN /LOW "%s" "%s" wpa "%s" "%s"' % (sys.executable, os.path.realpath(__file__), self.file, self.args.output)
self.log(cmd)
subprocess.Popen(cmd, shell=False, stdin=None, stdout=None, stderr=None, creationflags=0x00000008, env=sea.prepare_environ(self.args)) # DETACHED_PROCESS
while self.is_recording():
self.log("is_recording")
time.sleep(1)
return [self.file]
else:
env = sea.prepare_environ(self.args)
self.stop_wpr(self.wpr, self.file, self.args.output, env)
return [self.file]
@classmethod
def stop_wpr(cls, wpr, file, output, env=None):
(out, err) = cls.execute('"%s" -stop "%s"' % (wpr, file), env=env)
if err:
return []
assert(file in out)
@classmethod
def launch(cls, args):
cls.stop_wpr(cls.detect(), args[0], args[1])
class ETWTrace(Collector):
def __init__(self, args):
Collector.__init__(self, args)
wpr = WPRCollector.detect()
self.xperf = os.path.normpath(os.path.join(os.path.dirname(wpr), 'xperf')) if wpr else None
if not self.xperf or not os.path.exists(self.xperf):
variants = self.detect_instances('xperf')
if variants:
self.xperf = variants[0] # TODO: select by higher version
else:
self.xperf = None
self.files = []
self.start()
def start(self):
self.stop()
cmd = None
if self.args.cuts:
self.files.append('%s\\etw-%s.etl' % (self.args.output, (self.args.cuts[0] if self.args.cuts else '0')))
self.files.append('%s\\kernel-%s.etl' % (self.args.output, (self.args.cuts[0] if self.args.cuts else '0')))
else:
self.files.append('%s\\etw.etl' % self.args.output)
self.files.append('%s\\kernel.etl' % self.args.output)
logman_pf = os.path.join(tempfile.gettempdir(), 'gpa_logman.pf')
count = 0
with open(logman_pf, 'w') as file:
if is_domain_enabled('Microsoft-Windows-DxgKrnl'):
file.write('"Microsoft-Windows-DxgKrnl" (Base,GPUScheduler,Profiler,Resource,References,0x4000000000000001)\n')
count += 1
if is_domain_enabled('Microsoft-Windows-Dwm-Core'):
file.write('"Microsoft-Windows-Dwm-Core" (DetailedFrameInformation)\n')
count += 1
if is_domain_enabled('Microsoft-Windows-DXGI'):
file.write('"Microsoft-Windows-DXGI" (Events)\n')
count += 1
if is_domain_enabled('SteamVR'):
file.write('"{8C8F13B1-60EB-4B6A-A433-DE86104115AC}"\n')
count += 1
if is_domain_enabled('OculusVR'):
file.write('"{553787FC-D3D7-4F5E-ACB2-1597C7209B3C}"\n')
count += 1
if is_domain_enabled('Intel_Graphics_D3D10'):
file.write('"{AD367E62-97EF-4B20-8235-E8AB49DB0C23}"\n')
count += 1
if count:
cmd = 'logman start GPA_SEA -ct perf -bs 1024 -nb 120 480'
cmd += ' -pf "%s" -o "%s" %s -ets' % (logman_pf, self.files[0], (('-max %d -f bincirc' % (self.args.ring * 15)) if self.args.ring else ''))
else:
del self.files[0]
if cmd:
(out, err) = self.execute(cmd)
if err:
return None
if self.xperf:
time_multiplier = 0
kernel_logger = [] # logman query providers "Windows Kernel Trace"
complimentary = ''
if is_domain_enabled('Kernel::ContextSwitches'):
time_multiplier += 10
kernel_logger += ['PROC_THREAD', 'CSWITCH']
if is_domain_enabled('Kernel::Stacks', self.args.stacks):
time_multiplier += 20
kernel_logger += ['LOADER', 'PROFILE']
complimentary += ' -stackwalk PROFILE+CSWITCH -SetProfInt 1000000'
if is_domain_enabled('Kernel::IO'):
time_multiplier += 5
kernel_logger += ['FILE_IO', 'FILE_IO_INIT', 'DISK_IO', 'DISK_IO_INIT', 'FILENAME', 'OPTICAL_IO', 'OPTICAL_IO_INIT']
if is_domain_enabled('Kernel::Network', False):
time_multiplier += 5
kernel_logger += ['NETWORKTRACE']
if is_domain_enabled('Kernel::Memory', False):
time_multiplier += 5
kernel_logger += ['VIRT_ALLOC', 'MEMINFO', 'VAMAP', 'POOL', 'MEMINFO_WS'] # 'FOOTPRINT', 'MEMORY'
if is_domain_enabled('Kernel::PageFaults', False):
time_multiplier += 5
kernel_logger += ['ALL_FAULTS', 'HARD_FAULTS']
if kernel_logger:
cmd = '"%s" -on %s %s -f "%s" -ClockType PerfCounter -BufferSize 1024 -MinBuffers 120 -MaxBuffers 480' % (self.xperf, '+'.join(kernel_logger), complimentary, self.files[-1])
if self.args.ring:
cmd += ' -MaxFile %d -FileMode Circular' % (self.args.ring * time_multiplier) # turning seconds into megabytes...
(out, err) = self.execute(cmd)
if err or 'Error:' in out:
del self.files[-1]
return self
else:
del self.files[-1]
else:
time_multiplier = 0
kernel_logger = [] # logman query providers "Windows Kernel Trace"
if is_domain_enabled('Kernel::ContextSwitches'):
time_multiplier += 10
kernel_logger += ['process', 'thread', 'cswitch']
if is_domain_enabled('Kernel::Stacks', self.args.stacks):
time_multiplier += 10
kernel_logger += ['img', 'profile']
if is_domain_enabled('Kernel::IO'):
time_multiplier += 5
kernel_logger += ['fileio', 'disk']
if is_domain_enabled('Kernel::Network', False):
time_multiplier += 5
kernel_logger += ['net']
if is_domain_enabled('Kernel::Memory', False):
time_multiplier += 5
kernel_logger += ['virtalloc']
if is_domain_enabled('Kernel::PageFaults', False):
time_multiplier += 5
kernel_logger += ['pf', 'hf']
if kernel_logger:
cmd = 'logman start "NT Kernel Logger" -p "Windows Kernel Trace" (%s) -ct perf -bs 1024 -nb 120 480' % ','.join(kernel_logger)
cmd += ' -o "%s" %s -ets' % (self.files[-1], (('-max %d -f bincirc' % (self.args.ring * time_multiplier)) if self.args.ring else ''))
(out, err) = self.execute(cmd)
if err or 'Error:' in out:
del self.files[-1]
return self
else:
del self.files[-1]
self.files.append('%s/etw_profilers.logman' % self.args.output)
cmd = 'cmd /c logman query providers ^> "%s"' % self.files[-1]
async_exec(cmd, 'Collecting ETW providers')
return self
def stop(self, wait=True): # TODO: stop without waits
if self.xperf:
proc = subprocess.Popen('xperf -stop', shell=False)
if wait:
proc.wait()
else:
proc = subprocess.Popen('logman stop "NT Kernel Logger" -ets', shell=False)
if wait:
proc.wait()
proc = subprocess.Popen('logman stop "GPA_SEA" -ets', shell=False)
if wait:
proc.wait()
return self.files
COLLECTOR_DESCRIPTORS = [
{
'available': sys.platform == 'win32' and WPRCollector.detect(),
'collector': WPRCollector,
'format': 'wpa'
},
{
'available': sys.platform == 'win32',
'collector': ETWTrace,
'format': 'etw'
}
]
if __name__ == "__main__":
with open(os.path.join(tempfile.gettempdir(), datetime.now().strftime('sea_%H_%M_%S__%d_%m_%Y.log')), 'a') as log:
log.write(str(sys.argv) + '\n')
try:
name = sys.argv[1]
for desc in COLLECTOR_DESCRIPTORS:
if desc['format'] == name:
cls = desc['collector']
cls.set_output(log)
cls.launch(sys.argv[2:])
break
except:
log.write(traceback.format_exc())

View File

@@ -45,7 +45,6 @@ class Task:
return self
def arg(self, name, value):
assert self.id
try:
value = float(value)
self.itt.lib.itt_metadata_add(self.itt.domain, self.id, self.itt.get_string_id(name), value)
@@ -54,7 +53,6 @@ class Task:
return self
def blob(self, name, pointer, size):
assert self.id
self.itt.lib.itt_metadata_add_blob(self.itt.domain, self.id, self.itt.get_string_id(name), pointer, size)
return self

View File

@@ -83,22 +83,6 @@ class DummyWith(): # for conditional with statements
return False
class Profiler():
def __enter__(self):
try:
import cProfile as profile
except:
import profile
self.profiler = profile.Profile()
self.profiler.enable()
return self
def __exit__(self, type, value, traceback):
self.profiler.disable()
self.profiler.print_stats('time')
return False
def get_extensions(name, multiple=False):
big_name = (name + 's').upper()
this_module = sys.modules[__name__]
@@ -128,10 +112,6 @@ def get_exporters():
return get_extensions('exporter')
def get_collectors():
return get_extensions('collector')
verbose_choices = ['fatal', 'error', 'warning', 'info']
@@ -145,7 +125,6 @@ def parse_args(args):
format_choices.append("xcode")
elif sys.platform == 'linux':
format_choices.append("kernelshark")
parser.add_argument("-f", "--format", choices=format_choices, nargs='*', default=[], help='One or many output formats.')
parser.add_argument("-o", "--output", help='Output folder pattern -<pid> will be added to it')
parser.add_argument("-b", "--bindir", help='If you run script not from its location')
parser.add_argument("-i", "--input", help='Provide input folder for transformation (<the one you passed to -o>-<pid>)')
@@ -155,9 +134,7 @@ def parse_args(args):
parser.add_argument("-c", "--cuts", nargs='*', help='Set "all" to merge all cuts in one trace')
parser.add_argument("-r", "--ring", type=int, const='5', default=None, action='store', nargs='?', help='Makes trace to cycle inside ring buffer of given length in seconds')
parser.add_argument("--target", help='Pid of target')
parser.add_argument("--stacks", action="store_true", help='Collect stacks')
parser.add_argument("--profile", action="store_true", help='Internal: profile runtool execution')
parser.add_argument("--collector", choices=list(get_collectors().keys()) + ['default'])
parser.add_argument("-s", "--app_status", action="store_true", help='Script returns the application status')
separators = ['!', '?', '%']
separator = None
@@ -174,14 +151,6 @@ def parse_args(args):
sys.exit(-1)
victim = args[separator + 1:]
victim[-1] = victim[-1].strip() # removal of trailing '\r' - when launched from .sh
if not parsed_args.output:
if sys.platform != 'win32':
parsed_args.output = '/tmp/isea_collection'
print('Collection will be written into:' , parsed_args.output)
else:
parser.print_help()
print("Error: No output (-o) given in launch mode")
sys.exit(-1)
handle_args(parsed_args)
return parsed_args, victim
else: # nothing to launch, transformation mode
@@ -189,20 +158,6 @@ def parse_args(args):
args[-1] = args[-1].strip() # removal of trailing '\r' - when launched from .sh
parsed_args = parser.parse_args(args)
handle_args(parsed_args)
if not parsed_args.input:
if sys.platform != 'win32':
parsed_args.input = '/tmp/isea_collection'
if os.path.exists(parsed_args.input):
print('Collection will be read from:', parsed_args.input)
else:
parser.print_help()
sys.exit(-1)
else:
print("--input argument is required for transformation mode.")
parser.print_help()
sys.exit(-1)
if not parsed_args.format:
parsed_args.format = ['gt']
setattr(parsed_args, 'user_input', parsed_args.input)
if not parsed_args.output:
parsed_args.output = parsed_args.input
@@ -239,7 +194,8 @@ def verbose_level(level=None, statics={}):
def message(level, txt, statics={}):
assert isinstance(statics, dict)
if not isinstance(statics, dict):
return False
if level and verbose_level(level) > verbose_level(): # see default in "parse_args"
return False
@@ -265,14 +221,13 @@ def main():
(args, victim) = parse_args(sys.argv[1:]) # skipping the script name
reset_global('arguments', args)
if victim:
if args.output:
ensure_dir(args.output, clean=True)
launch(args, victim)
else:
ext = os.path.splitext(args.input)[1] if not os.path.isdir(args.input) else None
transform_all(args)
ret_code = launch(args, victim)
Collector.log('Started with arguments: %s' % str(sys.argv))
save_domains()
if ret_code != 0 and not args.app_status:
ret_code = 0
return ret_code
def os_lib_ext():
@@ -282,32 +237,11 @@ def os_lib_ext():
return '.dylib'
elif 'linux' in sys.platform:
return '.so'
assert (not "Unsupported platform")
def get_pids(victim, tracer):
assert len(victim) == 1 # one wildcard is supported yet
assert sys.platform != 'win32' # no Windows support yet
out, err = tracer.execute('ps -o pid,ppid,command -ax', log=False)
if err:
tracer.log(err)
return []
parsed = {}
for line in out.split('\n'):
if not line:
continue
parts = line.split()
if len(parts) < 3:
continue
cmd = ' '.join(parts[2:])
if fnmatch.fnmatch(cmd.lower(), victim[0].lower()) and __file__ not in cmd: # get matching cmd
parsed[parts[0]] = cmd
print("Matching cmd:\t", parts[0], cmd)
return set(parsed.keys())
raise "Unsupported platform"
def launch(args, victim):
ret_code = 0
sea.prepare_environ(args)
sea_itf = sea.ITT('tools')
@@ -340,8 +274,7 @@ def launch(args, victim):
env["INTEL_JIT_PROFILER64"] = paths['64']
env["INTEL_SEA_FEATURES"] = os.environ['INTEL_SEA_FEATURES'] if 'INTEL_SEA_FEATURES' in os.environ else ""
env["INTEL_SEA_FEATURES"] += (" " + str(args.format)) if args.format else ""
env["INTEL_SEA_FEATURES"] += " stacks" if args.stacks else ""
env["INTEL_SEA_FEATURES"] += (" stat")
if args.verbose == 'info':
env['INTEL_SEA_VERBOSE'] = '1'
@@ -365,27 +298,12 @@ def launch(args, victim):
environ = global_storage('sea_env')
for key, val in env.items():
if key in environ and val != environ[key]:
assert key in ['LD_PRELOAD', 'DYLD_INSERT_LIBRARIES']
if key not in ['LD_PRELOAD', 'DYLD_INSERT_LIBRARIES']:
raise key + ' wasn\'t found!'
environ[key] += ':' + val
else:
environ[key] = val
if 'kernelshark' in args.format:
victim = 'trace-cmd record -e IntelSEAPI/* ' + victim
tracer = None
if args.collector:
tracer = get_collectors()[args.collector]
elif not tracer: # using default collector per system
if 'linux' in sys.platform:
tracer = get_collectors()['ftrace']
elif 'win32' == sys.platform:
tracer = get_collectors()['etw']
elif 'darwin' in sys.platform:
tracer = get_collectors()['dtrace']
run_suspended = False
if args.dir:
full_victim = os.path.join(args.dir, victim[0])
if os.path.exists(full_victim):
@@ -393,28 +311,15 @@ def launch(args, victim):
setattr(args, 'victim', victim[0])
tracer = tracer(args) if tracer else None # turning class into instance
if '!' in sys.argv[1:]:
assert tracer
proc = subprocess.Popen(victim, env=environ, shell=False, cwd=args.dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if hasattr(tracer, 'launch_victim'):
victim[0] = victim[0].replace(' ', r'\ ')
proc = tracer.launch_victim(victim, env=environ)
else:
if run_suspended: # might consider using preload of SEA lib and do the suspend there. Or allow tracers to run it.
suspended = '(cd "%s"; kill -STOP $$; exec %s )' % (args.dir or '.', ' '.join(victim))
proc = subprocess.Popen(suspended, env=environ, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
else:
proc = subprocess.Popen(victim, env=environ, shell=False, cwd=args.dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if sys.platform != 'win32' and not run_suspended: # FIXME: implement suspended start on Windows!
if not args.ring:
proc.send_signal(signal.SIGSTOP)
if sys.platform != 'win32': # FIXME: implement suspended start on Windows!
if not args.ring:
proc.send_signal(signal.SIGSTOP)
args.target = proc.pid
tracer.start()
if sys.platform != 'win32': # collector start may be long, so we freeze victim during this time
print("PID:", proc.pid)
if not args.ring:
@@ -429,6 +334,7 @@ def launch(args, victim):
except KeyboardInterrupt:
print("Stopping all...")
proc.send_signal(signal.SIGABRT)
ret_code = -1
out, err = proc.communicate()
if out or err:
print("\n\n -= Target output =- {\n")
@@ -436,42 +342,16 @@ def launch(args, victim):
print("\n", "-" * 50, "\n")
print(err.decode().strip())
print("\n}\n\n")
elif '?' in sys.argv[1:]:
print("Attach to:", victim)
pids = get_pids(victim, tracer)
if not pids:
print("Error: nothing found...")
return
if tracer:
args.target = list(pids)
tracer.start()
print("Waiting for CTRL+C...")
global_storage('collection')['time']['before'] = time.time()
def is_running(pid):
try:
os.kill(int(pid), 0)
return True
except OSError:
return False
try:
while any(is_running(pid) for pid in pids):
time.sleep(0.5)
except KeyboardInterrupt:
pass
ret_code = proc.returncode
else:
message('error', 'unsupported separator')
return -1
global_storage('collection')['time']['after'] = time.time()
print("Stopping collectors...")
if tracer:
args.trace = tracer.stop()
if not args.output:
return []
return ret_code
args.input = args.output
@@ -486,8 +366,9 @@ def launch(args, victim):
allowed_pids = [args.target]
global_storage('collection').setdefault('targets', allowed_pids)
if args.format:
transform_all(args)
transform_all(args)
return ret_code
def subst_env_vars(path):
@@ -500,7 +381,6 @@ PermanentCache = os.path.join(UserProfile, '.isea_cache.dict')
def ensure_dir(path, clean, statics={}):
if path in statics:
assert(statics[path] or not clean)
return
statics[path] = clean
if os.path.exists(path):
@@ -837,7 +717,6 @@ class TaskCombinerCommon:
else:
message('warning', 'Negative length task: %s => %s' % (str(item), str(data)))
else:
assert (self.tree["ring_buffer"] or self.tree['cuts'])
if 'str' in data: # nothing to show without name
self.no_begin.append(data)
elif fn == "frame_begin":
@@ -848,8 +727,6 @@ class TaskCombinerCommon:
if index is not None:
item = frames.pop(index)
self.complete_task("frame", item, data)
else:
assert (self.tree["ring_buffer"] or self.tree['cuts'])
elif fn == "metadata_add":
if 'id' in data:
task = get_task(data['id'])
@@ -896,7 +773,7 @@ class TaskCombinerCommon:
get_task(data['parent']) or find_task(data['parent'])
)
else:
assert (not "Unsupported type:" + fn)
raise "Unsupported type:" + fn
def compress_counter(self, cache, data):
values = cache['values']
@@ -962,8 +839,7 @@ class Callbacks(TaskCombinerCommon):
self.tid_map.update(tid_map)
self.allowed_pids |= set(tid_map.values())
for fmt in args.format:
self.callbacks.append(get_exporters()[fmt](args, tree))
self.callbacks.append(get_exporters()['stat'](args, tree))
if args.target:
if isinstance(args.target, list):
@@ -1033,10 +909,7 @@ class Callbacks(TaskCombinerCommon):
if not type:
return False
if not is_domain_enabled(data['domain']):
return False
if data.get('internal_name', None) and not is_domain_enabled('%s.%s' % (data['domain'], data['internal_name'])):
if data.get('internal_name', None):
return False
self.__call__(type, data)
@@ -1050,9 +923,6 @@ class Callbacks(TaskCombinerCommon):
if self.handle_special(type, begin, end): # returns True if event is consumed and doesn't require processing
return True
if not is_domain_enabled(begin['domain']):
return False
if end:
# copy here as handler can change the data for own good - this shall not affect other handlers
[callback.complete_task(type, begin.copy(), end.copy() if end else end) for callback in self.callbacks]
@@ -1278,7 +1148,8 @@ class Callbacks(TaskCombinerCommon):
return args
def end(self, time_stamp):
assert self.data # expected to be initialized in self.begin call
if not self.data:
return
if time_stamp:
end_data = self.data.copy()
end_data.update({'time': time_stamp, 'type': self.event_type + 1})
@@ -1674,7 +1545,8 @@ class FileWrapper:
return None
call["time"] = tuple[0]
assert (tuple[1] < len(TaskTypes)) # sanity check
if tuple[1] >= len(TaskTypes):
return None
call["type"] = tuple[1]
flags = tuple[2]
@@ -1843,14 +1715,14 @@ def resolve_cmd(args, path, load_addr, ptr, cache={}):
elif 'linux' in sys.platform:
cmd = 'addr2line %s -e "%s" -i -p -f -C' % (to_hex(ptr), path)
else:
assert (not "Unsupported platform!")
raise "Unsupported platform!"
env = dict(os.environ)
if "INTEL_SEA_VERBOSE" in env:
del env["INTEL_SEA_VERBOSE"]
try:
proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
proc = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
(symbol, err) = proc.communicate()
except IOError:
err = traceback.format_exc()
@@ -2044,45 +1916,6 @@ def get_name(begin):
return "<unknown>"
def get_filter_path():
filter = os.environ.get('INTEL_SEA_FILTER')
if filter:
filter = subst_env_vars(filter)
else:
filter = os.path.join(UserProfile, '.isea_domains.fltr')
return filter
def is_domain_enabled(domain, default=True):
domains = global_storage('sea.is_domain_enabled', {})
if not domains:
filter = get_filter_path()
try:
with open(filter) as file:
for line in file:
enabled = not line.startswith('#')
name = line.strip(' #\n\r')
domains[name] = enabled
if not enabled:
message('warning', 'The domain "%s" is disabled in %s' % (name, filter))
except IOError:
pass
if domain not in domains:
domains[domain] = default
return domains[domain]
def save_domains():
domains = global_storage('sea.is_domain_enabled', {})
filter = get_filter_path()
print("Saving domains:", filter)
with open(filter, 'w') as file:
for key, value in domains.items():
file.write('%s%s\n' % ('#' if not value else '', key))
class GraphCombiner(TaskCombiner):
def __init__(self, args, tree):
TaskCombiner.__init__(self, args, tree)
@@ -2240,7 +2073,8 @@ class Collector:
@classmethod
def log(cls, msg, stack=False):
assert type(stack) is bool # to avoid "log" function being misused as "print" where comma allows more args
if not type(stack) is bool:
stack = False
msg = msg.strip()
cut = '\n' + '-' * 100 + '\n'
msg = cut + msg + '\n\n' + (''.join(traceback.format_stack()[:-1]) if stack else '') + cut
@@ -2260,7 +2094,7 @@ class Collector:
if sys.version[0] == '3':
kwargs['encoding'] = 'utf8'
(out, err) = subprocess.Popen(cmd, shell=True, **kwargs).communicate()
(out, err) = subprocess.Popen(cmd, shell=False, **kwargs).communicate()
if log:
cls.log("\ncmd:\t%s:\nout:\t%s\nerr:\t%s\ntime: %s" % (cmd, str(out).strip(), str(err).strip(), str(timedelta(seconds=(time.time() - start_time)))), stack=True if err else False)
if verbose_level() == verbose_level('info'):
@@ -2281,9 +2115,9 @@ class Collector:
info = subprocess.STARTUPINFO()
info.dwFlags = subprocess.STARTF_USESHOWWINDOW
info.wShowWindow = 0 # SW_HIDE
subprocess.Popen(cmd, shell=True, startupinfo=info, stdin=None, stdout=None, stderr=None, creationflags=(CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP), **kwargs)
subprocess.Popen(cmd, shell=False, startupinfo=info, stdin=None, stdout=None, stderr=None, creationflags=(CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP), **kwargs)
else:
subprocess.Popen(cmd, shell=True, stdin=None, stdout=None, stderr=None, **kwargs)
subprocess.Popen(cmd, shell=False, stdin=None, stdout=None, stderr=None, **kwargs)
def start(self):
raise NotImplementedError('Collector.start is not implemented!')
@@ -2308,6 +2142,7 @@ class Collector:
if __name__ == "__main__":
start_time = time.time()
main()
ret_code = main()
elapsed = time.time() - start_time
print("Time Elapsed:", str(timedelta(seconds=elapsed)).split('.')[0])
exit(ret_code)

View File

@@ -19,21 +19,11 @@ set(TARGET_NAME sea_itt_lib)
set(CMAKE_DEBUG_POSTFIX "")
set(CMAKE_RELEASE_POSTFIX "")
if (WIN32)
add_custom_command(OUTPUT "${PROJECT_BINARY_DIR}/IntelSEAPI.rc" "${PROJECT_BINARY_DIR}/IntelSEAPI.h"
PRE_BUILD
COMMAND mc -um ${CMAKE_CURRENT_SOURCE_DIR}/IntelSEAPI.man -h ${PROJECT_BINARY_DIR} -r ${PROJECT_BINARY_DIR}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/IntelSEAPI.man
MAIN_DEPENDENCY IttNotifyStdSrc.h
COMMENT "Generating ${PROJECT_BINARY_DIR}/IntelSEAPI.rc ${PROJECT_BINARY_DIR}/IntelSEAPI.h"
)
endif()
file(GLOB_RECURSE SOURCES "*.cpp" "*.h")
add_library(${TARGET_NAME} SHARED ${SOURCES})
target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
${PROJECT_BINARY_DIR})
target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
set_target_properties(${TARGET_NAME} PROPERTIES OUTPUT_NAME IntelSEAPI)
target_link_libraries(${TARGET_NAME} PRIVATE ittnotify)

View File

@@ -1333,7 +1333,7 @@ __itt_event UNICODE_AGNOSTIC(event_create)(const char *name, int namelen) {
ITT_FUNCTION_STAT();
__itt_domain* pEvents = get_events_domain();
__itt_string_handle* pStr = UNICODE_AGNOSTIC(string_handle_create)(name);
return intptr_t(pStr) - intptr_t(pEvents);
return __itt_event(intptr_t(pStr) - intptr_t(pEvents));
}
int event_start(__itt_event event) {
@@ -1682,34 +1682,6 @@ void SetRing(uint64_t nanoseconds) {
}
#endif
#ifdef _WIN32
typedef ULONG(__stdcall* TEtwNotificationRegister)(
LPCGUID Guid,
ULONG Type,
PVOID Callback,
PVOID Context,
REGHANDLE* RegHandle);
TEtwNotificationRegister g_fnOrigEtwNotificationRegister = nullptr;
ULONG _stdcall MyEtwNotificationRegister(
LPCGUID Guid,
ULONG Type,
PVOID Callback,
PVOID Context,
REGHANDLE* RegHandle) {
WCHAR strGuid[100] = {};
StringFromGUID2(*Guid, strGuid, sizeof(strGuid) - 1);
char str[100] = {};
sprintf_s(str, "%ls", strGuid);
VerbosePrint("\nEventRegister, provider: %s\n", str);
static __itt_string_handle* pKey = UNICODE_AGNOSTIC(string_handle_create)("EventRegister::Provider");
WriteMeta(GetRegularFields(), pKey, str);
return g_fnOrigEtwNotificationRegister(Guid, Type, Callback, Context, RegHandle);
}
#endif
void InitSEA() {
for (size_t i = 0; (i < MAX_HANDLERS) && g_handlers[i]; ++i) {
g_handlers[i]->Init(g_rfMainThread);

View File

@@ -102,7 +102,6 @@ struct ___itt_counter : public __itt_counter_info_t{};
#ifdef _WIN32
#include "windows.h"
#include "IntelSEAPI.h"
#elif defined(__linux__)
#ifndef USE_PROBES
__thread FILE* stdsrc_trace_info_t::pFile = nullptr;
@@ -125,11 +124,6 @@ struct ___itt_counter : public __itt_counter_info_t{};
__itt_id from; //d3 is not used, so we fit d1 and d2 into 16 bytes
GUID to;
};
//http://www.geoffchappell.com/studies/windows/win32/ntdll/api/etw/eventwritefull.htm
//http://helpdoc-online.com/Microsoft_Platform_SDK_August_2001_Performance_Monitoring_en/Tracing_Event_Instances.php
typedef NTSYSAPI NTSTATUS (NTAPI * FZwTraceEvent)(IN ULONG TraceHandle, IN ULONG Flags, IN ULONG TraceHeaderLength, IN PEVENT_TRACE_HEADER TraceHeader);
#else
#include <cstdio>
#define _strdup strdup

View File

@@ -29,7 +29,6 @@
#ifdef _WIN32
#define setenv _putenv
#include <windows.h>
#include "IntelSEAPI.h"
#undef API_VERSION
#include <Dbghelp.h>
#pragma comment(lib, "dbghelp")
@@ -187,9 +186,6 @@ extern "C" {
if (!bInitialized) {
bInitialized = true;
sea::InitSEA();
#ifdef _WIN32
EventRegisterIntelSEAPI();
#endif
atexit(AtExit);
}
}
@@ -204,9 +200,6 @@ extern "C" {
g_bInitialized = false;
sea::FinitaLaComedia();
#ifdef _WIN32
EventUnregisterIntelSEAPI();
#endif
}
}