* VRFS-1574 - network test, audio test GA tracking; VRFS-1653 - fixed bug on initial connect timeout causing runaway connections

This commit is contained in:
Seth Call 2014-06-18 20:59:52 -05:00
parent a38174b6e4
commit 4425d66484
12 changed files with 249 additions and 28 deletions

View File

@ -85,6 +85,7 @@
// handles logic if the websocket connection closes, and if it was in error then also prompt for reconnect
function closedCleanup(in_error) {
server.connected = false;
// stop future heartbeats
if (heartbeatInterval != null) {
@ -98,14 +99,7 @@
heartbeatAckCheckInterval = null;
}
if (server.connected) {
server.connected = false;
if (app.clientUpdating) {
// we don't want to do a 'cover the whole screen' dialog
// because the client update is already showing.
return;
}
if (!server.reconnecting) {
server.reconnecting = true;
var result = activeElementEvent('beforeDisconnect');
@ -240,7 +234,7 @@
var start = new Date().getTime();
server.connect()
.done(function () {
guardAgainstRapidTransition(start, performReconnect);
guardAgainstRapidTransition(start, finishReconnect);
})
.fail(function () {
guardAgainstRapidTransition(start, closedOnReconnectAttempt);
@ -252,7 +246,7 @@
failedReconnect();
}
function performReconnect() {
function finishReconnect() {
if(!clientClosedConnection) {
lastDisconnectedReason = 'WEBSOCKET_CLOSED_REMOTELY'
@ -675,7 +669,6 @@
}
this.initialize = initialize;
this.initiateReconnect = initiateReconnect;
return this;
}

View File

@ -85,10 +85,33 @@
google: 'google',
};
var audioTestFailReasons = {
latency : 'Latency',
ioVariance : 'ioVariance',
ioTarget : 'ioTarget'
}
var audioTestDataReasons = {
pass : 'Pass',
latencyFail : 'LatencyFail',
ioVarianceFail : 'ioVarianceFail',
ioTargetFail : 'ioTargetFail'
}
var networkTestFailReasons = {
stun : 'STUN',
bandwidth : 'Bandwidth',
packetRate : 'PacketRate',
jitter : 'Jitter',
jamerror : 'ServiceError',
noNetwork : 'NoNetwork'
}
var categories = {
register : "Register",
download : "DownloadClient",
audioTest : "AudioTest",
audioTestData : 'AudioTestData',
trackConfig : "AudioTrackConfig",
networkTest : "NetworkTest",
sessionCount : "SessionCount",
@ -105,7 +128,6 @@
jkFollow : 'jkFollow',
jkFavorite : 'jkFavorite',
jkComment : 'jkComment'
};
function translatePlatformForGA(platform) {
@ -289,18 +311,48 @@
context.ga('send', 'event', categories.networkTest, 'Passed', normalizedPlatform, numUsers);
}
function trackNetworkTestFailure(reason, data) {
assertOneOf(reason, networkTestFailReasons);
context.ga('send', 'event', categories.networkTest, 'Failed', reason, data);
}
function trackAudioTestCompletion(platform) {
var normalizedPlatform = translatePlatformForGA(platform);
context.ga('send', 'event', categories.audioTest, 'Passed', normalizedPlatform);
}
function trackAudioTestFailure(platform, reason, data) {
assertOneOf(reason, audioTestFailReasons);
var normalizedPlatform = translatePlatformForGA(platform);
if(normalizedPlatform == "Windows") {
var action = "FailedWin";
}
else if(normalizedPlatform == "Mac") {
var action = "FailedMac";
}
else {
var action = "FailedLinux";
}
context.ga('send', 'event', categories.audioTest, action, reason, data);
}
function trackConfigureTracksCompletion(platform) {
var normalizedPlatform = translatePlatformForGA(platform);
context.ga('send', 'event', categories.trackConfig, 'Passed', normalizedPlatform);
}
function trackAudioTestData(uniqueDeviceName, reason, data) {
assertOneOf(reason, audioTestDataReasons);
context.ga('send', 'event', categories.audioTestData, uniqueDeviceName, reason, data);
}
var GA = {};
GA.Categories = categories;
GA.SessionCreationTypes = sessionCreationTypes;
@ -310,11 +362,17 @@
GA.RecordingActions = recordingActions;
GA.BandActions = bandActions;
GA.JKSocialTargets = jkSocialTargets;
GA.AudioTestFailReasons = audioTestFailReasons;
GA.AudioTestDataReasons = audioTestDataReasons;
GA.NetworkTestFailReasons = networkTestFailReasons;
GA.trackRegister = trackRegister;
GA.trackDownload = trackDownload;
GA.trackFTUECompletion = trackFTUECompletion;
GA.trackNetworkTest = trackNetworkTest;
GA.trackNetworkTestFailure = trackNetworkTestFailure;
GA.trackAudioTestCompletion = trackAudioTestCompletion;
GA.trackAudioTestFailure = trackAudioTestFailure;
GA.trackAudioTestData = trackAudioTestData;
GA.trackConfigureTracksCompletion = trackConfigureTracksCompletion;
GA.trackSessionCount = trackSessionCount;
GA.trackSessionMusicians = trackSessionMusicians;

View File

@ -225,36 +225,42 @@
context.JK.AUDIO_DEVICE_BEHAVIOR = {
MacOSX_builtin: {
display: 'MacOSX Built-In',
shortName: 'Built-In',
videoURL: "https://www.youtube.com/watch?v=7-9PW50ygHk",
showKnobs: false,
showASIO: false
},
MacOSX_interface: {
display: 'MacOSX external interface',
shortName: 'External',
videoURL: "https://www.youtube.com/watch?v=7BLld6ogm14",
showKnobs: false,
showASIO: false
},
Win32_wdm: {
display: 'Windows WDM',
shortName : 'WDM',
videoURL: "https://www.youtube.com/watch?v=L36UBkAV14c",
showKnobs: true,
showASIO: false
},
Win32_asio: {
display: 'Windows ASIO',
shortName : 'ASIO',
videoURL: "https://www.youtube.com/watch?v=PGUmISTVVMY",
showKnobs: false,
showASIO: true
},
Win32_asio4all: {
display: 'Windows ASIO4ALL',
shortName : 'ASIO4ALL',
videoURL: "https://www.youtube.com/watch?v=PGUmISTVVMY",
showKnobs: false,
showASIO: true
},
Linux: {
display: 'Linux',
shortName : 'linux',
videoURL: undefined,
showKnobs: true,
showASIO: false

View File

@ -46,6 +46,11 @@
var inGearWizard = false;
var operatingSystem = null;
// these try to make it such that we only pass a NetworkTest Pass/Failed one time in a new user flow
var trackedPass = false;
var lastNetworkFailure = null;
var bandwidthSamples = [];
var NETWORK_TEST_START = 'network_test.start';
var NETWORK_TEST_DONE = 'network_test.done';
var NETWORK_TEST_FAIL = 'network_test.fail';
@ -68,7 +73,27 @@
}
}
// this averages bandwidthSamples; this method is meant just for GA data
function avgBandwidth(num_others) {
if(bandwidthSamples.length == 0) {
return 0;
}
else {
var total = 0;
context._.each(bandwidthSamples, function(sample) {
total += (sample * num_others * 400 * (100 + 28)); // sample is a percentage of 400. So sample * 400 gives us how many packets/sec. 100 is payload; 28 is UDP+ETHERNET overhead, to give us bandwidth
})
return total / bandwidthSamples.length;
}
}
function reset() {
trackedPass = false;
lastNetworkFailure = null;
resetTestState();
}
function resetTestState() {
serverClientId = '';
scoring = false;
numClientsToTest = STARTING_NUM_CLIENTS;
@ -79,6 +104,8 @@
$testText.empty();
$subscore.empty();
$currentScore.width(0);
bandwidthSamples = [];
}
function renderStartTest() {
@ -109,6 +136,17 @@
return '';
}
}
function getLastNetworkFailure() {
return lastNetworkFailure;
}
function storeLastNetworkFailure(reason, data) {
if(!trackedPass) {
lastNetworkFailure = {reason:reason, data:data};
}
}
function testFinished() {
var attempt = getCurrentAttempt();
@ -125,7 +163,14 @@
if(!testSummary.final.num_clients) {
testSummary.final.num_clients = attempt.num_clients;
}
context.JK.GA.trackNetworkTest(context.JK.detectOS(), testSummary.final.num_clients);
// context.jamClient.GetNetworkTestScore() == 0 is a rough approximation if the user has passed the FTUE before
if(inGearWizard || context.jamClient.GetNetworkTestScore() == 0) {
trackedPass = true;
lastNetworkFailure = null;
context.JK.GA.trackNetworkTest(context.JK.detectOS(), testSummary.final.num_clients);
}
context.jamClient.SetNetworkTestScore(attempt.num_clients);
if(testSummary.final.num_clients == 2) {
$testResults.addClass('acceptable');
@ -138,53 +183,65 @@
else if(reason == "minimum_client_threshold") {
context.jamClient.SetNetworkTestScore(0);
renderStopTest('', "We're sorry, but your router and Internet service will not effectively support JamKazam sessions. Please click the HELP button for more information.")
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.bandwidth, avgBandwidth(attempt.num_clients - 1));
}
else if(reason == "unreachable" || reason == "no-transmit") {
context.jamClient.SetNetworkTestScore(0);
renderStopTest('', "We're sorry, but your router will not support JamKazam in its current configuration. Please click the HELP button for more information.");
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.stun, attempt.num_clients);
}
else if(reason == "internal_error") {
context.JK.alertSupportedNeeded("The JamKazam client software had an unexpected problem while scoring your Internet connection.");
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
else if(reason == "remote_peer_cant_test") {
context.JK.alertSupportedNeeded("The JamKazam service is experiencing technical difficulties.");
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
else if(reason == "server_comm_timeout") {
context.JK.alertSupportedNeeded("Communication with the JamKazam network service has timed out." + appendContextualStatement());
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
else if(reason == 'backend_gone') {
context.JK.alertSupportedNeeded("The JamKazam client is experiencing technical difficulties.");
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
else if(reason == "invalid_response") {
context.JK.alertSupportedNeeded("The JamKazam client software had an unexpected problem while scoring your Internet connection.<br/><br/>Reason: " + attempt.backend_data.reason + '.');
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
else if(reason == 'no_servers') {
context.JK.alertSupportedNeeded("No network test servers are available." + appendContextualStatement());
renderStopTest('', '');
testedSuccessfully = true;
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
else if(reason == 'no_network') {
context.JK.Banner.showAlert("Please try again later. Your network appears down.");
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.noNetwork);
}
else if(reason == "rest_api_error") {
context.JK.alertSupportedNeeded("Unable to acquire a network test server." + appendContextualStatement());
testedSuccessfully = true;
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
else if(reason == "timeout") {
context.JK.alertSupportedNeeded("Communication with the JamKazam network service timed out." + appendContextualStatement());
testedSuccessfully = true;
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
else {
context.JK.alertSupportedNeeded("The JamKazam client software had a logic error while scoring your Internet connection.");
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
numClientsToTest = STARTING_NUM_CLIENTS;
@ -274,7 +331,7 @@
}
}
reset();
resetTestState();
scoring = true;
$self.triggerHandler(NETWORK_TEST_START);
renderStartTest();
@ -312,6 +369,7 @@
}
function updateProgress(throughput, showSubscore) {
var width = throughput * 100;
$currentScore.stop().data('showSubscore', showSubscore);
@ -388,6 +446,8 @@
// take the lower
var throughput= data.downthroughput < data.upthroughput ? data.downthroughput : data.upthroughput;
bandwidthSamples.push(data.upthroughput);
updateProgress(throughput, true);
}
}
@ -539,6 +599,7 @@
this.initialize = initialize;
this.reset = reset;
this.cancel = cancel;
this.getLastNetworkFailure = getLastNetworkFailure;
this.NETWORK_TEST_START = NETWORK_TEST_START;
this.NETWORK_TEST_DONE = NETWORK_TEST_DONE;

View File

@ -105,7 +105,30 @@
}
function reportFailureAnalytics() {
// on cancel, see if the user is leaving without having passed the audio test; if so, report it
var lastAnalytics = stepSelectGear.getLastAudioTestFailAnalytics();
if(lastAnalytics) {
logger.debug("reporting audio test failed")
context.JK.GA.trackAudioTestFailure(lastAnalytics.platform, lastAnalytics.reason, lastAnalytics.data);
}
else {
logger.debug("audiotest failure: nothing to report");
}
var lastAnalytics = stepNetworkTest.getLastNetworkFailAnalytics();
if(lastAnalytics) {
logger.debug("reporting network test failed");
context.JK.GA.trackNetworkTestFailure(lastAnalytics.reason, lastAnalytics.data);
}
else {
logger.debug("network test failure: nothing to report");
}
}
function onCanceled() {
reportFailureAnalytics();
if (app.cancelFtue) {
app.cancelFtue();
app.afterFtue = null;
@ -116,6 +139,8 @@
}
function onClosed() {
reportFailureAnalytics();
if (app.afterFtue) {
// If there's a function to invoke, invoke it.
app.afterFtue();

View File

@ -11,6 +11,10 @@
var $step = null;
function getLastNetworkFailAnalytics() {
return networkTest.getLastNetworkFailure();
}
function networkTestDone() {
updateButtons();
}
@ -62,6 +66,7 @@
this.beforeHide = beforeHide;
this.beforeShow = beforeShow;
this.initialize = initialize;
this.getLastNetworkFailAnalytics = getLastNetworkFailAnalytics;
return this;
}

View File

@ -20,6 +20,10 @@
var gearTest = new context.JK.GearTest(app);
var loopbackShowing = false;
// the goal of lastFailureAnalytics and trackedPass are to send only a single AudioTest 'Pass' or 'Failed' event, per FTUE wizard open/close
var lastFailureAnalytics = {};
var trackedPass = false;
var $watchVideoInput = null;
var $watchVideoOutput = null;
var $audioInput = null;
@ -733,7 +737,7 @@
// * reuse IO score if it was good/acceptable
// * rescore IO if it was bad or skipped from previous try
function attemptScore(refocused) {
gearTest.attemptScore(refocused);
gearTest.attemptScore(selectedDeviceInfo, refocused);
}
function initializeAudioInputChanged() {
@ -753,9 +757,38 @@
gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true);
}
function getLastAudioTestFailAnalytics() {
return lastFailureAnalytics;
}
function storeLastFailureForAnalytics(platform, reason, data) {
if(!trackedPass) {
lastFailureAnalytics = {
platform: platform,
reason: reason,
data: data
}
}
}
function onGearTestFail(e, data) {
renderScoringStopped();
gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true);
if(data.reason == "latency") {
storeLastFailureForAnalytics(context.JK.detectOS(), context.JK.GA.AudioTestFailReasons.latency, data.latencyScore);
}
else if(data.reason = "io") {
if(data.ioTarget == 'bad') {
storeLastFailureForAnalytics(context.JK.detectOS(), context.JK.GA.AudioTestFailReasons.ioTarget, data.ioTargetScore);
}
else {
storeLastFailureForAnalytics(context.JK.detectOS(), context.JK.GA.AudioTestFailReasons.ioVariance, data.ioVarianceScore);
}
}
else {
logger.error("unknown reason in onGearTestFail: " + data.reason)
}
}
function onGearTestInvalidated(e, data) {
@ -797,8 +830,10 @@
return false;
}
else {
savedProfile = true;
context.JK.GA.trackAudioTestCompletion(context.JK.detectOS());
trackedPass = true;
lastFailureAnalytics = null;
savedProfile = true;
return true;
}
}
@ -822,6 +857,11 @@
initializeResync();
}
function beforeWizardShow() {
lastFailureAnalytics = null;
trackedPass = false;
}
function beforeShow() {
$(window).on('focus', onFocus);
initializeNextButtonState();
@ -876,10 +916,12 @@
.on(gearTest.GEAR_TEST_INVALIDATED_ASYNC, onGearTestInvalidated)
}
this.getLastAudioTestFailAnalytics = getLastAudioTestFailAnalytics;
this.handleNext = handleNext;
this.newSession = newSession;
this.beforeShow = beforeShow;
this.beforeHide = beforeHide;
this.beforeWizardShow = beforeWizardShow;
this.initialize = initialize;
self = this;

View File

@ -17,6 +17,7 @@
var lastSavedTime = new Date();
// this should be marked TRUE when the backend sends an invalid_audio_device alert
var asynchronousInvalidDevice = false;
var selectedDeviceInfo = null;
var $scoreReport = null;
@ -99,7 +100,7 @@
$self.triggerHandler(GEAR_TEST_DONE)
}
else {
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'io'});
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'io', ioTarget: medianIOClass, ioTargetScore: median, ioVariance: stdIOClass, ioVarianceScore: std});
}
}
@ -133,11 +134,12 @@
// on refocus=true:
// * reuse IO score if it was good/acceptable
// * rescore IO if it was bad or skipped from previous try
function attemptScore(refocused) {
function attemptScore(_selectedDeviceInfo, refocused) {
if(scoring) {
logger.debug("gear-test: already scoring");
return;
}
selectedDeviceInfo = _selectedDeviceInfo;
scoring = true;
asynchronousInvalidDevice = false;
$self.triggerHandler(GEAR_TEST_START);
@ -206,7 +208,7 @@
}
else {
scoring = false;
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'latency'})
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'latency', latencyScore: latencyScore.latency})
}
})
}, 250);
@ -359,6 +361,18 @@
$ioCountdownSecs.text(secondsLeft);
}
function uniqueDeviceName() {
try {
return selectedDeviceInfo.input.info.displayName + '(' + selectedDeviceInfo.input.behavior.shortName + ')' + '-' +
selectedDeviceInfo.output.info.displayName + '(' + selectedDeviceInfo.output.behavior.shortName + ')' + '-' +
context.JK.GetOSAsString();
}
catch(e){
logger.error("unable to devise unique device name for stats: " + e.toString());
return "Unknown";
}
}
function handleUI($testResults) {
if(!$testResults.is('.ftue-box.results')) {
@ -416,6 +430,9 @@
function onGearTestDone(e, data) {
$resultsText.attr('scored', 'complete');
context.JK.GA.trackAudioTestData(uniqueDeviceName(), context.JK.GA.AudioTestDataReasons.pass, latencyScore);
rest.userCertifiedGear({success: true, client_id: app.clientId, audio_latency: getLatencyScore()});
}
@ -427,6 +444,21 @@
}
rest.userCertifiedGear({success: false});
if(data.reason == "latency") {
context.JK.GA.trackAudioTestData(uniqueDeviceName(), context.JK.GA.AudioTestDataReasons.latencyFail, data.latencyScore);
}
else if(data.reason = "io") {
if(data.ioTarget == 'bad') {
context.JK.GA.trackAudioTestData(uniqueDeviceName(), context.JK.GA.AudioTestDataReasons.ioTargetFail, data.ioTargetScore);
}
else {
context.JK.GA.trackAudioTestData(uniqueDeviceName(), context.JK.GA.AudioTestDataReasons.ioVarianceFail, data.ioVarianceScore);
}
}
else {
logger.error("unknown reason in onGearTestFail: " + data.reason)
}
}
$self

View File

@ -48,7 +48,7 @@
function attemptScore() {
gearTest.attemptScore();
gearTest.attemptScore(selectedDeviceInfo);
}

View File

@ -139,6 +139,13 @@
function onBeforeShow(args) {
context._.each(STEPS, function(step) {
// let every step know the wizard is being shown
if(step.beforeWizardShow) {
step.beforeWizardShow(args);
}
});
$currentWizardStep = null;
previousStep = null;

View File

@ -258,10 +258,6 @@
var testBridgeScreen = new JK.TestBridgeScreen(JK.app);
testBridgeScreen.initialize();
if(!connected) {
jamServer.initiateReconnect(null, true);
}
JK.app.initialRouting();
JK.hideCurtain(300);
}

View File

@ -42,10 +42,6 @@
function _initAfterConnect(connected) {
if (this.didInitAfterConnect) return;
this.didInitAfterConnect = true
if(!connected) {
jamServer.initiateReconnect(null, true);
}
}
JK.app = JK.JamKazam();