Bug 1607908 - Add support for sending multiple pings via a single invocation of the pingsender r=chutten

Differential Revision: https://phabricator.services.mozilla.com/D59705

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Gabriele Svelto 2020-01-16 19:09:49 +00:00
Родитель 13b7594c46
Коммит 86e7bf1e46
3 изменённых файлов: 164 добавлений и 100 удалений

Просмотреть файл

@ -312,20 +312,22 @@ var TelemetrySend = {
* This method is currently exposed here only for testing purposes as it's * This method is currently exposed here only for testing purposes as it's
* only used internally. * only used internally.
* *
* @param {String} aUrl The telemetry server URL * @param {Array}<Object> pings An array of objects holding url / path pairs
* @param {String} aPingFilePath The path to the file holding the ping * for each ping to be sent. The URL represent the telemetry server the
* contents, if if sent successfully the pingsender will delete it. * ping will be sent to and the path points to the ping data. The ping
* data files will be deleted if the pings have been submitted
* successfully.
* @param {callback} observer A function called with parameters * @param {callback} observer A function called with parameters
(subject, topic, data) and a topic of "process-finished" or * (subject, topic, data) and a topic of "process-finished" or
"process-failed" after pingsender completion. * "process-failed" after pingsender completion.
* *
* @throws NS_ERROR_FAILURE if we couldn't find or run the pingsender * @throws NS_ERROR_FAILURE if we couldn't find or run the pingsender
* executable. * executable.
* @throws NS_ERROR_NOT_IMPLEMENTED on Android as the pingsender is not * @throws NS_ERROR_NOT_IMPLEMENTED on Android as the pingsender is not
* available. * available.
*/ */
testRunPingSender(url, pingPath, observer) { testRunPingSender(pings, observer) {
return TelemetrySendImpl.runPingSender(url, pingPath, observer); return TelemetrySendImpl.runPingSender(pings, observer);
}, },
}; };
@ -951,7 +953,7 @@ var TelemetrySendImpl = {
); );
try { try {
const pingPath = OS.Path.join(TelemetryStorage.pingDirectoryPath, pingId); const pingPath = OS.Path.join(TelemetryStorage.pingDirectoryPath, pingId);
this.runPingSender(submissionURL, pingPath); this.runPingSender([{ url: submissionURL, path: pingPath }]);
} catch (e) { } catch (e) {
this._log.error("_sendWithPingSender - failed to submit ping", e); this._log.error("_sendWithPingSender - failed to submit ping", e);
} }
@ -1545,7 +1547,7 @@ var TelemetrySendImpl = {
}; };
}, },
runPingSender(url, pingPath, observer) { runPingSender(pings, observer) {
if (AppConstants.platform === "android") { if (AppConstants.platform === "android") {
throw Cr.NS_ERROR_NOT_IMPLEMENTED; throw Cr.NS_ERROR_NOT_IMPLEMENTED;
} }
@ -1556,12 +1558,13 @@ var TelemetrySendImpl = {
let exe = Services.dirsvc.get("GreBinD", Ci.nsIFile); let exe = Services.dirsvc.get("GreBinD", Ci.nsIFile);
exe.append(exeName); exe.append(exeName);
let params = pings.flatMap(ping => [ping.url, ping.path]);
let process = Cc["@mozilla.org/process/util;1"].createInstance( let process = Cc["@mozilla.org/process/util;1"].createInstance(
Ci.nsIProcess Ci.nsIProcess
); );
process.init(exe); process.init(exe);
process.startHidden = true; process.startHidden = true;
process.noShell = true; process.noShell = true;
process.runAsync([url, pingPath], 2, observer); process.runAsync(params, params.length, observer);
}, },
}; };

Просмотреть файл

