mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1019816 - Developer option to store logcat to sdcard by shaking the phone. r=gerard-majax
This commit is contained in:
parent
ad693ed26e
commit
78da0cea96
@ -194,6 +194,24 @@ SettingsListener.observe('devtools.overlay', false, (value) => {
|
||||
}
|
||||
});
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
let LogShake;
|
||||
SettingsListener.observe('devtools.logshake', false, (value) => {
|
||||
if (value) {
|
||||
if (!LogShake) {
|
||||
let scope = {};
|
||||
Cu.import('resource://gre/modules/LogShake.jsm', scope);
|
||||
LogShake = scope.LogShake;
|
||||
}
|
||||
LogShake.init();
|
||||
} else {
|
||||
if (LogShake) {
|
||||
LogShake.uninit();
|
||||
}
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
// =================== Device Storage ====================
|
||||
SettingsListener.observe('device.storage.writable.name', 'sdcard', function(value) {
|
||||
if (Services.prefs.getPrefType('device.storage.writable.name') != Ci.nsIPrefBranch.PREF_STRING) {
|
||||
|
91
b2g/components/LogCapture.jsm
Normal file
91
b2g/components/LogCapture.jsm
Normal file
@ -0,0 +1,91 @@
|
||||
/* 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/. */
|
||||
/* jshint moz: true */
|
||||
/* global Uint8Array, Components, dump */
|
||||
|
||||
'use strict';
|
||||
|
||||
this.EXPORTED_SYMBOLS = ['LogCapture'];
|
||||
|
||||
/**
|
||||
* readLogFile
|
||||
* Read in /dev/log/{{log}} in nonblocking mode, which will return -1 if
|
||||
* reading would block the thread.
|
||||
*
|
||||
* @param log {String} The log from which to read. Must be present in /dev/log
|
||||
* @return {Uint8Array} Raw log data
|
||||
*/
|
||||
let readLogFile = function(logLocation) {
|
||||
if (!this.ctypes) {
|
||||
// load in everything on first use
|
||||
Components.utils.import('resource://gre/modules/ctypes.jsm', this);
|
||||
|
||||
this.lib = this.ctypes.open(this.ctypes.libraryName('c'));
|
||||
|
||||
this.read = this.lib.declare('read',
|
||||
this.ctypes.default_abi,
|
||||
this.ctypes.int, // bytes read (out)
|
||||
this.ctypes.int, // file descriptor (in)
|
||||
this.ctypes.voidptr_t, // buffer to read into (in)
|
||||
this.ctypes.size_t // size_t size of buffer (in)
|
||||
);
|
||||
|
||||
this.open = this.lib.declare('open',
|
||||
this.ctypes.default_abi,
|
||||
this.ctypes.int, // file descriptor (returned)
|
||||
this.ctypes.char.ptr, // path
|
||||
this.ctypes.int // flags
|
||||
);
|
||||
|
||||
this.close = this.lib.declare('close',
|
||||
this.ctypes.default_abi,
|
||||
this.ctypes.int, // error code (returned)
|
||||
this.ctypes.int // file descriptor
|
||||
);
|
||||
}
|
||||
|
||||
const O_READONLY = 0;
|
||||
const O_NONBLOCK = 1 << 11;
|
||||
|
||||
const BUF_SIZE = 2048;
|
||||
|
||||
let BufType = this.ctypes.ArrayType(this.ctypes.char);
|
||||
let buf = new BufType(BUF_SIZE);
|
||||
let logArray = [];
|
||||
|
||||
let logFd = this.open(logLocation, O_READONLY | O_NONBLOCK);
|
||||
if (logFd === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let readStart = Date.now();
|
||||
let readCount = 0;
|
||||
while (true) {
|
||||
let count = this.read(logFd, buf, BUF_SIZE);
|
||||
readCount += 1;
|
||||
|
||||
if (count <= 0) {
|
||||
// log has return due to being nonblocking or running out of things
|
||||
break;
|
||||
}
|
||||
for(let i = 0; i < count; i++) {
|
||||
logArray.push(buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
let logTypedArray = new Uint8Array(logArray);
|
||||
|
||||
this.close(logFd);
|
||||
|
||||
return logTypedArray;
|
||||
};
|
||||
|
||||
let cleanup = function() {
|
||||
this.lib.close();
|
||||
this.read = this.open = this.close = null;
|
||||
this.lib = null;
|
||||
this.ctypes = null;
|
||||
};
|
||||
|
||||
this.LogCapture = { readLogFile: readLogFile, cleanup: cleanup };
|
301
b2g/components/LogParser.jsm
Normal file
301
b2g/components/LogParser.jsm
Normal file
@ -0,0 +1,301 @@
|
||||
/* 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);
|
||||
|
||||
let logMessages = [];
|
||||
let pos = 0;
|
||||
|
||||
while (pos < 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {String} Pretty-printed log
|
||||
*/
|
||||
function prettyPrintLogArray(array) {
|
||||
let logMessages = parseLogArray(array);
|
||||
return logMessages.map(formatLogMessage).join("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an array of bytes as a properties file. The structure of the
|
||||
* properties file is derived from bionic/libc/bionic/system_properties.c
|
||||
* @param array {Uint8Array} Array containing property data
|
||||
* @return {Object} Map from property name to property value, both strings
|
||||
*/
|
||||
function parsePropertiesArray(array) {
|
||||
let data = new DataView(array.buffer);
|
||||
let byteString = String.fromCharCode.apply(null, array);
|
||||
|
||||
let properties = {};
|
||||
|
||||
let propIndex = 0;
|
||||
let propCount = data.getUint32(0, true);
|
||||
|
||||
// first TOC entry is at 32
|
||||
let tocOffset = 32;
|
||||
|
||||
const PROP_NAME_MAX = 32;
|
||||
const PROP_VALUE_MAX = 92;
|
||||
|
||||
while (propIndex < propCount) {
|
||||
// Retrieve offset from file start
|
||||
let infoOffset = data.getUint32(tocOffset, true) & 0xffffff;
|
||||
|
||||
// Now read the name, integer serial, and value
|
||||
let propName = "";
|
||||
let nameOffset = infoOffset;
|
||||
while (byteString[nameOffset] != "\0" &&
|
||||
(nameOffset - infoOffset) < PROP_NAME_MAX) {
|
||||
propName += byteString[nameOffset];
|
||||
nameOffset ++;
|
||||
}
|
||||
|
||||
infoOffset += PROP_NAME_MAX;
|
||||
// Skip serial number
|
||||
infoOffset += 4;
|
||||
|
||||
let propValue = "";
|
||||
nameOffset = infoOffset;
|
||||
while (byteString[nameOffset] != "\0" &&
|
||||
(nameOffset - infoOffset) < PROP_VALUE_MAX) {
|
||||
propValue += byteString[nameOffset];
|
||||
nameOffset ++;
|
||||
}
|
||||
|
||||
// Move to next table of contents entry
|
||||
tocOffset += 4;
|
||||
|
||||
properties[propName] = propValue;
|
||||
propIndex += 1;
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty-print an array read from the /dev/__properties__ file.
|
||||
* @param array {Uint8Array} File data array
|
||||
* @return {String} Human-readable string of property name: property value
|
||||
*/
|
||||
function prettyPrintPropertiesArray(array) {
|
||||
let properties = parsePropertiesArray(array);
|
||||
let propertiesString = "";
|
||||
for(let propName in properties) {
|
||||
propertiesString += propName + ": " + properties[propName] + "\n";
|
||||
}
|
||||
return propertiesString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty-print a normal array. Does nothing.
|
||||
* @param array {Uint8Array} Input array
|
||||
*/
|
||||
function prettyPrintArray(array) {
|
||||
return array;
|
||||
}
|
||||
|
||||
this.LogParser = {
|
||||
parseLogArray: parseLogArray,
|
||||
parsePropertiesArray: parsePropertiesArray,
|
||||
prettyPrintArray: prettyPrintArray,
|
||||
prettyPrintLogArray: prettyPrintLogArray,
|
||||
prettyPrintPropertiesArray: prettyPrintPropertiesArray
|
||||
};
|
301
b2g/components/LogShake.jsm
Normal file
301
b2g/components/LogShake.jsm
Normal file
@ -0,0 +1,301 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* LogShake is a module which listens for log requests sent by Gaia. In
|
||||
* response to a sufficiently large acceleration (a shake), it will save log
|
||||
* files to an arbitrary directory which it will then return on a
|
||||
* 'capture-logs-success' event with detail.logFilenames representing each log
|
||||
* file's filename in the directory. If an error occurs it will instead produce
|
||||
* a 'capture-logs-error' event.
|
||||
*/
|
||||
|
||||
/* enable Mozilla javascript extensions and global strictness declaration,
|
||||
* disable valid this checking */
|
||||
/* jshint moz: true */
|
||||
/* jshint -W097 */
|
||||
/* jshint -W040 */
|
||||
/* global Services, Components, dump, LogCapture, LogParser,
|
||||
OS, Promise, volumeService, XPCOMUtils, SystemAppProxy */
|
||||
|
||||
'use strict';
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'LogCapture', 'resource://gre/modules/LogCapture.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'LogParser', 'resource://gre/modules/LogParser.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'OS', 'resource://gre/modules/osfile.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Promise', 'resource://gre/modules/Promise.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Services', 'resource://gre/modules/Services.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'SystemAppProxy', 'resource://gre/modules/SystemAppProxy.jsm');
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, 'powerManagerService',
|
||||
'@mozilla.org/power/powermanagerservice;1',
|
||||
'nsIPowerManagerService');
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, 'volumeService',
|
||||
'@mozilla.org/telephony/volume-service;1',
|
||||
'nsIVolumeService');
|
||||
|
||||
this.EXPORTED_SYMBOLS = ['LogShake'];
|
||||
|
||||
function debug(msg) {
|
||||
dump('LogShake.jsm: '+msg+'\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* An empirically determined amount of acceleration corresponding to a
|
||||
* shake
|
||||
*/
|
||||
const EXCITEMENT_THRESHOLD = 500;
|
||||
const DEVICE_MOTION_EVENT = 'devicemotion';
|
||||
const SCREEN_CHANGE_EVENT = 'screenchange';
|
||||
const CAPTURE_LOGS_ERROR_EVENT = 'capture-logs-error';
|
||||
const CAPTURE_LOGS_SUCCESS_EVENT = 'capture-logs-success';
|
||||
|
||||
// Map of files which have log-type information to their parsers
|
||||
const LOGS_WITH_PARSERS = {
|
||||
'/dev/__properties__': LogParser.prettyPrintPropertiesArray,
|
||||
'/dev/log/main': LogParser.prettyPrintLogArray,
|
||||
'/dev/log/system': LogParser.prettyPrintLogArray,
|
||||
'/dev/log/radio': LogParser.prettyPrintLogArray,
|
||||
'/dev/log/events': LogParser.prettyPrintLogArray,
|
||||
'/proc/cmdline': LogParser.prettyPrintArray,
|
||||
'/proc/kmsg': LogParser.prettyPrintArray,
|
||||
'/proc/meminfo': LogParser.prettyPrintArray,
|
||||
'/proc/uptime': LogParser.prettyPrintArray,
|
||||
'/proc/version': LogParser.prettyPrintArray,
|
||||
'/proc/vmallocinfo': LogParser.prettyPrintArray,
|
||||
'/proc/vmstat': LogParser.prettyPrintArray
|
||||
};
|
||||
|
||||
let LogShake = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
|
||||
/**
|
||||
* If LogShake is listening for device motion events. Required due to lag
|
||||
* between HAL layer of device motion events and listening for device motion
|
||||
* events.
|
||||
*/
|
||||
deviceMotionEnabled: false,
|
||||
|
||||
/**
|
||||
* If a capture has been requested and is waiting for reads/parsing. Used for
|
||||
* debouncing.
|
||||
*/
|
||||
captureRequested: false,
|
||||
|
||||
/**
|
||||
* Start existing, observing motion events if the screen is turned on.
|
||||
*/
|
||||
init: function() {
|
||||
// TODO: no way of querying screen state from power manager
|
||||
// this.handleScreenChangeEvent({ detail: {
|
||||
// screenEnabled: powerManagerService.screenEnabled
|
||||
// }});
|
||||
|
||||
// However, the screen is always on when we are being enabled because it is
|
||||
// either due to the phone starting up or a user enabling us directly.
|
||||
this.handleScreenChangeEvent({ detail: {
|
||||
screenEnabled: true
|
||||
}});
|
||||
|
||||
SystemAppProxy.addEventListener(SCREEN_CHANGE_EVENT, this, false);
|
||||
|
||||
Services.obs.addObserver(this, 'xpcom-shutdown', false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle an arbitrary event, passing it along to the proper function
|
||||
*/
|
||||
handleEvent: function(event) {
|
||||
switch (event.type) {
|
||||
case DEVICE_MOTION_EVENT:
|
||||
if (!this.deviceMotionEnabled) {
|
||||
return;
|
||||
}
|
||||
this.handleDeviceMotionEvent(event);
|
||||
break;
|
||||
|
||||
case SCREEN_CHANGE_EVENT:
|
||||
this.handleScreenChangeEvent(event);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle an observation from Services.obs
|
||||
*/
|
||||
observe: function(subject, topic) {
|
||||
if (topic === 'xpcom-shutdown') {
|
||||
this.uninit();
|
||||
}
|
||||
},
|
||||
|
||||
startDeviceMotionListener: function() {
|
||||
if (!this.deviceMotionEnabled) {
|
||||
SystemAppProxy.addEventListener(DEVICE_MOTION_EVENT, this, false);
|
||||
this.deviceMotionEnabled = true;
|
||||
}
|
||||
},
|
||||
|
||||
stopDeviceMotionListener: function() {
|
||||
SystemAppProxy.removeEventListener(DEVICE_MOTION_EVENT, this, false);
|
||||
this.deviceMotionEnabled = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle a motion event, keeping track of 'excitement', the magnitude
|
||||
* of the device's acceleration.
|
||||
*/
|
||||
handleDeviceMotionEvent: function(event) {
|
||||
// There is a lag between disabling the event listener and event arrival
|
||||
// ceasing.
|
||||
if (!this.deviceMotionEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
var acc = event.accelerationIncludingGravity;
|
||||
|
||||
var excitement = acc.x * acc.x + acc.y * acc.y + acc.z * acc.z;
|
||||
|
||||
if (excitement > EXCITEMENT_THRESHOLD) {
|
||||
if (!this.captureRequested) {
|
||||
this.captureRequested = true;
|
||||
captureLogs().then(logResults => {
|
||||
// On resolution send the success event to the requester
|
||||
SystemAppProxy._sendCustomEvent(CAPTURE_LOGS_SUCCESS_EVENT, {
|
||||
logFilenames: logResults.logFilenames,
|
||||
logPrefix: logResults.logPrefix
|
||||
});
|
||||
this.captureRequested = false;
|
||||
},
|
||||
error => {
|
||||
// On an error send the error event
|
||||
SystemAppProxy._sendCustomEvent(CAPTURE_LOGS_ERROR_EVENT, {error: error});
|
||||
this.captureRequested = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleScreenChangeEvent: function(event) {
|
||||
if (event.detail.screenEnabled) {
|
||||
this.startDeviceMotionListener();
|
||||
} else {
|
||||
this.stopDeviceMotionListener();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop logshake, removing all listeners
|
||||
*/
|
||||
uninit: function() {
|
||||
this.stopDeviceMotionListener();
|
||||
SystemAppProxy.removeEventListener(SCREEN_CHANGE_EVENT, this, false);
|
||||
Services.obs.removeObserver(this, 'xpcom-shutdown');
|
||||
}
|
||||
};
|
||||
|
||||
function getLogFilename(logLocation) {
|
||||
// sanitize the log location
|
||||
let logName = logLocation.replace(/\//g, '-');
|
||||
if (logName[0] === '-') {
|
||||
logName = logName.substring(1);
|
||||
}
|
||||
return logName + '.log';
|
||||
}
|
||||
|
||||
function getSdcardPrefix() {
|
||||
return volumeService.getVolumeByName('sdcard').mountPoint;
|
||||
}
|
||||
|
||||
function getLogDirectory() {
|
||||
let d = new Date();
|
||||
d = new Date(d.getTime() - d.getTimezoneOffset() * 60000);
|
||||
let timestamp = d.toISOString().slice(0, -5).replace(/[:T]/g, '-');
|
||||
// return directory name of format 'logs/timestamp/'
|
||||
return OS.Path.join('logs', timestamp, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures and saves the current device logs, returning a promise that will
|
||||
* resolve to an array of log filenames.
|
||||
*/
|
||||
function captureLogs() {
|
||||
let logArrays = readLogs();
|
||||
return saveLogs(logArrays);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read in all log files, returning their formatted contents
|
||||
*/
|
||||
function readLogs() {
|
||||
let logArrays = {};
|
||||
for (let loc in LOGS_WITH_PARSERS) {
|
||||
let logArray = LogCapture.readLogFile(loc);
|
||||
if (!logArray) {
|
||||
continue;
|
||||
}
|
||||
let prettyLogArray = LOGS_WITH_PARSERS[loc](logArray);
|
||||
|
||||
logArrays[loc] = prettyLogArray;
|
||||
}
|
||||
return logArrays;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the formatted arrays of log files to an sdcard if available
|
||||
*/
|
||||
function saveLogs(logArrays) {
|
||||
if (!logArrays || Object.keys(logArrays).length === 0) {
|
||||
return Promise.resolve({
|
||||
logFilenames: [],
|
||||
logPrefix: ''
|
||||
});
|
||||
}
|
||||
|
||||
let sdcardPrefix, dirName;
|
||||
try {
|
||||
sdcardPrefix = getSdcardPrefix();
|
||||
dirName = getLogDirectory();
|
||||
} catch(e) {
|
||||
// Return promise failed with exception e
|
||||
// Handles missing sdcard
|
||||
return Promise.reject(e);
|
||||
}
|
||||
|
||||
debug('making a directory all the way from '+sdcardPrefix+' to '+(sdcardPrefix + '/' + dirName));
|
||||
return OS.File.makeDir(OS.Path.join(sdcardPrefix, dirName), {from: sdcardPrefix})
|
||||
.then(function() {
|
||||
// Now the directory is guaranteed to exist, save the logs
|
||||
let logFilenames = [];
|
||||
let saveRequests = [];
|
||||
|
||||
for (let logLocation in logArrays) {
|
||||
debug('requesting save of ' + logLocation);
|
||||
let logArray = logArrays[logLocation];
|
||||
// The filename represents the relative path within the SD card, not the
|
||||
// absolute path because Gaia will refer to it using the DeviceStorage
|
||||
// API
|
||||
let filename = dirName + getLogFilename(logLocation);
|
||||
logFilenames.push(filename);
|
||||
let saveRequest = OS.File.writeAtomic(OS.Path.join(sdcardPrefix, filename), logArray);
|
||||
saveRequests.push(saveRequest);
|
||||
}
|
||||
|
||||
return Promise.all(saveRequests).then(function() {
|
||||
debug('returning logfilenames: '+logFilenames.toSource());
|
||||
return {
|
||||
logFilenames: logFilenames,
|
||||
logPrefix: dirName
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
LogShake.init();
|
||||
this.LogShake = LogShake;
|
@ -52,6 +52,9 @@ EXTRA_JS_MODULES += [
|
||||
'ContentRequestHelper.jsm',
|
||||
'ErrorPage.jsm',
|
||||
'FxAccountsMgmtService.jsm',
|
||||
'LogCapture.jsm',
|
||||
'LogParser.jsm',
|
||||
'LogShake.jsm',
|
||||
'SignInToWebsite.jsm',
|
||||
'SystemAppProxy.jsm',
|
||||
'TelURIParser.jsm',
|
||||
|
BIN
b2g/components/test/unit/data/test_logger_file
Normal file
BIN
b2g/components/test/unit/data/test_logger_file
Normal file
Binary file not shown.
BIN
b2g/components/test/unit/data/test_properties
Normal file
BIN
b2g/components/test/unit/data/test_properties
Normal file
Binary file not shown.
25
b2g/components/test/unit/test_logcapture.js
Normal file
25
b2g/components/test/unit/test_logcapture.js
Normal file
@ -0,0 +1,25 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
|
||||
/**
|
||||
* Test that LogCapture successfully reads from the /dev/log devices, returning
|
||||
* a Uint8Array of some length, including zero. This tests a few standard
|
||||
* log devices
|
||||
*/
|
||||
function run_test() {
|
||||
Components.utils.import('resource:///modules/LogCapture.jsm');
|
||||
|
||||
function verifyLog(log) {
|
||||
// log exists
|
||||
notEqual(log, null);
|
||||
// log has a length and it is non-negative (is probably array-like)
|
||||
ok(log.length >= 0);
|
||||
}
|
||||
|
||||
let mainLog = LogCapture.readLogFile('/dev/log/main');
|
||||
verifyLog(mainLog);
|
||||
|
||||
let meminfoLog = LogCapture.readLogFile('/proc/meminfo');
|
||||
verifyLog(meminfoLog);
|
||||
}
|
49
b2g/components/test/unit/test_logparser.js
Normal file
49
b2g/components/test/unit/test_logparser.js
Normal file
@ -0,0 +1,49 @@
|
||||
/* jshint moz: true */
|
||||
|
||||
const {utils: Cu, classes: Cc, interfaces: Ci} = Components;
|
||||
|
||||
function run_test() {
|
||||
Cu.import('resource:///modules/LogParser.jsm');
|
||||
|
||||
let propertiesFile = do_get_file('data/test_properties');
|
||||
let loggerFile = do_get_file('data/test_logger_file');
|
||||
|
||||
let propertiesStream = makeStream(propertiesFile);
|
||||
let loggerStream = makeStream(loggerFile);
|
||||
|
||||
// Initialize arrays to hold the file contents (lengths are hardcoded)
|
||||
let propertiesArray = new Uint8Array(propertiesStream.readByteArray(65536));
|
||||
let loggerArray = new Uint8Array(loggerStream.readByteArray(4037));
|
||||
|
||||
propertiesStream.close();
|
||||
loggerStream.close();
|
||||
|
||||
let properties = LogParser.parsePropertiesArray(propertiesArray);
|
||||
let logMessages = LogParser.parseLogArray(loggerArray);
|
||||
|
||||
// Test arbitrary property entries for correctness
|
||||
equal(properties['ro.boot.console'], 'ttyHSL0');
|
||||
equal(properties['net.tcp.buffersize.lte'],
|
||||
'524288,1048576,2097152,262144,524288,1048576');
|
||||
|
||||
ok(logMessages.length === 58, 'There should be 58 messages in the log');
|
||||
|
||||
let expectedLogEntry = {
|
||||
processId: 271, threadId: 271,
|
||||
seconds: 790796, nanoseconds: 620000001, time: 790796620.000001,
|
||||
priority: 4, tag: 'Vold',
|
||||
message: 'Vold 2.1 (the revenge) firing up\n'
|
||||
};
|
||||
|
||||
deepEqual(expectedLogEntry, logMessages[0]);
|
||||
}
|
||||
|
||||
function makeStream(file) {
|
||||
var fileStream = Cc['@mozilla.org/network/file-input-stream;1']
|
||||
.createInstance(Ci.nsIFileInputStream);
|
||||
fileStream.init(file, -1, -1, 0);
|
||||
var bis = Cc['@mozilla.org/binaryinputstream;1']
|
||||
.createInstance(Ci.nsIBinaryInputStream);
|
||||
bis.setInputStream(fileStream);
|
||||
return bis;
|
||||
}
|
141
b2g/components/test/unit/test_logshake.js
Normal file
141
b2g/components/test/unit/test_logshake.js
Normal file
@ -0,0 +1,141 @@
|
||||
/**
|
||||
* Test the log capturing capabilities of LogShake.jsm
|
||||
*/
|
||||
|
||||
/* jshint moz: true */
|
||||
/* global Components, LogCapture, LogShake, ok, add_test, run_next_test, dump */
|
||||
/* exported run_test */
|
||||
|
||||
/* disable use strict warning */
|
||||
/* jshint -W097 */
|
||||
'use strict';
|
||||
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import('resource://gre/modules/LogCapture.jsm');
|
||||
Cu.import('resource://gre/modules/LogShake.jsm');
|
||||
|
||||
// Force logshake to handle a device motion event with given components
|
||||
// Does not use SystemAppProxy because event needs special
|
||||
// accelerationIncludingGravity property
|
||||
function sendDeviceMotionEvent(x, y, z) {
|
||||
let event = {
|
||||
type: 'devicemotion',
|
||||
accelerationIncludingGravity: {
|
||||
x: x,
|
||||
y: y,
|
||||
z: z
|
||||
}
|
||||
};
|
||||
LogShake.handleEvent(event);
|
||||
}
|
||||
|
||||
// Send a screen change event directly, does not use SystemAppProxy due to race
|
||||
// conditions.
|
||||
function sendScreenChangeEvent(screenEnabled) {
|
||||
let event = {
|
||||
type: 'screenchange',
|
||||
detail: {
|
||||
screenEnabled: screenEnabled
|
||||
}
|
||||
};
|
||||
LogShake.handleEvent(event);
|
||||
}
|
||||
|
||||
function debug(msg) {
|
||||
var timestamp = Date.now();
|
||||
dump('LogShake: ' + timestamp + ': ' + msg);
|
||||
}
|
||||
|
||||
add_test(function test_do_log_capture_after_shaking() {
|
||||
// Enable LogShake
|
||||
LogShake.init();
|
||||
|
||||
let readLocations = [];
|
||||
LogCapture.readLogFile = function(loc) {
|
||||
readLocations.push(loc);
|
||||
return null; // we don't want to provide invalid data to a parser
|
||||
};
|
||||
|
||||
// Fire a devicemotion event that is of shake magnitude
|
||||
sendDeviceMotionEvent(9001, 9001, 9001);
|
||||
|
||||
ok(readLocations.length > 0,
|
||||
'LogShake should attempt to read at least one log');
|
||||
|
||||
LogShake.uninit();
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_do_nothing_when_resting() {
|
||||
// Enable LogShake
|
||||
LogShake.init();
|
||||
|
||||
let readLocations = [];
|
||||
LogCapture.readLogFile = function(loc) {
|
||||
readLocations.push(loc);
|
||||
return null; // we don't want to provide invalid data to a parser
|
||||
};
|
||||
|
||||
// Fire a devicemotion event that is relatively tiny
|
||||
sendDeviceMotionEvent(0, 9.8, 9.8);
|
||||
|
||||
ok(readLocations.length === 0,
|
||||
'LogShake should not read any logs');
|
||||
|
||||
debug('test_do_nothing_when_resting: stop');
|
||||
LogShake.uninit();
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_do_nothing_when_disabled() {
|
||||
debug('test_do_nothing_when_disabled: start');
|
||||
// Disable LogShake
|
||||
LogShake.uninit();
|
||||
|
||||
let readLocations = [];
|
||||
LogCapture.readLogFile = function(loc) {
|
||||
readLocations.push(loc);
|
||||
return null; // we don't want to provide invalid data to a parser
|
||||
};
|
||||
|
||||
// Fire a devicemotion event that would normally be a shake
|
||||
sendDeviceMotionEvent(0, 9001, 9001);
|
||||
|
||||
ok(readLocations.length === 0,
|
||||
'LogShake should not read any logs');
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_do_nothing_when_screen_off() {
|
||||
// Enable LogShake
|
||||
LogShake.init();
|
||||
|
||||
|
||||
// Send an event as if the screen has been turned off
|
||||
sendScreenChangeEvent(false);
|
||||
|
||||
let readLocations = [];
|
||||
LogCapture.readLogFile = function(loc) {
|
||||
readLocations.push(loc);
|
||||
return null; // we don't want to provide invalid data to a parser
|
||||
};
|
||||
|
||||
// Fire a devicemotion event that would normally be a shake
|
||||
sendDeviceMotionEvent(0, 9001, 9001);
|
||||
|
||||
ok(readLocations.length === 0,
|
||||
'LogShake should not read any logs');
|
||||
|
||||
// Restore the screen
|
||||
sendScreenChangeEvent(true);
|
||||
|
||||
LogShake.uninit();
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
debug('Starting');
|
||||
run_next_test();
|
||||
}
|
@ -2,6 +2,10 @@
|
||||
head =
|
||||
tail =
|
||||
|
||||
support-files =
|
||||
data/test_logger_file
|
||||
data/test_properties
|
||||
|
||||
[test_bug793310.js]
|
||||
|
||||
[test_bug832946.js]
|
||||
@ -11,4 +15,10 @@ tail =
|
||||
head = head_identity.js
|
||||
tail =
|
||||
|
||||
[test_logcapture.js]
|
||||
# only run on b2g builds due to requiring b2g-specific log files to exist
|
||||
skip-if = toolkit != "gonk"
|
||||
|
||||
[test_logparser.js]
|
||||
|
||||
[test_logshake.js]
|
||||
|
Loading…
Reference in New Issue
Block a user