Bug 1171488 - Attach host and client system data to recording profiles r=vp,mossop

This commit is contained in:
Jordan Santell 2015-09-11 09:37:20 -07:00
Родитель 5691e5b19a
Коммит 742c1b026f
12 изменённых файлов: 231 добавлений и 317 удалений

Просмотреть файл

@ -11,13 +11,27 @@ module.metadata = {
const { Cc, Ci } = require('chrome');
const system = require('../sdk/system');
const runtime = require('../sdk/system/runtime');
const oscpu = Cc["@mozilla.org/network/protocol;1?name=http"]
.getService(Ci.nsIHttpProtocolHandler).oscpu;
const hostname = Cc["@mozilla.org/network/dns-service;1"]
.getService(Ci.nsIDNSService).myHostName;
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
const isWindows = system.platform === 'win32';
const endianness = ((new Uint32Array((new Uint8Array([1,2,3,4])).buffer))[0] === 0x04030201) ? 'LE' : 'BE';
XPCOMUtils.defineLazyGetter(this, "oscpu", () => {
try {
return Cc["@mozilla.org/network/protocol;1?name=http"].getService(Ci.nsIHttpProtocolHandler).oscpu;
} catch (e) {
return "";
}
});
XPCOMUtils.defineLazyGetter(this, "hostname", () => {
try {
// On some platforms (Linux according to try), this service does not exist and fails.
return Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService).myHostName;
} catch (e) {
return "";
}
});
/**
* Returns a path to a temp directory
*/

Просмотреть файл

@ -153,7 +153,9 @@ function convertLegacyData (legacyData) {
withMarkers: false,
withMemory: false,
withAllocations: false
}
},
systemHost: {},
systemClient: {},
};
return data;

Просмотреть файл

