зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1310703 - Make the crashreporter client send a crash ping via the ping sender executable; r=ted
MozReview-Commit-ID: JV6HkUZYkUk
This commit is contained in:
Родитель
276f7423bb
Коммит
d1f13764e1
|
@ -22,6 +22,7 @@ Cu.import("resource://gre/modules/Timer.jsm");
|
|||
Cu.import("resource://gre/modules/TelemetrySend.jsm", this);
|
||||
Cu.import("resource://gre/modules/TelemetryUtils.jsm", this);
|
||||
Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||
Cu.import("resource://gre/modules/ClientID.jsm");
|
||||
|
||||
const Utils = TelemetryUtils;
|
||||
|
||||
|
@ -66,7 +67,7 @@ const PREF_PREVIOUS_BUILDID = PREF_BRANCH + "previousBuildID";
|
|||
const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
|
||||
const PREF_ASYNC_PLUGIN_INIT = "dom.ipc.plugins.asyncInit.enabled";
|
||||
const PREF_UNIFIED = PREF_BRANCH + "unified";
|
||||
|
||||
const PREF_SERVER = PREF_BRANCH + "server";
|
||||
|
||||
const MESSAGE_TELEMETRY_PAYLOAD = "Telemetry:Payload";
|
||||
const MESSAGE_TELEMETRY_THREAD_HANGS = "Telemetry:ChildThreadHangs";
|
||||
|
@ -203,12 +204,25 @@ function getPingType(aPayload) {
|
|||
/**
|
||||
* Annotate the current session ID with the crash reporter to map potential
|
||||
* crash pings with the related main ping.
|
||||
*
|
||||
* @param sessionId {String} The telemetry session ID
|
||||
* @param clientId {String} The telemetry client ID
|
||||
* @param telemetryServer {String} The URL of the telemetry server
|
||||
*/
|
||||
function annotateCrashReport(sessionId) {
|
||||
function annotateCrashReport(sessionId, clientId, telemetryServer) {
|
||||
try {
|
||||
const cr = Cc["@mozilla.org/toolkit/crash-reporter;1"];
|
||||
if (cr) {
|
||||
cr.getService(Ci.nsICrashReporter).setTelemetrySessionId(sessionId);
|
||||
const crs = cr.getService(Ci.nsICrashReporter);
|
||||
|
||||
crs.setTelemetrySessionId(sessionId);
|
||||
|
||||
// We do not annotate the crash if telemetry is disabled to prevent the
|
||||
// crashreporter client from sending the crash ping.
|
||||
if (Utils.isTelemetryEnabled) {
|
||||
crs.annotateCrashReport("TelemetryClientId", clientId);
|
||||
crs.annotateCrashReport("TelemetryServerURL", telemetryServer);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors when crash reporting is disabled
|
||||
|
@ -1471,7 +1485,10 @@ var Impl = {
|
|||
// the very same value for |_sessionStartDate|.
|
||||
this._sessionStartDate = this._subsessionStartDate;
|
||||
|
||||
annotateCrashReport(this._sessionId);
|
||||
// Annotate crash reports using the cached client ID which can be accessed
|
||||
// synchronously. If it is not available yet, we'll update it later.
|
||||
annotateCrashReport(this._sessionId, ClientID.getCachedClientID(),
|
||||
Preferences.get(PREF_SERVER, undefined));
|
||||
|
||||
// Initialize some probes that are kept in their own modules
|
||||
this._thirdPartyCookies = new ThirdPartyCookieProbe();
|
||||
|
@ -1498,13 +1515,13 @@ var Impl = {
|
|||
ppml.addMessageListener(MESSAGE_TELEMETRY_PAYLOAD, this);
|
||||
ppml.addMessageListener(MESSAGE_TELEMETRY_THREAD_HANGS, this);
|
||||
ppml.addMessageListener(MESSAGE_TELEMETRY_USS, this);
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Does the "heavy" Telemetry initialization later on, so we
|
||||
* don't impact startup performance.
|
||||
* @return {Promise} Resolved when the initialization completes.
|
||||
*/
|
||||
/**
|
||||
* Does the "heavy" Telemetry initialization later on, so we
|
||||
* don't impact startup performance.
|
||||
* @return {Promise} Resolved when the initialization completes.
|
||||
*/
|
||||
delayedInit() {
|
||||
this._log.trace("delayedInit");
|
||||
|
||||
|
@ -1526,6 +1543,10 @@ var Impl = {
|
|||
|
||||
Telemetry.asyncFetchTelemetryData(function() {});
|
||||
|
||||
// Update the crash annotation with the proper client ID.
|
||||
annotateCrashReport(this._sessionId, yield ClientID.getClientID(),
|
||||
Preferences.get(PREF_SERVER, undefined));
|
||||
|
||||
if (IS_UNIFIED_TELEMETRY) {
|
||||
// Check for a previously written aborted session ping.
|
||||
yield TelemetryController.checkAbortedSessionPing();
|
||||
|
|
|
@ -2,7 +2,18 @@
|
|||
"crash" ping
|
||||
============
|
||||
|
||||
This ping is captured after the main Firefox process crashes, whether or not the crash report is submitted to crash-stats.mozilla.org. It includes non-identifying metadata about the crash.
|
||||
This ping is captured after the main Firefox process crashes or after a content
|
||||
process crashes, whether or not the crash report is submitted to
|
||||
crash-stats.mozilla.org. It includes non-identifying metadata about the crash.
|
||||
|
||||
This ping is sent either by the ```CrashManager``` or by the crash reporter
|
||||
client. The ```CrashManager``` is responsible for sending crash pings for the
|
||||
content process crashes, which are sent right after the crash is detected,
|
||||
as well as for main process crashes, which are sent after Firefox restarts
|
||||
successfully. The crash reporter client sends crash pings only for main process
|
||||
crashes whether or not the user also reports the crash. The crash reporter
|
||||
client will not send the crash ping if telemetry has been disabled in Firefox
|
||||
though.
|
||||
|
||||
The environment block that is sent with this ping varies: if Firefox was running long enough to record the environment block before the crash, then the environment at the time of the crash will be recorded and ``hasCrashEnvironment`` will be true. If Firefox crashed before the environment was recorded, ``hasCrashEnvironment`` will be false and the recorded environment will be the environment at time of submission.
|
||||
|
||||
|
@ -13,7 +24,6 @@ Structure:
|
|||
.. code-block:: js
|
||||
|
||||
{
|
||||
version: 1,
|
||||
type: "crash",
|
||||
... common ping data
|
||||
clientId: <UUID>,
|
||||
|
@ -21,6 +31,7 @@ Structure:
|
|||
processType: <type>, // Type of process that crashed, see below for a list of types
|
||||
payload: {
|
||||
crashDate: "YYYY-MM-DD",
|
||||
version: 1,
|
||||
sessionId: <UUID>, // may be missing for crashes that happen early
|
||||
// in startup. Added in Firefox 48 with the
|
||||
// intention of uplifting to Firefox 46
|
||||
|
|
|
@ -13,9 +13,10 @@
|
|||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <memory>
|
||||
#include <time.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctime>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
using std::string;
|
||||
using std::istream;
|
||||
|
@ -196,9 +197,9 @@ static string GetDumpLocalID()
|
|||
return localId.substr(0, dot);
|
||||
}
|
||||
|
||||
// This appends the StackTraces entry generated by the minidump analyzer to the
|
||||
// main crash event so that it can be picked up by Firefox once it restarts
|
||||
static void AppendStackTracesToEventFile(const string& aStackTraces)
|
||||
// This appends the aKey/aValue entry to the main crash event so that it can
|
||||
// be picked up by Firefox once it restarts
|
||||
static void AppendToEventFile(const string& aKey, const string& aValue)
|
||||
{
|
||||
if (gEventsPath.empty()) {
|
||||
// If there is no path for finding the crash event, skip this step.
|
||||
|
@ -210,7 +211,7 @@ static void AppendStackTracesToEventFile(const string& aStackTraces)
|
|||
ofstream* f = UIOpenWrite(path.c_str(), true);
|
||||
|
||||
if (f->is_open()) {
|
||||
*f << "StackTraces=" << aStackTraces;
|
||||
*f << aKey << "=" << aValue << std::endl;
|
||||
f->close();
|
||||
}
|
||||
|
||||
|
@ -265,8 +266,9 @@ static void OpenLogFile()
|
|||
static bool ReadConfig()
|
||||
{
|
||||
string iniPath;
|
||||
if (!UIGetIniPath(iniPath))
|
||||
if (!UIGetIniPath(iniPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadStringsFromFile(iniPath, gStrings, true))
|
||||
return false;
|
||||
|
@ -513,21 +515,17 @@ bool CheckEndOfLifed(string version)
|
|||
return UIFileExists(reportPath);
|
||||
}
|
||||
|
||||
#ifndef RELEASE_OR_BETA
|
||||
|
||||
static string
|
||||
GetMinidumpAnalyzerPath()
|
||||
GetProgramPath(const string& exename)
|
||||
{
|
||||
string path = gArgv[0];
|
||||
size_t pos = path.rfind(UI_CRASH_REPORTER_FILENAME BIN_SUFFIX);
|
||||
path.erase(pos);
|
||||
path.append(UI_MINIDUMP_ANALYZER_FILENAME BIN_SUFFIX);
|
||||
path.append(exename + BIN_SUFFIX);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
gArgc = argc;
|
||||
|
@ -552,7 +550,9 @@ int main(int argc, char** argv)
|
|||
#ifndef RELEASE_OR_BETA
|
||||
// start by running minidump analyzer, this is currently enabled only in
|
||||
// nightly and aurora
|
||||
UIRunMinidumpAnalyzer(GetMinidumpAnalyzerPath(), gReporterDumpFile);
|
||||
string empty;
|
||||
UIRunProgram(GetProgramPath(UI_MINIDUMP_ANALYZER_FILENAME),
|
||||
gReporterDumpFile, empty, /* wait */ true);
|
||||
#endif
|
||||
|
||||
// go ahead with the crash reporter
|
||||
|
@ -640,10 +640,17 @@ int main(int argc, char** argv)
|
|||
gEventsPath.clear();
|
||||
}
|
||||
|
||||
// Assemble and send the crash ping
|
||||
string pingUuid;
|
||||
|
||||
if (SendCrashPing(queryParameters, pingUuid)) {
|
||||
AppendToEventFile("CrashPingUUID", pingUuid);
|
||||
}
|
||||
|
||||
// Update the crash event with stacks if they are present
|
||||
auto stackTracesItr = queryParameters.find("StackTraces");
|
||||
if (stackTracesItr != queryParameters.end()) {
|
||||
AppendStackTracesToEventFile(stackTracesItr->second);
|
||||
AppendToEventFile(stackTracesItr->first, stackTracesItr->second);
|
||||
}
|
||||
|
||||
if (!UIFileExists(gReporterDumpFile)) {
|
||||
|
|
|
@ -39,6 +39,11 @@ std::string WideToUTF8(const std::wstring& wide, bool* success = 0);
|
|||
|
||||
#define UI_CRASH_REPORTER_FILENAME "crashreporter"
|
||||
#define UI_MINIDUMP_ANALYZER_FILENAME "minidump-analyzer"
|
||||
#ifndef XP_MACOSX
|
||||
#define UI_PING_SENDER_FILENAME "pingsender"
|
||||
#else
|
||||
#define UI_PING_SENDER_FILENAME "../../../pingsender"
|
||||
#endif
|
||||
|
||||
typedef std::map<std::string, std::string> StringTable;
|
||||
|
||||
|
@ -79,7 +84,7 @@ typedef std::map<std::string, std::string> StringTable;
|
|||
#define ST_ERROR_ENDOFLIFE "ErrorEndOfLife"
|
||||
|
||||
//=============================================================================
|
||||
// implemented in crashreporter.cpp
|
||||
// implemented in crashreporter.cpp and ping.cpp
|
||||
//=============================================================================
|
||||
|
||||
namespace CrashReporter {
|
||||
|
@ -112,6 +117,9 @@ namespace CrashReporter {
|
|||
void DeleteDump();
|
||||
bool ShouldEnableSending();
|
||||
|
||||
// Telemetry ping
|
||||
bool SendCrashPing(StringTable& strings, std::string& pingUuid);
|
||||
|
||||
static const unsigned int kSaveCount = 10;
|
||||
}
|
||||
|
||||
|
@ -148,8 +156,16 @@ std::ofstream* UIOpenWrite(const std::string& filename,
|
|||
bool append=false,
|
||||
bool binary=false);
|
||||
void UIPruneSavedDumps(const std::string& directory);
|
||||
void UIRunMinidumpAnalyzer(const std::string& exename,
|
||||
const std::string& filename);
|
||||
|
||||
// Run the program specified by exename, passing it the parameter in arg and
|
||||
// then sending it the contents of data (if any) via a pipe tied to its stdin.
|
||||
// If wait is true, wait for the program to terminate execution before
|
||||
// returning. Returns true if the program was launched correctly, false
|
||||
// otherwise.
|
||||
bool UIRunProgram(const std::string& exename,
|
||||
const std::string& arg,
|
||||
const std::string& data,
|
||||
bool wait = false);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# pragma warning( pop )
|
||||
|
|
|
@ -70,16 +70,67 @@ void UIPruneSavedDumps(const std::string& directory)
|
|||
}
|
||||
}
|
||||
|
||||
void UIRunMinidumpAnalyzer(const string& exename, const string& filename)
|
||||
bool UIRunProgram(const std::string& exename, const std::string& arg,
|
||||
const std::string& data, bool wait)
|
||||
{
|
||||
// Run the minidump analyzer and wait for it to finish
|
||||
bool usePipes = !data.empty();
|
||||
int fd[2];
|
||||
|
||||
if (usePipes && (pipe(fd) != 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
|
||||
if (pid == -1) {
|
||||
return; // Nothing to do upon failure
|
||||
return false;
|
||||
} else if (pid == 0) {
|
||||
execl(exename.c_str(), exename.c_str(), filename.c_str(), nullptr);
|
||||
// Child
|
||||
if (usePipes) {
|
||||
if (dup2(fd[0], STDIN_FILENO) == -1) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
close(fd[0]);
|
||||
close(fd[1]);
|
||||
}
|
||||
|
||||
char* argv[] = {
|
||||
const_cast<char*>(exename.c_str()),
|
||||
const_cast<char*>(arg.c_str()),
|
||||
nullptr
|
||||
};
|
||||
|
||||
// Run the program
|
||||
int rv = execv(exename.c_str(), argv);
|
||||
|
||||
if (rv == -1) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
waitpid(pid, nullptr, 0);
|
||||
// Parent
|
||||
if (usePipes) {
|
||||
ssize_t rv;
|
||||
size_t offset = 0;
|
||||
size_t size = data.size();
|
||||
|
||||
do {
|
||||
rv = write(fd[1], data.c_str() + offset, size);
|
||||
|
||||
if (rv > 0) {
|
||||
size -= rv;
|
||||
offset += rv;
|
||||
}
|
||||
} while (size && ((rv > 0) || ((rv == -1) && errno == EINTR)));
|
||||
|
||||
close(fd[0]);
|
||||
close(fd[1]);
|
||||
}
|
||||
|
||||
if (wait) {
|
||||
waitpid(pid, nullptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1545,24 +1545,81 @@ void UIPruneSavedDumps(const std::string& directory)
|
|||
}
|
||||
}
|
||||
|
||||
void UIRunMinidumpAnalyzer(const string& exename, const string& filename)
|
||||
bool UIRunProgram(const string& exename, const string& arg,
|
||||
const string& data, bool wait)
|
||||
{
|
||||
wstring cmdLine;
|
||||
bool usePipes = !data.empty();
|
||||
HANDLE childStdinPipeRead = nullptr;
|
||||
HANDLE childStdinPipeWrite = nullptr;
|
||||
|
||||
cmdLine += L"\"" + UTF8ToWide(exename) + L"\" ";
|
||||
cmdLine += L"\"" + UTF8ToWide(filename) + L"\" ";
|
||||
if (usePipes) {
|
||||
// Set up the pipes
|
||||
SECURITY_ATTRIBUTES sa = {};
|
||||
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
sa.lpSecurityDescriptor = nullptr;
|
||||
sa.bInheritHandle = true;
|
||||
|
||||
// Create a pipe for the child process's stdin and ensure its writable end is
|
||||
// not inherited by the child process.
|
||||
if (!CreatePipe(&childStdinPipeRead, &childStdinPipeWrite, &sa, 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SetHandleInformation(childStdinPipeWrite, HANDLE_FLAG_INHERIT, 0)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
wstring cmdLine = L"\"" + UTF8ToWide(exename) + L"\" " +
|
||||
L"\"" + UTF8ToWide(arg) + L"\" ";
|
||||
|
||||
STARTUPINFO si = {};
|
||||
si.cb = sizeof(si);
|
||||
|
||||
if (usePipes) {
|
||||
si.dwFlags |= STARTF_USESTDHANDLES;
|
||||
si.hStdInput = childStdinPipeRead;
|
||||
si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
||||
}
|
||||
|
||||
PROCESS_INFORMATION pi = {};
|
||||
|
||||
si.cb = sizeof(si);
|
||||
si.dwFlags = STARTF_USESHOWWINDOW;
|
||||
si.wShowWindow = SW_SHOWNORMAL;
|
||||
|
||||
if (CreateProcess(nullptr, (LPWSTR)cmdLine.c_str(), nullptr, nullptr, FALSE,
|
||||
0, nullptr, nullptr, &si, &pi)) {
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
if (!CreateProcess(/* lpApplicationName */ nullptr,
|
||||
(LPWSTR)cmdLine.c_str(),
|
||||
/* lpProcessAttributes */ nullptr,
|
||||
/* lpThreadAttributes */ nullptr,
|
||||
usePipes,
|
||||
NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW,
|
||||
/* lpEnvironment */ nullptr,
|
||||
/* lpCurrentDirectory */ nullptr,
|
||||
&si, &pi)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (usePipes) {
|
||||
size_t offset = 0;
|
||||
size_t size = data.size();
|
||||
while (size > 0) {
|
||||
DWORD written = 0;
|
||||
bool success = WriteFile(childStdinPipeWrite, data.data() + offset, size,
|
||||
&written, nullptr);
|
||||
if (!success) {
|
||||
break;
|
||||
} else {
|
||||
size -= written;
|
||||
offset += written;
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(childStdinPipeWrite);
|
||||
}
|
||||
|
||||
if (wait) {
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
}
|
||||
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,15 @@ if CONFIG['OS_TARGET'] != 'Android':
|
|||
|
||||
UNIFIED_SOURCES += [
|
||||
'crashreporter.cpp',
|
||||
'ping.cpp',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/toolkit/crashreporter/jsoncpp/include',
|
||||
]
|
||||
|
||||
USE_LIBS += [
|
||||
'jsoncpp',
|
||||
]
|
||||
|
||||
if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
|
@ -22,6 +31,7 @@ if CONFIG['OS_ARCH'] == 'WINNT':
|
|||
]
|
||||
OS_LIBS += [
|
||||
'comctl32',
|
||||
'ole32',
|
||||
'shell32',
|
||||
'wininet',
|
||||
'shlwapi',
|
||||
|
|
|
@ -0,0 +1,323 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "crashreporter.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#if defined(XP_LINUX)
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#elif defined(XP_MACOSX)
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#elif defined(XP_WIN)
|
||||
#include <Objbase.h>
|
||||
#endif
|
||||
|
||||
#include "json/json.h"
|
||||
|
||||
using std::string;
|
||||
|
||||
namespace CrashReporter {
|
||||
|
||||
struct UUID {
|
||||
uint32_t m0;
|
||||
uint16_t m1;
|
||||
uint16_t m2;
|
||||
uint8_t m3[8];
|
||||
};
|
||||
|
||||
// Generates an UUID; the code here is mostly copied from nsUUIDGenerator.cpp
|
||||
static string
|
||||
GenerateUUID()
|
||||
{
|
||||
UUID id = {};
|
||||
|
||||
#if defined(XP_WIN) // Windows
|
||||
HRESULT hr = CoCreateGuid((GUID*)&id);
|
||||
if (FAILED(hr)) {
|
||||
return "";
|
||||
}
|
||||
#elif defined(XP_MACOSX) // MacOS X
|
||||
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
|
||||
if (!uuid) {
|
||||
return "";
|
||||
}
|
||||
|
||||
CFUUIDBytes bytes = CFUUIDGetUUIDBytes(uuid);
|
||||
memcpy(&id, &bytes, sizeof(UUID));
|
||||
|
||||
CFRelease(uuid);
|
||||
#elif defined(HAVE_ARC4RANDOM_BUF) // Android, BSD, ...
|
||||
arc4random_buf(id, sizeof(UUID));
|
||||
#else // Linux
|
||||
int fd = open("/dev/urandom", O_RDONLY);
|
||||
|
||||
if (fd == -1) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (read(fd, &id, sizeof(UUID)) != sizeof(UUID)) {
|
||||
close(fd);
|
||||
return "";
|
||||
}
|
||||
|
||||
close(fd);
|
||||
#endif
|
||||
|
||||
/* Put in the version */
|
||||
id.m2 &= 0x0fff;
|
||||
id.m2 |= 0x4000;
|
||||
|
||||
/* Put in the variant */
|
||||
id.m3[0] &= 0x3f;
|
||||
id.m3[0] |= 0x80;
|
||||
|
||||
const char* kUUIDFormatString =
|
||||
"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x";
|
||||
const size_t kUUIDFormatStringLength = 36;
|
||||
char str[kUUIDFormatStringLength + 1] = { '\0' };
|
||||
|
||||
int num = snprintf(str, kUUIDFormatStringLength + 1, kUUIDFormatString,
|
||||
id.m0, id.m1, id.m2, id.m3[0], id.m3[1], id.m3[2],
|
||||
id.m3[3], id.m3[4], id.m3[5], id.m3[6], id.m3[7]);
|
||||
|
||||
if (num != kUUIDFormatStringLength) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
const char kISO8601Date[] = "%F";
|
||||
const char kISO8601FullDate[] = "%FT%T.000Z";
|
||||
|
||||
// Return the current date as a string in the specified format, the following
|
||||
// constants are provided:
|
||||
// - kISO8601Date, the ISO 8601 date format, YYYY-MM-DD
|
||||
// - kISO8601FullDate, the ISO 8601 full date format, YYYY-MM-DDTHH:MM:SS.000Z
|
||||
static string
|
||||
CurrentDate(string format)
|
||||
{
|
||||
time_t now;
|
||||
time(&now);
|
||||
char buf[64]; // This should be plenty
|
||||
strftime(buf, sizeof buf, format.c_str(), gmtime(&now));
|
||||
return buf;
|
||||
}
|
||||
|
||||
const char kTelemetryClientId[] = "TelemetryClientId";
|
||||
const char kTelemetryUrl[] = "TelemetryServerURL";
|
||||
const char kTelemetrySessionId[] = "TelemetrySessionId";
|
||||
const int kTelemetryVersion = 4;
|
||||
|
||||
// Create the payload.metadata node of the crash ping using fields extracted
|
||||
// from the .extra file
|
||||
static Json::Value
|
||||
CreateMetadataNode(StringTable& strings)
|
||||
{
|
||||
// The following list should be kept in sync with the one in CrashManager.jsm
|
||||
const char *entries[] = {
|
||||
"AvailablePageFile",
|
||||
"AvailablePhysicalMemory",
|
||||
"AvailableVirtualMemory",
|
||||
"BlockedDllList",
|
||||
"BlocklistInitFailed",
|
||||
"BuildID",
|
||||
"ContainsMemoryReport",
|
||||
"CrashTime",
|
||||
"EventLoopNestingLevel",
|
||||
"IsGarbageCollecting",
|
||||
"MozCrashReason",
|
||||
"OOMAllocationSize",
|
||||
"ProductID",
|
||||
"ProductName",
|
||||
"ReleaseChannel",
|
||||
"SecondsSinceLastCrash",
|
||||
"StartupCrash",
|
||||
"SystemMemoryUsePercentage",
|
||||
"TelemetrySessionId",
|
||||
"TextureUsage",
|
||||
"TotalPageFile",
|
||||
"TotalPhysicalMemory",
|
||||
"TotalVirtualMemory",
|
||||
"UptimeTS",
|
||||
"User32BeforeBlocklist",
|
||||
"Version",
|
||||
};
|
||||
|
||||
Json::Value node;
|
||||
|
||||
for (auto entry : entries) {
|
||||
if ((strings.find(entry) != strings.end()) && !strings[entry].empty()) {
|
||||
node[entry] = strings[entry];
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
// Create the payload node of the crash ping
|
||||
static Json::Value
|
||||
CreatePayloadNode(StringTable& strings, const string& aSessionId)
|
||||
{
|
||||
Json::Value payload;
|
||||
|
||||
payload["sessionId"] = aSessionId;
|
||||
payload["version"] = 1;
|
||||
payload["crashDate"] = CurrentDate(kISO8601Date);
|
||||
payload["hasCrashEnvironment"] = true;
|
||||
payload["crashId"] = GetDumpLocalID();
|
||||
payload["processType"] = "main"; // This is always a main crash
|
||||
|
||||
// Parse the stack traces
|
||||
Json::Value stackTracesValue;
|
||||
Json::Reader reader;
|
||||
|
||||
if (reader.parse(strings["StackTraces"], stackTracesValue,
|
||||
/* collectComments */ false)) {
|
||||
payload["stackTraces"] = stackTracesValue;
|
||||
}
|
||||
|
||||
// Assemble the payload metadata
|
||||
payload["metadata"] = CreateMetadataNode(strings);
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
// Create the application node of the crash ping
|
||||
static Json::Value
|
||||
CreateApplicationNode(const string& aVendor, const string& aName,
|
||||
const string& aVersion, const string& aChannel,
|
||||
const string& aBuildId, const string& aArchitecture,
|
||||
const string& aXpcomAbi)
|
||||
{
|
||||
Json::Value application;
|
||||
|
||||
application["vendor"] = aVendor;
|
||||
application["name"] = aName;
|
||||
application["buildId"] = aBuildId;
|
||||
application["displayVersion"] = aVersion;
|
||||
application["platformVersion"] = aVersion;
|
||||
application["version"] = aVersion;
|
||||
application["channel"] = aChannel;
|
||||
if (!aArchitecture.empty()) {
|
||||
application["architecture"] = aArchitecture;
|
||||
}
|
||||
if (!aXpcomAbi.empty()) {
|
||||
application["xpcomAbi"] = aXpcomAbi;
|
||||
}
|
||||
|
||||
return application;
|
||||
}
|
||||
|
||||
// Create the root node of the crash ping
|
||||
static Json::Value
|
||||
CreateRootNode(StringTable& strings, const string& aUuid,
|
||||
const string& aClientId, const string& aSessionId,
|
||||
const string& aName, const string& aVersion,
|
||||
const string& aChannel, const string& aBuildId)
|
||||
{
|
||||
Json::Value root;
|
||||
root["type"] = "crash"; // This is a crash ping
|
||||
root["id"] = aUuid;
|
||||
root["version"] = kTelemetryVersion;
|
||||
root["creationDate"] = CurrentDate(kISO8601FullDate);
|
||||
root["clientId"] = aClientId;
|
||||
|
||||
// Parse the telemetry environment
|
||||
Json::Value environment;
|
||||
Json::Reader reader;
|
||||
string architecture;
|
||||
string xpcomAbi;
|
||||
|
||||
if (reader.parse(strings["TelemetryEnvironment"], environment,
|
||||
/* collectComments */ false)) {
|
||||
if (environment.isMember("build") && environment["build"].isObject()) {
|
||||
Json::Value build = environment["build"];
|
||||
if (build.isMember("architecture") && build["architecture"].isString()) {
|
||||
architecture = build["architecture"].asString();
|
||||
}
|
||||
if (build.isMember("xpcomAbi") && build["xpcomAbi"].isString()) {
|
||||
xpcomAbi = build["xpcomAbi"].asString();
|
||||
}
|
||||
}
|
||||
|
||||
root["environment"] = environment;
|
||||
}
|
||||
|
||||
root["payload"] = CreatePayloadNode(strings, aSessionId);
|
||||
root["application"] = CreateApplicationNode(strings["Vendor"], aName,
|
||||
aVersion, aChannel, aBuildId,
|
||||
architecture, xpcomAbi);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
// Generates the URL used to submit the crash ping, see TelemetrySend.jsm
|
||||
string
|
||||
GenerateSubmissionUrl(const string& aUrl, const string& aId,
|
||||
const string& aName, const string& aVersion,
|
||||
const string& aChannel, const string& aBuildId)
|
||||
{
|
||||
return aUrl + "/submit/telemetry/" + aId + "/crash/" + aName + "/" +
|
||||
aVersion + "/" + aChannel + "/" + aBuildId +
|
||||
"?v=" + std::to_string(kTelemetryVersion);
|
||||
}
|
||||
|
||||
// Assembles the crash ping using the strings extracted from the .extra file
|
||||
// and sends it using the crash sender. All the telemetry specific data but the
|
||||
// environment will be stripped from the annotations so that it won't be sent
|
||||
// together with the crash report.
|
||||
//
|
||||
// Note that the crash ping sender is invoked in a fire-and-forget way so this
|
||||
// won't block waiting for the ping to be delivered.
|
||||
//
|
||||
// Returns true if the ping was assembled and handed over to the pingsender
|
||||
// correctly, false otherwise and populates the aUUID field with the ping UUID.
|
||||
bool
|
||||
SendCrashPing(StringTable& strings, string& pingUuid)
|
||||
{
|
||||
string clientId = strings[kTelemetryClientId];
|
||||
string serverUrl = strings[kTelemetryUrl];
|
||||
string sessionId = strings[kTelemetrySessionId];
|
||||
|
||||
// Remove the telemetry-related data from the crash annotations
|
||||
strings.erase(kTelemetryClientId);
|
||||
strings.erase(kTelemetryUrl);
|
||||
strings.erase(kTelemetrySessionId);
|
||||
|
||||
string buildId = strings["BuildID"];
|
||||
string channel = strings["ReleaseChannel"];
|
||||
string name = strings["ProductName"];
|
||||
string version = strings["Version"];
|
||||
string uuid = GenerateUUID();
|
||||
string url = GenerateSubmissionUrl(serverUrl, uuid, name, version,
|
||||
channel, buildId);
|
||||
|
||||
if (serverUrl.empty() || uuid.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Json::Value root = CreateRootNode(strings, uuid, clientId, sessionId,
|
||||
name, version, channel, buildId);
|
||||
|
||||
// Write out the result
|
||||
Json::FastWriter writer;
|
||||
string ping = writer.write(root);
|
||||
|
||||
// Hand over the ping to the sender
|
||||
if (UIRunProgram(GetProgramPath(UI_PING_SENDER_FILENAME), url, ping)) {
|
||||
pingUuid = uuid;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace crashreporter
|
||||
|
|
@ -38,6 +38,12 @@ Minidump Analyzer
|
|||
the dump files generated during a crash. It appends the stack traces to the
|
||||
.extra file associated with the crash dump.
|
||||
|
||||
Ping Sender
|
||||
The ping sender is a standalone executable that is launched by the crash
|
||||
reporter client to deliver a crash ping to our telemetry servers. The ping
|
||||
sender is used to speed up delivery of the crash ping which would otherwise
|
||||
have to wait for Firefox to be restarted in order to be sent.
|
||||
|
||||
How Main-Process Crash Handling Works
|
||||
=====================================
|
||||
|
||||
|
@ -90,7 +96,13 @@ argument.
|
|||
The *crash reporter client* performs a number of roles. There's a lot going
|
||||
on, so you may want to look at ``main()`` in ``crashreporter.cpp``. First,
|
||||
stack traces are extracted from the dump via the *minidump analyzer* tool.
|
||||
The resulting traces are appended to the .extra file of the crash. Then, the
|
||||
The resulting traces are appended to the .extra file of the crash. Once this
|
||||
is done a crash ping is assembled holding the same information as the one
|
||||
generated by the ```CrashManager``` and it's sent to the telemetry servers via
|
||||
the *ping sender* program. The UUID of the ping is then stored in the extra
|
||||
file; the ```CrashManager``` will later pick it up and generate a new ping
|
||||
with the same UUID so that the telemetry server can deduplicate both pings.
|
||||
Then, the
|
||||
*crash reporter client* verifies that the dump data is sane. If it isn't
|
||||
(e.g. required metadata is missing), the dump data is ignored. If dump data
|
||||
looks sane, the dump data
|
||||
|
|
|
@ -1097,9 +1097,6 @@ bool MinidumpCallback(
|
|||
WriteLiteral(eventFile, "\n");
|
||||
WriteString(eventFile, id_ascii);
|
||||
WriteLiteral(eventFile, "\n");
|
||||
if (currentSessionId) {
|
||||
WriteAnnotation(eventFile, "TelemetrySessionId", currentSessionId);
|
||||
}
|
||||
if (crashEventAPIData) {
|
||||
eventFile.WriteBuffer(crashEventAPIData->get(), crashEventAPIData->Length());
|
||||
}
|
||||
|
@ -1114,6 +1111,12 @@ bool MinidumpCallback(
|
|||
#endif
|
||||
apiData.WriteBuffer(crashReporterAPIData->get(), crashReporterAPIData->Length());
|
||||
}
|
||||
|
||||
if (currentSessionId) {
|
||||
WriteAnnotation(apiData, "TelemetrySessionId", crashTimeString);
|
||||
WriteAnnotation(eventFile, "TelemetrySessionId", currentSessionId);
|
||||
}
|
||||
|
||||
WriteAnnotation(apiData, "CrashTime", crashTimeString);
|
||||
WriteAnnotation(eventFile, "CrashTime", crashTimeString);
|
||||
|
||||
|
|
|
@ -45,6 +45,30 @@ function run_test() {
|
|||
do_check_eq(extra.TestKey, "TestValue");
|
||||
do_check_eq(extra["\u2665"], "\u{1F4A9}");
|
||||
do_check_eq(extra.Notes, "JunkMoreJunk");
|
||||
do_check_true(!("TelemetrySessionId" in extra));
|
||||
do_check_true("TelemetrySessionId" in extra);
|
||||
Assert.ok(
|
||||
"TelemetryServerURL" in extra,
|
||||
"The TelemetryServerURL field is omitted when telemetry is off"
|
||||
);
|
||||
});
|
||||
|
||||
do_crash(function() {
|
||||
// Enable telemetry
|
||||
let prefs = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefBranch);
|
||||
|
||||
// Turn on telemetry and specify a telemetry server
|
||||
prefs.setCharPref("toolkit.telemetry.server", "http://a.telemetry.server");
|
||||
prefs.setBoolPref("toolkit.telemetry.enabled", true);
|
||||
|
||||
// TelemetrySession setup will trigger the session annotation
|
||||
let scope = {};
|
||||
Components.utils.import("resource://gre/modules/TelemetryController.jsm", scope);
|
||||
scope.TelemetryController.testSetup();
|
||||
}, function(mdump, extra) {
|
||||
Assert.ok("TelemetryServerURL" in extra,
|
||||
"The TelemetryServerURL field is present in the extra file");
|
||||
Assert.equal(extra.TelemetryServerURL, "http://a.telemetry.server",
|
||||
"The TelemetryServerURL field is properly set");
|
||||
});
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче