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