summaryrefslogtreecommitdiffstats
path: root/b2g/components/LogParser.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'b2g/components/LogParser.jsm')
-rw-r--r--b2g/components/LogParser.jsm257
1 files changed, 257 insertions, 0 deletions
diff --git a/b2g/components/LogParser.jsm b/b2g/components/LogParser.jsm
new file mode 100644
index 000000000..c40db9767
--- /dev/null
+++ b/b2g/components/LogParser.jsm
@@ -0,0 +1,257 @@
+/* jshint esnext: true */
+/* global DataView */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["LogParser"];
+
+/**
+ * Parse an array read from a /dev/log/ file. Format taken from
+ * kernel/drivers/staging/android/logger.h and system/core/logcat/logcat.cpp
+ *
+ * @param array {Uint8Array} Array read from /dev/log/ file
+ * @return {Array} List of log messages
+ */
+function parseLogArray(array) {
+ let data = new DataView(array.buffer);
+ let byteString = String.fromCharCode.apply(null, array);
+
+ // Length of bytes that precede the payload of a log message
+ // From the 5 Uint32 and 1 Uint8 reads
+ const HEADER_LENGTH = 21;
+
+ let logMessages = [];
+ let pos = 0;
+
+ while (pos + HEADER_LENGTH < byteString.length) {
+ // Parse a single log entry
+
+ // Track current offset from global position
+ let offset = 0;
+
+ // Length of the entry, discarded
+ let length = data.getUint32(pos + offset, true);
+ offset += 4;
+ // Id of the process which generated the message
+ let processId = data.getUint32(pos + offset, true);
+ offset += 4;
+ // Id of the thread which generated the message
+ let threadId = data.getUint32(pos + offset, true);
+ offset += 4;
+ // Seconds since epoch when this message was logged
+ let seconds = data.getUint32(pos + offset, true);
+ offset += 4;
+ // Nanoseconds since the last second
+ let nanoseconds = data.getUint32(pos + offset, true);
+ offset += 4;
+
+ // Priority in terms of the ANDROID_LOG_* constants (see below)
+ // This is where the length field begins counting
+ let priority = data.getUint8(pos + offset);
+
+ // Reset pos and offset to count from here
+ pos += offset;
+ offset = 0;
+ offset += 1;
+
+ // Read the tag and message, represented as null-terminated c-style strings
+ let tag = "";
+ while (byteString[pos + offset] != "\0") {
+ tag += byteString[pos + offset];
+ offset ++;
+ }
+ offset ++;
+
+ let message = "";
+ // The kernel log driver may have cut off the null byte (logprint.c)
+ while (byteString[pos + offset] != "\0" && offset < length) {
+ message += byteString[pos + offset];
+ offset ++;
+ }
+
+ // Un-skip the missing null terminator
+ if (offset === length) {
+ offset --;
+ }
+
+ offset ++;
+
+ pos += offset;
+
+ // Log messages are occasionally delimited by newlines, but are also
+ // sometimes followed by newlines as well
+ if (message.charAt(message.length - 1) === "\n") {
+ message = message.substring(0, message.length - 1);
+ }
+
+ // Add an aditional time property to mimic the milliseconds since UTC
+ // expected by Date
+ let time = seconds * 1000.0 + nanoseconds/1000000.0;
+
+ // Log messages with interleaved newlines are considered to be separate log
+ // messages by logcat
+ for (let lineMessage of message.split("\n")) {
+ logMessages.push({
+ processId: processId,
+ threadId: threadId,
+ seconds: seconds,
+ nanoseconds: nanoseconds,
+ time: time,
+ priority: priority,
+ tag: tag,
+ message: lineMessage + "\n"
+ });
+ }
+ }
+
+ return logMessages;
+}
+
+/**
+ * Get a thread-time style formatted string from time
+ * @param time {Number} Milliseconds since epoch
+ * @return {String} Formatted time string
+ */
+function getTimeString(time) {
+ let date = new Date(time);
+ function pad(number) {
+ if ( number < 10 ) {
+ return "0" + number;
+ }
+ return number;
+ }
+ return pad( date.getMonth() + 1 ) +
+ "-" + pad( date.getDate() ) +
+ " " + pad( date.getHours() ) +
+ ":" + pad( date.getMinutes() ) +
+ ":" + pad( date.getSeconds() ) +
+ "." + (date.getMilliseconds() / 1000).toFixed(3).slice(2, 5);
+}
+
+/**
+ * Pad a string using spaces on the left
+ * @param str {String} String to pad
+ * @param width {Number} Desired string length
+ */
+function padLeft(str, width) {
+ while (str.length < width) {
+ str = " " + str;
+ }
+ return str;
+}
+
+/**
+ * Pad a string using spaces on the right
+ * @param str {String} String to pad
+ * @param width {Number} Desired string length
+ */
+function padRight(str, width) {
+ while (str.length < width) {
+ str = str + " ";
+ }
+ return str;
+}
+
+/** Constant values taken from system/core/liblog */
+const ANDROID_LOG_UNKNOWN = 0;
+const ANDROID_LOG_DEFAULT = 1;
+const ANDROID_LOG_VERBOSE = 2;
+const ANDROID_LOG_DEBUG = 3;
+const ANDROID_LOG_INFO = 4;
+const ANDROID_LOG_WARN = 5;
+const ANDROID_LOG_ERROR = 6;
+const ANDROID_LOG_FATAL = 7;
+const ANDROID_LOG_SILENT = 8;
+
+/**
+ * Map a priority number to its abbreviated string equivalent
+ * @param priorityNumber {Number} Log-provided priority number
+ * @return {String} Priority number's abbreviation
+ */
+function getPriorityString(priorityNumber) {
+ switch (priorityNumber) {
+ case ANDROID_LOG_VERBOSE:
+ return "V";
+ case ANDROID_LOG_DEBUG:
+ return "D";
+ case ANDROID_LOG_INFO:
+ return "I";
+ case ANDROID_LOG_WARN:
+ return "W";
+ case ANDROID_LOG_ERROR:
+ return "E";
+ case ANDROID_LOG_FATAL:
+ return "F";
+ case ANDROID_LOG_SILENT:
+ return "S";
+ default:
+ return "?";
+ }
+}
+
+
+/**
+ * Mimic the logcat "threadtime" format, generating a formatted string from a
+ * log message object.
+ * @param logMessage {Object} A log message from the list returned by parseLogArray
+ * @return {String} threadtime formatted summary of the message
+ */
+function formatLogMessage(logMessage) {
+ // MM-DD HH:MM:SS.ms pid tid priority tag: message
+ // from system/core/liblog/logprint.c:
+ return getTimeString(logMessage.time) +
+ " " + padLeft(""+logMessage.processId, 5) +
+ " " + padLeft(""+logMessage.threadId, 5) +
+ " " + getPriorityString(logMessage.priority) +
+ " " + padRight(logMessage.tag, 8) +
+ ": " + logMessage.message;
+}
+
+/**
+ * Convert a string to a utf-8 Uint8Array
+ * @param {String} str
+ * @return {Uint8Array}
+ */
+function textEncode(str) {
+ return new TextEncoder("utf-8").encode(str);
+}
+
+/**
+ * Pretty-print an array of bytes read from a log file by parsing then
+ * threadtime formatting its entries.
+ * @param array {Uint8Array} Array of a log file's bytes
+ * @return {Uint8Array} Pretty-printed log
+ */
+function prettyPrintLogArray(array) {
+ let logMessages = parseLogArray(array);
+ return textEncode(logMessages.map(formatLogMessage).join(""));
+}
+
+/**
+ * Pretty-print an array read from the list of propreties.
+ * @param {Object} Object representing the properties
+ * @return {Uint8Array} Human-readable string of property name: property value
+ */
+function prettyPrintPropertiesArray(properties) {
+ let propertiesString = "";
+ for(let propName in properties) {
+ propertiesString += "[" + propName + "]: [" + properties[propName] + "]\n";
+ }
+ return textEncode(propertiesString);
+}
+
+/**
+ * Pretty-print a normal array. Does nothing.
+ * @param array {Uint8Array} Input array
+ * @return {Uint8Array} The same array
+ */
+function prettyPrintArray(array) {
+ return array;
+}
+
+this.LogParser = {
+ parseLogArray: parseLogArray,
+ prettyPrintArray: prettyPrintArray,
+ prettyPrintLogArray: prettyPrintLogArray,
+ prettyPrintPropertiesArray: prettyPrintPropertiesArray
+};