summaryrefslogtreecommitdiffstats
path: root/devtools/client/performance/modules/io.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/performance/modules/io.js')
-rw-r--r--devtools/client/performance/modules/io.js173
1 files changed, 173 insertions, 0 deletions
diff --git a/devtools/client/performance/modules/io.js b/devtools/client/performance/modules/io.js
new file mode 100644
index 0000000000..97fb16dc41
--- /dev/null
+++ b/devtools/client/performance/modules/io.js
@@ -0,0 +1,173 @@
+/* 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 } = require("chrome");
+
+const RecordingUtils = require("devtools/shared/performance/recording-utils");
+const { FileUtils } = require("resource://gre/modules/FileUtils.jsm");
+const { NetUtil } = require("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() {
+ const cname = "@mozilla.org/intl/scriptableunicodeconverter";
+ const converter = Cc[cname].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 nsIFile 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) {
+ recordingData.fileType = PERF_TOOL_SERIALIZER_IDENTIFIER;
+ recordingData.version = PERF_TOOL_SERIALIZER_CURRENT_VERSION;
+
+ const string = JSON.stringify(recordingData);
+ const inputStream = getUnicodeConverter().convertToInputStream(string);
+ const outputStream = FileUtils.openSafeFileOutputStream(file);
+
+ return new Promise(resolve => {
+ NetUtil.asyncCopy(inputStream, outputStream, resolve);
+ });
+}
+
+/**
+ * Loads a recording stored as JSON from a file.
+ *
+ * @param nsIFile 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) {
+ const channel = NetUtil.newChannel({
+ uri: NetUtil.newURI(file),
+ loadUsingSystemPrincipal: true,
+ });
+
+ channel.contentType = "text/plain";
+
+ return new Promise((resolve, reject) => {
+ NetUtil.asyncFetch(channel, inputStream => {
+ let recordingData;
+
+ try {
+ const string = NetUtil.readInputStreamToString(
+ inputStream,
+ inputStream.available()
+ );
+ recordingData = JSON.parse(string);
+ } catch (e) {
+ reject(new Error("Could not read recording data file."));
+ return;
+ }
+
+ if (recordingData.fileType != PERF_TOOL_SERIALIZER_IDENTIFIER) {
+ reject(new Error("Unrecognized recording data file."));
+ return;
+ }
+
+ if (!isValidSerializerVersion(recordingData.version)) {
+ 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 the recording has no label, set it to be the
+ // filename without its extension.
+ if (!recordingData.label) {
+ recordingData.label = file.leafName.replace(/\.[^.]+$/, "");
+ }
+
+ resolve(recordingData);
+ });
+ });
+}
+
+/**
+ * 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) {
+ const { profilerData, ticksData, recordingDuration } = legacyData;
+
+ // The `profilerData` and `ticksData` stay, but the previously unrecorded
+ // fields just are empty arrays or objects.
+ const 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.saveRecordingToFile = saveRecordingToFile;
+exports.loadRecordingFromFile = loadRecordingFromFile;