mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
GEEKING
This commit is contained in:
@@ -0,0 +1,737 @@
|
||||
//
|
||||
// SessionReporter.m
|
||||
// RobloxMobile
|
||||
//
|
||||
// Created by Ganesh Agrawal on 7/12/13.
|
||||
// Copyright (c) 2013 ROBLOX. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SessionReporter.h"
|
||||
#import "RobloxGoogleAnalytics.h"
|
||||
#import "RobloxInfo.h"
|
||||
#import "PlaceLauncher.h"
|
||||
#import "UserInfo.h"
|
||||
#import "iOSSettingsService.h"
|
||||
#import "RobloxMemoryManager.h"
|
||||
#import "RobloxNotifications.h"
|
||||
#import "RBXFunctions.h"
|
||||
#import "v8datamodel/Stats.h"
|
||||
#import "NSDictionary+Parsing.h"
|
||||
|
||||
#define KEY_PLACE_ID "PlaceId"
|
||||
#define KEY_GAME_START_TIME "GameStartTime"
|
||||
#define KEY_GAME_END_TIME "GameEndTime"
|
||||
#define KEY_APP_CRASH "CRASHED"
|
||||
#define KEY_FREE_MEMORY "FreeMemory"
|
||||
#define KEY_USED_MEMORY "UsedMemory"
|
||||
#define KEY_NUM_MEM_WARNING "NumMemoryWarning"
|
||||
#define KEY_LAST_OUT_OF_MEM_TIME "LastOutOfMemWarningTime"
|
||||
|
||||
#define APP_STATUS_SUCCESS "AppStatusSuccess"
|
||||
#define APP_STATUS_CRASH "AppStatusCrash"
|
||||
#define APP_OUT_OF_MEM_ON_LOAD "AppStatusOutOfMemoryOnLoad"
|
||||
#define APP_OUT_OF_MEM_IN_GAME "AppStatusOutOfMemoryInGame"
|
||||
|
||||
DYNAMIC_FASTINTVARIABLE(iOSInfluxHundredthsPercentage, 1000)
|
||||
DYNAMIC_FASTINTVARIABLE(GenerateOutOfMemoryReportIfBelowMB, 10)
|
||||
static int calculateSessionDataEveryMilliSeconds = 100;
|
||||
|
||||
@implementation SessionReporter
|
||||
|
||||
+ (id)sharedInstance
|
||||
{
|
||||
static dispatch_once_t rbxSessionReporterFlagsPred = 0;
|
||||
__strong static id _sharedObject = nil;
|
||||
dispatch_once(&rbxSessionReporterFlagsPred, ^{ // Need to use GCD for thread-safe allocation
|
||||
_sharedObject = [[self alloc] init];
|
||||
});
|
||||
return _sharedObject;
|
||||
}
|
||||
|
||||
|
||||
|
||||
-(id) init
|
||||
{
|
||||
if(self = [super init])
|
||||
{
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void)pushSessionData: (NSString*) eventType PlaceId:(NSInteger) placeId GamePlayTime:(NSInteger) playTime
|
||||
{
|
||||
RBX::Analytics::InfluxDb::Points points;
|
||||
points.addPoint("SessionReport" , [eventType UTF8String]);
|
||||
|
||||
|
||||
[RobloxGoogleAnalytics setEventTracking:@"SessionReport" withAction:eventType withLabel:[NSString stringWithFormat:@"%ld", (long)placeId] withValue:playTime];
|
||||
|
||||
NSString *appVersion = [[NSUserDefaults standardUserDefaults] stringForKey:@"LastRunningAppVersion"];
|
||||
if (!appVersion)
|
||||
appVersion = [RobloxInfo appVersion];
|
||||
|
||||
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"LastUserLoggedIn"])
|
||||
{
|
||||
points.addPoint("LastUserLoggedIn" , [[[NSUserDefaults standardUserDefaults] stringForKey:@"LastUserLoggedIn"] UTF8String]);
|
||||
RBX::Analytics::setUserId([[NSUserDefaults standardUserDefaults] integerForKey:@"LastUserIDLoggedIn"]);
|
||||
|
||||
}
|
||||
|
||||
if([eventType isEqualToString:@APP_STATUS_CRASH])
|
||||
[RobloxGoogleAnalytics setEventTracking:@"CrashCountByUser"
|
||||
withAction:appVersion
|
||||
withLabel:[[NSUserDefaults standardUserDefaults] stringForKey:@"LastUserLoggedIn"]
|
||||
withValue:0];
|
||||
|
||||
|
||||
NSString *gameStateString = [[NSUserDefaults standardUserDefaults] stringForKey:@"RobloxGameState"];
|
||||
NSString *appStateString = [[NSUserDefaults standardUserDefaults] stringForKey:@"RobloxAppState"];
|
||||
if (gameStateString || appStateString)
|
||||
{
|
||||
NSString * crashStateString = [[NSUserDefaults standardUserDefaults] stringForKey:(gameStateString ? @"RobloxGameState" : @"RobloxAppState")];
|
||||
|
||||
points.addPoint("GameState" , [crashStateString UTF8String]);
|
||||
points.addPoint("FreeMemoryKB" , (int)[[NSUserDefaults standardUserDefaults] integerForKey:@KEY_FREE_MEMORY]);
|
||||
points.addPoint("UsedMemoryKB" , (int)[[NSUserDefaults standardUserDefaults] integerForKey:@KEY_USED_MEMORY]);
|
||||
|
||||
if (gameStateString)
|
||||
{
|
||||
RBX::Analytics::setPlaceId(placeId);
|
||||
points.addPoint("PlayTime" , (int)playTime);
|
||||
points.addPoint("NumMemoryWarning" , (int)[[NSUserDefaults standardUserDefaults] integerForKey:@KEY_NUM_MEM_WARNING]);
|
||||
|
||||
// If we had received some memory warnings before crashing
|
||||
if ([eventType isEqualToString:@APP_STATUS_CRASH] && [[NSUserDefaults standardUserDefaults] objectForKey:@KEY_LAST_OUT_OF_MEM_TIME])
|
||||
{
|
||||
if ([[NSUserDefaults standardUserDefaults] objectForKey:@KEY_GAME_END_TIME])
|
||||
{
|
||||
int lastMemWarningMSec = 1000 * ([[NSUserDefaults standardUserDefaults] doubleForKey:@KEY_GAME_END_TIME] - [[NSUserDefaults standardUserDefaults] doubleForKey:@KEY_LAST_OUT_OF_MEM_TIME]);
|
||||
points.addPoint("MillSecMemoryWarn" , lastMemWarningMSec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
points.report("iOS-RobloxPlayer-SessionReport", DFInt::iOSInfluxHundredthsPercentage);
|
||||
|
||||
if ([eventType isEqualToString:@APP_STATUS_CRASH] && [[NSUserDefaults standardUserDefaults] integerForKey:@KEY_FREE_MEMORY] < (DFInt::GenerateOutOfMemoryReportIfBelowMB * 1024) && gameStateString)
|
||||
eventType = [gameStateString isEqualToString:@"inGame"] ? @APP_OUT_OF_MEM_IN_GAME : @APP_OUT_OF_MEM_ON_LOAD;
|
||||
|
||||
NSString* apiUrl = [NSString stringWithFormat:@"%@/game/sessions/report?placeId=%ld&eventType=%@", [RobloxInfo getApiBaseUrl], (long)placeId, eventType];
|
||||
NSURL *url = [NSURL URLWithString: apiUrl];
|
||||
|
||||
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url
|
||||
cachePolicy:NSURLRequestReloadIgnoringCacheData
|
||||
timeoutInterval:60*7];
|
||||
|
||||
[RobloxInfo setDefaultHTTPHeadersForRequest:theRequest];
|
||||
[theRequest setHTTPMethod:@"POST"];
|
||||
|
||||
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
|
||||
[NSURLConnection sendAsynchronousRequest:theRequest queue:queue completionHandler:
|
||||
^(NSURLResponse *response, NSData *receiptResponseData, NSError *error){/*Do not care for response*/}];
|
||||
}
|
||||
|
||||
- (BOOL) getPlayData: (NSInteger&) placeId PlayTimeSeconds: (NSInteger&) gamePlayTime CalculateNow: (BOOL) endNow
|
||||
{
|
||||
if ([[NSUserDefaults standardUserDefaults] objectForKey:@KEY_PLACE_ID])
|
||||
{
|
||||
if ([[NSUserDefaults standardUserDefaults] objectForKey:@KEY_GAME_START_TIME])
|
||||
{
|
||||
if ([[NSUserDefaults standardUserDefaults] objectForKey:@KEY_GAME_END_TIME])
|
||||
gamePlayTime = [[NSUserDefaults standardUserDefaults] doubleForKey:@KEY_GAME_END_TIME] - [[NSUserDefaults standardUserDefaults] doubleForKey:@KEY_GAME_START_TIME];
|
||||
else if (endNow)
|
||||
gamePlayTime = [[NSDate date] timeIntervalSince1970] - [[NSUserDefaults standardUserDefaults] doubleForKey:@KEY_GAME_START_TIME];
|
||||
|
||||
placeId = [[NSUserDefaults standardUserDefaults] integerForKey:@KEY_PLACE_ID];
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
-(void) callTimerFn
|
||||
{
|
||||
if([[PlaceLauncher sharedInstance] getIsCurrentlyPlayingGame])
|
||||
{
|
||||
[self reportSessionFor:GAME_RUNNING];
|
||||
[self performSelector:@selector(callTimerFn) withObject:nil afterDelay:((NSTimeInterval)calculateSessionDataEveryMilliSeconds/1000)];
|
||||
}
|
||||
}
|
||||
|
||||
-(void) reportSessionFor:(ApplicationState) appState
|
||||
{
|
||||
[self reportSessionFor:appState PlaceId:0];
|
||||
}
|
||||
|
||||
|
||||
-(void) postAnalyticPayloadFromContext:(RBXAnalyticsContextName)context
|
||||
result:(RBXAnalyticsResult)result
|
||||
rbxError:(RBXAnalyticsErrorName)rbxError
|
||||
startingTime:(NSDate*)startTime
|
||||
attemptedUsername:(NSString*)username
|
||||
URLRequest:(NSURLRequest*)request
|
||||
HTTPResponseCode:(NSInteger)responseCode
|
||||
responseData:(NSData*)responseData
|
||||
additionalData:(NSDictionary*)additionalData
|
||||
{
|
||||
[RBXFunctions dispatchOnBackgroundThread:^{
|
||||
|
||||
//NOTE - REQUIRE ALL INPUT INTO THE DICTIONARY TO BE STRINGS
|
||||
NSMutableDictionary<NSString*, NSString*> *analyticsPayload = [NSMutableDictionary dictionary];
|
||||
|
||||
//NSTimeInterval is a measurement given in seconds, be sure to cast it to milliseconds
|
||||
NSTimeInterval responseTime = MAX(0, ([[NSDate date] timeIntervalSinceDate:startTime] * 1000));
|
||||
[analyticsPayload setObject:[NSString stringWithFormat:@"%d", (int)responseTime] forKey:@"responseTimeMs"];
|
||||
|
||||
//Keep track of the type of action we are doing
|
||||
if (context == RBXAContextLogin || context == RBXAContextSocialLogin || context == RBXAContextAppLaunch)
|
||||
{
|
||||
//NOTE - REQUIRES ADDITIONAL DATA
|
||||
NSString* loginType = @"manual";
|
||||
bool performingAutoLogin = [additionalData boolValueForKey:@"isAutoLogin" withDefault:NO];
|
||||
if (context == RBXAContextSocialLogin && performingAutoLogin == YES) loginType = @"socialAuto";
|
||||
else if (context == RBXAContextSocialLogin && performingAutoLogin == NO) loginType = @"social";
|
||||
else if (context != RBXAContextSocialLogin && performingAutoLogin == YES) loginType = @"auto";
|
||||
[analyticsPayload setObject:loginType forKey:@"loginType"];
|
||||
}
|
||||
else if (context == RBXAContextSocialLogin || context == RBXAContextSignup)
|
||||
{
|
||||
NSString* signupType = (context == RBXAContextSignup) ? @"regular" : @"social";
|
||||
[analyticsPayload setObject:signupType forKey:@"signupType"];
|
||||
}
|
||||
else if (context == RBXAContextSettingsSocial)
|
||||
{
|
||||
// NOTE - REQUIRES ADDITIONAL DATA
|
||||
//save what social network we are connecting to
|
||||
NSString* socialNetworkName = [additionalData stringForKey:@"provider" withDefault:nil];
|
||||
if (socialNetworkName)
|
||||
[analyticsPayload setObject:socialNetworkName forKey:@"provider"];
|
||||
}
|
||||
|
||||
if (result == RBXAResultFailure) {
|
||||
|
||||
//Failure reason
|
||||
[analyticsPayload setObject:[[RBXEventReporter sharedInstance] nameForError:rbxError] forKey:@"Status"];
|
||||
|
||||
//Request URL
|
||||
if (![RBXFunctions isEmpty:request.URL.description])
|
||||
[analyticsPayload setObject:request.URL.description forKey:@"requestUrl"];
|
||||
|
||||
//Response body as a string, but clean it up so it reports properly
|
||||
NSString *responseBody = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
|
||||
responseBody = [responseBody stringByReplacingOccurrencesOfString:@"\'" withString:@"\\\'"];
|
||||
responseBody = [responseBody stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
|
||||
responseBody = [responseBody stringByReplacingOccurrencesOfString:@"\n" withString:@" "]; //replace new line with space
|
||||
responseBody = [responseBody stringByReplacingOccurrencesOfString:@"\r" withString:@" "]; //replace line return with space
|
||||
if (![RBXFunctions isEmpty:responseBody])
|
||||
[analyticsPayload setObject:responseBody forKey:@"responseBody"];
|
||||
|
||||
// Username <- simply pulling it from the UserInfo object is not accurate
|
||||
if (![RBXFunctions isEmpty:username])
|
||||
[analyticsPayload setObject:username forKey:@"username"];
|
||||
|
||||
// Browser Tracker Id
|
||||
if (![RBXFunctions isEmpty:[RobloxData browserTrackerId]])
|
||||
[analyticsPayload setObject:[RobloxData browserTrackerId] forKey:@"browserTrackerId"];
|
||||
|
||||
// App Version
|
||||
if (![RBXFunctions isEmpty:[RobloxInfo appVersion]])
|
||||
[analyticsPayload setObject:[RobloxInfo appVersion] forKey:@"appVersion"];
|
||||
|
||||
// Device Type
|
||||
if (![RBXFunctions isEmpty:[RobloxInfo deviceType]])
|
||||
[analyticsPayload setObject:[RobloxInfo deviceType] forKey:@"deviceType"];
|
||||
|
||||
// OS Version
|
||||
if (![RBXFunctions isEmpty:[RobloxInfo deviceOSVersion]])
|
||||
[analyticsPayload setObject:[RobloxInfo deviceOSVersion] forKey:@"deviceOSVersion"];
|
||||
|
||||
//HTTP Response Code
|
||||
if (responseCode)
|
||||
[analyticsPayload setObject:[NSString stringWithFormat:@"%ld", (long)responseCode] forKey:@"httpResponseCode"];
|
||||
}
|
||||
|
||||
// Post analytics information
|
||||
[self reportSessionForContext:context
|
||||
result:result
|
||||
errorName:rbxError
|
||||
responseCode:responseCode
|
||||
data:analyticsPayload];
|
||||
}];
|
||||
}
|
||||
|
||||
-(void) postStartupPayloadForEvent:(NSString*)requestName
|
||||
completionTime:(NSTimeInterval)timeSeconds
|
||||
{
|
||||
[RBXFunctions dispatchOnBackgroundThread:^{
|
||||
NSNumber* timeNumber = [NSNumber numberWithInt:(int)(timeSeconds * 1000.0)];
|
||||
|
||||
[self sendSessionDataToInfluxSeries:@"AppStartupTimeiOS"
|
||||
data:@{@"requestName":requestName,
|
||||
@"completionTime":timeNumber.stringValue}];
|
||||
|
||||
}];
|
||||
}
|
||||
-(void) postAutoLoginFailurePayload:(NSTimeInterval)loginTimestamp
|
||||
cookieExpiration:(NSTimeInterval)actualExpirationTimestamp
|
||||
expectedExpiration:(NSTimeInterval)expectedExpirationTimestamp
|
||||
{
|
||||
[RBXFunctions dispatchOnBackgroundThread:^{
|
||||
|
||||
//create a number formatter to handle the conversion from timeInterval to string
|
||||
NSNumberFormatter* nf = [[NSNumberFormatter alloc] init];
|
||||
[nf setMaximumFractionDigits:0];
|
||||
[nf setRoundingMode:NSNumberFormatterRoundHalfEven];
|
||||
|
||||
//convert NSTimeInterval from seconds to milliseconds
|
||||
NSNumber* numLogin = [NSNumber numberWithDouble:(double)loginTimestamp * 1000.0] ;
|
||||
NSNumber* numActual = [NSNumber numberWithDouble:(double)actualExpirationTimestamp * 1000.0] ;
|
||||
NSNumber* numExpected = [NSNumber numberWithDouble:(double)expectedExpirationTimestamp * 1000.0];
|
||||
|
||||
//report the data to Influx
|
||||
[self sendSessionDataToInfluxSeries:@"AutoLoginFailures"
|
||||
data:@{@"initialLoginTimestamp":[nf stringFromNumber:numLogin],
|
||||
@"cookieExpirationTimestamp":[nf stringFromNumber:numActual],
|
||||
@"expectedCookieExpirationTimestamp":[nf stringFromNumber:numExpected]}];
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
-(void) sendSessionDataToInfluxSeries:(NSString*)seriesName data:(NSDictionary<NSString*, NSString*>*)dataDictionary
|
||||
{
|
||||
// Post result to Influx
|
||||
RBX::Analytics::InfluxDb::Points analyticsPoints;
|
||||
|
||||
for (NSString *key in dataDictionary) {
|
||||
NSString* obj = (NSString*)[dataDictionary objectForKey:key];
|
||||
//NSLog(@"Session Data [%s] = %@", [key UTF8String], obj);
|
||||
analyticsPoints.addPoint(std::string([key UTF8String]).c_str(), [obj UTF8String]);
|
||||
}
|
||||
analyticsPoints.report(std::string([seriesName UTF8String]), 10000, true);
|
||||
}
|
||||
|
||||
-(void) reportSessionForContext:(RBXAnalyticsContextName)context result:(RBXAnalyticsResult)result errorName:(RBXAnalyticsErrorName)errorName responseCode:(NSInteger)responseCode data:(NSDictionary *)dataDictionary
|
||||
{
|
||||
//Log In Documentation Reference :
|
||||
//Sign Up Documentation Reference : https://docs.google.com/document/d/1q2T4--wtMgDtv8Nnv9xJm3V0rJLeXB0hn1GpjnNnEJk/edit?ts=5627cc46
|
||||
|
||||
/*
|
||||
here are the counter names for diag:
|
||||
diag: iOS-AppLogin-Success
|
||||
diag: iOS-AppLogin-Failure
|
||||
diag: iOS-SocialLogin-Success
|
||||
diag: iOS-SocialLogin-Failure
|
||||
diag: iOS-AppSignup-Success
|
||||
diag: iOS-AppSignup-Failure
|
||||
diag: iOS-SocialSignup-Success
|
||||
diag: iOS-SocialSignup-Failure
|
||||
value will be 1 for each report
|
||||
*/
|
||||
|
||||
//sanitize some inputs
|
||||
if (result == RBXAResultFailure) {
|
||||
if ([RBXFunctions isEmpty:@(errorName)]) {
|
||||
errorName = RBXAErrorNoError;
|
||||
}
|
||||
}
|
||||
|
||||
NSString* GAContextName = nil;
|
||||
switch (context) {
|
||||
|
||||
//login metrics
|
||||
case RBXAContextLogin:
|
||||
{
|
||||
// Post result to Google Analytics
|
||||
GAContextName = [[RBXEventReporter sharedInstance] nameForContext:context]; //"login"
|
||||
|
||||
//Diag
|
||||
if (result == RBXAResultSuccess) {
|
||||
RBX::Analytics::EphemeralCounter::reportCounter("iOS-AppLogin-Success", 1);
|
||||
|
||||
}
|
||||
else if (result == RBXAResultFailure) {
|
||||
RBX::Analytics::EphemeralCounter::reportCounter("iOS-AppLogin-Failure", 1);
|
||||
|
||||
// Post additional details to Influx
|
||||
[self sendSessionDataToInfluxSeries:@"LoginFailureiOS" data:dataDictionary];
|
||||
}
|
||||
} break;
|
||||
|
||||
// Social Login metrics
|
||||
case RBXAContextSocialLogin:
|
||||
{
|
||||
// Post result to Google Analytics
|
||||
GAContextName = [[RBXEventReporter sharedInstance] nameForContext:context]; //"socialLogin"
|
||||
//Diag
|
||||
if (result == RBXAResultSuccess) {
|
||||
RBX::Analytics::EphemeralCounter::reportCounter("iOS-SocialLogin-Success", 1);
|
||||
}
|
||||
else if (result == RBXAResultFailure) {
|
||||
RBX::Analytics::EphemeralCounter::reportCounter("iOS-SocialLogin-Failure", 1);
|
||||
|
||||
// Post additional details to Influx
|
||||
[self sendSessionDataToInfluxSeries:@"LoginFailureiOS" data:dataDictionary];
|
||||
}
|
||||
} break;
|
||||
|
||||
// Automatic login success metrics
|
||||
case RBXAContextAppLaunch:
|
||||
{
|
||||
// Post result to Google Analytics
|
||||
GAContextName = [[RBXEventReporter sharedInstance] nameForContext:context]; //"appLaunch"
|
||||
|
||||
// Diag
|
||||
if (responseCode == 999)
|
||||
{
|
||||
RBX::Analytics::EphemeralCounter::reportCounter("iOS-inAppBrowserTrackerChanged-Success", 1);
|
||||
|
||||
//Nothing is being done with the Data dictionary ??
|
||||
}
|
||||
} break;
|
||||
|
||||
|
||||
//Signup metrics
|
||||
case RBXAContextSignup:
|
||||
{
|
||||
// Post result to Google Analytics
|
||||
GAContextName = @"SignupAttempt";
|
||||
|
||||
//Diag
|
||||
if (result == RBXAResultSuccess) {
|
||||
RBX::Analytics::EphemeralCounter::reportCounter("iOS-AppSignup-Success", 1);
|
||||
}
|
||||
else if (result == RBXAResultFailure) {
|
||||
RBX::Analytics::EphemeralCounter::reportCounter("iOS-AppSignup-Failure", 1);
|
||||
|
||||
// Post additional details to Influx
|
||||
[self sendSessionDataToInfluxSeries:@"SignupFailureiOS" data:dataDictionary];
|
||||
}
|
||||
|
||||
} break;
|
||||
|
||||
//Social Signup metrics
|
||||
case RBXAContextSocialSignup:
|
||||
{
|
||||
// Post result to Google Analytics
|
||||
GAContextName = @"SocialSignupAttempt";
|
||||
|
||||
//Diag
|
||||
if (result == RBXAResultSuccess) {
|
||||
RBX::Analytics::EphemeralCounter::reportCounter("iOS-SocialSignup-Success", 1);
|
||||
}
|
||||
else if (result == RBXAResultFailure) {
|
||||
RBX::Analytics::EphemeralCounter::reportCounter("iOS-SocialSignup-Failure", 1);
|
||||
|
||||
// Post additional details to Influx
|
||||
[self sendSessionDataToInfluxSeries:@"SignupFailureiOS" data:dataDictionary];
|
||||
}
|
||||
|
||||
} break;
|
||||
|
||||
// Social Connect / Disconnect metrics
|
||||
case RBXAContextSettingsSocialConnect:
|
||||
{
|
||||
GAContextName = @"SocialConnectAttempt";
|
||||
|
||||
if (result == RBXAResultFailure)
|
||||
{
|
||||
// Post additional details to Influx
|
||||
[self sendSessionDataToInfluxSeries:@"SocialConnectFailureiOS" data:dataDictionary];
|
||||
}
|
||||
} break;
|
||||
|
||||
case RBXAContextSettingsSocialDisconnect:
|
||||
{
|
||||
GAContextName = @"SocialDisconnectAttempt";
|
||||
if (result == RBXAResultFailure)
|
||||
{
|
||||
// Post additional details to Influx
|
||||
[self sendSessionDataToInfluxSeries:@"SocialDisconnectFailureiOS" data:dataDictionary];
|
||||
}
|
||||
|
||||
} break;
|
||||
|
||||
// Application Did Enter Foreground
|
||||
case RBXAContextAppEnterForeground:
|
||||
{
|
||||
// Post result to Google Analytics
|
||||
GAContextName = [[RBXEventReporter sharedInstance] nameForContext:context];
|
||||
|
||||
//Diag
|
||||
if (result == RBXAResultFailure) {
|
||||
RBX::Analytics::EphemeralCounter::reportCounter("iOS-AppEnterForeground-Failure", 1);
|
||||
|
||||
// Post additional details to Influx
|
||||
[self sendSessionDataToInfluxSeries:@"AppEnterForegroundFailureiOS" data:dataDictionary];
|
||||
}
|
||||
} break;
|
||||
|
||||
|
||||
//for all other cases, do nothing
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
//Report to GA
|
||||
if (GAContextName)
|
||||
{
|
||||
NSString* GAReportString = (result == RBXAResultSuccess) ? @"Success" : [[RBXEventReporter sharedInstance] nameForError:errorName];
|
||||
[RobloxGoogleAnalytics setEventTracking:GAContextName
|
||||
withAction:GAReportString
|
||||
withLabel:[NSString stringWithFormat:@"%ld", (long)responseCode]
|
||||
withValue:0];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-(void) reportSessionFor:(ApplicationState) appState PlaceId:(NSInteger) idPlace
|
||||
{
|
||||
|
||||
switch (appState)
|
||||
{
|
||||
case APPLICATION_FRESH_START:
|
||||
{
|
||||
|
||||
NSString *gameStateString = [[NSUserDefaults standardUserDefaults] stringForKey:@"RobloxGameState"];
|
||||
NSString *appStateString = [[NSUserDefaults standardUserDefaults] stringForKey:@"RobloxAppState"];
|
||||
|
||||
if (gameStateString || appStateString)
|
||||
{
|
||||
NSString * crashStateString = [[NSUserDefaults standardUserDefaults] stringForKey:(gameStateString ? @"RobloxGameState" : @"RobloxAppState")];
|
||||
|
||||
[RobloxGoogleAnalytics debugCounterIncrement:crashStateString];
|
||||
|
||||
// create "terminated_gameStateString" string if needed
|
||||
if (appStateString && [appStateString isEqualToString:@"terminated"])
|
||||
{
|
||||
if (gameStateString)
|
||||
crashStateString = [NSString stringWithFormat:@"terminated_%@", gameStateString];
|
||||
}
|
||||
|
||||
NSString *appVersion = [[NSUserDefaults standardUserDefaults] stringForKey:@"LastRunningAppVersion"];
|
||||
if (!appVersion)
|
||||
appVersion = [RobloxInfo appVersion];
|
||||
|
||||
[RobloxGoogleAnalytics setEventTracking:@"CrashState"
|
||||
withAction:appVersion
|
||||
withLabel:crashStateString
|
||||
withValue:0];
|
||||
|
||||
// MemState reporting
|
||||
NSString *memMgrStateString = [[NSUserDefaults standardUserDefaults] stringForKey:@"RobloxMemMgrState"];
|
||||
if (memMgrStateString)
|
||||
{
|
||||
|
||||
[RobloxGoogleAnalytics setEventTracking:@"RobloxMemMgrState"
|
||||
withAction:appVersion
|
||||
withLabel:memMgrStateString
|
||||
withValue:0];
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"RobloxMemMgrState"];
|
||||
|
||||
if ([memMgrStateString isEqualToString:@"Bouncer"])
|
||||
[[RobloxMemoryManager sharedInstance] memBouncerCrashed];
|
||||
}
|
||||
|
||||
if (gameStateString)
|
||||
{
|
||||
NSInteger placeId, gamePlayTime = 0;
|
||||
BOOL hasPlayData = [self getPlayData:placeId PlayTimeSeconds:gamePlayTime CalculateNow:NO];
|
||||
|
||||
if (hasPlayData)
|
||||
{
|
||||
// Do not report to Stats if the Player did not crash, we do similar on Mac/PC/Android
|
||||
// App Crash will not be considered into MTBF
|
||||
[self pushSessionData:@APP_STATUS_CRASH PlaceId:placeId GamePlayTime:gamePlayTime];
|
||||
RBX::Analytics::EphemeralCounter::reportCounter("iOS-ROBLOXPlayer-Crash", 1, true);
|
||||
RBX::Analytics::EphemeralCounter::reportCounter("ROBLOXPlayer-Crash", 1, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
[self pushSessionData:@APP_STATUS_CRASH PlaceId:0 GamePlayTime:0];
|
||||
|
||||
|
||||
[RobloxGoogleAnalytics setEventTracking:@APP_STATUS_CRASH withAction:@"UserAgent" withLabel:[RobloxInfo getUserAgentString] withValue:0];
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
gameStateString = nil;
|
||||
appStateString = nil;
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"RobloxGameState"];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"RobloxAppState"];
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setObject:[RobloxInfo appVersion] forKey:@"LastRunningAppVersion"];
|
||||
|
||||
// Since we have reported the last session data time to clear it up.
|
||||
[self clearSession];
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(callTimerFn) object:nil];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case APPLICATION_ACTIVE:
|
||||
{
|
||||
NSInteger placeId, gamePlayTime = 0;
|
||||
BOOL hasPlayData = [self getPlayData:placeId PlayTimeSeconds:gamePlayTime CalculateNow:NO];
|
||||
|
||||
|
||||
if(hasPlayData)
|
||||
{
|
||||
NSString* eventType = [NSString stringWithFormat:@"%s", APP_STATUS_SUCCESS];
|
||||
[self pushSessionData:eventType PlaceId:placeId GamePlayTime:gamePlayTime];
|
||||
}
|
||||
|
||||
// Since we have reported the last session data time to clear it up.
|
||||
[self clearSession];
|
||||
|
||||
//Scenario where user was in game, double clicked the home buton & then came back quickly and still in Game.
|
||||
NSString *gameStateString = [[NSUserDefaults standardUserDefaults] stringForKey:@"RobloxGameState"];
|
||||
if([gameStateString isEqualToString:@"inGame"])
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setDouble:[[NSDate date] timeIntervalSince1970] forKey:@KEY_GAME_START_TIME];
|
||||
[[NSUserDefaults standardUserDefaults] setDouble:[[NSDate date] timeIntervalSince1970] forKey:@KEY_GAME_END_TIME];
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:placeId forKey:@KEY_PLACE_ID];
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case APPLICATION_BACKGROUND:
|
||||
{
|
||||
NSInteger placeId, gamePlayTime = 0;
|
||||
BOOL hasPlayData = [self getPlayData:placeId PlayTimeSeconds:gamePlayTime CalculateNow:YES];
|
||||
|
||||
// Report if we have gamePlayTime available
|
||||
// This will be a successful exit case & would never be a crash case
|
||||
// Report State, report successState state only if placeId is not zero & gamePlayTime is not zero
|
||||
if(hasPlayData && gamePlayTime)
|
||||
[self pushSessionData:@APP_STATUS_SUCCESS PlaceId:placeId GamePlayTime:gamePlayTime];
|
||||
|
||||
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(callTimerFn) object:nil];
|
||||
|
||||
// Since we have reported the last session data time to clear it up.
|
||||
[self clearSession];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case GAME_RUNNING:
|
||||
{
|
||||
if([[NSUserDefaults standardUserDefaults] objectForKey:@KEY_GAME_START_TIME])
|
||||
{
|
||||
int kiloBytesFree = freeMemory()/1024.0f;
|
||||
int kiloBytesUsed = usedMemory()/1024.0f;
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:kiloBytesFree forKey:@KEY_FREE_MEMORY];
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:kiloBytesUsed forKey:@KEY_USED_MEMORY];
|
||||
[[NSUserDefaults standardUserDefaults] setDouble:[[NSDate date] timeIntervalSince1970] forKey:@KEY_GAME_END_TIME];
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ENTER_GAME:
|
||||
{
|
||||
int kiloBytesFree = freeMemory()/1024.0f;
|
||||
int kiloBytesUsed = usedMemory()/1024.0f;
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:kiloBytesFree forKey:@KEY_FREE_MEMORY];
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:kiloBytesUsed forKey:@KEY_USED_MEMORY];
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setDouble:[[NSDate date] timeIntervalSince1970] forKey:@KEY_GAME_START_TIME];
|
||||
[[NSUserDefaults standardUserDefaults] setDouble:[[NSDate date] timeIntervalSince1970] forKey:@KEY_GAME_END_TIME];
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:idPlace forKey:@KEY_PLACE_ID];
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
|
||||
|
||||
iOSSettingsService * iosSettings = [[RobloxWebUtility sharedInstance] getCachediOSSettings];
|
||||
calculateSessionDataEveryMilliSeconds = iosSettings->GetValueCalculateSessionReportEveryMilliSeconds();
|
||||
|
||||
|
||||
// Keep recording Game End Time, as will be useful in the event of Crash
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
[self performSelector:@selector(callTimerFn) withObject:nil afterDelay:((NSTimeInterval)calculateSessionDataEveryMilliSeconds/1000)];
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case EXIT_GAME:
|
||||
case OUT_MEMORY_ON_LOAD:
|
||||
case OUT_MEMORY_IN_GAME:
|
||||
{
|
||||
if([[NSUserDefaults standardUserDefaults] objectForKey:@KEY_GAME_START_TIME])
|
||||
{
|
||||
int kiloBytesFree = freeMemory()/1024.0f;
|
||||
int kiloBytesUsed = usedMemory()/1024.0f;
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:kiloBytesFree forKey:@KEY_FREE_MEMORY];
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:kiloBytesUsed forKey:@KEY_USED_MEMORY];
|
||||
[[NSUserDefaults standardUserDefaults] setDouble:[[NSDate date] timeIntervalSince1970] forKey:@KEY_GAME_END_TIME];
|
||||
}
|
||||
|
||||
if (appState != EXIT_GAME) // It is not a Exit, but a memory warning & we are continuing to play the Game
|
||||
{
|
||||
int numOutOfMemWarning = [[NSUserDefaults standardUserDefaults] integerForKey:@KEY_NUM_MEM_WARNING];
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:++numOutOfMemWarning forKey:@KEY_NUM_MEM_WARNING];
|
||||
[[NSUserDefaults standardUserDefaults] setDouble:[[NSDate date] timeIntervalSince1970] forKey:@KEY_LAST_OUT_OF_MEM_TIME];
|
||||
}
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
|
||||
|
||||
|
||||
NSInteger placeId = 0;
|
||||
NSInteger gamePlayTime = 0;
|
||||
BOOL hasPlayData = [self getPlayData:placeId PlayTimeSeconds:gamePlayTime CalculateNow:YES];
|
||||
|
||||
|
||||
if (appState == EXIT_GAME) // It is a Clean Game Exit, Not a memory warning
|
||||
{
|
||||
if (hasPlayData)
|
||||
{
|
||||
[RobloxInfo reportMaxMemoryUsedForPlaceID:placeId];
|
||||
[self pushSessionData:@APP_STATUS_SUCCESS PlaceId:placeId GamePlayTime:gamePlayTime];
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RBX_NOTIFY_DID_LEAVE_GAME object:nil];
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(callTimerFn) object:nil];
|
||||
[self clearSession];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-(void) clearSession
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@KEY_GAME_START_TIME];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@KEY_GAME_END_TIME];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@KEY_PLACE_ID];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@KEY_FREE_MEMORY];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@KEY_USED_MEMORY];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@KEY_NUM_MEM_WARNING];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@KEY_LAST_OUT_OF_MEM_TIME];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"ExceptionName"];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"ExceptionDesc"];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"ExceptionReason"];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"ExceptionUserInfo"];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"ExceptionCallStackTop"];
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user