Bug 941685 - Ping metrics server with snippets impression data. r=bnicholson

This commit is contained in:
Margaret Leibovic 2013-12-02 16:54:21 -08:00
parent eb6892a43b
commit 73928f1858
2 changed files with 98 additions and 5 deletions

View File

@ -800,5 +800,8 @@ pref("browser.snippets.updateInterval", 86400);
// URL used to check for user's country code
pref("browser.snippets.geoUrl", "https://geo.mozilla.org/country.json");
// URL used to ping metrics with stats about which snippets have been shown
pref("browser.snippets.statsUrl", "https://snippets-stats.mozilla.org/mobile");
// This pref requires a restart to take effect.
pref("browser.snippets.enabled", false);

View File

@ -9,6 +9,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Home", "resource://gre/modules/Home.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyGetter(this, "gEncoder", function() { return new gChromeWin.TextEncoder(); });
XPCOMUtils.defineLazyGetter(this, "gDecoder", function() { return new gChromeWin.TextDecoder(); });
@ -18,6 +19,9 @@ const SNIPPETS_ENABLED = Services.prefs.getBoolPref("browser.snippets.enabled");
// URL to fetch snippets, in the urlFormatter service format.
const SNIPPETS_UPDATE_URL_PREF = "browser.snippets.updateUrl";
// URL to send stats data to metrics.
const SNIPPETS_STATS_URL_PREF = "browser.snippets.statsUrl";
// URL to fetch country code, a value that's cached and refreshed once per month.
const SNIPPETS_GEO_URL_PREF = "browser.snippets.geoUrl";
@ -38,6 +42,20 @@ XPCOMUtils.defineLazyGetter(this, "gSnippetsURL", function() {
return Services.urlFormatter.formatURL(updateURL);
});
// Where we cache snippets data
XPCOMUtils.defineLazyGetter(this, "gSnippetsPath", function() {
return OS.Path.join(OS.Constants.Path.profileDir, "snippets.json");
});
XPCOMUtils.defineLazyGetter(this, "gStatsURL", function() {
return Services.prefs.getCharPref(SNIPPETS_STATS_URL_PREF);
});
// Where we store stats about which snippets have been shown
XPCOMUtils.defineLazyGetter(this, "gStatsPath", function() {
return OS.Path.join(OS.Constants.Path.profileDir, "snippets-stats.txt");
});
XPCOMUtils.defineLazyGetter(this, "gGeoURL", function() {
return Services.prefs.getCharPref(SNIPPETS_GEO_URL_PREF);
});
@ -109,9 +127,8 @@ function updateSnippets() {
* @param response responseText returned from snippets server
*/
function cacheSnippets(response) {
let path = OS.Path.join(OS.Constants.Path.profileDir, "snippets.json");
let data = gEncoder.encode(response);
let promise = OS.File.writeAtomic(path, data, { tmpPath: path + ".tmp" });
let promise = OS.File.writeAtomic(gSnippetsPath, data, { tmpPath: gSnippetsPath + ".tmp" });
promise.then(null, e => Cu.reportError("Error caching snippets: " + e));
}
@ -119,8 +136,7 @@ function cacheSnippets(response) {
* Loads snippets from cached `snippets.json`.
*/
function loadSnippetsFromCache() {
let path = OS.Path.join(OS.Constants.Path.profileDir, "snippets.json");
let promise = OS.File.read(path);
let promise = OS.File.read(gSnippetsPath);
promise.then(array => updateBanner(gDecoder.decode(array)), e => {
// If snippets.json doesn't exist, update data from the server.
if (e instanceof OS.File.Error && e.becauseNoSuchFile) {
@ -167,7 +183,10 @@ function updateBanner(response) {
gChromeWin.BrowserApp.addTab(message.url);
},
onshown: function() {
// XXX: 10% of the time, let the metrics server know which message was shown (bug 937373)
// 10% of the time, record the snippet id and a timestamp
if (Math.random() < .1) {
writeStat(message.id, new Date().toISOString());
}
}
});
// Keep track of the message we added so that we can remove it later.
@ -175,6 +194,76 @@ function updateBanner(response) {
});
}
/**
* Appends snippet id and timestamp to the end of `snippets-stats.txt`.
*
* @param snippetId unique id for snippet, sent from snippets server
* @param timestamp in ISO8601
*/
function writeStat(snippetId, timestamp) {
let data = gEncoder.encode(snippetId + "," + timestamp + ";");
Task.spawn(function() {
try {
let file = yield OS.File.open(gStatsPath, { append: true, write: true });
try {
yield file.write(data);
} finally {
yield file.close();
}
} catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
// If the file doesn't exist yet, create it.
yield OS.File.writeAtomic(gStatsPath, data, { tmpPath: gStatsPath + ".tmp" });
}
}).then(null, e => Cu.reportError("Error writing snippets stats: " + e));
}
/**
* Reads snippets stats data from `snippets-stats.txt` and sends the data to metrics.
*/
function sendStats() {
let promise = OS.File.read(gStatsPath);
promise.then(array => sendStatsRequest(gDecoder.decode(array)), e => {
if (e instanceof OS.File.Error && e.becauseNoSuchFile) {
// If the file doesn't exist, there aren't any stats to send.
} else {
Cu.reportError("Error eading snippets stats: " + e);
}
});
}
/**
* Sends stats to metrics about which snippets have been shown.
* Appends snippet ids and timestamps as parameters to a GET request.
* e.g. https://snippets-stats.mozilla.org/mobile?s1=3825&t1=2013-11-17T18:27Z&s2=6326&t2=2013-11-18T18:27Z
*
* @param data contents of stats data file
*/
function sendStatsRequest(data) {
let params = [];
let stats = data.split(";");
// The last item in the array will be an empty string, so stop before then.
for (let i = 0; i < stats.length - 1; i++) {
let stat = stats[i].split(",");
params.push("s" + i + "=" + encodeURIComponent(stat[0]));
params.push("t" + i + "=" + encodeURIComponent(stat[1]));
}
let url = gStatsURL + "?" + params.join("&");
// Remove the file after succesfully sending the data.
_httpGetRequest(url, removeStats);
}
/**
* Removes text file where we store snippets stats.
*/
function removeStats() {
let promise = OS.File.remove(gStatsPath);
promise.then(null, e => Cu.reportError("Error removing snippets stats: " + e));
}
/**
* Helper function to make HTTP GET requests.
*
@ -227,6 +316,7 @@ Snippets.prototype = {
return;
}
update();
sendStats();
}
};