Bug 826053 - Add main ANRReporter logic; r=blassey

This commit is contained in:
Jim Chen 2013-02-08 18:17:42 -05:00
parent dea4b5c827
commit b34df63443

View File

@ -260,6 +260,188 @@ public final class ANRReporter extends BroadcastReceiver
return 0L;
}
/*
a saved telemetry ping file consists of JSON in the following format,
{
"slug": "<uuid-string>",
"payload": "<escaped-json-data-string>",
"checksum": "<base64-sha-256-string>"
}
for Android ANR, our unescaped JSON payload should look like,
{
"ver": 1,
"simpleMeasurements": {
"uptime": <uptime>
},
"info": {
"reason": "android-anr-report",
"OS": "Android",
...
},
"androidANR": "...",
"androidLogcat": "..."
}
*/
private static int writePingPayload(OutputStream ping,
MessageDigest checksum,
String payload) throws IOException {
byte [] data = payload.getBytes(PING_CHARSET);
checksum.update(data);
data = JSONObject.quote(payload).getBytes(PING_CHARSET);
// first and last bytes are quotes inserted by JSONObject.quote; discard them
ping.write(data, 1, data.length - 2);
return data.length - 2;
}
private static void fillPingHeader(OutputStream ping, MessageDigest checksum, String slug)
throws IOException {
// ping file header
byte [] data = ("{" +
"\"slug\":" + JSONObject.quote(slug) + "," +
"\"payload\":\"").getBytes(PING_CHARSET);
ping.write(data);
if (DEBUG) {
Log.d(LOGTAG, "wrote ping header, size = " + String.valueOf(data.length));
}
// payload start
int size = writePingPayload(ping, checksum, ("{" +
"\"ver\":1," +
"\"simpleMeasurements\":{" +
"\"uptime\":" + String.valueOf(getUptimeMins()) +
"}," +
"\"info\":{" +
"\"reason\":\"android-anr-report\"," +
"\"OS\":" + JSONObject.quote(GeckoAppInfo.getOS()) + "," +
"\"version\":\"" + String.valueOf(Build.VERSION.SDK_INT) + "\"," +
"\"appID\":" + JSONObject.quote(GeckoAppInfo.getID()) + "," +
"\"appVersion\":" + JSONObject.quote(GeckoAppInfo.getVersion()) + "," +
"\"appName\":" + JSONObject.quote(GeckoAppInfo.getName()) + "," +
"\"appBuildID\":" + JSONObject.quote(GeckoAppInfo.getBuildID()) + "," +
"\"appUpdateChannel\":" + JSONObject.quote(GeckoAppInfo.getUpdateChannel()) + "," +
"\"platformBuildID\":" + JSONObject.quote(GeckoAppInfo.getPlatformBuildID()) + "," +
"\"locale\":" + JSONObject.quote(GeckoAppInfo.getLocale()) + "," +
"\"cpucount\":" + String.valueOf(Runtime.getRuntime().availableProcessors()) + "," +
"\"memsize\":" + String.valueOf(getTotalMem()) + "," +
"\"arch\":" + JSONObject.quote(System.getProperty("os.arch")) + "," +
"\"kernel_version\":" + JSONObject.quote(System.getProperty("os.version")) + "," +
"\"device\":" + JSONObject.quote(Build.MODEL) + "," +
"\"manufacturer\":" + JSONObject.quote(Build.MANUFACTURER) + "," +
"\"hardware\":" + JSONObject.quote(Build.VERSION.SDK_INT < 8 ? "" :
Build.HARDWARE) +
"}," +
"\"androidANR\":\""));
if (DEBUG) {
Log.d(LOGTAG, "wrote metadata, size = " + String.valueOf(size));
}
// We are at the start of ANR data
}
private static int fillPingBlock(OutputStream ping, MessageDigest checksum, Reader reader)
throws IOException {
int total = 0;
char [] block = new char[TRACES_BLOCK_SIZE];
for (int size = reader.read(block); size >= 0; size = reader.read(block)) {
String quoted = JSONObject.quote(new String(block, 0, size));
total += writePingPayload(ping, checksum, quoted.substring(1, quoted.length() - 1));
}
return total;
}
private static void fillPingFooter(OutputStream ping, MessageDigest checksum)
throws IOException {
// We are at the end of ANR data
int total = writePingPayload(ping, checksum, ("\"," +
"\"androidLogcat\":\""));
try {
// get the last 200 lines of logcat
Process proc = (new ProcessBuilder())
.command("/system/bin/logcat", "-v", "threadtime", "-t", "200", "-d", "*:D")
.redirectErrorStream(true)
.start();
try {
Reader procOut = new InputStreamReader(proc.getInputStream(), TRACES_CHARSET);
int size = fillPingBlock(ping, checksum, procOut);
if (DEBUG) {
Log.d(LOGTAG, "wrote logcat, size = " + String.valueOf(size));
}
} finally {
proc.destroy();
}
} catch (IOException e) {
// ignore because logcat is not essential
Log.w(LOGTAG, e);
}
total += writePingPayload(ping, checksum, "\"}");
String base64Checksum = Base64.encodeToString(checksum.digest(), Base64.NO_WRAP);
if (DEBUG) {
Log.d(LOGTAG, "checksum: " + base64Checksum);
}
byte [] data = (
"\"," +
"\"checksum\":" + JSONObject.quote(base64Checksum) +
"}").getBytes(PING_CHARSET);
ping.write(data);
if (DEBUG) {
Log.d(LOGTAG, "wrote ping footer, size = " + String.valueOf(data.length + total));
}
}
private static void processTraces(Reader traces, File pingFile) {
try {
OutputStream ping = new BufferedOutputStream(
new FileOutputStream(pingFile), TRACES_BLOCK_SIZE);
try {
MessageDigest checksum = MessageDigest.getInstance("SHA-256");
fillPingHeader(ping, checksum, pingFile.getName());
int size = fillPingBlock(ping, checksum, traces);
if (DEBUG) {
Log.d(LOGTAG, "wrote traces, size = " + String.valueOf(size));
}
fillPingFooter(ping, checksum);
if (DEBUG) {
Log.d(LOGTAG, "finished creating ping file");
}
return;
} finally {
ping.close();
}
} catch (GeneralSecurityException e) {
Log.w(LOGTAG, e);
} catch (IOException e) {
Log.w(LOGTAG, e);
}
// exception; delete ping file
if (pingFile.exists()) {
pingFile.delete();
}
}
private static void processTraces(File tracesFile, File pingFile) {
try {
Reader traces = new InputStreamReader(
new FileInputStream(tracesFile), TRACES_CHARSET);
try {
processTraces(traces, pingFile);
} finally {
traces.close();
}
} catch (IOException e) {
Log.w(LOGTAG, e);
}
}
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) {
@ -268,6 +450,32 @@ public final class ANRReporter extends BroadcastReceiver
if (!ANR_ACTION.equals(intent.getAction())) {
return;
}
// make sure we have a good save location first
File pingFile = getPingFile();
if (DEBUG) {
Log.d(LOGTAG, "using ping file: " + String.valueOf(pingFile));
}
if (pingFile == null) {
return;
}
File tracesFile = getTracesFile();
if (DEBUG) {
Log.d(LOGTAG, "using traces file: " + String.valueOf(tracesFile));
}
if (tracesFile == null) {
return;
}
// We get ANR intents from all ANRs in the system, but we only want Gecko ANRs
if (!isGeckoTraces(context.getPackageName(), tracesFile)) {
if (DEBUG) {
Log.d(LOGTAG, "traces is not Gecko ANR");
}
return;
}
Log.i(LOGTAG, "processing Gecko ANR");
processTraces(tracesFile, pingFile);
}
}