gecko-dev/devtools/client/performance/modules/io.js

171 lines
5.4 KiB
JavaScript

/* 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/. */
"use strict";
const { Cc, Ci, Cu, Cr } = require("chrome");
loader.lazyRequireGetter(this, "Services");
loader.lazyRequireGetter(this, "promise");
loader.lazyRequireGetter(this, "RecordingUtils",
"devtools/shared/performance/recording-utils");
loader.lazyImporter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
loader.lazyImporter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
// This identifier string is used to tentatively ascertain whether or not
// a JSON loaded from disk is actually something generated by this tool.
// It isn't, of course, a definitive verification, but a Good Enough™
// approximation before continuing the import. Don't localize this.
const PERF_TOOL_SERIALIZER_IDENTIFIER = "Recorded Performance Data";
const PERF_TOOL_SERIALIZER_LEGACY_VERSION = 1;
const PERF_TOOL_SERIALIZER_CURRENT_VERSION = 2;
/**
* Helpers for importing/exporting JSON.
*/
/**
* Gets a nsIScriptableUnicodeConverter instance with a default UTF-8 charset.
* @return object
*/
function getUnicodeConverter () {
let className = "@mozilla.org/intl/scriptableunicodeconverter";
let converter = Cc[className].createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
return converter;
}
/**
* Saves a recording as JSON to a file. The provided data is assumed to be
* acyclical, so that it can be properly serialized.
*
* @param object recordingData
* The recording data to stream as JSON.
* @param nsILocalFile file
* The file to stream the data into.
* @return object
* A promise that is resolved once streaming finishes, or rejected
* if there was an error.
*/
function saveRecordingToFile (recordingData, file) {
let deferred = promise.defer();
recordingData.fileType = PERF_TOOL_SERIALIZER_IDENTIFIER;
recordingData.version = PERF_TOOL_SERIALIZER_CURRENT_VERSION;
let string = JSON.stringify(recordingData);
let inputStream = this.getUnicodeConverter().convertToInputStream(string);
let outputStream = FileUtils.openSafeFileOutputStream(file);
NetUtil.asyncCopy(inputStream, outputStream, deferred.resolve);
return deferred.promise;
}
/**
* Loads a recording stored as JSON from a file.
*
* @param nsILocalFile file
* The file to import the data from.
* @return object
* A promise that is resolved once importing finishes, or rejected
* if there was an error.
*/
function loadRecordingFromFile (file) {
let deferred = promise.defer();
let channel = NetUtil.newChannel({
uri: NetUtil.newURI(file),
loadUsingSystemPrincipal: true});
channel.contentType = "text/plain";
NetUtil.asyncFetch(channel, (inputStream, status) => {
try {
let string = NetUtil.readInputStreamToString(inputStream, inputStream.available());
var recordingData = JSON.parse(string);
} catch (e) {
deferred.reject(new Error("Could not read recording data file."));
return;
}
if (recordingData.fileType != PERF_TOOL_SERIALIZER_IDENTIFIER) {
deferred.reject(new Error("Unrecognized recording data file."));
return;
}
if (!isValidSerializerVersion(recordingData.version)) {
deferred.reject(new Error("Unsupported recording data file version."));
return;
}
if (recordingData.version === PERF_TOOL_SERIALIZER_LEGACY_VERSION) {
recordingData = convertLegacyData(recordingData);
}
if (recordingData.profile.meta.version === 2) {
RecordingUtils.deflateProfile(recordingData.profile);
}
if (!recordingData.label) {
// set the label to the filename without its extension
recordingData.label = file.leafName.replace(/\..+$/, "");
}
deferred.resolve(recordingData);
});
return deferred.promise;
}
/**
* Returns a boolean indicating whether or not the passed in `version`
* is supported by this serializer.
*
* @param number version
* @return boolean
*/
function isValidSerializerVersion (version) {
return !!~[
PERF_TOOL_SERIALIZER_LEGACY_VERSION,
PERF_TOOL_SERIALIZER_CURRENT_VERSION
].indexOf(version);
}
/**
* Takes recording data (with version `1`, from the original profiler tool), and
* massages the data to be line with the current performance tool's property names
* and values.
*
* @param object legacyData
* @return object
*/
function convertLegacyData (legacyData) {
let { profilerData, ticksData, recordingDuration } = legacyData;
// The `profilerData` and `ticksData` stay, but the previously unrecorded
// fields just are empty arrays or objects.
let data = {
label: profilerData.profilerLabel,
duration: recordingDuration,
markers: [],
frames: [],
memory: [],
ticks: ticksData,
allocations: { sites: [], timestamps: [], frames: [], sizes: [] },
profile: profilerData.profile,
// Fake a configuration object here if there's tick data,
// so that it can be rendered
configuration: {
withTicks: !!ticksData.length,
withMarkers: false,
withMemory: false,
withAllocations: false
},
systemHost: {},
systemClient: {},
};
return data;
}
exports.getUnicodeConverter = getUnicodeConverter;
exports.saveRecordingToFile = saveRecordingToFile;
exports.loadRecordingFromFile = loadRecordingFromFile;