summaryrefslogtreecommitdiffstats
path: root/b2g/components/LogShake.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'b2g/components/LogShake.jsm')
-rw-r--r--b2g/components/LogShake.jsm588
1 files changed, 0 insertions, 588 deletions
diff --git a/b2g/components/LogShake.jsm b/b2g/components/LogShake.jsm
deleted file mode 100644
index 6426c21de..000000000
--- a/b2g/components/LogShake.jsm
+++ /dev/null
@@ -1,588 +0,0 @@
-/* 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 name and detail.logPaths representing the patch to each log file or
- * the path to the archive.
- * If an error occurs it will instead produce a 'capture-logs-error' event.
- * We send a capture-logs-start events to notify the system app and the user,
- * since dumping can be a bit long sometimes.
- */
-
-/* enable Mozilla javascript extensions and global strictness declaration,
- * disable valid this checking */
-/* jshint moz: true, esnext: true */
-/* jshint -W097 */
-/* jshint -W040 */
-/* global Services, Components, dump, LogCapture, LogParser,
- OS, Promise, volumeService, XPCOMUtils, SystemAppProxy */
-
-"use strict";
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-// Constants for creating zip file taken from toolkit/webapps/tests/head.js
-const PR_RDWR = 0x04;
-const PR_CREATE_FILE = 0x08;
-const PR_TRUNCATE = 0x20;
-
-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;
-/**
- * The maximum fraction to update the excitement value per frame. This
- * corresponds to requiring shaking for approximately 10 motion events (1.6
- * seconds)
- */
-const EXCITEMENT_FILTER_ALPHA = 0.2;
-const DEVICE_MOTION_EVENT = "devicemotion";
-const SCREEN_CHANGE_EVENT = "screenchange";
-const CAPTURE_LOGS_CONTENT_EVENT = "requestSystemLogs";
-const CAPTURE_LOGS_START_EVENT = "capture-logs-start";
-const CAPTURE_LOGS_ERROR_EVENT = "capture-logs-error";
-const CAPTURE_LOGS_SUCCESS_EVENT = "capture-logs-success";
-
-var LogShake = {
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
-
- /**
- * If LogShake is in QA Mode, which bundles all files into a compressed archive
- */
- qaModeEnabled: false,
-
- /**
- * 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,
-
- /**
- * We only listen to motion events when the screen is enabled, keep track
- * of its state.
- */
- screenEnabled: true,
-
- /**
- * Flag monitoring if the preference to enable shake to capture is
- * enabled in gaia.
- */
- listenToDeviceMotion: true,
-
- /**
- * If a capture has been requested and is waiting for reads/parsing. Used for
- * debouncing.
- */
- captureRequested: false,
-
- /**
- * The current excitement (movement) level
- */
- excitement: 0,
-
- /**
- * Map of files which have log-type information to their parsers
- */
- LOGS_WITH_PARSERS: {
- "/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/last_kmsg": LogParser.prettyPrintArray,
- "/proc/meminfo": LogParser.prettyPrintArray,
- "/proc/uptime": LogParser.prettyPrintArray,
- "/proc/version": LogParser.prettyPrintArray,
- "/proc/vmallocinfo": LogParser.prettyPrintArray,
- "/proc/vmstat": LogParser.prettyPrintArray,
- "/system/b2g/application.ini": LogParser.prettyPrintArray,
- "/cache/recovery/last_install": LogParser.prettyPrintArray,
- "/cache/recovery/last_kmsg": LogParser.prettyPrintArray,
- "/cache/recovery/last_log": LogParser.prettyPrintArray
- },
-
- /**
- * 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
- }});
-
- // Reset excitement to clear residual motion
- this.excitement = 0;
-
- SystemAppProxy.addEventListener(CAPTURE_LOGS_CONTENT_EVENT, this, false);
- 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;
-
- case CAPTURE_LOGS_CONTENT_EVENT:
- this.startCapture();
- break;
- }
- },
-
- /**
- * Handle an observation from Services.obs
- */
- observe: function(subject, topic) {
- if (topic === "xpcom-shutdown") {
- this.uninit();
- }
- },
-
- enableQAMode: function() {
- debug("Enabling QA Mode");
- this.qaModeEnabled = true;
- },
-
- disableQAMode: function() {
- debug("Disabling QA Mode");
- this.qaModeEnabled = false;
- },
-
- enableDeviceMotionListener: function() {
- this.listenToDeviceMotion = true;
- this.startDeviceMotionListener();
- },
-
- disableDeviceMotionListener: function() {
- this.listenToDeviceMotion = false;
- this.stopDeviceMotionListener();
- },
-
- startDeviceMotionListener: function() {
- if (!this.deviceMotionEnabled &&
- this.listenToDeviceMotion &&
- this.screenEnabled) {
- 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;
- }
-
- let acc = event.accelerationIncludingGravity;
-
- // Updates excitement by a factor of at most alpha, ignoring sudden device
- // motion. See bug #1101994 for more information.
- let newExcitement = acc.x * acc.x + acc.y * acc.y + acc.z * acc.z;
- this.excitement += (newExcitement - this.excitement) * EXCITEMENT_FILTER_ALPHA;
-
- if (this.excitement > EXCITEMENT_THRESHOLD) {
- this.startCapture();
- }
- },
-
- startCapture: function() {
- if (this.captureRequested) {
- return;
- }
- this.captureRequested = true;
- SystemAppProxy._sendCustomEvent(CAPTURE_LOGS_START_EVENT, {});
- this.captureLogs().then(logResults => {
- // On resolution send the success event to the requester
- SystemAppProxy._sendCustomEvent(CAPTURE_LOGS_SUCCESS_EVENT, {
- logPaths: logResults.logPaths,
- logFilenames: logResults.logFilenames
- });
- 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) {
- this.screenEnabled = event.detail.screenEnabled;
- if (this.screenEnabled) {
- this.startDeviceMotionListener();
- } else {
- this.stopDeviceMotionListener();
- }
- },
-
- /**
- * Captures and saves the current device logs, returning a promise that will
- * resolve to an array of log filenames.
- */
- captureLogs: function() {
- return this.readLogs().then(logArrays => {
- return this.saveLogs(logArrays);
- });
- },
-
- /**
- * Read in all log files, returning their formatted contents
- * @return {Promise<Array>}
- */
- readLogs: function() {
- let logArrays = {};
- let readPromises = [];
-
- try {
- logArrays["properties"] =
- LogParser.prettyPrintPropertiesArray(LogCapture.readProperties());
- } catch (ex) {
- Cu.reportError("Unable to get device properties: " + ex);
- }
-
- // Let Gecko perfom the dump to a file, and just collect it
- let readAboutMemoryPromise = new Promise(resolve => {
- // Wrap the readAboutMemory promise to make it infallible
- LogCapture.readAboutMemory().then(aboutMemory => {
- let file = OS.Path.basename(aboutMemory);
- let logArray;
- try {
- logArray = LogCapture.readLogFile(aboutMemory);
- if (!logArray) {
- debug("LogCapture.readLogFile() returned nothing for about:memory");
- }
- // We need to remove the dumped file, now that we have it in memory
- OS.File.remove(aboutMemory);
- } catch (ex) {
- Cu.reportError("Unable to handle about:memory dump: " + ex);
- }
- logArrays[file] = LogParser.prettyPrintArray(logArray);
- resolve();
- }, ex => {
- Cu.reportError("Unable to get about:memory dump: " + ex);
- resolve();
- });
- });
- readPromises.push(readAboutMemoryPromise);
-
- // Wrap the promise to make it infallible
- let readScreenshotPromise = new Promise(resolve => {
- LogCapture.getScreenshot().then(screenshot => {
- logArrays["screenshot.png"] = screenshot;
- resolve();
- }, ex => {
- Cu.reportError("Unable to get screenshot dump: " + ex);
- resolve();
- });
- });
- readPromises.push(readScreenshotPromise);
-
- for (let loc in this.LOGS_WITH_PARSERS) {
- let logArray;
- try {
- logArray = LogCapture.readLogFile(loc);
- if (!logArray) {
- debug("LogCapture.readLogFile() returned nothing for: " + loc);
- continue;
- }
- } catch (ex) {
- Cu.reportError("Unable to LogCapture.readLogFile('" + loc + "'): " + ex);
- continue;
- }
-
- try {
- logArrays[loc] = this.LOGS_WITH_PARSERS[loc](logArray);
- } catch (ex) {
- Cu.reportError("Unable to parse content of '" + loc + "': " + ex);
- continue;
- }
- }
-
- // Because the promises we depend upon can't fail this means that the
- // blocking log reads will always be honored.
- return Promise.all(readPromises).then(() => {
- return logArrays;
- });
- },
-
- /**
- * Save the formatted arrays of log files to an sdcard if available
- */
- saveLogs: function(logArrays) {
- if (!logArrays || Object.keys(logArrays).length === 0) {
- return Promise.reject("Zero logs saved");
- }
-
- if (this.qaModeEnabled) {
- return makeBaseLogsDirectory().then(writeLogArchive(logArrays),
- rejectFunction("Error making base log directory"));
- } else {
- return makeBaseLogsDirectory().then(makeLogsDirectory,
- rejectFunction("Error making base log directory"))
- .then(writeLogFiles(logArrays),
- rejectFunction("Error creating log directory"));
- }
- },
-
- /**
- * 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);
- }
-
- // If no extension is provided, default to forcing .log
- let extension = ".log";
- let logLocationExt = logLocation.split(".");
- if (logLocationExt.length > 1) {
- // otherwise, just append nothing
- extension = "";
- }
-
- return logName + extension;
-}
-
-function getSdcardPrefix() {
- return volumeService.getVolumeByName("sdcard").mountPoint;
-}
-
-function getLogDirectoryRoot() {
- return "logs";
-}
-
-function getLogIdentifier() {
- let d = new Date();
- d = new Date(d.getTime() - d.getTimezoneOffset() * 60000);
- let timestamp = d.toISOString().slice(0, -5).replace(/[:T]/g, "-");
- return timestamp;
-}
-
-function rejectFunction(message) {
- return function(err) {
- debug(message + ": " + err);
- return Promise.reject(err);
- };
-}
-
-function makeBaseLogsDirectory() {
- let sdcardPrefix;
- try {
- sdcardPrefix = getSdcardPrefix();
- } catch(e) {
- // Handles missing sdcard
- return Promise.reject(e);
- }
-
- let dirNameRoot = getLogDirectoryRoot();
-
- let logsRoot = OS.Path.join(sdcardPrefix, dirNameRoot);
-
- debug("Creating base log directory at root " + sdcardPrefix);
-
- return OS.File.makeDir(logsRoot, {from: sdcardPrefix}).then(
- function() {
- return {
- sdcardPrefix: sdcardPrefix,
- basePrefix: dirNameRoot
- };
- }
- );
-}
-
-function makeLogsDirectory({sdcardPrefix, basePrefix}) {
- let dirName = getLogIdentifier();
-
- let logsRoot = OS.Path.join(sdcardPrefix, basePrefix);
- let logsDir = OS.Path.join(logsRoot, dirName);
-
- debug("Creating base log directory at root " + sdcardPrefix);
- debug("Final created directory will be " + logsDir);
-
- return OS.File.makeDir(logsDir, {ignoreExisting: false}).then(
- function() {
- debug("Created: " + logsDir);
- return {
- logPrefix: OS.Path.join(basePrefix, dirName),
- sdcardPrefix: sdcardPrefix
- };
- },
- rejectFunction("Error at OS.File.makeDir for " + logsDir)
- );
-}
-
-function getFile(filename) {
- let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
- file.initWithPath(filename);
- return file;
-}
-
-/**
- * Make a zip file
- * @param {String} absoluteZipFilename - Fully qualified desired location of the zip file
- * @param {Map<String, Uint8Array>} logArrays - Map from log location to log data
- * @return {Array<String>} Paths of entries in the archive
- */
-function makeZipFile(absoluteZipFilename, logArrays) {
- let logFilenames = [];
- let zipWriter = Cc["@mozilla.org/zipwriter;1"].createInstance(Ci.nsIZipWriter);
- let zipFile = getFile(absoluteZipFilename);
- zipWriter.open(zipFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
-
- for (let logLocation in logArrays) {
- let logArray = logArrays[logLocation];
- let logFilename = getLogFilename(logLocation);
- logFilenames.push(logFilename);
-
- debug("Adding " + logFilename + " to the zip");
- let logArrayStream = Cc["@mozilla.org/io/arraybuffer-input-stream;1"]
- .createInstance(Ci.nsIArrayBufferInputStream);
- // Set data to be copied, default offset to 0 because it is not present on
- // ArrayBuffer objects
- logArrayStream.setData(logArray.buffer, logArray.byteOffset || 0,
- logArray.byteLength);
-
- zipWriter.addEntryStream(logFilename, Date.now(),
- Ci.nsIZipWriter.COMPRESSION_DEFAULT,
- logArrayStream, false);
- }
- zipWriter.close();
-
- return logFilenames;
-}
-
-function writeLogArchive(logArrays) {
- return function({sdcardPrefix, basePrefix}) {
- // Now the directory is guaranteed to exist, save the logs into their
- // archive file
-
- let zipFilename = getLogIdentifier() + "-logs.zip";
- let zipPath = OS.Path.join(basePrefix, zipFilename);
- let zipPrefix = OS.Path.dirname(zipPath);
- let absoluteZipPath = OS.Path.join(sdcardPrefix, zipPath);
-
- debug("Creating zip file at " + zipPath);
- let logFilenames = [];
- try {
- logFilenames = makeZipFile(absoluteZipPath, logArrays);
- } catch(e) {
- return Promise.reject(e);
- }
- debug("Zip file created");
-
- return {
- logFilenames: logFilenames,
- logPaths: [zipPath],
- compressed: true
- };
- };
-}
-
-function writeLogFiles(logArrays) {
- return function({sdcardPrefix, logPrefix}) {
- // Now the directory is guaranteed to exist, save the logs
- let logFilenames = [];
- let logPaths = [];
- let saveRequests = [];
-
- for (let logLocation in logArrays) {
- debug("Requesting save of " + logLocation);
- let logArray = logArrays[logLocation];
- let logFilename = getLogFilename(logLocation);
- // The local pathrepresents the relative path within the SD card, not the
- // absolute path because Gaia will refer to it using the DeviceStorage
- // API
- let localPath = OS.Path.join(logPrefix, logFilename);
-
- logFilenames.push(logFilename);
- logPaths.push(localPath);
-
- let absolutePath = OS.Path.join(sdcardPrefix, localPath);
- let saveRequest = OS.File.writeAtomic(absolutePath, logArray);
- saveRequests.push(saveRequest);
- }
-
- return Promise.all(saveRequests).then(
- function() {
- return {
- logFilenames: logFilenames,
- logPaths: logPaths,
- compressed: false
- };
- },
- rejectFunction("Error at some save request")
- );
- };
-}
-
-LogShake.init();
-this.LogShake = LogShake;