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

clientStatsSvc.py (40836B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\sys\clientStatsSvc.py
      2 import cPickle
      3 import datetime
      4 import os
      5 import sys
      6 import time
      7 import yaml
      8 import zlib
      9 import blue
     10 import evegraphics.settings as gfxsettings
     11 import log
     12 import telemetry
     13 import trinity
     14 import uthread
     15 import uthread2
     16 from carbon.common.script.sys.service import Service
     17 from carbon.common.script.sys.serviceConst import SERVICE_RUNNING
     18 from carbon.common.script.util.format import FmtDateEng
     19 from eve.common.script.util.clientStatsCommon import *
     20 from eveprefs import boot
     21 import monolithsentry
     22 import eveProto.generated.eve_public.app.platform_pb2 as pdm_proto
     23 import eveProto.generated.eve_public.app.eveonline.login.login_pb2 as login_proto
     24 import eveProto.generated.eve_public.app.client.graphics.graphics_pb2 as graphics_proto
     25 import sysinfo
     26 BLUE_STATS_NAMES = {'Trinity/FrameTimeAbove100ms': (int, 'frameTimeAbove100ms', False),
     27  'Trinity/FrameTimeAbove200ms': (int, 'frameTimeAbove200ms', False),
     28  'Trinity/FrameTimeAbove300ms': (int, 'frameTimeAbove300ms', False),
     29  'Trinity/FrameTimeAbove400ms': (int, 'frameTimeAbove400ms', False),
     30  'Trinity/FrameTimeAbove500ms': (int, 'frameTimeAbove500ms', False),
     31  'Trinity/FrameTime/ActiveMean': (float, 'frameTimeMean', False),
     32  'Trinity/FrameTime/ActiveStdDev': (float, 'frameTimeStdDev', False),
     33  'Blue/Memory/Python': (long, 'memoryPython', True),
     34  'Blue/Memory/Malloc': (long, 'memoryMalloc', True),
     35  'Blue/Memory/WorkingSet': (long, 'memoryWorkingSet', True),
     36  'Blue/Memory/PageFileUsage': (long, 'memoryPageFileUsage', True),
     37  'Blue/resMan/LoadObject': (float, 'loadObject', False),
     38  'Blue/resMan/LoadObjectCalls': (int, 'loadObjectCalls', False),
     39  'Blue/resMan/LoadObjectCacheHit': (int, 'loadObjectCacheHits', False),
     40  'Blue/resMan/LoadObjectShared': (int, 'loadObjectShared', False),
     41  'Blue/resMan/GetResourceCalls': (int, 'getResourceCalls', False),
     42  'Blue/resMan/GetResourceCacheHit': (int, 'getResourceCacheHits', False),
     43  'Blue/resMan/GetResourceShared': (int, 'getResourceShared', False),
     44  'Blue/logInfo': (int, 'logInfo', False),
     45  'Blue/logNotice': (int, 'logNotice', False),
     46  'Blue/logWarn': (int, 'logWarn', False),
     47  'Blue/logErr': (int, 'logErr', False),
     48  'Blue/BlueRemoteStream/BytesDownloaded': (int, 'bytesDownloaded', False),
     49  'Blue/BlueRemoteStream/PretransferTime': (float, 'pretransferTime', False),
     50  'Blue/BlueRemoteStream/DownloadTime': (float, 'downloadTime', False),
     51  'Blue/RemoteFileCache/FailedPrimary': (int, 'failedDownloadsPrimary', False),
     52  'Blue/RemoteFileCache/FailedBackup': (int, 'failedDownloadsSecondary', False),
     53  'Blue/RemoteFileCache/CorruptDownloads': (int, 'corruptDownloads', False),
     54  'Blue/RemoteFileCache/CorruptFiles': (int, 'corruptFiles', False),
     55  'Blue/TimesliceWarnings': (int, 'timesliceWarnings', False)}
     56 _WIN_SHARING_VIOLATION = 32
     57 
     58 def _retry_on_sharing_violation(op, attempts = 10, initial_delay = 0.05, max_delay = 0.5):
     59     delay = initial_delay
     60     for attempt in range(attempts):
     61         try:
     62             op()
     63             return
     64         except OSError as e:
     65             if getattr(e, 'winerror', None) == _WIN_SHARING_VIOLATION and attempt < attempts - 1:
     66                 uthread2.sleep(delay)
     67                 delay = min(delay * 2, max_delay)
     68                 continue
     69             raise
     70 
     71 
     72 def _retry_os_remove(path):
     73     _retry_on_sharing_violation(lambda : os.remove(path))
     74 
     75 
     76 def _retry_os_rename(src, dst):
     77     _retry_on_sharing_violation(lambda : os.rename(src, dst))
     78 
     79 
     80 class ClientStatsSvc(Service):
     81     __guid__ = 'svc.clientStatsSvc'
     82     __notifyevents__ = ['OnClientReady',
     83      'OnDisconnect',
     84      'OnProcessLoginProgress',
     85      'ProcessShutdown',
     86      'DoSessionChanging',
     87      'OnViewStateChanged']
     88     __displayname__ = 'Client Statistics Service'
     89     __dependencies__ = ['machoNet']
     90 
     91     def __init__(self):
     92         Service.__init__(self)
     93         self.entries = {}
     94         self.currentState = STATE_STARTUP
     95         self.version = 2
     96         self.stateMask = 0
     97         self.lastStageSampleTime = time.clock()
     98         self.hasEnteredGame = 0
     99         self.hasProcessedExit = False
    100         self.statsEntries = {}
    101         self.clientStatsMaxBatch = 5
    102         self.fileStarted = False
    103         self.filename = os.path.join(blue.paths.ResolvePathForWriting(u'cache:/clientStats.dat'))
    104         if os.path.exists(self.filename):
    105             setattr(self, 'prevContents', self.ReadFile(self.filename))
    106         else:
    107             self.SampleStats(STATE_UNINITIALIZEDSTART)
    108 
    109     def Run(self, memStream = None):
    110         self.sessionFilePath = blue.paths.ResolvePathForWriting('cache:/%d_%d.session' % (blue.os.pid, session.sid))
    111         try:
    112             self.LogInfo('Creating session file at', self.sessionFilePath)
    113             self.sessionFile = open(self.sessionFilePath, 'w')
    114         except OSError:
    115             log.LogException()
    116             sys.exc_clear()
    117 
    118         try:
    119             self.osPlatform = PLATFORM_WINDOWS
    120             if blue.sysinfo.isWine:
    121                 if blue.sysinfo.wineHostOs.startswith('Darwin'):
    122                     self.osPlatform = PLATFORM_MACOS_WINE
    123                 else:
    124                     self.osPlatform = PLATFORM_LINUX_WINE
    125         except Exception:
    126             log.LogException()
    127             sys.exc_clear()
    128 
    129         self.statsWaitingToBeSent = []
    130         self.lastClientState = 'None'
    131         self.otherClients = 0
    132         for key, value in BLUE_STATS_NAMES.iteritems():
    133             name = value[1]
    134             self.statsEntries[name] = blue.statistics.Find(key)
    135 
    136         self.frameTimeStat = blue.statistics.Find('Trinity/FrameTime')
    137         self.browserRequestsStat = None
    138         self.adapterInfo = trinity.adapters.GetAdapterInfo(trinity.adapters.DEFAULT_ADAPTER)
    139         self.otherRunningClients = 0
    140         self.crashedClients = 0
    141         self.state = SERVICE_RUNNING
    142         self.SampleStats(STATE_STARTUP)
    143         self.timeWhenStateEntered = blue.os.GetWallclockTime()
    144         blue.statistics.ResetPeaks()
    145         blue.statistics.ResetDerived()
    146 
    147     def Stop(self, ms):
    148         self.LogInfo('ClientStatsSvc::Stop - Sampling')
    149         self.OnProcessExit()
    150         self.LogInfo('ClientStatsSvc::Stop - DONE')
    151         Service.Stop(self)
    152 
    153     def ReadFile(self, filename):
    154         try:
    155             filein = file(filename, 'r')
    156             datain = cPickle.load(filein)
    157             return datain
    158         except Exception as e:
    159             log.LogException('Error reading file')
    160             sys.exc_clear()
    161         finally:
    162             filein.close()
    163 
    164     @telemetry.ZONE_METHOD
    165     def SendContentsToServer(self, contents = None):
    166         try:
    167             if not sm.services['machoNet'].IsConnected():
    168                 return
    169         except:
    170             sys.exc_clear()
    171             return
    172 
    173         if contents is None:
    174             contents = self.prevContents
    175         if contents is None or contents[0] != self.version:
    176             contents = {}
    177         else:
    178             contents = contents[1]
    179         build = boot.GetValue('build', None)
    180         contentType = CONTENT_TYPE_PREMIUM
    181         operatingSystem = PLATFORM_WINDOWS
    182         blendedContents = self.entries
    183         blendedStateMask = self.stateMask
    184         self.entries = dict()
    185         self.stateMask = 0
    186         if STATE_DISCONNECT in contents:
    187             blendedContents[STATE_DISCONNECT] = contents[STATE_DISCONNECT]
    188             blendedStateMask += STATE_DISCONNECT
    189         if STATE_GAMESHUTDOWN in contents:
    190             blendedContents[STATE_GAMESHUTDOWN] = contents[STATE_GAMESHUTDOWN]
    191             blendedStateMask += STATE_GAMESHUTDOWN
    192         header = (self.version,
    193          blendedStateMask,
    194          build,
    195          operatingSystem,
    196          contentType)
    197         data = (header, blendedContents)
    198         try:
    199             uthread.Lock(self, 'sendContents')
    200             if hasattr(self, 'prevContents'):
    201                 delattr(self, 'prevContents')
    202             return True
    203         finally:
    204             uthread.UnLock(self, 'sendContents')
    205 
    206     def Persist(self):
    207         outfile = file(self.filename, 'wb')
    208         data = (self.version, self.entries)
    209         cPickle.dump(data, outfile)
    210         outfile.close()
    211         self.fileStarted = True
    212 
    213     @telemetry.ZONE_METHOD
    214     def SampleStats(self, state):
    215         self.currentState = state
    216         try:
    217             uthread.Lock(self, 'sampleStats')
    218             if state in self.entries:
    219                 stats = self.entries[state]
    220             else:
    221                 stats = {}
    222             lastStageSampleTime = self.lastStageSampleTime
    223             self.lastStageSampleTime = time.clock()
    224             stats[STAT_TIME_SINCE_LAST_STATE] = int((self.lastStageSampleTime - lastStageSampleTime) * 1000)
    225             if state < STATE_GAMEEXITING:
    226                 stats[STAT_MACHONET_AVG_PINGTIME] = self.GetMachoPingTime()
    227             if len(blue.pyos.cpuUsage) > 0:
    228                 memdata = blue.pyos.cpuUsage[-1]
    229                 stats[STAT_PYTHONMEMORY] = memdata.virtualMemory
    230             else:
    231                 stats[STAT_PYTHONMEMORY] = 0
    232             cpuProcessTime = blue.sysinfo.GetProcessTimes()
    233             cpuProcessTime = cpuProcessTime.userTime + cpuProcessTime.systemTime
    234             stats[STAT_CPU] = int(cpuProcessTime * 10000000.0)
    235             self.entries[state] = stats
    236             self.stateMask = self.stateMask + state
    237             if not hasattr(self, 'prevContents'):
    238                 self.Persist()
    239             blue.SetCrashKeyValues('ClientStatsState', SHORT_STATE_STRINGS.get(state, 'Unknown'))
    240         except Exception as e:
    241             log.LogException('Error while sampling clientStats')
    242             sys.exc_clear()
    243         finally:
    244             uthread.UnLock(self, 'sampleStats')
    245 
    246     def GetMachoPingTime(self):
    247         if sm.services['machoNet'] is not None and sm.services['machoNet'].IsConnected():
    248             numSamples = 5
    249             totalTime = 0
    250             for i in range(numSamples):
    251                 stat = sm.services['machoNet'].Ping(1, silent=True)
    252                 startTime = stat[0][1]
    253                 endTime = stat[-1][1]
    254                 took = endTime - startTime
    255                 totalTime += took
    256                 blue.pyos.BeNice()
    257 
    258             return totalTime / numSamples
    259         else:
    260             return -1
    261 
    262     @telemetry.ZONE_METHOD
    263     def OnProcessExit(self):
    264         if not self.hasProcessedExit:
    265             self.hasProcessedExit = True
    266             self.SampleStats(STATE_GAMESHUTDOWN)
    267             try:
    268                 self.sessionFile.write('- shutdown\n')
    269                 self.sessionFile.flush()
    270                 finalStats = self.CaptureStats()
    271                 finalStats['shutdown'] = 1
    272                 finalPackage = self.PrepareStatsPackage(self.lastClientState, finalStats)
    273                 statsSent = False
    274                 try:
    275                     if session.charid and sm.GetService('machoNet').IsConnected():
    276                         self.LogInfo('Sending final stats')
    277                         sm.ProxySvc('eventLog').LogClientStats(session.sid, session.locationid, [finalPackage], isShutdown=True)
    278                         statsSent = True
    279                     if sm.GetService('machoNet').IsConnected():
    280                         sm.ProxySvc('eventLog').LogPlayerRequestedDisconnect()
    281                 except:
    282                     log.LogException()
    283                     sys.exc_clear()
    284 
    285                 if statsSent:
    286                     self.LogInfo('Removing session file', self.sessionFilePath)
    287                     self.sessionFile.close()
    288                     if os.path.exists(self.sessionFilePath):
    289                         try:
    290                             _retry_os_remove(self.sessionFilePath)
    291                         except OSError:
    292                             self.LogWarn('Failed to remove session file', self.sessionFilePath)
    293                             sys.exc_clear()
    294 
    295                 else:
    296                     self.LogInfo('Storing client stats in session file')
    297                     contents = yaml.safe_dump(finalPackage)
    298                     checksum = zlib.crc32(contents)
    299                     try:
    300                         self.sessionFile.write(contents)
    301                         self.sessionFile.flush()
    302                         os.fsync(self.sessionFile.fileno())
    303                     finally:
    304                         self.sessionFile.close()
    305                         self.sessionFile = None
    306 
    307                     closedSessionFilePath = blue.paths.ResolvePathForWriting('cache:/closed%d_%x.session' % (blue.os.pid, checksum))
    308                     try:
    309                         _retry_os_rename(self.sessionFilePath, closedSessionFilePath)
    310                         self.LogInfo('Session file renamed to', closedSessionFilePath)
    311                     except OSError:
    312                         self.LogWarn('Failed to rename session file', self.sessionFilePath)
    313                         sys.exc_clear()
    314                         try:
    315                             with open(self.sessionFilePath, 'w') as f:
    316                                 f.write('- shutdown\n')
    317                         except (OSError, IOError):
    318                             self.LogWarn('Failed to mark session file as shutdown', self.sessionFilePath)
    319                             sys.exc_clear()
    320 
    321             except:
    322                 log.LogException()
    323                 sys.exc_clear()
    324 
    325     @telemetry.ZONE_METHOD
    326     def OnClientReady(self, *args):
    327         clientState = args[0]
    328         if clientState == 'login':
    329             self.SampleStats(STATE_LOGINWINDOW)
    330             self.lastClientState = clientState
    331         elif clientState == 'charsel':
    332             uthread.new(self.GatherStats, self.lastClientState, session.locationid)
    333             self.SampleStats(STATE_CHARSELECTION)
    334             self.lastClientState = clientState
    335         elif clientState in ('inflight', 'station', 'hangar'):
    336             if not self.hasEnteredGame:
    337                 uthread.new(self.GatherStats, self.lastClientState, session.locationid)
    338                 self.SampleStats(STATE_GAMEENTERED)
    339                 self.hasEnteredGame = 1
    340                 if hasattr(self, 'prevContents'):
    341                     uthread.new(self.SendContentsToServer)
    342             if clientState in ('station', 'hangar'):
    343                 if self.lastClientState in ('station', 'hangar') and clientState != self.lastClientState:
    344                     uthread.new(self.GatherStats, self.lastClientState, session.locationid)
    345                 elif self.lastClientState == 'charCustomization':
    346                     uthread.new(self.GatherStats, self.lastClientState, session.locationid)
    347             self.lastClientState = clientState
    348 
    349     def OnViewStateChanged(self, oldState, newState):
    350         if newState == 'charactercreation':
    351             uthread.new(self.GatherStats, self.lastClientState, session.locationid)
    352             if oldState in ('station', 'hangar'):
    353                 self.lastClientState = 'charCustomization'
    354             else:
    355                 self.lastClientState = newState
    356 
    357     def OnLoginStarted(self):
    358         self.SampleStats(STATE_LOGINSTARTED)
    359 
    360     def OnDisconnect(self, reason = 0, msg = ''):
    361         self.SampleStats(STATE_DISCONNECT)
    362 
    363     def getAoQualityToProto(self):
    364         aoQuality = gfxsettings.Get(gfxsettings.GFX_AO_QUALITY)
    365         if aoQuality == gfxsettings.GFX_AO_QUALITY_HIGH:
    366             return graphics_proto.Settings.AO_QUALITY_HIGH
    367         elif aoQuality == gfxsettings.GFX_AO_QUALITY_MEDIUM:
    368             return graphics_proto.Settings.AO_QUALITY_MEDIUM
    369         elif aoQuality == gfxsettings.GFX_AO_QUALITY_LOW:
    370             return graphics_proto.Settings.AO_QUALITY_LOW
    371         elif aoQuality == gfxsettings.GFX_AO_QUALITY_OFF:
    372             return graphics_proto.Settings.AO_QUALITY_DISABLED
    373         else:
    374             return graphics_proto.Settings.AO_QUALITY_UNSPECIFIED
    375 
    376     def getReflectionQualityToProto(self):
    377         reflectionQuality = gfxsettings.Get(gfxsettings.GFX_REFLECTION_QUALITY)
    378         if reflectionQuality == gfxsettings.GFX_REFLECTION_QUALITY_ULTRA:
    379             return graphics_proto.Settings.REFLECTIONS_ULTRA
    380         elif reflectionQuality == gfxsettings.GFX_REFLECTION_QUALITY_HIGH:
    381             return graphics_proto.Settings.REFLECTIONS_HIGH
    382         elif reflectionQuality == gfxsettings.GFX_REFLECTION_QUALITY_MEDIUM:
    383             return graphics_proto.Settings.REFLECTIONS_MEDIUM
    384         elif reflectionQuality == gfxsettings.GFX_REFLECTION_QUALITY_LOW:
    385             return graphics_proto.Settings.REFLECTIONS_LOW
    386         else:
    387             return graphics_proto.Settings.REFLECTIONS_UNSPECIFIED
    388 
    389     def getLODQualityToProto(self):
    390         lodQuality = gfxsettings.Get(gfxsettings.GFX_LOD_QUALITY)
    391         if lodQuality == gfxsettings.GFX_LOD_QUALITY_HIGH:
    392             return graphics_proto.Settings.LOD_QUALITY_HIGH
    393         elif lodQuality == gfxsettings.GFX_LOD_QUALITY_MEDIUM:
    394             return graphics_proto.Settings.LOD_QUALITY_MEDIUM
    395         elif lodQuality == gfxsettings.GFX_LOD_QUALITY_LOW:
    396             return graphics_proto.Settings.LOD_QUALITY_LOW
    397         else:
    398             return graphics_proto.Settings.LOD_QUALITY_UNSPECIFIED
    399 
    400     def getDisplayModeToProto(self):
    401         displayMode = trinity.mainWindow.GetWindowState().windowMode
    402         if displayMode == trinity.Tr2WindowMode.FULL_SCREEN:
    403             return graphics_proto.Settings.DISPLAY_FULLSCREEN
    404         elif displayMode == trinity.Tr2WindowMode.WINDOWED:
    405             return graphics_proto.Settings.DISPLAY_WINDOW_MODE
    406         elif displayMode == trinity.Tr2WindowMode.FIXED_WINDOW:
    407             return graphics_proto.Settings.DISPLAY_FIXED_WINDOW
    408         else:
    409             return graphics_proto.Settings.DISPLAY_UNSPECIFIED
    410 
    411     def getAntiAliasingToProto(self):
    412         antiAliasing = gfxsettings.Get(gfxsettings.GFX_ANTI_ALIASING)
    413         if antiAliasing == gfxsettings.AA_QUALITY_TAA_HIGH:
    414             return graphics_proto.Settings.ANTI_ALIASING_HIGH
    415         elif antiAliasing == gfxsettings.AA_QUALITY_TAA_MEDIUM:
    416             return graphics_proto.Settings.ANTI_ALIASING_MEDIUM
    417         elif antiAliasing == gfxsettings.AA_QUALITY_TAA_LOW:
    418             return graphics_proto.Settings.ANTI_ALIASING_LOW
    419         elif antiAliasing == gfxsettings.AA_QUALITY_DISABLED:
    420             return graphics_proto.Settings.ANTI_ALIASING_DISABLED
    421         else:
    422             return graphics_proto.Settings.ANTI_ALIASING_UNSPECIFIED
    423 
    424     def getShadowQualityToProto(self):
    425         shadowQuality = gfxsettings.Get(gfxsettings.GFX_SHADOW_QUALITY)
    426         if shadowQuality == 2:
    427             return graphics_proto.Settings.SHADOW_QUALITY_HIGH
    428         elif shadowQuality == 1:
    429             return graphics_proto.Settings.SHADOW_QUALITY_LOW
    430         elif shadowQuality == 0:
    431             return graphics_proto.Settings.SHADOW_QUALITY_DISABLED
    432         else:
    433             return graphics_proto.Settings.SHADOW_QUALITY_UNSPECIFIED
    434 
    435     def getTextureQualityToProto(self):
    436         textureQuality = gfxsettings.Get(gfxsettings.GFX_TEXTURE_QUALITY)
    437         if textureQuality == 0:
    438             return graphics_proto.Settings.TEXTURE_QUALITY_HIGH
    439         elif textureQuality == 1:
    440             return graphics_proto.Settings.TEXTURE_QUALITY_MEDIUM
    441         elif textureQuality == 2:
    442             return graphics_proto.Settings.TEXTURE_QUALITY_LOW
    443         else:
    444             return graphics_proto.Settings.TEXTURE_QUALITY_UNSPECIFIED
    445 
    446     def getFsrModeToProto(self):
    447         fsrMode = gfxsettings.Get(gfxsettings.GFX_FSR_MODE)
    448         if fsrMode == 4:
    449             return graphics_proto.Settings.AMD_FSR_PERFORMANCE
    450         elif fsrMode == 3:
    451             return graphics_proto.Settings.AMD_FSR_BALANCED
    452         elif fsrMode == 2:
    453             return graphics_proto.Settings.AMD_FSR_QUALITY
    454         elif fsrMode == 1:
    455             return graphics_proto.Settings.AMD_FSR_ULTRA_QUALITY
    456         elif fsrMode == 0:
    457             return graphics_proto.Settings.AMD_FSR_OFF
    458         else:
    459             return graphics_proto.Settings.AMD_FSR_UNSPECIFIED
    460 
    461     def getPostProcessingToProto(self):
    462         postProcessing = gfxsettings.Get(gfxsettings.GFX_POST_PROCESSING_QUALITY)
    463         if postProcessing == 2:
    464             return graphics_proto.Settings.POST_PROCESSING_HIGH
    465         elif postProcessing == 1:
    466             return graphics_proto.Settings.POST_PROCESSING_MEDIUM
    467         elif postProcessing == 0:
    468             return graphics_proto.Settings.POST_PROCESSING_LOW
    469         else:
    470             return graphics_proto.Settings.POST_PROCESSING_UNSPECIFIED
    471 
    472     def getShaderQualityToProto(self):
    473         shaderQuality = gfxsettings.Get(gfxsettings.GFX_SHADER_QUALITY)
    474         if shaderQuality == gfxsettings.SHADER_MODEL_HIGH:
    475             return graphics_proto.Settings.SHADER_QUALITY_HIGH
    476         elif shaderQuality == gfxsettings.SHADER_MODEL_MEDIUM:
    477             return graphics_proto.Settings.SHADER_QUALITY_MEDIUM
    478         elif shaderQuality == gfxsettings.SHADER_MODEL_LOW:
    479             return graphics_proto.Settings.SHADER_QUALITY_LOW
    480         else:
    481             return graphics_proto.Settings.SHADER_QUALITY_UNSPECIFIED
    482 
    483     def getUIScalingToProto(self):
    484         deviceSvc = sm.GetService('device')
    485         windowed = trinity.mainWindow.GetWindowState().windowMode != trinity.Tr2WindowMode.FULL_SCREEN
    486         return int(deviceSvc.GetUIScaleValue(windowed) * 100)
    487 
    488     def getUpscalingTechniqueToProto(self):
    489         upscalingTechnique = gfxsettings.Get(gfxsettings.GFX_UPSCALING_TECHNIQUE)
    490         if upscalingTechnique == gfxsettings.GFX_UPSCALING_TECHNIQUE_NONE:
    491             return graphics_proto.Settings.UPSCALING_TECHNIQUE_NONE
    492         elif upscalingTechnique == gfxsettings.GFX_UPSCALING_TECHNIQUE_FSR1:
    493             return graphics_proto.Settings.UPSCALING_TECHNIQUE_FSR1
    494         elif upscalingTechnique == gfxsettings.GFX_UPSCALING_TECHNIQUE_FSR3:
    495             return graphics_proto.Settings.UPSCALING_TECHNIQUE_FSR3
    496         elif upscalingTechnique == gfxsettings.GFX_UPSCALING_TECHNIQUE_METALFX:
    497             return graphics_proto.Settings.UPSCALING_TECHNIQUE_METALFX
    498         elif upscalingTechnique == gfxsettings.GFX_UPSCALING_TECHNIQUE_DLSS:
    499             return graphics_proto.Settings.UPSCALING_TECHNIQUE_DLSS
    500         elif upscalingTechnique == gfxsettings.GFX_UPSCALING_TECHNIQUE_XESS:
    501             return graphics_proto.Settings.UPSCALING_TECHNIQUE_XESS
    502         else:
    503             return graphics_proto.Settings.UPSCALING_TECHNIQUE_UNSPECIFIED
    504 
    505     def getUpscalingSettingToProto(self):
    506         upscalingSettings = gfxsettings.Get(gfxsettings.GFX_UPSCALING_SETTING)
    507         if upscalingSettings == gfxsettings.GFX_UPSCALING_SETTING_NATIVE:
    508             return graphics_proto.Settings.UPSCALING_SETTING_NATIVE
    509         elif upscalingSettings == gfxsettings.GFX_UPSCALING_SETTING_ULTRA_PERFORMANCE:
    510             return graphics_proto.Settings.UPSCALING_SETTING_ULTRA_PERFORMANCE
    511         elif upscalingSettings == gfxsettings.GFX_UPSCALING_SETTING_PERFORMANCE:
    512             return graphics_proto.Settings.UPSCALING_SETTING_PERFORMANCE
    513         elif upscalingSettings == gfxsettings.GFX_UPSCALING_SETTING_BALANCED:
    514             return graphics_proto.Settings.UPSCALING_SETTING_BALANCED
    515         elif upscalingSettings == gfxsettings.GFX_UPSCALING_SETTING_QUALITY:
    516             return graphics_proto.Settings.UPSCALING_SETTING_QUALITY
    517         elif upscalingSettings == gfxsettings.GFX_UPSCALING_SETTING_ULTRA_QUALITY:
    518             return graphics_proto.Settings.UPSCALING_SETTING_ULTRA_QUALITY
    519         else:
    520             return graphics_proto.Settings.UPSCALING_SETTING_UNSPECIFIED
    521 
    522     def getFrameGenerationToProto(self):
    523         frameGeneration = gfxsettings.Get(gfxsettings.GFX_FRAMEGENERATION_ENABLED)
    524         if frameGeneration:
    525             return graphics_proto.Settings.FRAMEGENERATION_ENABLED
    526         else:
    527             return graphics_proto.Settings.FRAMEGENERATION_DISABLED
    528 
    529     def getRenderingApiToProto(self):
    530         platform = trinity.platform
    531         if platform == 'dx11':
    532             return graphics_proto.Settings.RENDERING_API_DX11
    533         elif platform == 'dx12':
    534             return graphics_proto.Settings.RENDERING_API_DX12
    535         elif platform == 'metal':
    536             return graphics_proto.Settings.RENDERING_API_METAL
    537         elif platform == 'stub':
    538             return graphics_proto.Settings.RENDERING_API_STUB
    539         else:
    540             return graphics_proto.Settings.RENDERING_API_UNSPECIFIED
    541 
    542     def getCharacterTextureQualityToProto(self):
    543         q = gfxsettings.Get(gfxsettings.GFX_CHAR_TEXTURE_QUALITY)
    544         if q == gfxsettings.GFX_CHAR_TEXTURE_QUALITY_HIGH:
    545             return graphics_proto.Settings.Characters.CHARACTER_TEXTURE_QUALITY_HIGH
    546         elif q == gfxsettings.GFX_CHAR_TEXTURE_QUALITY_MEDIUM:
    547             return graphics_proto.Settings.Characters.CHARACTER_TEXTURE_QUALITY_MEDIUM
    548         elif q == gfxsettings.GFX_CHAR_TEXTURE_QUALITY_LOW:
    549             return graphics_proto.Settings.Characters.CHARACTER_TEXTURE_QUALITY_LOW
    550         else:
    551             return graphics_proto.Settings.Characters.CHARACTER_TEXTURE_QUALITY_UNSPECIFIED
    552 
    553     def getBrightnessToProto(self):
    554         return gfxsettings.Get(gfxsettings.GFX_BRIGHTNESS, 1.0)
    555 
    556     def getSelectedGpuToProto(self):
    557         state = trinity.mainWindow.GetWindowState()
    558         try:
    559             adapter = trinity.adapters.GetAdapterInfo(state.adapter)
    560             return adapter.description
    561         except:
    562             log.LogException('Failed to get adapter info for selected GPU %i' % state.adapter)
    563             return 'unknown'
    564 
    565     def sendUserStatInfo(self):
    566         event = login_proto.UserInformationGathered()
    567         try:
    568             pdmProtoData = sysinfo.get_pdm_byte_data()
    569             event.pdm_information.ParseFromString(pdmProtoData)
    570         except IOError:
    571             log.LogWarn('pdm data aquistion failed')
    572 
    573         if session.userid is None:
    574             uthread2.sleep(1)
    575         if session.userid:
    576             event.device_user_context.user.sequential = session.userid
    577         event.device_user_context.device = sysinfo.get_device_id().encode('UTF-8')
    578         event.client_settings.graphics_settings.ao_quality = self.getAoQualityToProto()
    579         event.client_settings.graphics_settings.reflection_quality = self.getReflectionQualityToProto()
    580         event.client_settings.graphics_settings.lod_quality = self.getLODQualityToProto()
    581         event.client_settings.graphics_settings.display_mode = self.getDisplayModeToProto()
    582         event.client_settings.graphics_settings.anti_aliasing = self.getAntiAliasingToProto()
    583         event.client_settings.graphics_settings.shadow_quality = self.getShadowQualityToProto()
    584         event.client_settings.graphics_settings.texture_quality = self.getTextureQualityToProto()
    585         event.client_settings.graphics_settings.post_processing_quality = self.getPostProcessingToProto()
    586         event.client_settings.graphics_settings.shader_quality = self.getShaderQualityToProto()
    587         event.client_settings.graphics_settings.window_size.width = trinity.device.width
    588         event.client_settings.graphics_settings.window_size.height = trinity.device.height
    589         event.client_settings.graphics_settings.ui_scaling = self.getUIScalingToProto()
    590         event.client_settings.graphics_settings.vsync = trinity.device.presentationInterval == 1
    591         event.client_settings.graphics_settings.upscaling_technique = self.getUpscalingTechniqueToProto()
    592         event.client_settings.graphics_settings.upscaling_setting = self.getUpscalingSettingToProto()
    593         event.client_settings.graphics_settings.frame_generation = self.getFrameGenerationToProto()
    594         event.client_settings.graphics_settings.rendering_api = self.getRenderingApiToProto()
    595         event.client_settings.graphics_settings.brightness = self.getBrightnessToProto()
    596         event.client_settings.graphics_settings.selected_gpu = self.getSelectedGpuToProto()
    597         event.client_settings.graphics_settings.characters.texture_quality = self.getCharacterTextureQualityToProto()
    598         event.client_settings.graphics_settings.characters.low_quality_characters = gfxsettings.Get(gfxsettings.GFX_CHAR_FAST_CHARACTER_CREATION)
    599         event.client_settings.graphics_settings.effects.depth_of_field_enabled = gfxsettings.Get(gfxsettings.GFX_DOF_POSTPROCESS_ENABLED)
    600         event.client_settings.audio_settings.is_audio_enabled = sm.GetService('audio').IsActivated()
    601         sm.GetService('publicGatewaySvc').publish_event_payload(event)
    602 
    603     def OnProcessLoginProgress(self, *args):
    604         if args[0] == 'loginprogress::done':
    605             self.SampleStats(STATE_LOGINDONE)
    606             self.sendUserStatInfo()
    607         elif args[0] == 'loginprogress::connecting':
    608             self.SampleStats(STATE_LOGINSTARTED)
    609 
    610     def ProcessShutdown(self):
    611         self.OnProcessExit()
    612 
    613     def OnFatalDesync(self):
    614         if self.currentState not in self.entries:
    615             self.entries[self.currentState] = {}
    616         if STAT_FATAL_DESYNCS in self.entries[self.currentState]:
    617             self.entries[self.currentState][STAT_FATAL_DESYNCS] += 1
    618         else:
    619             self.entries[self.currentState][STAT_FATAL_DESYNCS] = 1
    620 
    621     def OnRecoverableDesync(self):
    622         if self.currentState not in self.entries:
    623             self.entries[self.currentState] = {}
    624         if STAT_RECOVERABLE_DESYNCS in self.entries[self.currentState]:
    625             self.entries[self.currentState][STAT_RECOVERABLE_DESYNCS] += 1
    626         else:
    627             self.entries[self.currentState][STAT_RECOVERABLE_DESYNCS] = 1
    628 
    629     def BuildCrashInfo(self, build, uploadResult, sid):
    630         crashInfo = [build,
    631          uploadResult,
    632          sid,
    633          self.adapterInfo.vendorID,
    634          self.adapterInfo.deviceID,
    635          self.adapterInfo.driverVersion,
    636          self.osPlatform,
    637          blue.sysinfo.os.majorVersion,
    638          blue.sysinfo.os.minorVersion,
    639          blue.sysinfo.os.buildNumber,
    640          boot.build]
    641         return crashInfo
    642 
    643     @telemetry.ZONE_METHOD
    644     def ValidateCrashData(self, crashData, sid):
    645         try:
    646             crashKwd = crashData[0]
    647             if crashKwd == 'shutdown':
    648                 return ['shutdown']
    649             userid = crashData[1]
    650             clientid = crashData[2]
    651             timeStamp = crashData[3]
    652             build = crashData[4]
    653             dumpId = crashData[5]
    654             uploadResult = crashData[6]
    655             if crashKwd != 'crashed':
    656                 return None
    657             if build < 0:
    658                 return None
    659             if len(dumpId) != 36:
    660                 return None
    661             parts = dumpId.split('-')
    662             if len(parts) != 5:
    663                 return None
    664             if len(parts[0]) != 8:
    665                 return None
    666             if len(parts[1]) != 4:
    667                 return None
    668             if len(parts[2]) != 4:
    669                 return None
    670             if len(parts[3]) != 4:
    671                 return None
    672             if len(parts[4]) != 12:
    673                 return None
    674             if uploadResult < -1 or uploadResult > 3:
    675                 return None
    676             crashInfo = self.BuildCrashInfo(build, uploadResult, sid)
    677             return [dumpId,
    678              userid,
    679              clientid,
    680              FmtDateEng(timeStamp, 'sl'),
    681              crashInfo]
    682         except:
    683             return None
    684 
    685     def ReadSavedStats(self, pathOnDisk, checksum):
    686         package = None
    687         f = open(pathOnDisk)
    688         try:
    689             self.LogInfo('Found session file from an earlier session - reading stats data')
    690             contents = f.read()
    691             contentsChecksum = zlib.crc32(contents)
    692             if checksum == contentsChecksum:
    693                 package = yaml.safe_load(contents)
    694                 if package is None:
    695                     self.LogInfo('No stats data found')
    696                 elif package[0] in ('login', 'charsel'):
    697                     package = None
    698             else:
    699                 self.LogInfo('Invalid session file')
    700         except Exception:
    701             log.LogException()
    702             sys.exc_clear()
    703             package = None
    704         finally:
    705             f.close()
    706 
    707         return package
    708 
    709     @telemetry.ZONE_METHOD
    710     def ScanSessionFiles(self):
    711         packagesToSend = []
    712         crashesToSend = []
    713         numClients = 0
    714         crashedClients = 0
    715         exeFilePids = blue.os.GetExeFilePids()
    716         files = blue.paths.listdir('cache:/')
    717         for each in files:
    718             if each.endswith('.session'):
    719                 baseName = each[:-8]
    720                 pathOnDisk = blue.paths.ResolvePath('cache:/' + each)
    721                 if baseName.startswith('closed'):
    722                     baseName = baseName[6:]
    723                     try:
    724                         pid, checksum = baseName.split('_')
    725                         checksum = int(checksum, 16)
    726                         package = self.ReadSavedStats(pathOnDisk, checksum)
    727                         if package:
    728                             packagesToSend.append(package)
    729                     except ValueError:
    730                         pass
    731                     finally:
    732                         try:
    733                             _retry_os_remove(pathOnDisk)
    734                         except OSError:
    735                             pass
    736 
    737                 else:
    738                     pid = 0
    739                     sid = 0
    740                     try:
    741                         pidString, sidString = baseName.split('_')
    742                         pid = int(pidString)
    743                         sid = long(sidString)
    744                     except ValueError:
    745                         pass
    746 
    747                     if pid == blue.os.pid or pid in exeFilePids:
    748                         numClients += 1
    749                     else:
    750                         crashData = None
    751                         if pid:
    752                             self.LogInfo('Process %d exited abnormally' % pid)
    753                             crashedClients += 1
    754                             foundCrashData = False
    755                             f = open(pathOnDisk)
    756                             try:
    757                                 crashData = yaml.safe_load(f)
    758                                 crashData = self.ValidateCrashData(crashData, sid)
    759                                 if crashData:
    760                                     foundCrashData = True
    761                                     if crashData[0] == 'shutdown':
    762                                         crashData = None
    763                                     else:
    764                                         self.LogInfo('Found crash info:', crashData[0], crashData[1], crashData[2], crashData[3])
    765                             except Exception:
    766                                 log.LogException()
    767                                 sys.exc_clear()
    768                             finally:
    769                                 f.close()
    770 
    771                             if not foundCrashData:
    772                                 sr = os.stat(pathOnDisk)
    773                                 dt = datetime.datetime.fromtimestamp(sr.st_ctime)
    774                                 timestamp = dt.strftime('%Y.%m.%d %H:%M:%S')
    775                                 crashData = ['',
    776                                  0,
    777                                  0,
    778                                  timestamp,
    779                                  self.BuildCrashInfo(0, -1, sid)]
    780                         try:
    781                             _retry_os_remove(pathOnDisk)
    782                         except OSError:
    783                             crashData = None
    784 
    785                         if crashData:
    786                             crashesToSend.append(crashData)
    787 
    788         if numClients > 1:
    789             self.otherClients = numClients - 1
    790             self.LogInfo('Found %d other clients' % self.otherClients)
    791         else:
    792             self.otherClients = 0
    793             self.LogInfo('Found no other clients')
    794         if crashedClients > 0:
    795             self.crashedClients += crashedClients
    796             self.LogInfo('Found evidence of %d crashed clients (%d total found from session start)' % (crashedClients, self.crashedClients))
    797         return (packagesToSend, crashesToSend)
    798 
    799     @telemetry.ZONE_METHOD
    800     def GatherStats(self, event, locationID):
    801         self.LogInfo('Gathering stats:', event)
    802         stats = self.CaptureStats()
    803         if session.charid:
    804             package = []
    805             for each in self.statsWaitingToBeSent:
    806                 oldEvent = each[0]
    807                 stats = each[1]
    808                 package.append(self.PrepareStatsPackage(oldEvent, stats))
    809 
    810             self.statsWaitingToBeSent = []
    811             package.append(self.PrepareStatsPackage(event, stats))
    812             oldStats, crashes = self.ScanSessionFiles()
    813             package += oldStats
    814             del package[self.clientStatsMaxBatch:]
    815             sm.ProxySvc('eventLog').LogClientStats(session.sid, locationID, package, crashes)
    816         else:
    817             self.statsWaitingToBeSent.append((event, stats))
    818 
    819     @telemetry.ZONE_METHOD
    820     def DoSessionChanging(self, isremote, session, change):
    821         if 'locationid' not in change or change['locationid'][0] is None:
    822             return
    823         uthread.new(self.GatherStats, self.lastClientState, session.locationid)
    824 
    825     @telemetry.ZONE_METHOD
    826     def PrepareStatsPackage(self, event, stats):
    827         try:
    828             charid = session.charid or 0
    829         except AttributeError:
    830             charid = 0
    831 
    832         try:
    833             userid = session.userid or 0
    834         except AttributeError:
    835             userid = 0
    836 
    837         try:
    838             sid = session.sid
    839         except AttributeError:
    840             sid = 0
    841 
    842         monolithsentry.set_sentry_crash_key()
    843         package = [event,
    844          charid,
    845          userid,
    846          sid,
    847          FmtDateEng(blue.os.GetWallclockTime(), 'sl'),
    848          stats.values()]
    849         return package
    850 
    851     @telemetry.ZONE_METHOD
    852     def CaptureStats(self):
    853         stats = ClientStatsDict()
    854         stats['gpuVendorId'] = self.adapterInfo.vendorID
    855         stats['gpuDeviceId'] = self.adapterInfo.deviceID
    856         stats['gpuDriverVersion'] = self.adapterInfo.driverVersion
    857         stats['osPlatform'] = self.osPlatform
    858         stats['eveBuild'] = boot.build
    859         stats['osMajor'] = blue.sysinfo.os.majorVersion
    860         stats['osMinor'] = blue.sysinfo.os.minorVersion
    861         stats['osBuild'] = blue.sysinfo.os.buildNumber
    862         otherClients = len(blue.os.GetExeFilePids()) - 1
    863         if otherClients < 0:
    864             otherClients = 0
    865         stats['otherClients'] = otherClients
    866         stats['windowed'] = sm.GetService('device').IsWindowed()
    867         stats['deviceWidth'] = trinity.device.width
    868         stats['deviceHeight'] = trinity.device.height
    869         stats['presentInterval'] = trinity.device.presentationInterval
    870         stats['antiAliasing'] = gfxsettings.Get(gfxsettings.GFX_ANTI_ALIASING)
    871         stats['postProcessingQuality'] = gfxsettings.Get(gfxsettings.GFX_POST_PROCESSING_QUALITY)
    872         stats['shaderQuality'] = gfxsettings.Get(gfxsettings.GFX_SHADER_QUALITY)
    873         stats['textureQuality'] = gfxsettings.Get(gfxsettings.GFX_TEXTURE_QUALITY)
    874         stats['reflectionQuality'] = gfxsettings.Get(gfxsettings.GFX_REFLECTION_QUALITY)
    875         stats['aoQuality'] = gfxsettings.Get(gfxsettings.GFX_AO_QUALITY)
    876         stats['volumetricQuality'] = gfxsettings.Get(gfxsettings.GFX_VOLUMETRIC_QUALITY)
    877         stats['upscalingTechnique'] = gfxsettings.Get(gfxsettings.GFX_UPSCALING_TECHNIQUE)
    878         stats['upscalingSetting'] = gfxsettings.Get(gfxsettings.GFX_UPSCALING_SETTING)
    879         stats['frameGeneration'] = gfxsettings.Get(gfxsettings.GFX_FRAMEGENERATION_ENABLED)
    880         stats['lodQuality'] = gfxsettings.Get(gfxsettings.GFX_LOD_QUALITY)
    881         stats['shadowQuality'] = gfxsettings.Get(gfxsettings.GFX_SHADOW_QUALITY)
    882         stats['audioEnabled'] = sm.GetService('audio').IsActivated()
    883         timeInState = blue.os.GetWallclockTime() - self.timeWhenStateEntered
    884         timeInState = blue.os.TimeAsDouble(timeInState)
    885         if timeInState < 0.0:
    886             timeInState = 0.0
    887         stats['timeInState'] = timeInState
    888         stats['frameTimePeak'] = self.frameTimeStat.peak
    889         for value in BLUE_STATS_NAMES.itervalues():
    890             valueType, valueName, wantPeakValue = value
    891             statsEntry = self.statsEntries[valueName]
    892             statsValue = valueType(statsEntry.value)
    893             stats[valueName] = statsValue
    894             if wantPeakValue:
    895                 peakValue = valueType(statsEntry.peak)
    896                 stats[valueName + 'Peak'] = peakValue
    897             statsEntry.Set(0)
    898 
    899         if self.browserRequestsStat is None:
    900             self.browserRequestsStat = blue.statistics.Find('browser/numRequests')
    901         if self.browserRequestsStat is not None:
    902             stats['browserRequests'] = int(self.browserRequestsStat.value)
    903             self.browserRequestsStat.Set(0)
    904         self.timeWhenStateEntered = blue.os.GetWallclockTime()
    905         blue.statistics.ResetPeaks()
    906         blue.statistics.ResetDerived()
    907         stats['trinityPlatform'] = trinity.device.GetRenderingPlatformID()
    908         return stats