前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ETW - 事件消费者(Event Consumer)

ETW - 事件消费者(Event Consumer)

原创
作者头像
lealc
修改2024-07-15 21:55:50
1520
修改2024-07-15 21:55:50

官方介绍:Event Tracing for Windows (ETW) - Windows drivers | Microsoft Learn

官方示例:Eventdrv - Code Samples | Microsoft Learn

另一篇文章:ETW - 事件提供者

事件消费者(Event Consumer)

事件消费者(Event Consumer): 事件消费者是一个应用程序,它订阅事件提供者生成的事件,并对这些事件进行处理。事件消费者可以实时处理事件,也可以将事件保存到日志文件中以供离线分析。为了实现事件消费者,开发者需要完成以下任务:

  • 订阅事件:事件消费者需要订阅一个或多个事件提供者的事件。订阅过程通常包括指定事件提供者的 GUID、事件级别和关键字等信息。
  • 处理事件:事件消费者需要实现一个回调函数,用于处理收到的事件。回调函数可以实时处理事件,也可以将事件保存到日志文件中以供离线分析。
  • 取消订阅:在不再需要处理事件时,事件消费者需要取消订阅事件提供者的事件。

通过控制器可以启用事件跟踪,这时候针对生成的事件集合,我们有两种手段进行消费

离线消费:离线ETL文件+Python脚本

借助Windows Kits里面的工具,可以从etl文件中导出我们想要的任意数据到csv文件中,借助脚本语言进行进一步分析,这里可以使用python

1、录制etl

使用控制器或者Windows Kit自带的WPRUI.exe(C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\WPRUI.exe)录制etl

2、分析过程

使用wpaexporter.exe进行导出文件

3、使用示例

命令行:python IdentifyChromeProcesses.py "D:\系统\文档\etwtraces\*.etl" -c

代码语言:python
代码运行次数:0
复制
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
复制
<?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}&#xD;&#xA;{\f2 \li0\ri0\sa0\sb0\fi0\ql\par}&#xD;&#xA;{\f2 {\ltrch More on context switching }\li0\ri0\sa0\sb0\fi0\ql\par}&#xD;&#xA;{\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}&#xD;&#xA;}&#xD;&#xA;}">
            <Preset Name="Randomascii CPU Usage by Process" BarGraphIntervalCount="50" IsThreadActivityTable="false" GraphColumnCount="61" KeyColumnCount="13" LeftFrozenColumnCount="0" RightFrozenColumnCount="59" InitialFilterShouldKeep="true" InitialSelectionQuery="([Series Name]:=&quot;New Process&quot; AND NOT ([New Process]:=&quot;Idle (0)&quot;))" 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}&#xD;&#xA;}&#xD;&#xA;}">
              <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>
导出数据
代码语言:python
代码运行次数:0
复制
# -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()
分析数据
代码语言:python
代码运行次数:0
复制
# 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
生成文件
代码语言:python
代码运行次数:0
复制
  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()

离线消费:离线ETL文件+C#工程

官方文档:宣布推出 TraceProcessor 预览版 0.1.0 - Windows Developer Blog

发布:使用 Visual Studio 发布 .NET 控制台应用程序 - .NET | Microsoft Learn

1、录制etl

使用控制器或者Windows Kit自带的WPRUI.exe(C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\WPRUI.exe)录制etl

2、工程创建

Visual Studio创建C#工程

选择不使用顶级语句

右键项目属性,进入包管理界面

输入并 搜索包然后安装:Microsoft.Windows.EventTracing.Processing.All

Program.csDemo示意:遍历所有Etl中存在的进程列表

代码语言:C#
复制
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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 事件消费者(Event Consumer)
    • 离线消费:离线ETL文件+Python脚本
      • 1、录制etl
      • 2、分析过程
      • 3、使用示例
    • 离线消费:离线ETL文件+C#工程
      • 1、录制etl
      • 2、工程创建
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档