@ -21,6 +21,10 @@ loader.lazyRequireGetter(this, "normalizePerformanceFeatures",
"devtools/toolkit/performance/utils", true);
loader.lazyRequireGetter(this, "DevToolsUtils",
"devtools/toolkit/DevToolsUtils");
loader.lazyRequireGetter(this, "getDeviceFront",
"devtools/toolkit/server/actors/device", true);
loader.lazyRequireGetter(this, "getSystemInfo",
"devtools/toolkit/shared/system", true);
loader.lazyRequireGetter(this, "events",
"sdk/event/core");
loader.lazyRequireGetter(this, "EventTarget",
@ -333,7 +337,7 @@ const LegacyPerformanceFront = Class({
this._recordings.splice(this._recordings.indexOf(model), 1);
let config = model.getConfiguration();
let startTime = model.getProfilerStartTime();
let startTime = model._getProfilerStartTime();
let profilerData = yield this._profiler.getProfile({ startTime });
let timelineEndTime = Date.now();
@ -348,6 +352,13 @@ const LegacyPerformanceFront = Class({
timelineEndTime = yield this._timeline.stop(config);
}
let systemDeferred = promise.defer();
this._client.listTabs(form => {
systemDeferred.resolve(getDeviceFront(this._client, form).getDescription());
});
let systemHost = yield systemDeferred.promise;
let systemClient = yield getSystemInfo();
// Set the results on the LegacyPerformanceRecording itself.
model._onStopRecording({
// Data available only at the end of a recording.
@ -355,7 +366,9 @@ const LegacyPerformanceFront = Class({
// End times for all the actors.
profilerEndTime: profilerData.currentTime,
timelineEndTime: timelineEndTime
timelineEndTime: timelineEndTime,
systemHost,
systemClient,
});
events.emit(this, "recording-stopped", model);

Просмотреть файл

@ -10,6 +10,9 @@ loader.lazyRequireGetter(this, "PerformanceIO",
"devtools/toolkit/performance/io");
loader.lazyRequireGetter(this, "RecordingUtils",
"devtools/toolkit/performance/utils");
loader.lazyRequireGetter(this, "PerformanceRecordingCommon",
"devtools/toolkit/performance/recording-common", true);
loader.lazyRequireGetter(this, "merge", "sdk/util/object", true);
/**
* Model for a wholistic profile, containing the duration, profiling data,
@ -33,49 +36,10 @@ const LegacyPerformanceRecording = function (options={}) {
};
};
LegacyPerformanceRecording.prototype = {
// Private fields, only needed when a recording is started or stopped.
_console: false,
_imported: false,
_recording: false,
_completed: false,
LegacyPerformanceRecording.prototype = merge({
_profilerStartTime: 0,
_timelineStartTime: 0,
_memoryStartTime: 0,
_configuration: {},
_startingBufferStatus: null,
_bufferPercent: null,
// Serializable fields, necessary and sufficient for import and export.
_label: "",
_duration: 0,
_markers: null,
_frames: null,
_memory: null,
_ticks: null,
_allocations: null,
_profile: null,
/**
* Loads a recording from a file.
*
* @param nsILocalFile file
* The file to import the data form.
*/
importRecording: Task.async(function *(file) {
let recordingData = yield PerformanceIO.loadRecordingFromFile(file);
this._imported = true;
this._label = recordingData.label || "";
this._duration = recordingData.duration;
this._markers = recordingData.markers;
this._frames = recordingData.frames;
this._memory = recordingData.memory;
this._ticks = recordingData.ticks;
this._allocations = recordingData.allocations;
this._profile = recordingData.profile;
this._configuration = recordingData.configuration || {};
}),
/**
* Saves the current recording to a file.
@ -107,11 +71,11 @@ LegacyPerformanceRecording.prototype = {
totalSize: info.totalSize,
generation: info.generation
};
// initialize the _bufferPercent if the server supports it.
this._bufferPercent = info.position !== void 0 ? 0 : null;
this._recording = true;
this._systemHost = {};
this._systemClient = {};
this._markers = [];
this._frames = [];
this._memory = [];
@ -134,7 +98,7 @@ LegacyPerformanceRecording.prototype = {
* Sets results available from stopping a recording from PerformanceFront.
* Should only be called by PerformanceFront.
*/
_onStopRecording: Task.async(function *({ profilerEndTime, profile }) {
_onStopRecording: Task.async(function *({ profilerEndTime, profile, systemClient, systemHost }) {
// Update the duration with the accurate profilerEndTime, so we don't have
// samples outside of the approximate duration set in `_onStoppingRecording`.
this._duration = profilerEndTime - this._profilerStartTime;
@ -150,164 +114,19 @@ LegacyPerformanceRecording.prototype = {
// Markers need to be sorted ascending by time, to be properly displayed
// in a waterfall view.
this._markers = this._markers.sort((a, b) => (a.start > b.start));
this._systemHost = systemHost;
this._systemClient = systemClient;
}),
/**
* Gets the profile's start time.
* @return number
*/
getProfilerStartTime: function () {
_getProfilerStartTime: function () {
return this._profilerStartTime;
},
/**
* Gets the profile's label, from `console.profile(LABEL)`.
* @return string
*/
getLabel: function () {
return this._label;
},
/**
* Gets duration of this recording, in milliseconds.
* @return number
*/
getDuration: function () {
// Compute an approximate ending time for the current recording if it is
// still in progress. This is needed to ensure that the view updates even
// when new data is not being generated.
if (this._recording) {
return Date.now() - this._localStartTime;
} else {
return this._duration;
}
},
/**
* Returns configuration object of specifying whether the recording
* was started withTicks, withMemory and withAllocations, and other configurations.
* @return object
*/
getConfiguration: function () {
return this._configuration;
},
/**
* Gets the accumulated markers in the current recording.
* @return array
*/
getMarkers: function() {
return this._markers;
},
/**
* Gets the accumulated stack frames in the current recording.
* @return array
*/
getFrames: function() {
return this._frames;
},
/**
* Gets the accumulated memory measurements in this recording.
* @return array
*/
getMemory: function() {
return this._memory;
},
/**
* Gets the accumulated refresh driver ticks in this recording.
* @return array
*/
getTicks: function() {
return this._ticks;
},
/**
* Gets the memory allocations data in this recording.
* @return array
*/
getAllocations: function() {
return this._allocations;
},
/**
* Gets the profiler data in this recording.
* @return array
*/
getProfile: function() {
return this._profile;
},
/**
* Gets all the data in this recording.
*/
getAllData: function() {
let label = this.getLabel();
let duration = this.getDuration();
let markers = this.getMarkers();
let frames = this.getFrames();
let memory = this.getMemory();
let ticks = this.getTicks();
let allocations = this.getAllocations();
let profile = this.getProfile();
let configuration = this.getConfiguration();
return { label, duration, markers, frames, memory, ticks, allocations, profile, configuration };
},
/**
* Returns a boolean indicating whether or not this recording model
* was imported via file.
*/
isImported: function () {
return this._imported;
},
/**
* Returns a boolean indicating whether or not this recording model
* was started via a `console.profile` call.
*/
isConsole: function () {
return this._console;
},
/**
* Returns a boolean indicating whether or not this recording model
* has finished recording.
* There is some delay in fetching data between when the recording stops, and
* when the recording is considered completed once it has all the profiler and timeline data.
*/
isCompleted: function () {
return this._completed || this.isImported();
},
/**
* Returns a boolean indicating whether or not this recording model
* is recording.
* A model may no longer be recording, yet still not have the profiler data. In that
* case, use `isCompleted()`.
*/
isRecording: function () {
return this._recording;
},
/**
* Returns a boolean indicating if this recording is no longer recording, but
* not yet completed.
*/
isFinalizing: function () {
return !this.isRecording() && !this.isCompleted();
},
/**
* Returns the position, generation and totalSize of the profiler
* when this recording was started.
*/
getStartingBufferStatus: function () {
return this._startingBufferStatus;
},
/**
* Fired whenever the PerformanceFront emits markers, memory or ticks.
*/
@ -348,6 +167,6 @@ LegacyPerformanceRecording.prototype = {
},
toString: () => "[object LegacyPerformanceRecording]"
};
}, PerformanceRecordingCommon);
exports.LegacyPerformanceRecording = LegacyPerformanceRecording;

Просмотреть файл

@ -10,6 +10,7 @@ EXTRA_JS_MODULES.devtools.performance += [
'io.js',
'process-communication.js',
'recorder.js',
'recording-common.js',
'utils.js',
]

Просмотреть файл

@ -25,13 +25,14 @@ loader.lazyRequireGetter(this, "Profiler",
"devtools/toolkit/shared/profiler", true);
loader.lazyRequireGetter(this, "PerformanceRecordingActor",
"devtools/server/actors/performance-recording", true);
loader.lazyRequireGetter(this, "PerformanceRecordingFront",
"devtools/server/actors/performance-recording", true);
loader.lazyRequireGetter(this, "mapRecordingOptions",
"devtools/toolkit/performance/utils", true);
loader.lazyRequireGetter(this, "DevToolsUtils",
"devtools/toolkit/DevToolsUtils");
loader.lazyRequireGetter(this, "getSystemInfo",
"devtools/toolkit/shared/system", true);
const PROFILER_EVENTS = [
"console-api-profiler",
@ -69,10 +70,13 @@ const PerformanceRecorder = exports.PerformanceRecorder = Class({
* Initializes a connection to the profiler and other miscellaneous actors.
* If in the process of opening, or already open, nothing happens.
*
* @param {Object} options.systemClient
* Metadata about the client's system to attach to the recording models.
*
* @return object
* A promise that is resolved once the connection is established.
*/
connect: function () {
connect: function (options) {
if (this._connected) {
return;
}
@ -84,6 +88,8 @@ const PerformanceRecorder = exports.PerformanceRecorder = Class({
this._connectComponents();
this._registerListeners();
this._systemClient = options.systemClient;
this._connected = true;
},
@ -341,6 +347,9 @@ const PerformanceRecorder = exports.PerformanceRecorder = Class({
data.generation = profilerStartData.generation;
data.totalSize = profilerStartData.totalSize;
data.systemClient = this._systemClient;
data.systemHost = yield getSystemInfo();
let model = new PerformanceRecordingActor(this.conn, options, data);
this._recordings.push(model);

Просмотреть файл

@ -0,0 +1,97 @@
/* 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";
/**
* A mixin to be used for PerformanceRecordingActor, PerformanceRecordingFront,
* and LegacyPerformanceRecording for helper methods to access data.
*/
const PerformanceRecordingCommon = exports.PerformanceRecordingCommon = {
// Private fields, only needed when a recording is started or stopped.
_console: false,
_imported: false,
_recording: false,
_completed: false,
_configuration: {},
_startingBufferStatus: null,
_localStartTime: 0,
// Serializable fields, necessary and sufficient for import and export.
_label: "",
_duration: 0,
_markers: null,
_frames: null,
_memory: null,
_ticks: null,
_allocations: null,
_profile: null,
_systemHost: null,
_systemClient: null,
/**
* Helper methods for returning the status of the recording.
* These methods should be consistent on both the front and actor.
*/
isRecording: function () { return this._recording; },
isCompleted: function () { return this._completed || this.isImported(); },
isFinalizing: function () { return !this.isRecording() && !this.isCompleted(); },
isConsole: function () { return this._console; },
isImported: function () { return this._imported; },
/**
* Helper methods for returning configuration for the recording.
* These methods should be consistent on both the front and actor.
*/
getConfiguration: function () { return this._configuration; },
getLabel: function () { return this._label; },
/**
* Gets duration of this recording, in milliseconds.
* @return number
*/
getDuration: function () {
// Compute an approximate ending time for the current recording if it is
// still in progress. This is needed to ensure that the view updates even
// when new data is not being generated. If recording is completed, use
// the duration from the profiler; if between recording and being finalized,
// use the last estimated duration.
if (this.isRecording()) {
return this._estimatedDuration = Date.now() - this._localStartTime;
} else {
return this._duration || this._estimatedDuration || 0;
}
},
/**
* Helper methods for returning recording data.
* These methods should be consistent on both the front and actor.
*/
getMarkers: function() { return this._markers; },
getFrames: function() { return this._frames; },
getMemory: function() { return this._memory; },
getTicks: function() { return this._ticks; },
getAllocations: function() { return this._allocations; },
getProfile: function() { return this._profile; },
getHostSystemInfo: function() { return this._systemHost; },
getClientSystemInfo: function() { return this._systemClient; },
getStartingBufferStatus: function() { return this._startingBufferStatus; },
getAllData: function () {
let label = this.getLabel();
let duration = this.getDuration();
let markers = this.getMarkers();
let frames = this.getFrames();
let memory = this.getMemory();
let ticks = this.getTicks();
let allocations = this.getAllocations();
let profile = this.getProfile();
let configuration = this.getConfiguration();
let systemHost = this.getHostSystemInfo();
let systemClient = this.getClientSystemInfo();
return { label, duration, markers, frames, memory, ticks, allocations, profile, configuration, systemHost, systemClient };
},
};

Просмотреть файл

@ -15,72 +15,8 @@ loader.lazyRequireGetter(this, "PerformanceIO",
"devtools/toolkit/performance/io");
loader.lazyRequireGetter(this, "RecordingUtils",
"devtools/toolkit/performance/utils");
/**
* A set of functions used by both the front and actor to access
* internal properties.
*/
const PerformanceRecordingCommon = {
// Private fields, only needed when a recording is started or stopped.
_console: false,
_imported: false,
_recording: false,
_completed: false,
_configuration: {},
_startingBufferStatus: null,
_localStartTime: null,
// Serializable fields, necessary and sufficient for import and export.
_label: "",
_duration: 0,
_markers: null,
_frames: null,
_memory: null,
_ticks: null,
_allocations: null,
_profile: null,
/**
* Helper methods for returning the status of the recording.
* These methods should be consistent on both the front and actor.
*/
isRecording: function () { return this._recording; },
isCompleted: function () { return this._completed || this.isImported(); },
isFinalizing: function () { return !this.isRecording() && !this.isCompleted(); },
isConsole: function () { return this._console; },
isImported: function () { return this._imported; },
/**
* Helper methods for returning configuration for the recording.
* These methods should be consistent on both the front and actor.
*/
getConfiguration: function () { return this._configuration; },
getLabel: function () { return this._label; },
/**
* Helper methods for returning recording data.
* These methods should be consistent on both the front and actor.
*/
getMarkers: function() { return this._markers; },
getFrames: function() { return this._frames; },
getMemory: function() { return this._memory; },
getTicks: function() { return this._ticks; },
getAllocations: function() { return this._allocations; },
getProfile: function() { return this._profile; },
getAllData: function () {
let label = this.getLabel();
let duration = this.getDuration();
let markers = this.getMarkers();
let frames = this.getFrames();
let memory = this.getMemory();
let ticks = this.getTicks();
let allocations = this.getAllocations();
let profile = this.getProfile();
let configuration = this.getConfiguration();
return { label, duration, markers, frames, memory, ticks, allocations, profile, configuration };
},
};
loader.lazyRequireGetter(this, "PerformanceRecordingCommon",
"devtools/toolkit/performance/recording-common", true);
/**
* This actor wraps the Performance module at toolkit/devtools/shared/performance.js
@ -111,9 +47,12 @@ let PerformanceRecordingActor = exports.PerformanceRecordingActor = protocol.Act
// Only send profiler data once it exists and it has
// not yet been sent
if (this._profile && !this._sentProfilerData) {
form.profile = this._profile;
this._sentProfilerData = true;
if (this._profile && !this._sentFinalizedData) {
form.finalizedData = true;
form.profile = this.getProfile();
form.systemHost = this.getHostSystemInfo();
form.systemClient = this.getClientSystemInfo();
this._sentFinalizedData = true;
}
return form;
@ -164,6 +103,9 @@ let PerformanceRecordingActor = exports.PerformanceRecordingActor = protocol.Act
this._memory = [];
this._ticks = [];
this._allocations = { sites: [], timestamps: [], frames: [], sizes: [] };
this._systemHost = meta.systemHost || {};
this._systemClient = meta.systemClient || {};
}
},
@ -233,8 +175,10 @@ let PerformanceRecordingFront = exports.PerformanceRecordingFront = protocol.Fro
this._completed = form.completed;
this._duration = form.duration;
if (form.profile) {
if (form.finalizedData) {
this._profile = form.profile;
this._systemHost = form.systemHost;
this._systemClient = form.systemClient;
}
// Sort again on the client side if we're using realtime markers and the recording
@ -270,33 +214,6 @@ let PerformanceRecordingFront = exports.PerformanceRecordingFront = protocol.Fro
return PerformanceIO.saveRecordingToFile(recordingData, file);
},
/**
* Returns the position, generation, and totalSize of the profiler
* when this recording was started.
*
* @return {object}
*/
getStartingBufferStatus: function () {
return this._form.startingBufferStatus;
},
/**
* Gets duration of this recording, in milliseconds.
* @return number
*/
getDuration: function () {
// Compute an approximate ending time for the current recording if it is
// still in progress. This is needed to ensure that the view updates even
// when new data is not being generated. If recording is completed, use
// the duration from the profiler; if between recording and being finalized,
// use the last estimated duration.
if (this.isRecording()) {
return this._estimatedDuration = Date.now() - this._localStartTime;
} else {
return this._duration || this._estimatedDuration || 0;
}
},
/**
* Fired whenever the PerformanceFront emits markers, memory or ticks.
*/

Просмотреть файл

@ -22,6 +22,8 @@ loader.lazyRequireGetter(this, "normalizePerformanceFeatures",
"devtools/toolkit/performance/utils", true);
loader.lazyRequireGetter(this, "LegacyPerformanceFront",
"devtools/toolkit/performance/legacy/front", true);
loader.lazyRequireGetter(this, "getSystemInfo",
"devtools/toolkit/shared/system", true);
const PIPE_TO_FRONT_EVENTS = new Set([
"recording-started", "recording-stopping", "recording-stopped",
@ -97,10 +99,13 @@ let PerformanceActor = exports.PerformanceActor = protocol.ActorClass({
protocol.Actor.prototype.destroy.call(this);
},
connect: method(function () {
this.bridge.connect();
return this.traits;
}, { response: RetVal("json") }),
connect: method(function (config) {
this.bridge.connect({ systemClient: config.systemClient });
return { traits: this.traits };
}, {
request: { options: Arg(0, "nullable:json") },
response: RetVal("json")
}),
startRecording: method(Task.async(function *(options={}) {
let normalizedOptions = normalizePerformanceFeatures(options, this.traits.features);
@ -182,9 +187,17 @@ const PerformanceFront = exports.PerformanceFront = protocol.FrontClass(Performa
protocol.Front.prototype.destroy.call(this);
},
connect: custom(function () {
return this._connect().then(traits => this._traits = traits);
}, {
/**
* Conenct to the server, and handle once-off tasks like storing traits
* or system info.
*/
connect: custom(Task.async(function *() {
let systemClient = yield getSystemInfo();
let { traits } = yield this._connect({ systemClient });
this._traits = traits;
return this._traits;
}), {
impl: "_connect"
}),
@ -239,6 +252,8 @@ const PerformanceFront = exports.PerformanceFront = protocol.FrontClass(Performa
model._allocations = recordingData.allocations;
model._profile = recordingData.profile;
model._configuration = recordingData.configuration || {};
model._systemHost = recordingData.systemHost;
model._systemClient = recordingData.systemClient;
return model;
});
},

Просмотреть файл

@ -53,6 +53,8 @@ add_task(function*() {
isEmptyArray(recording.getAllocations().timestamps, "allocations.timestamps");
isEmptyArray(recording.getAllocations().frames, "allocations.frames");
ok(recording.getProfile().threads[0].samples.data.length, "profile data has some samples");
checkSystemInfo(recording, "Host");
checkSystemInfo(recording, "Client");
yield front.destroy();
yield closeDebuggerClient(target.client);
@ -76,3 +78,10 @@ function getTab (url) {
});
});
}
function checkSystemInfo (recording, type) {
let data = recording[`get${type}SystemInfo`]();
for (let field of ["appid", "apptype", "vendor", "name", "version"]) {
ok(data[field], `get${type}SystemInfo() has ${field} property`);
}
}

Просмотреть файл

@ -49,6 +49,9 @@ add_task(function*() {
ok(!rec.isRecording(), "on 'recording-stopped', model is still no longer recording");
ok(rec.isCompleted(), "on 'recording-stopped', model is considered 'complete'");
checkSystemInfo(rec, "Host");
checkSystemInfo(rec, "Client");
// Export and import a rec, and ensure it has the correct state.
let file = FileUtils.getFile("TmpD", ["tmpprofile.json"]);
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
@ -60,7 +63,17 @@ add_task(function*() {
ok(!importedModel.isRecording(), "All imported recordings should not be recording");
ok(importedModel.isImported(), "All imported recordings should be considerd imported");
checkSystemInfo(importedModel, "Host");
checkSystemInfo(importedModel, "Client");
yield front.destroy();
yield closeDebuggerClient(client);
gBrowser.removeCurrentTab();
});
function checkSystemInfo (recording, type) {
let data = recording[`get${type}SystemInfo`]();
for (let field of ["appid", "apptype", "vendor", "name", "version"]) {
ok(data[field], `get${type}SystemInfo() has ${field} property`);
}
}

Просмотреть файл

@ -8,7 +8,7 @@ const { Task } = require("resource://gre/modules/Task.jsm");
loader.lazyRequireGetter(this, "Services");
loader.lazyRequireGetter(this, "promise");
loader.lazyRequireGetter(this, "OS", "resource://gre/modules/commonjs/node/os");
loader.lazyRequireGetter(this, "OS", "resource://gre/modules/commonjs/node/os.js");
loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
loader.lazyRequireGetter(this, "AppConstants",
"resource://gre/modules/AppConstants.jsm", true);
@ -164,18 +164,23 @@ function *getSystemInfo() {
return info;
}
function getProfileLocation() {
let profd = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
let profservice = Cc["@mozilla.org/toolkit/profile-service;1"].getService(Ci.nsIToolkitProfileService);
var profiles = profservice.profiles;
while (profiles.hasMoreElements()) {
let profile = profiles.getNext().QueryInterface(Ci.nsIToolkitProfile);
if (profile.rootDir.path == profd.path) {
return profile = profile.name;
function getProfileLocation () {
// In child processes, we cannot access the profile location.
try {
let profd = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
let profservice = Cc["@mozilla.org/toolkit/profile-service;1"].getService(Ci.nsIToolkitProfileService);
var profiles = profservice.profiles;
while (profiles.hasMoreElements()) {
let profile = profiles.getNext().QueryInterface(Ci.nsIToolkitProfile);
if (profile.rootDir.path == profd.path) {
return profile = profile.name;
}
}
}
return profd.leafName;
return profd.leafName;
} catch (e) {
return "";
}
}
function getAppIniString(section, key) {