@ -9,6 +9,8 @@
#include <fstream> #include <fstream>
#include <iomanip> #include <iomanip>
#include <string> #include <string>
#include <vector>
#include <zlib.h> #include <zlib.h>
#include "pingsender.h" #include "pingsender.h"
@ -16,6 +18,7 @@
using std::ifstream; using std::ifstream;
using std::ios; using std::ios;
using std::string; using std::string;
using std::vector;
namespace PingSender { namespace PingSender {
@ -56,36 +59,6 @@ std::string GenerateDateHeader() {
return string(buffer); return string(buffer);
} }
/**
* Read the ping contents from the specified file
*/
static std::string ReadPing(const string& aPingPath) {
string ping;
ifstream file;
file.open(aPingPath.c_str(), ios::in | ios::binary);
if (!file.is_open()) {
PINGSENDER_LOG("ERROR: Could not open ping file\n");
return "";
}
do {
char buff[4096];
file.read(buff, sizeof(buff));
if (file.bad()) {
PINGSENDER_LOG("ERROR: Could not read ping contents\n");
return "";
}
ping.append(buff, file.gcount());
} while (!file.eof());
return ping;
}
std::string GzipCompress(const std::string& rawData) { std::string GzipCompress(const std::string& rawData) {
z_stream deflater = {}; z_stream deflater = {};
@ -153,33 +126,25 @@ std::string GzipCompress(const std::string& rawData) {
return gzipData; return gzipData;
} }
} // namespace PingSender class Ping {
public:
Ping(const string& aUrl, const string& aPath) : mUrl(aUrl), mPath(aPath) {}
bool Send() const;
bool Delete() const;
using namespace PingSender; private:
string Read() const;
int main(int argc, char* argv[]) { const string mUrl;
string url; const string mPath;
string pingPath; };
if (argc == 3) { bool Ping::Send() const {
url = argv[1]; string ping(Read());
pingPath = argv[2];
} else {
PINGSENDER_LOG(
"Usage: pingsender URL PATH\n"
"Send the payload stored in PATH to the specified URL using "
"an HTTP POST message\n"
"then delete the file after a successful send.\n");
return EXIT_FAILURE;
}
ChangeCurrentWorkingDirectory(pingPath);
string ping(ReadPing(pingPath));
if (ping.empty()) { if (ping.empty()) {
PINGSENDER_LOG("ERROR: Ping payload is empty\n"); PINGSENDER_LOG("ERROR: Ping payload is empty\n");
return EXIT_FAILURE; return false;
} }
// Compress the ping using gzip. // Compress the ping using gzip.
@ -190,18 +155,80 @@ int main(int argc, char* argv[]) {
// it compressed. // it compressed.
if (gzipPing.empty()) { if (gzipPing.empty()) {
PINGSENDER_LOG("ERROR: Ping compression failed\n"); PINGSENDER_LOG("ERROR: Ping compression failed\n");
return false;
}
if (!Post(mUrl, gzipPing)) {
return false;
}
return true;
}
bool Ping::Delete() const {
return !mPath.empty() && !std::remove(mPath.c_str());
}
string Ping::Read() const {
string ping;
ifstream file;
file.open(mPath.c_str(), ios::in | ios::binary);
if (!file.is_open()) {
PINGSENDER_LOG("ERROR: Could not open ping file\n");
return "";
}
do {
char buff[4096];
file.read(buff, sizeof(buff));
if (file.bad()) {
PINGSENDER_LOG("ERROR: Could not read ping contents\n");
return "";
}
ping.append(buff, file.gcount());
} while (!file.eof());
return ping;
}
} // namespace PingSender
using namespace PingSender;
int main(int argc, char* argv[]) {
vector<Ping> pings;
if ((argc >= 3) && ((argc - 1) % 2 == 0)) {
for (int i = 1; i < argc; i += 2) {
Ping ping(argv[i], argv[i + 1]);
pings.push_back(ping);
}
} else {
PINGSENDER_LOG(
"Usage: pingsender URL1 PATH1 URL2 PATH2 ...\n"
"Send the payloads stored in PATH<n> to the specified URL<n> using an "
"HTTP POST\nmessage for each payload then delete the file after a "
"successful send.\n");
return EXIT_FAILURE; return EXIT_FAILURE;
} }
if (!Post(url, gzipPing)) { ChangeCurrentWorkingDirectory(argv[2]);
for (const auto& ping : pings) {
if (!ping.Send()) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
// If the ping was successfully sent, delete the file. if (!ping.Delete()) {
if (!pingPath.empty() && std::remove(pingPath.c_str())) { PINGSENDER_LOG("ERROR: Could not delete the ping file\n");
// We failed to remove the pending ping file.
return EXIT_FAILURE; return EXIT_FAILURE;
} }
}
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

Просмотреть файл

@ -16,6 +16,38 @@ ChromeUtils.import("resource://gre/modules/TelemetryStorage.jsm", this);
ChromeUtils.import("resource://gre/modules/TelemetryUtils.jsm", this); ChromeUtils.import("resource://gre/modules/TelemetryUtils.jsm", this);
ChromeUtils.import("resource://gre/modules/Timer.jsm", this); ChromeUtils.import("resource://gre/modules/Timer.jsm", this);
function generateTestPingData() {
return {
type: "test-pingsender-type",
id: TelemetryUtils.generateUUID(),
creationDate: new Date().toISOString(),
version: 4,
payload: {
dummy: "stuff",
},
};
}
function testSendingPings(pingPaths) {
const url = "http://localhost:" + PingServer.port + "/submit/telemetry/";
const pings = pingPaths.map(path => {
return {
url,
path,
};
});
TelemetrySend.testRunPingSender(pings, (_, topic, __) => {
switch (topic) {
case "process-finished": // finished indicates an exit code of 0
Assert.ok(true, "Pingsender should be able to post to localhost");
break;
case "process-failed": // failed indicates an exit code != 0
Assert.ok(false, "Pingsender should be able to post to localhost");
break;
}
});
}
/** /**
* Wait for a ping file to be deleted from the pending pings directory. * Wait for a ping file to be deleted from the pending pings directory.
*/ */
@ -49,15 +81,7 @@ add_task(async function setup() {
add_task(async function test_pingSender() { add_task(async function test_pingSender() {
// Generate a new ping and save it among the pending pings. // Generate a new ping and save it among the pending pings.
const data = { const data = generateTestPingData();
type: "test-pingsender-type",
id: TelemetryUtils.generateUUID(),
creationDate: new Date(1485810000).toISOString(),
version: 4,
payload: {
dummy: "stuff",
},
};
await TelemetryStorage.savePing(data, true); await TelemetryStorage.savePing(data, true);
// Get the local path of the saved ping. // Get the local path of the saved ping.
@ -83,8 +107,8 @@ add_task(async function test_pingSender() {
// Try to send the ping twice using the pingsender (we expect 404 both times). // Try to send the ping twice using the pingsender (we expect 404 both times).
const errorUrl = const errorUrl =
"http://localhost:" + failingServer.identity.primaryPort + "/lookup_fail"; "http://localhost:" + failingServer.identity.primaryPort + "/lookup_fail";
TelemetrySend.testRunPingSender(errorUrl, pingPath); TelemetrySend.testRunPingSender([{ url: errorUrl, path: pingPath }]);
TelemetrySend.testRunPingSender(errorUrl, pingPath); TelemetrySend.testRunPingSender([{ url: errorUrl, path: pingPath }]);
// Wait until we hit the 404 server twice. After that, make sure that the ping // Wait until we hit the 404 server twice. After that, make sure that the ping
// still exists locally. // still exists locally.
@ -95,25 +119,7 @@ add_task(async function test_pingSender() {
); );
// Try to send it using the pingsender. // Try to send it using the pingsender.
const url = "http://localhost:" + PingServer.port + "/submit/telemetry/"; testSendingPings([pingPath]);
TelemetrySend.testRunPingSender(url, pingPath, (_, topic, __) => {
switch (topic) {
case "process-finished": // finished indicates an exit code of 0
Assert.equal(
true,
true,
"Pingsender should be able to post to localhost"
);
break;
case "process-failed": // failed indicates an exit code != 0
Assert.equal(
true,
false,
"Pingsender should be able to post to localhost"
);
break;
}
});
let req = await PingServer.promiseNextRequest(); let req = await PingServer.promiseNextRequest();
let ping = decodeRequestPayload(req); let ping = decodeRequestPayload(req);
@ -160,8 +166,7 @@ add_task(async function test_pingSender() {
]; ];
for (let indx in bannedUris) { for (let indx in bannedUris) {
TelemetrySend.testRunPingSender( TelemetrySend.testRunPingSender(
bannedUris[indx], [{ url: bannedUris[indx], path: pingPath }],
pingPath,
(_, topic, __) => { (_, topic, __) => {
switch (topic) { switch (topic) {
case "process-finished": // finished indicates an exit code of 0 case "process-finished": // finished indicates an exit code of 0
@ -190,6 +195,35 @@ add_task(async function test_pingSender() {
await new Promise(r => failingServer.stop(r)); await new Promise(r => failingServer.stop(r));
}); });
add_task(async function test_pingSender_multiple_pings() {
// Generate two new pings and save them among the pending pings.
const data = [generateTestPingData(), generateTestPingData()];
for (const d of data) {
await TelemetryStorage.savePing(d, true);
}
// Get the local path of the saved pings.
const pingPaths = data.map(d =>
OS.Path.join(TelemetryStorage.pingDirectoryPath, d.id)
);
// Try to send them using the pingsender.
testSendingPings(pingPaths);
// Check the pings
for (const d of data) {
let req = await PingServer.promiseNextRequest();
let ping = decodeRequestPayload(req);
Assert.equal(ping.id, d.id, "Should have received the correct ping id.");
}
// Check that the PingSender removed the pending pings.
for (const d of data) {
await waitForPingDeletion(d.id);
}
});
add_task(async function cleanup() { add_task(async function cleanup() {
await PingServer.stop(); await PingServer.stop();
}); });