官方介绍:Event Tracing for Windows (ETW) - Windows drivers | Microsoft Learn
官方示例:Eventdrv - Code Samples | Microsoft Learn
另一篇文章:ETW - 事件提供者
事件消费者(Event Consumer): 事件消费者是一个应用程序,它订阅事件提供者生成的事件,并对这些事件进行处理。事件消费者可以实时处理事件,也可以将事件保存到日志文件中以供离线分析。为了实现事件消费者,开发者需要完成以下任务:
通过控制器可以启用事件跟踪,这时候针对生成的事件集合,我们有两种手段进行消费
借助Windows Kits里面的工具,可以从etl文件中导出我们想要的任意数据到csv文件中,借助脚本语言进行进一步分析,这里可以使用python
使用控制器或者Windows Kit自带的WPRUI.exe(C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\WPRUI.exe)录制etl
使用wpaexporter.exe进行导出文件
命令行:python IdentifyChromeProcesses.py "D:\系统\文档\etwtraces\*.etl" -c
def _IdentifyChromeProcesses(tracename, show_cpu_usage, tabbed_output, return_pid_map):
if not os.path.exists(tracename):
print('Trace file "%s" does not exist.' % tracename)
sys.exit(0)
script_dir = os.path.dirname(sys.argv[0])
if len(script_dir) == 0:
script_dir = '.'
cpu_usage_by_pid = {}
context_switches_by_pid = {}
if show_cpu_usage:
csv_filename = os.path.join(script_dir, 'CPU_Usage_(Precise)_Randomascii_CPU_Usage_by_Process.csv')
profile_filename = os.path.join(script_dir, 'CPUUsageByProcess.wpaProfile')
try:
# Try to delete any old results files but continue if this fails.
os.remove(csv_filename)
except:
pass
# -tle and -tti are undocumented for wpaexporter but they do work. They tell wpaexporter to ignore
# lost events and time inversions, just like with xperf.
command = 'wpaexporter "%s" -outputfolder "%s" -tle -tti -profile "%s"' % (tracename, script_dir, profile_filename)
# If there is no CPU usage data then this will return -2147008507.
try:
output = str(subprocess.check_output(command, stderr=subprocess.STDOUT))
except subprocess.CalledProcessError as e:
if e.returncode == -2147008507:
print('No CPU Usage (Precise) data found, no report generated.')
return
raise(e)
# Typical output in the .csv file looks like this:
# New Process,Count,CPU Usage (in view) (ms)
# Idle (0),7237,"26,420.482528"
# We can't just split on commas because the CPU Usage often has embedded commas so
# we need to use an actual csv reader.
if os.path.exists(csv_filename):
lines = open(csv_filename, 'r').readlines()
process_and_pid_re = re.compile(r'(.*) \(([\d ]*)\)')
for row_parts in csv.reader(lines[1:], delimiter = ',', quotechar = '"', skipinitialspace=True):
process, context_switches, cpu_usage = row_parts
match = process_and_pid_re.match(process)
if match:
_, pid = match.groups()
pid = int(pid)
cpu_usage_by_pid[pid] = float(cpu_usage.replace(',', ''))
context_switches_by_pid[pid] = int(context_switches)
else:
print('Expected output file not found.')
print('Expected to find: %s' % csv_filename)
print('Should have been produced by: %s' % command)
# Typical output of -a process -withcmdline looks like:
# MIN, 24656403, Process, 0XA1141C60, chrome.exe ( 748), 10760, 1, 0x11e8c260, "C:\...\chrome.exe" --type=renderer ...
# Find the PID and ParentPID
pidsRe = re.compile(r'.*\(([\d ]*)\), *(\d*),.*')
# Find the space-terminated word after 'type='. This used to require that it
# be the first command-line option, but that is likely to not always be true.
# Mark the first .* as lazy/ungreedy/reluctant so that if there are multiple
# --type options (such as with the V8 Proxy Resolver utility process) the
# first one will win.
processTypeRe = re.compile(r'.*? --type=([^ ]*) .*')
# Starting around M84 Chrome's utility processes have a --utility-sub-type
# parameter which identifies the type of utility process. Typical command
# lines look something like this:
# --type=utility --utility-sub-type=audio.mojom.AudioService --field-trial...
processSubTypeRe = re.compile(r'.*? --utility-sub-type=([^ ]*) .*')
#-a process = show process, thread, image information (see xperf -help processing)
#-withcmdline = show command line in process reports (see xperf -help process)
command = 'xperf -i "%s" -a process -withcmdline' % tracename
# Group all of the chrome.exe processes by browser Pid, then by type.
# pathByBrowserPid just maps from the browser Pid to the disk path to chrome.exe
pathByBrowserPid = {}
# pidsByParent is a dictionary that is indexed by the browser Pid. It contains
# a dictionary that is indexed by process type with each entry's payload
# being a list of Pids (for example, a list of renderer processes).
pidsByParent = {}
# Dictionary of Pids and their lines of data
lineByPid = {}
# Dictionary of Pids and their types.
types_by_pid = {}
# Dictionary of Pids and their sub-types (currently utility-processes only).
sub_types_by_pid = {}
try:
output = subprocess.check_output(command, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError:
# Try again. If it succeeds then there were lost events or a time inversion.
#-tle = tolerate lost events
#-tti = tolerate time inversions
command = 'xperf -i "%s" -tle -tti -a process -withcmdline' % tracename
output = subprocess.check_output(command, stderr=subprocess.STDOUT)
print('Trace had a time inversion or (most likely) lost events. Results may be anomalous.')
print()
# Extra processes to print information about, when cpu_usage is requested
extra_processes = []
for line in output.splitlines():
~~~
分析截图
这里需要注意的是,脚本文件配合Profile文件会比较快一些。
下面示例就是结合Profile来进行分析,CPUUsageByProcess.wpaProfile
<?xml version="1.0" encoding="utf-8"?>
<WpaProfileContainer xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="2" xmlns="http://tempuri.org/SerializableElement.xsd">
<Content xsi:type="WpaProfile2">
<Sessions>
<Session Index="0">
<FileReferences />
</Session>
</Sessions>
<Views>
<View Guid="263ce2e9-0112-4010-a5cc-7bc4aec7ff31" Title="Analysis" IsVisible="true">
<Graphs>
<Graph Guid="c58f5fea-0319-4046-932d-e695ebe20b47" LayoutStyle="DataTable" Color="#FFFF0000" GraphHeight="125" IsMinimized="false" IsShown="true" IsExpanded="false" HelpText="{}{\rtf1\ansi\ansicpg1252\uc1\htmautsp\deff2{\fonttbl{\f0\fcharset0 Times New Roman;}{\f2\fcharset0 Segoe UI;}}{\colortbl\red0\green0\blue0;\red255\green255\blue255;}\loch\hich\dbch\pard\plain\ltrpar\itap0{\lang1033\fs18\f2\cf0 \cf0\ql{\f2 {\ltrch Uses context switch events to provide a precise view of CPU usage in the trace. You can view a timeline of when threads are switched in and out, a graph of usage, and many other visualizations.}\li0\ri0\sa0\sb0\fi0\ql\par}
{\f2 \li0\ri0\sa0\sb0\fi0\ql\par}
{\f2 {\ltrch More on context switching }\li0\ri0\sa0\sb0\fi0\ql\par}
{\f2 {\ltrch Because the number of processors in a system is limited, all threads cannot run at the same time. Windows uses processor time-sharing, which allows a thread to run for a period of time before the processor switches to another thread. Switching between threads is called a context-switch and it is performed by a Windows component called the dispatcher. The dispatcher makes thread scheduling decisions based on priority, ideal processor and affinity, quantum, and state. This graph captures the data by the dispatcher.}\li0\ri0\sa0\sb0\fi0\ql\par}
}
}">
<Preset Name="Randomascii CPU Usage by Process" BarGraphIntervalCount="50" IsThreadActivityTable="false" GraphColumnCount="61" KeyColumnCount="13" LeftFrozenColumnCount="0" RightFrozenColumnCount="59" InitialFilterShouldKeep="true" InitialSelectionQuery="([Series Name]:="New Process" AND NOT ([New Process]:="Idle (0)"))" GraphFilterColumnGuid="17a03387-5d14-405a-a5b2-0201c934f917" GraphFilterTopValue="0" GraphFilterThresholdValue="0" HelpText="{}{\rtf1\ansi\ansicpg1252\uc1\htmautsp\deff2{\fonttbl{\f0\fcharset0 Times New Roman;}{\f2\fcharset0 Segoe UI;}}{\colortbl\red0\green0\blue0;\red255\green255\blue255;}\loch\hich\dbch\pard\plain\ltrpar\itap0{\lang1033\fs18\f2\cf0 \cf0\ql{\f2 {\ltrch Shows how work is distributed among processors.}\li0\ri0\sa0\sb0\fi0\ql\par}
}
}">
<MetadataEntries>
<MetadataEntry Guid="944ed37a-5774-421e-b2d5-84f17a4b3a05" Name="New Thread Id" ColumnMetadata="EndThreadId" />
<MetadataEntry Guid="5417f63c-9b79-45aa-beb9-73e3c1959221" Name="Switch-In Time" ColumnMetadata="StartTime" />
<MetadataEntry Guid="03a1d898-7231-4cc5-9712-4bfbf53908c7" Name="New Switch-In Time" ColumnMetadata="Duration" />
<MetadataEntry Guid="2575f38e-f991-4ce5-bafb-793e2ba1936a" Name="CPU" ColumnMetadata="ResourceId" />
<MetadataEntry Guid="17a03387-5d14-405a-a5b2-0201c934f917" Name="Time Since Last" ColumnMetadata="WaitDuration" />
<MetadataEntry Guid="5417f63c-9b79-45aa-beb9-73e3c1959221" Name="Switch-In Time" ColumnMetadata="WaitEndTime" />
</MetadataEntries>
<HighlightEntries />
<Columns>
<Column Guid="f64abe19-837a-4e53-8087-4547026b82b2" Name="New Process Name" SortPriority="1" Width="180" IsVisible="false" />
<Column Guid="b065487c-5e32-4f1f-a2cd-581e086ce29e" Name="New Process" SortPriority="2" Width="200" IsVisible="true" />
<Column Guid="944ed37a-5774-421e-b2d5-84f17a4b3a05" Name="New Thread Id" SortPriority="3" TextAlignment="Right" Width="80" IsVisible="false" />
<Column Guid="68482a06-b6a3-4eb9-922f-9fa43537148b" Name="New Thread Stack" SortPriority="4" Width="200" IsVisible="false">
<StackOptionsParameter Mode="StackTag" />
</Column>
<Column Guid="68482a06-b6a3-4eb9-922f-9fa43537148b" Name="New Thread Stack" SortPriority="5" Width="200" IsVisible="false">
<StackOptionsParameter Mode="FrameTags" />
</Column>
<Column Guid="68482a06-b6a3-4eb9-922f-9fa43537148b" Name="New Thread Stack" SortPriority="6" Width="200" IsVisible="false">
<StackOptionsParameter />
</Column>
<Column Guid="74714606-d216-4cfa-a7d8-7ccb9c67de76" Name="Ready Thread Stack" SortPriority="7" Width="200" IsVisible="false">
<StackOptionsParameter Mode="StackTag" />
</Column>
<Column Guid="74714606-d216-4cfa-a7d8-7ccb9c67de76" Name="Ready Thread Stack" SortPriority="8" Width="200" IsVisible="false">
<StackOptionsParameter Mode="FrameTags" />
</Column>
<Column Guid="74714606-d216-4cfa-a7d8-7ccb9c67de76" Name="Ready Thread Stack" SortPriority="9" Width="200" IsVisible="false">
<StackOptionsParameter />
</Column>
<Column Guid="5b7c10a2-868c-415c-826e-0b8065de3872" Name="Readying Process Name" SortPriority="10" Width="180" IsVisible="false" />
<Column Guid="a91e1d66-1316-4baa-b95d-e69aeeef891e" Name="Readying Process" SortPriority="11" Width="200" IsVisible="false" />
<Column Guid="4ce38ba6-1665-4d3a-91cb-35b64f8c4280" Name="Readying Thread Id" SortPriority="12" TextAlignment="Right" Width="80" IsVisible="false" />
<Column Guid="cb796d44-2927-5ac1-d231-4b71904c18f5" Name="Thread Name" SortPriority="13" Width="80" IsVisible="false" />
<Column Guid="82ddfdff-ee93-5f35-08ac-4705069618dc" Name="Thread Activity Tag" SortPriority="14" Width="80" IsVisible="false" />
<Column Guid="2818954f-2d30-5569-4510-dade0a5a605c" Name="Annotation" SortPriority="15" Width="80" IsVisible="false" />
<Column Guid="7bb1053b-8f03-4c72-89f1-a5f8b9c45a9e" Name="Ready Time" SortPriority="16" TextAlignment="Right" Width="120" IsVisible="false" />
<Column Guid="d227f58f-ec9b-4a52-8fe5-e082771c55c6" Name="Count" AggregationMode="Count" SortPriority="17" TextAlignment="Right" Width="50" IsVisible="true" />
<Column Guid="17a03387-5d14-405a-a5b2-0201c934f917" Name="Time Since Last" AggregationMode="Sum" SortPriority="18" TextAlignment="Right" Width="120" CellFormat="uN" IsVisible="false" />
<Column Guid="17a03387-5d14-405a-a5b2-0201c934f917" Name="Time Since Last" AggregationMode="Max" SortPriority="19" TextAlignment="Right" Width="120" CellFormat="uN" IsVisible="false" />
<Column Guid="906ea81e-ab68-4dfd-9b9f-3adafab60f83" Name="Ready" AggregationMode="Sum" SortPriority="20" TextAlignment="Right" Width="100" CellFormat="uN" IsVisible="false" />
<Column Guid="906ea81e-ab68-4dfd-9b9f-3adafab60f83" Name="Ready" AggregationMode="Max" SortPriority="21" TextAlignment="Right" Width="100" CellFormat="uN" IsVisible="false" />
<Column Guid="6d598aa8-2ec4-46cd-b71a-88a239dfacf7" Name="Waits" AggregationMode="Sum" SortPriority="22" TextAlignment="Right" Width="100" CellFormat="uN" IsVisible="false" />
<Column Guid="6d598aa8-2ec4-46cd-b71a-88a239dfacf7" Name="Waits" AggregationMode="Max" SortPriority="23" TextAlignment="Right" Width="100" CellFormat="uN" IsVisible="false" />
<Column Guid="1db45bc8-4cd5-49f3-a0ec-7f861d33c7a2" Name="Count: Waits" AggregationMode="Sum" SortPriority="24" TextAlignment="Right" Width="70" IsVisible="false" />
<Column Guid="5417f63c-9b79-45aa-beb9-73e3c1959221" Name="Switch-In Time" SortPriority="25" TextAlignment="Right" Width="120" IsVisible="false" />
<Column Guid="71d9a1a9-f32c-4b0b-8f09-09b56cbbb843" Name="Last Switch-Out Time" SortPriority="26" TextAlignment="Right" Width="120" IsVisible="false" />
<Column Guid="b0394c3a-60ba-4305-84d6-82384bd863cb" Name="Next Switch-Out Time" SortPriority="27" TextAlignment="Right" Width="120" IsVisible="false" />
<Column Guid="f611f788-c420-49a0-84b2-21ad7cfea811" Name="New Prev Out Pri" SortPriority="28" TextAlignment="Right" Width="70" IsVisible="false" />
<Column Guid="5664dbf4-e565-4b15-add0-3e826ee25c10" Name="New In Pri" SortPriority="29" TextAlignment="Right" Width="70" IsVisible="false" />
<Column Guid="f88c657f-5f13-4477-a713-eaf1abf81ece" Name="New Out Pri" SortPriority="30" TextAlignment="Right" Width="70" IsVisible="false" />
<Column Guid="11628f2a-3000-43af-9924-d99198ae9be2" Name="New Prev State" SortPriority="31" Width="100" IsVisible="false" />
<Column Guid="fe7ccd45-6275-413e-a538-3587f0eb452b" Name="New Prev Wait Reason" SortPriority="32" Width="100" IsVisible="false" />
<Column Guid="ef1e0e6d-e243-48e8-b40a-71c408eeae9f" Name="New State" SortPriority="33" Width="100" IsVisible="false" />
<Column Guid="d8d5d554-5877-4b5a-8df5-7cfa1935430a" Name="New Wait Reason" SortPriority="34" Width="100" IsVisible="false" />
<Column Guid="e93e468d-86a2-4f3a-a750-bb3b07c14a0b" Name="Old Process" SortPriority="35" Width="200" IsVisible="false" />
<Column Guid="7e24b8da-fe12-4e69-b8cc-9ab0c22c9734" Name="Old Thread Id" SortPriority="36" TextAlignment="Right" Width="80" IsVisible="false" />
<Column Guid="2575f38e-f991-4ce5-bafb-793e2ba1936a" Name="CPU" SortPriority="37" TextAlignment="Right" Width="50" IsVisible="false" />
<Column Guid="e93dcc3d-9960-446a-aa1d-fa05d47d5aa4" Name="Ideal Cpu" SortPriority="38" TextAlignment="Right" Width="60" IsVisible="false" />
<Column Guid="59117e0d-0465-42c7-a758-52728c5b0099" Name="New Thread Start Module" SortPriority="39" Width="200" IsVisible="false" />
<Column Guid="b09b3bba-08ea-4e1b-9c16-4d0bb97926fb" Name="New Thread Start Function" SortPriority="40" Width="200" IsVisible="false" />
<Column Guid="de478623-0270-43d8-b2b4-c7df7b93ec7a" Name="Readying Thread Start Module" SortPriority="41" Width="200" IsVisible="false" />
<Column Guid="3a13296d-8e2d-40d7-8ab6-46ebb2646caa" Name="Readying Thread Start Function" SortPriority="42" Width="200" IsVisible="false" />
<Column Guid="96d10b76-4157-42d0-8163-91dc0b8b12b5" Name="Old Thread Start Module" SortPriority="43" Width="200" IsVisible="false" />
<Column Guid="e99e2971-b205-41e9-894d-ba0c19e2af83" Name="Old Thread Start Function" SortPriority="44" Width="200" IsVisible="false" />
<Column Guid="03a1d898-7231-4cc5-9712-4bfbf53908c7" Name="New Switch-In Time" AggregationMode="Sum" SortPriority="45" TextAlignment="Right" Width="100" CellFormat="uN" IsVisible="false" />
<Column Guid="145e9709-1fc8-4cbf-8119-6a4c9b6bf945" Name="NewThreadRank" SortPriority="46" TextAlignment="Right" Width="80" IsVisible="false" />
<Column Guid="10400bbf-b55c-4034-94d7-9f190ae0b2c5" Name="New Prev Thread Rank" SortPriority="47" Width="146" IsVisible="false" />
<Column Guid="12d422df-3a1b-4030-afad-e99eaf4f0bb8" Name="New Thread Remaining Quantum" SortPriority="48" TextAlignment="Right" Width="100" IsVisible="false" />
<Column Guid="92e39fbc-474d-4552-9b6b-102c770c66a8" Name="New Thread Prev Remaining Quantum" SortPriority="49" TextAlignment="Right" Width="100" IsVisible="false" />
<Column Guid="f0350651-fec4-4716-9fdb-8ccc82bd5c8b" Name="Old Thread Rank" SortPriority="50" TextAlignment="Right" Width="80" IsVisible="false" />
<Column Guid="f3c40e2a-ad5b-4ec3-ac26-7b966a6da1cb" Name="Old Thread Remaining Quantum" SortPriority="51" TextAlignment="Right" Width="100" IsVisible="false" />
<Column Guid="009bfba2-9a07-4a98-92a8-56b5970d365d" Name="Adjust Reason" SortPriority="52" Width="120" IsVisible="false" />
<Column Guid="c0c14208-2fc6-4077-928c-3df6f2f847fe" Name="Adjust Increment" SortPriority="53" TextAlignment="Right" Width="120" IsVisible="false" />
<Column Guid="cecd6383-5322-4832-9e8b-6706fe3dcaa1" Name="Ready Thread In DPC" SortPriority="54" Width="120" IsVisible="false" />
<Column Guid="196563c2-6480-4353-8ea4-ed9663fd5a6d" Name="Kernel Stack Not Resident" SortPriority="55" Width="120" IsVisible="false" />
<Column Guid="c730d632-ba9f-41db-8c2b-ed97918ecd50" Name="Process Out of Memory" SortPriority="56" Width="120" IsVisible="false" />
<Column Guid="6536b3d9-1bbd-4268-974d-4f9377122feb" Name="Direct Switch Attempt" SortPriority="57" Width="120" IsVisible="false" />
<Column Guid="e008ed7a-15b0-40ab-854b-b5f6392f298b" Name="CPU Usage (in view)" AggregationMode="Sum" SortOrder="Descending" SortPriority="0" TextAlignment="Right" Width="100" CellFormat="mN" IsVisible="true" />
<Column Guid="fc672588-da05-4f43-991f-6b644a3f5b3d" Name="% CPU Usage" AggregationMode="Sum" SortPriority="59" TextAlignment="Right" Width="100" CellFormat="N2" IsVisible="false" />
</Columns>
</Preset>
</Graph>
</Graphs>
<SessionIndices>
<SessionIndex>0</SessionIndex>
</SessionIndices>
</View>
</Views>
</Content>
</WpaProfileContainer>
# -tle and -tti are undocumented for wpaexporter but they do work. They tell wpaexporter to ignore
# lost events and time inversions, just like with xperf.
command = 'wpaexporter "%s" -outputfolder "%s" -tle -tti -profile "%s"' % (tracename, script_dir, profile_filename)
# If there is no CPU usage data then this will return -2147008507.
try:
output = str(subprocess.check_output(command, stderr=subprocess.STDOUT))
except subprocess.CalledProcessError as e:
if e.returncode == -2147008507:
print('No CPU Usage (Precise) data found, no report generated.')
return
raise(e)
# Typical output in the .csv file looks like this:
# New Process,Count,CPU Usage (in view) (ms)
# Idle (0),7237,"26,420.482528"
# We can't just split on commas because the CPU Usage often has embedded commas so
# we need to use an actual csv reader.
if os.path.exists(csv_filename):
lines = open(csv_filename, 'r').readlines()
process_and_pid_re = re.compile(r'(.*) \(([\d ]*)\)')
for row_parts in csv.reader(lines[1:], delimiter = ',', quotechar = '"', skipinitialspace=True):
process, context_switches, cpu_usage = row_parts
match = process_and_pid_re.match(process)
if match:
_, pid = match.groups()
pid = int(pid)
cpu_usage_by_pid[pid] = float(cpu_usage.replace(',', ''))
context_switches_by_pid[pid] = int(context_switches)
else:
print('Expected output file not found.')
print('Expected to find: %s' % csv_filename)
print('Should have been produced by: %s' % command)
# Typical output of -a process -withcmdline looks like:
# MIN, 24656403, Process, 0XA1141C60, chrome.exe ( 748), 10760, 1, 0x11e8c260, "C:\...\chrome.exe" --type=renderer ...
# Find the PID and ParentPID
pidsRe = re.compile(r'.*\(([\d ]*)\), *(\d*),.*')
# Find the space-terminated word after 'type='. This used to require that it
# be the first command-line option, but that is likely to not always be true.
# Mark the first .* as lazy/ungreedy/reluctant so that if there are multiple
# --type options (such as with the V8 Proxy Resolver utility process) the
# first one will win.
processTypeRe = re.compile(r'.*? --type=([^ ]*) .*')
# Starting around M84 Chrome's utility processes have a --utility-sub-type
# parameter which identifies the type of utility process. Typical command
# lines look something like this:
# --type=utility --utility-sub-type=audio.mojom.AudioService --field-trial...
processSubTypeRe = re.compile(r'.*? --utility-sub-type=([^ ]*) .*')
#-a process = show process, thread, image information (see xperf -help processing)
#-withcmdline = show command line in process reports (see xperf -help process)
command = 'xperf -i "%s" -a process -withcmdline' % tracename
# Group all of the chrome.exe processes by browser Pid, then by type.
# pathByBrowserPid just maps from the browser Pid to the disk path to chrome.exe
pathByBrowserPid = {}
# pidsByParent is a dictionary that is indexed by the browser Pid. It contains
# a dictionary that is indexed by process type with each entry's payload
# being a list of Pids (for example, a list of renderer processes).
pidsByParent = {}
# Dictionary of Pids and their lines of data
lineByPid = {}
# Dictionary of Pids and their types.
types_by_pid = {}
# Dictionary of Pids and their sub-types (currently utility-processes only).
sub_types_by_pid = {}
try:
output = subprocess.check_output(command, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError:
# Try again. If it succeeds then there were lost events or a time inversion.
#-tle = tolerate lost events
#-tti = tolerate time inversions
command = 'xperf -i "%s" -tle -tti -a process -withcmdline' % tracename
output = subprocess.check_output(command, stderr=subprocess.STDOUT)
print('Trace had a time inversion or (most likely) lost events. Results may be anomalous.')
print()
# Extra processes to print information about, when cpu_usage is requested
extra_processes = []
for line in output.splitlines():
# Python 3 needs the line translated from bytes to str.
line = line.decode(encoding='utf-8', errors='ignore')
# Split the commandline from the .csv data and then extract the exePath.
# It may or may not be quoted, and may or not have the .exe suffix.
parts = line.split(', ')
if len(parts) > 8:
pids = pidsRe.match(line)
if not pids:
continue
pid = int(pids.groups()[0])
parentPid = int(pids.groups()[1])
processName = parts[4]
commandLine = parts[8]
# Deal with quoted and unquoted command lines.
if commandLine[0] == '"':
exePath = commandLine[1:commandLine.find('"', 1)]
else:
exePath = commandLine.split(' ')[0]
# The exepath may omit the ".exe" suffix so we need to look at processName
# instead. Split out the process name from the PID (this is imperfect but
# good enough for the processes we care about).
processName = processName.strip().split(' ')[0]
if show_cpu_usage:
# Look for the FrameServer service used in video conferencing. Full
# command-line seems to be -k Camera -s FrameServer but I don't know how
# stable/consistent that is. Also report on OS processes that Chrome
# frequently triggers:
if processName == 'svchost.exe' and commandLine.count('-s FrameServer') > 0:
processName = 'svchost (FrameServer)'
if processName in ['dwm.exe', 'audiodg.exe', 'System', 'MsMpEng.exe',
'software_reporter_tool.exe', 'svchost (FrameServer)']:
# Use get() because if the process has not run during this trace
# there will be no entries in the dictionary.
extra_processes.append((processName, pid, context_switches_by_pid.get(pid, 0), cpu_usage_by_pid.get(pid, 0)))
if processName == 'chrome.exe':
lineByPid[pid] = line
match = processTypeRe.match(commandLine)
if match:
process_type = match.groups()[0]
if commandLine.count(' --extension-process ') > 0:
# Extension processes have renderer type, but it is helpful to give
# them their own meta-type.
process_type = 'extension'
if process_type == 'crashpad-handler':
process_type = 'crashpad' # Shorten the tag for better formatting
browserPid = parentPid
else:
process_type = 'browser'
browserPid = pid
pathByBrowserPid[browserPid] = exePath
sub_type_match = processSubTypeRe.match(commandLine)
if sub_type_match:
sub_types_by_pid[pid] = sub_type_match.groups()[0].split('.')[-1]
types_by_pid[pid] = process_type
# Retrieve or create the list of processes associated with this
# browser (parent) pid.
pidsByType = pidsByParent.get(browserPid, {})
pidList = list(pidsByType.get(process_type, []))
pidList.append(pid)
pidsByType[process_type] = pidList
pidsByParent[browserPid] = pidsByType
if return_pid_map:
return types_by_pid
if extra_processes:
if tabbed_output:
print('Process name\tPID\tContext switches\tCPU Usage (ms)')
# Make sure the extra processes are printed in a consistent order.
extra_processes.sort(key=lambda process: process[0].lower())
for process in extra_processes:
processName, pid, context_switches, cpu_usage = process
if tabbed_output:
print('%s\t%d\t%d\t%.2f' % (processName, pid, context_switches, cpu_usage))
else:
print('%21s - %6d context switches, %8.2f ms CPU' % (processName,
context_switches, cpu_usage))
print()
# Scan a copy of the list of browser Pids looking for those with parents
# in the list and no children. These represent child processes whose --type=
# option was too far along in the command line for ETW's 512-character capture
# to get. See crbug.com/614502 for how this happened.
# This should probably be deleted at some point, along with the declaration and
# initialization of lineByPid.
for pid in list(pathByBrowserPid.keys())[:]:
# Checking that there is only one entry (itself) in the list is important
# to avoid problems caused by Pid reuse that could cause one browser process
# to appear to be another browser process' parent.
if len(pidsByParent[pid]) == 1: # The 'browser' appears in its own list
line = lineByPid[pid]
pids = pidsRe.match(line)
pid = int(pids.groups()[0])
parentPid = int(pids.groups()[1])
if pathByBrowserPid.has_key(parentPid):
browserPid = parentPid
# Retrieve the list of processes associated with this
# browser (parent) pid.
pidsByType = pidsByParent[browserPid]
process_type = 'gpu???'
pidList = list(pidsByType.get(process_type, []))
pidList.append(pid)
pidsByType[process_type] = pidList
pidsByParent[browserPid] = pidsByType
# Delete the references to the process that we now know isn't a browser
# process.
del pathByBrowserPid[pid]
del pidsByParent[pid]
# In many cases there are two crash-handler processes and one is the
# parent of the other. This seems to be related to --monitor-self.
# This script will initially report the parent crashpad process as being a
# browser process, leaving the child orphaned. This fixes that up by
# looking for singleton crashpad browsers and then finding their real
# crashpad parent. This will then cause two crashpad processes to be
# listed, which is correct.
for browserPid in list(pidsByParent.keys()):
childPids = pidsByParent[browserPid]
if len(childPids) == 1 and 'crashpad' in childPids:
# Scan the list of child processes to see if this processes parent can be
# found, as one of the crashpad processes.
for innerBrowserPid in list(pidsByParent.keys()):
innerChildPids = pidsByParent[innerBrowserPid]
if 'crashpad' in innerChildPids and browserPid in innerChildPids['crashpad']:
# Append the orphaned crashpad process to the right list, then
# delete it from the list of browser processes.
innerChildPids['crashpad'].append(childPids['crashpad'][0])
del pidsByParent[browserPid]
# It's important to break out of this loop now that we have
# deleted an entry, or else we might try to look it up.
break
if len(pidsByParent.keys()) > 0:
if not tabbed_output:
print('Chrome PIDs by process type:')
else:
print('No Chrome processes found.')
# Make sure the browsers are printed in a predictable order, sorted by Pid
browserPids = list(pidsByParent.keys())
browserPids.sort()
for browserPid in browserPids:
# The crashpad fixes above should avoid this situation, but I'm leaving the
# check to maintain robustness.
exePath = pathByBrowserPid.get(browserPid, 'Unknown parent')
# Any paths with no entries in them should be ignored.
pidsByType = pidsByParent[browserPid]
if len(pidsByType) == 0:
assert False
continue
keys = list(pidsByType.keys())
keys.sort()
total_processes = 0
total_context_switches = 0
total_cpu_usage = 0.0
for process_type in keys:
for pid in pidsByType[process_type]:
total_processes += 1
if show_cpu_usage and pid in cpu_usage_by_pid:
total_context_switches += context_switches_by_pid[pid]
total_cpu_usage += cpu_usage_by_pid[pid]
# Summarize all of the processes for this browser process.
if show_cpu_usage:
print('%s (%d) - %d context switches, %8.2f ms CPU, %d processes' % (
exePath, browserPid, total_context_switches, total_cpu_usage,
total_processes))
else:
print('%s (%d) - %d processes' % (exePath, browserPid, total_processes))
for process_type in keys:
if not tabbed_output:
print(' %-11s : ' % process_type, end='')
context_switches = 0
cpu_usage = 0.0
num_processes_of_type = 0
if show_cpu_usage:
for pid in pidsByType[process_type]:
if pid in cpu_usage_by_pid:
context_switches += context_switches_by_pid[pid]
cpu_usage += cpu_usage_by_pid[pid]
num_processes_of_type += 1
if num_processes_of_type > 1:
# Summarize by type when relevant.
if not tabbed_output:
print('total - %6d context switches, %8.2f ms CPU' % (context_switches, cpu_usage), end='')
list_by_type = pidsByType[process_type]
# Make sure the PIDs are printed in a consistent order.
list_by_type.sort()
for pid in list_by_type:
sub_type_text = ''
if pid in sub_types_by_pid:
sub_type_text = ' (%s)' % sub_types_by_pid[pid]
if show_cpu_usage:
if tabbed_output:
type = 'utility (%s)' % sub_types_by_pid[pid] if pid in sub_types_by_pid else process_type
print('%s\t%s\t%d\t%.2f' % (type, pid, context_switches_by_pid.get(pid, 0), cpu_usage_by_pid.get(pid, 0)))
else:
print('\n ', end='')
if pid in cpu_usage_by_pid:
# Print CPU usage details if they exist
print('%5d - %6d context switches, %8.2f ms CPU%s' % (pid, context_switches_by_pid[pid], cpu_usage_by_pid[pid], sub_type_text), end='')
else:
print('%5d%s' % (pid, sub_type_text), end='')
else:
print('%d%s ' % (pid, sub_type_text), end='')
if not tabbed_output:
print()
print()
官方文档:宣布推出 TraceProcessor 预览版 0.1.0 - Windows Developer Blog
发布:使用 Visual Studio 发布 .NET 控制台应用程序 - .NET | Microsoft Learn
使用控制器或者Windows Kit自带的WPRUI.exe(C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\WPRUI.exe)录制etl
Visual Studio创建C#工程
选择不使用顶级语句
右键项目属性,进入包管理界面
输入并 搜索包然后安装:Microsoft.Windows.EventTracing.Processing.All
Program.cs
Demo示意:遍历所有Etl中存在的进程列表
using Microsoft.Windows.EventTracing;
using Microsoft.Windows.EventTracing.Processes;
using System;
namespace TraceProcess
{
internal class Program
{
static void Main(string[] args)
{
if (args.Length != 1)
{
Console.Error.WriteLine("Usage: <trace.etl>");
return;
}
using (ITraceProcessor trace = TraceProcessor.Create(args[0]))
{
// TODO: calL trace.Use..
IPendingResult<IProcessDataSource> pendingProcessData = trace.UseProcesses();
trace.Process();
Console.WriteLine("TODO: Access data from the trace");
IProcessDataSource processData = pendingProcessData.Result;
foreach (IProcess process in processData.Processes)
Console.WriteLine(process.CommandLine);
}
}
}
}
右键项目属性,发布 + 打包
命令行启动TraceProcess.exe即可输出所有进程列表
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。