NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

performancemeasurements.py (15766B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\statscapture\performancemeasurements.py
      2 import json
      3 import os
      4 import shutil
      5 import tempfile
      6 import time
      7 import blue
      8 import evegraphics.settings as gfxsettings
      9 import trinity
     10 import uthread2
     11 from functools import reduce
     12 PERFMEASUREMENTS_URL = 'http://trilambda-ws:8088/'
     13 PERFMEASUREMENTS_UPLOAD_URL = PERFMEASUREMENTS_URL + 'measurement/'
     14 
     15 def _CaptureRenderSteps():
     16     steps = []
     17     for rjIndex, rj in enumerate(trinity.renderJobs.recurring):
     18         for stepIndex, step in enumerate(rj.steps):
     19             step.statName = 'Trinity/RenderJobs/Recurring/%s/%s' % (rj.name or 'RJ%s' % (stepIndex + 1), step.name or 'Step%s' % (stepIndex + 1))
     20             step.debugCaptureCpuTime = True
     21             step.debugCaptureGpuTime = True
     22             steps.append(step)
     23 
     24     return steps
     25 
     26 
     27 def _GetRenderTargetBitmap():
     28     rt = trinity.device.GetRenderContext().GetDefaultBackBuffer()
     29     if not rt.isReadable:
     30         readable = trinity.Tr2RenderTarget(rt.width, rt.height, 1, rt.format)
     31         rt.Resolve(readable)
     32         return trinity.Tr2HostBitmap(readable)
     33     else:
     34         return trinity.Tr2HostBitmap(rt)
     35 
     36 
     37 def _GetMemoryUsage():
     38     try:
     39         meg = 1.0 / 1024.0 / 1024.0
     40         return blue.pyos.cpuUsage[-1].virtualMemory * meg
     41     except:
     42         return 0
     43 
     44 
     45 class _FrameTimeInfo(object):
     46 
     47     def __init__(self, frameTimes):
     48         times = sorted(frameTimes)
     49         self.median = frameTimes[len(times) / 2]
     50         self.min = times[0]
     51         self.max = times[-1]
     52         summed = reduce(lambda x, y: x + y, times)
     53         self.avg = summed / len(times)
     54 
     55 
     56 class _MemoryInfo(object):
     57 
     58     def __init__(self, start, end):
     59         self.start = start
     60         self.end = end
     61 
     62 
     63 class _DrawPrimitivesInfo(object):
     64 
     65     def __init__(self, drawPrimitives):
     66         if drawPrimitives:
     67             self.min = int(min(drawPrimitives.values()))
     68             self.max = int(max(drawPrimitives.values()))
     69             self.avg = sum(drawPrimitives.values()) / len(drawPrimitives)
     70         else:
     71             self.min = 0
     72             self.max = 0
     73             self.avg = 0
     74 
     75 
     76 class _MeasurementInfo(object):
     77 
     78     def __init__(self, frameTimes, startSysMem, endSysMem, startGpuMem, endGpuMem, drawPrimitives):
     79         self.frameTime = _FrameTimeInfo(frameTimes)
     80         self.systemMemory = _MemoryInfo(startSysMem, endSysMem)
     81         self.gpuMemory = _MemoryInfo(startGpuMem, endGpuMem)
     82         self.drawPrimitives = _DrawPrimitivesInfo(drawPrimitives)
     83 
     84 
     85 class Measurement(object):
     86     STATE_NOT_STARTED = 0
     87     STATE_RUNNING = 1
     88     STATE_FINISHED = 2
     89 
     90     def __init__(self, captureRenderSteps = True, perFrameCapture = True, statFilter = ()):
     91         self._steps = []
     92         self._captureRenderSteps = captureRenderSteps
     93         self._perFrameCapture = perFrameCapture
     94         self._startTime = 0
     95         self._endTime = time.localtime()
     96         self._statistics = []
     97         self._state = self.STATE_NOT_STARTED
     98         self._frameTimes = {}
     99         self._drawPrimitives = {}
    100         self._screenshots = []
    101         self._startSysMem = 0
    102         self._endSysMem = 0
    103         self._startGpuMem = 0
    104         self._endGpuMem = 0
    105         self._statFilter = statFilter
    106 
    107     def _Ticker(self):
    108         self._startTime = blue.os.GetWallclockTime()
    109         t0 = self._startTime
    110         dp = blue.statistics.Find('Trinity/AL/sceneDrawcallCount')
    111         while self._state == self.STATE_RUNNING:
    112             uthread2.Yield()
    113             if self._state != self.STATE_RUNNING:
    114                 break
    115             t1 = blue.os.GetWallclockTime()
    116             ms = float(blue.os.TimeDiffInUs(t0, t1)) / 1000.0
    117             t0 = t1
    118             timeFromStartInMs = float(blue.os.TimeDiffInUs(self._startTime, t1)) / 1000.0
    119             self._frameTimes[timeFromStartInMs] = ms
    120             self._drawPrimitives[timeFromStartInMs] = int(dp.value) if dp else 0
    121 
    122     def GetState(self):
    123         return self._state
    124 
    125     def Begin(self):
    126         if self._state == self.STATE_RUNNING:
    127             raise RuntimeError('already started')
    128         if self._captureRenderSteps:
    129             self._steps = _CaptureRenderSteps()
    130             uthread2.Yield()
    131             uthread2.Yield()
    132         self._startTime = blue.os.GetWallclockTime()
    133         self._frameTimes.clear()
    134         self._drawPrimitives.clear()
    135         del self._screenshots[:]
    136         blue.statistics.BeginCapture()
    137         self._startSysMem = _GetMemoryUsage()
    138         self._startGpuMem = getattr(blue.statistics.Find('Trinity/AL/gpuMemoryEst/total'), 'value', 0) / 1024.0 / 1024.0
    139         self._state = self.STATE_RUNNING
    140         uthread2.StartTasklet(self._Ticker)
    141 
    142     def End(self):
    143         if self._state != self.STATE_RUNNING:
    144             raise RuntimeError('measurement is not running')
    145         try:
    146             self._endTime = time.localtime()
    147             self._endSysMem = _GetMemoryUsage()
    148             self._endGpuMem = getattr(blue.statistics.Find('Trinity/AL/gpuMemoryEst/total'), 'value', 0) / 1024.0 / 1024.0
    149             statistics = blue.statistics.EndCapture()
    150             types = {0: 0,
    151              1: 0,
    152              2: 1,
    153              3: 2}
    154             self._statistics = [ {'name': name,
    155              'type': types.get(getattr(blue.statistics.Find(name), 'type', 0), 0),
    156              'perFrame': values} for name, values in statistics.items() if not self._statFilter or name in self._statFilter ]
    157             for step in self._steps:
    158                 step.debugCaptureCpuTime = False
    159                 step.debugCaptureGpuTime = False
    160                 step.statName = ''
    161 
    162             del self._steps[:]
    163         finally:
    164             self._state = self.STATE_FINISHED
    165 
    166     def GetEndTime(self):
    167         if self._state != self.STATE_FINISHED:
    168             raise RuntimeError('measurement data is not available')
    169         return self._endTime
    170 
    171     def GetInfo(self):
    172         if self._state != self.STATE_FINISHED:
    173             raise RuntimeError('measurement data is not available')
    174         return _MeasurementInfo(self._frameTimes.values(), self._startSysMem, self._endSysMem, self._startGpuMem, self._endGpuMem, self._drawPrimitives)
    175 
    176     def GetFrameTimes(self):
    177         if self._state != self.STATE_FINISHED:
    178             raise RuntimeError('measurement data is not available')
    179         return self._frameTimes
    180 
    181     def TakeScreenshot(self):
    182         if self._state != self.STATE_RUNNING:
    183             raise RuntimeError('measurement is not running')
    184         screenshot = _GetRenderTargetBitmap()
    185         screenshotTime = float(blue.os.TimeDiffInUs(self._startTime, blue.os.GetWallclockTime())) / 1000.0
    186         self.AddScreenshot(screenshotTime, screenshot)
    187 
    188     def AddScreenshot(self, screenshotTime, screenshot):
    189         self._screenshots.append((screenshotTime, screenshot))
    190 
    191     def ExportCsv(self, path):
    192         if self._state != self.STATE_FINISHED:
    193             raise RuntimeError('measurement data is not available')
    194         if not os.path.exists(os.path.dirname(path)):
    195             os.makedirs(os.path.dirname(path))
    196         dataTable = [','.join(map(str, sorted(self._frameTimes.keys()))),
    197          ','.join([ str(self._frameTimes[k]) for k in sorted(self._frameTimes.keys()) ]),
    198          ','.join((str(self._drawPrimitives.get(k, '')) for k in sorted(self._frameTimes.keys()))),
    199          '%s' % self._endSysMem]
    200         data = '\n'.join(dataTable)
    201         with open(path, 'w') as f:
    202             f.write(data)
    203 
    204     def GetData(self, name, appData, benchmarkData = None):
    205         import socket
    206         aaLabels = {gfxsettings.AA_QUALITY_DISABLED: 'Disabled',
    207          gfxsettings.AA_QUALITY_TAA_LOW: 'Low',
    208          gfxsettings.AA_QUALITY_TAA_MEDIUM: 'Medium',
    209          gfxsettings.AA_QUALITY_TAA_HIGH: 'High'}
    210         if blue.sysinfo.isWine:
    211             osLabel = blue.sysinfo.wineHostOs
    212         elif blue.sysinfo.os.platform == blue.OsPlatform.OSX:
    213             osLabel = 'macOS %s.%s.%s' % (blue.sysinfo.os.majorVersion, blue.sysinfo.os.minorVersion, blue.sysinfo.os.buildNumber)
    214         else:
    215             osLabel = 'Windows %s.%s' % (blue.sysinfo.os.majorVersion, blue.sysinfo.os.minorVersion)
    216 
    217         def yesNo(value):
    218             if value:
    219                 return 'Yes'
    220             return 'No'
    221 
    222         arch = blue.sysinfo.cpu.architecture
    223         if blue.sysinfo.isRosetta:
    224             arch = 'x64 (Rosetta)'
    225         data = {'machine': {'name': socket.gethostname(),
    226                      'cpu': blue.sysinfo.cpu.brand,
    227                      'memory': blue.sysinfo.GetMemory().totalPhysical,
    228                      'gpu': trinity.adapters.GetAdapterInfo(trinity.device.adapter).description,
    229                      'os': osLabel},
    230          'app': {'trinityPlatform': trinity.platform,
    231                  'architecture': arch,
    232                  'settings': {'FullScreen': trinity.mainWindow.GetWindowState().windowMode == trinity.Tr2WindowMode.FULL_SCREEN,
    233                               'Size': '%s x %s' % (trinity.device.width, trinity.device.height),
    234                               'Present Interval': trinity.PRESENT_INTERVAL.GetNameFromValue(trinity.device.presentationInterval),
    235                               'Anti-Aliasing': aaLabels.get(gfxsettings.Get(gfxsettings.GFX_ANTI_ALIASING), '?'),
    236                               'Post Processing': ['Low', 'Medium', 'High'][gfxsettings.Get(gfxsettings.GFX_POST_PROCESSING_QUALITY)],
    237                               'Shader Quality': ['',
    238                                                  'Low',
    239                                                  'Medium',
    240                                                  'High'][gfxsettings.Get(gfxsettings.GFX_SHADER_QUALITY)],
    241                               'Texture Quality': ['High', 'Medium', 'Low'][gfxsettings.Get(gfxsettings.GFX_TEXTURE_QUALITY)],
    242                               'LOD Quality': ['',
    243                                               'Low',
    244                                               'Medium',
    245                                               'High'][gfxsettings.Get(gfxsettings.GFX_LOD_QUALITY)],
    246                               'Shadow Quality': ['Disabled',
    247                                                  'Low',
    248                                                  'High',
    249                                                  'Raytraced'][gfxsettings.Get(gfxsettings.GFX_SHADOW_QUALITY)],
    250                               'Reflection Quality': ['Disabled',
    251                                                      'Low',
    252                                                      'Medium',
    253                                                      'High',
    254                                                      'Highest'][gfxsettings.Get(gfxsettings.GFX_REFLECTION_QUALITY)],
    255                               'AO Quality': ['Disabled',
    256                                              'Low',
    257                                              'Medium',
    258                                              'High'][gfxsettings.Get(gfxsettings.GFX_AO_QUALITY)],
    259                               'Volumetric Quality': ['Low',
    260                                                      'Medium',
    261                                                      'High',
    262                                                      'Ultra'][gfxsettings.Get(gfxsettings.GFX_VOLUMETRIC_QUALITY)],
    263                               'Upscaling Technique': trinity.UPSCALING_TECHNIQUE.GetNameFromValue(trinity.device.upscalingTechnique).lower(),
    264                               'Upscaling Setting': trinity.UPSCALING_SETTING.GetNameFromValue(trinity.device.upscalingSetting).lower(),
    265                               'Framegeneration': yesNo(gfxsettings.Get(gfxsettings.GFX_FRAMEGENERATION_ENABLED)),
    266                               'Turrets Enabled': yesNo(gfxsettings.Get(gfxsettings.UI_TURRETS_ENABLED)),
    267                               'Effects': yesNo(gfxsettings.Get(gfxsettings.UI_EFFECTS_ENABLED)),
    268                               'Missile Effects': yesNo(gfxsettings.Get(gfxsettings.UI_MISSILES_ENABLED)),
    269                               'Ship Explosions': yesNo(gfxsettings.Get(gfxsettings.UI_EXPLOSION_EFFECTS_ENABLED)),
    270                               'Drone Models': yesNo(gfxsettings.Get(gfxsettings.UI_DRONE_MODELS_ENABLED)),
    271                               'Trails': yesNo(gfxsettings.Get(gfxsettings.UI_TRAILS_ENABLED)),
    272                               'GPU Particles': yesNo(gfxsettings.Get(gfxsettings.UI_GPU_PARTICLES_ENABLED)),
    273                               'Asteroid Environments': yesNo(gfxsettings.Get(gfxsettings.UI_ASTEROID_ATMOSPHERICS)),
    274                               'Inspace Skinning Effect': yesNo(gfxsettings.Get(gfxsettings.UI_MODELSKINSINSPACE_ENABLED)),
    275                               'DOF Enabled': yesNo(gfxsettings.Get(gfxsettings.GFX_DOF_POSTPROCESS_ENABLED)),
    276                               'GDPR Enabled': yesNo(trinity.settings.GetValue('gdrEnabled'))}},
    277          'benchmark': {'name': name,
    278                        'timestamp': time.mktime(self._endTime) * 1000},
    279          'frameTimes': sorted(self._frameTimes.keys()),
    280          'stats': self._statistics}
    281         data['app'].update(appData)
    282         if benchmarkData:
    283             data['benchmark'].update(benchmarkData)
    284         try:
    285             driverInfo = trinity.adapters.GetAdapterInfo(trinity.device.adapter).GetDriverInfo()
    286             data['machine']['gpu driver'] = '%s %s' % (driverInfo.driverVersionString, driverInfo.driverDate)
    287         except trinity.ALError:
    288             pass
    289 
    290         return data
    291 
    292     def Export(self, name, path, appData, benchmarkData = None):
    293         if self._state != self.STATE_FINISHED:
    294             raise RuntimeError('measurement data is not available')
    295         if not os.path.exists(os.path.dirname(path)):
    296             os.makedirs(os.path.dirname(path))
    297         exportName = os.path.splitext(os.path.basename(path))[0]
    298         exportFolderPath = os.path.dirname(path)
    299         data = self.GetData(name, appData, benchmarkData)
    300         if self._screenshots:
    301             data['screenshots'] = [ {'time': s[0] / 1000,
    302              'href': '%s-%s.png' % (exportName, i)} for i, s in enumerate(self._screenshots) ]
    303         outPaths = []
    304         with open(path, 'w') as f:
    305             json.dump(data, f, indent=2)
    306             outPaths.append(path)
    307         for i, each in enumerate(self._screenshots):
    308             screenPath = os.path.abspath(os.path.join(exportFolderPath, '%s-%s.png' % (exportName, i)))
    309             each[1].Save(screenPath)
    310             outPaths.append(screenPath)
    311 
    312         return outPaths
    313 
    314     def Upload(self, name, appData, benchmarkData = None, tags = '', saveDir = ''):
    315         import requests
    316         if saveDir:
    317             try:
    318                 os.makedirs(saveDir)
    319             except OSError:
    320                 pass
    321 
    322         tempDir = saveDir if saveDir else tempfile.mkdtemp()
    323         try:
    324             paths = self.Export(name, os.path.join(tempDir, 'measurement.json'), appData, benchmarkData)
    325             files = [ (os.path.basename(x), open(x, 'rb')) for x in paths ]
    326             try:
    327                 ret = requests.request('POST', PERFMEASUREMENTS_UPLOAD_URL, data={'tags': tags}, files={str(i):v for i, v in enumerate(files)}).json()
    328                 result = bool(ret['success'])
    329             finally:
    330                 for each in files:
    331                     each[1].close()
    332 
    333             if not result:
    334                 raise RuntimeError('failed to upload')
    335         finally:
    336             if not saveDir:
    337                 shutil.rmtree(tempDir)
    338 
    339     def GetAggregatedStats(self, statsFilter):
    340         aggregatedStats = {}
    341         for statistic in self._statistics:
    342             if statistic['name'] in statsFilter and len(statistic['perFrame']):
    343                 mean = sum(statistic['perFrame']) / len(statistic['perFrame'])
    344                 aggregatedStats[statistic['name']] = mean
    345 
    346         return aggregatedStats