mirror of
https://github.com/servo/servo.git
synced 2025-07-24 15:50:21 +01:00
Add a method for dumping self-contained HTML timeline profiles
This commit adds the `--profiler-trace-path` flag. When combined with `-p` to enable profiling, it dumps a profile as a self-contained HTML file to the given path. The profile visualizes the traced operations as a gant-chart style timeline.
This commit is contained in:
parent
311dd0f930
commit
9fbb5c720e
17 changed files with 758 additions and 11 deletions
|
@ -16,6 +16,9 @@ ipc-channel = {git = "https://github.com/servo/ipc-channel"}
|
||||||
hbs-pow = "0.2"
|
hbs-pow = "0.2"
|
||||||
log = "0.3.5"
|
log = "0.3.5"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
serde = "0.7"
|
||||||
|
serde_json = "0.7"
|
||||||
|
serde_macros = "0.7"
|
||||||
time = "0.1.12"
|
time = "0.1.12"
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
#![feature(iter_arith)]
|
#![feature(iter_arith)]
|
||||||
#![feature(plugin)]
|
#![feature(plugin)]
|
||||||
#![plugin(plugins)]
|
#![plugin(plugins)]
|
||||||
|
#![feature(custom_derive)]
|
||||||
|
#![plugin(serde_macros)]
|
||||||
|
|
||||||
#![deny(unsafe_code)]
|
#![deny(unsafe_code)]
|
||||||
|
|
||||||
|
@ -22,6 +24,8 @@ extern crate log;
|
||||||
extern crate profile_traits;
|
extern crate profile_traits;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
extern crate regex;
|
extern crate regex;
|
||||||
|
extern crate serde;
|
||||||
|
extern crate serde_json;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
extern crate task_info;
|
extern crate task_info;
|
||||||
extern crate time as std_time;
|
extern crate time as std_time;
|
||||||
|
@ -32,3 +36,4 @@ mod heartbeats;
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
pub mod mem;
|
pub mod mem;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
|
pub mod trace_dump;
|
||||||
|
|
|
@ -12,10 +12,13 @@ use profile_traits::time::{TimerMetadataReflowType, TimerMetadataFrameType};
|
||||||
use std::borrow::ToOwned;
|
use std::borrow::ToOwned;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::fs;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
use std::path;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{thread, f64};
|
use std::{thread, f64};
|
||||||
use std_time::precise_time_ns;
|
use std_time::precise_time_ns;
|
||||||
|
use trace_dump::TraceDump;
|
||||||
use util::thread::spawn_named;
|
use util::thread::spawn_named;
|
||||||
use util::time::duration_from_seconds;
|
use util::time::duration_from_seconds;
|
||||||
|
|
||||||
|
@ -125,10 +128,11 @@ pub struct Profiler {
|
||||||
pub port: IpcReceiver<ProfilerMsg>,
|
pub port: IpcReceiver<ProfilerMsg>,
|
||||||
buckets: ProfilerBuckets,
|
buckets: ProfilerBuckets,
|
||||||
pub last_msg: Option<ProfilerMsg>,
|
pub last_msg: Option<ProfilerMsg>,
|
||||||
|
trace: Option<TraceDump>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Profiler {
|
impl Profiler {
|
||||||
pub fn create(period: Option<f64>) -> ProfilerChan {
|
pub fn create(period: Option<f64>, file_path: Option<String>) -> ProfilerChan {
|
||||||
let (chan, port) = ipc::channel().unwrap();
|
let (chan, port) = ipc::channel().unwrap();
|
||||||
match period {
|
match period {
|
||||||
Some(period) => {
|
Some(period) => {
|
||||||
|
@ -143,7 +147,11 @@ impl Profiler {
|
||||||
});
|
});
|
||||||
// Spawn the time profiler.
|
// Spawn the time profiler.
|
||||||
spawn_named("Time profiler".to_owned(), move || {
|
spawn_named("Time profiler".to_owned(), move || {
|
||||||
let mut profiler = Profiler::new(port);
|
let trace = file_path.as_ref()
|
||||||
|
.map(path::Path::new)
|
||||||
|
.map(fs::File::create)
|
||||||
|
.map(|res| TraceDump::new(res.unwrap()));
|
||||||
|
let mut profiler = Profiler::new(port, trace);
|
||||||
profiler.start();
|
profiler.start();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -206,11 +214,12 @@ impl Profiler {
|
||||||
profiler_chan
|
profiler_chan
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(port: IpcReceiver<ProfilerMsg>) -> Profiler {
|
pub fn new(port: IpcReceiver<ProfilerMsg>, trace: Option<TraceDump>) -> Profiler {
|
||||||
Profiler {
|
Profiler {
|
||||||
port: port,
|
port: port,
|
||||||
buckets: BTreeMap::new(),
|
buckets: BTreeMap::new(),
|
||||||
last_msg: None,
|
last_msg: None,
|
||||||
|
trace: trace,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,6 +244,9 @@ impl Profiler {
|
||||||
match msg.clone() {
|
match msg.clone() {
|
||||||
ProfilerMsg::Time(k, t, e) => {
|
ProfilerMsg::Time(k, t, e) => {
|
||||||
heartbeats::maybe_heartbeat(&k.0, t.0, t.1, e.0, e.1);
|
heartbeats::maybe_heartbeat(&k.0, t.0, t.1, e.0, e.1);
|
||||||
|
if let Some(ref mut trace) = self.trace {
|
||||||
|
trace.write_one(&k, t, e);
|
||||||
|
}
|
||||||
let ms = (t.1 - t.0) as f64 / 1000000f64;
|
let ms = (t.1 - t.0) as f64 / 1000000f64;
|
||||||
self.find_or_insert(k, ms);
|
self.find_or_insert(k, ms);
|
||||||
},
|
},
|
||||||
|
|
3
components/profile/trace-dump-epilogue-1.html
Normal file
3
components/profile/trace-dump-epilogue-1.html
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript">
|
4
components/profile/trace-dump-epilogue-2.html
Normal file
4
components/profile/trace-dump-epilogue-2.html
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
//# sourceURL=trace-dump.js
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
5
components/profile/trace-dump-prologue-1.html
Normal file
5
components/profile/trace-dump-prologue-1.html
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<style>
|
5
components/profile/trace-dump-prologue-2.html
Normal file
5
components/profile/trace-dump-prologue-2.html
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
window.TRACES = [
|
100
components/profile/trace-dump.css
Normal file
100
components/profile/trace-dump.css
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
body, html {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#slider {
|
||||||
|
height: 50px;
|
||||||
|
background-color: rgba(210, 210, 210, .5);
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0px 0px 5px #999;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
#slider-viewport {
|
||||||
|
background-color: rgba(255, 255, 255, .8);
|
||||||
|
min-width: 5px;
|
||||||
|
cursor: grab;
|
||||||
|
display: inline-block;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grabby {
|
||||||
|
background-color: #000;
|
||||||
|
width: 3px;
|
||||||
|
cursor: ew-resize;
|
||||||
|
height: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-tick {
|
||||||
|
position: absolute;
|
||||||
|
height: 50px;
|
||||||
|
top: 0;
|
||||||
|
color: #000;
|
||||||
|
border-left: 1px solid #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.traces-tick {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
top: 50px;
|
||||||
|
color: #aaa;
|
||||||
|
border-left: 1px solid #ddd;
|
||||||
|
z-index: -1;
|
||||||
|
overflow: hidden;
|
||||||
|
padding-top: calc(50% - .5em);
|
||||||
|
}
|
||||||
|
|
||||||
|
#traces {
|
||||||
|
flex: 1;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.outer {
|
||||||
|
flex: 1;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.outer:hover {
|
||||||
|
background-color: rgba(255, 255, 200, .7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
|
color: white;
|
||||||
|
min-width: 1px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.outer:hover > .tooltip {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 50px;
|
||||||
|
right: 20px;
|
||||||
|
background-color: rgba(255, 255, 200, .7);
|
||||||
|
min-width: 20em;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
504
components/profile/trace-dump.js
Normal file
504
components/profile/trace-dump.js
Normal file
|
@ -0,0 +1,504 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
/*** State *******************************************************************/
|
||||||
|
|
||||||
|
window.COLORS = [
|
||||||
|
"#0088cc",
|
||||||
|
"#5b5fff",
|
||||||
|
"#b82ee5",
|
||||||
|
"#ed2655",
|
||||||
|
"#f13c00",
|
||||||
|
"#d97e00",
|
||||||
|
"#2cbb0f",
|
||||||
|
"#0072ab",
|
||||||
|
];
|
||||||
|
|
||||||
|
window.MIN_TRACE_TIME = 100000; // .1 ms
|
||||||
|
|
||||||
|
// A class containing the cleaned up trace state.
|
||||||
|
window.State = (function () {
|
||||||
|
return class {
|
||||||
|
constructor() {
|
||||||
|
// The traces themselves.
|
||||||
|
this.traces = null;
|
||||||
|
|
||||||
|
// Maximimum and minimum times seen in traces. These get normalized to be
|
||||||
|
// relative to 0, so after initialization minTime is always 0.
|
||||||
|
this.minTime = Infinity;
|
||||||
|
this.maxTime = 0;
|
||||||
|
|
||||||
|
// The current start and end of the viewport selection.
|
||||||
|
this.startSelection = 0;
|
||||||
|
this.endSelection = 0;
|
||||||
|
|
||||||
|
// The current width of the window.
|
||||||
|
this.windowWidth = window.innerWidth;
|
||||||
|
|
||||||
|
// Whether the user is actively grabbing the left or right grabby, or the
|
||||||
|
// viewport slider.
|
||||||
|
this.grabbingLeft = false;
|
||||||
|
this.grabbingRight = false;
|
||||||
|
this.grabbingSlider = false;
|
||||||
|
|
||||||
|
// Maps category labels to a persistent color so that they are always
|
||||||
|
// rendered the same color.
|
||||||
|
this.colorIndex = 0;
|
||||||
|
this.categoryToColor = Object.create(null);
|
||||||
|
|
||||||
|
this.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up and massage the trace data.
|
||||||
|
initialize() {
|
||||||
|
this.traces = TRACES.filter(t => t.endTime - t.startTime >= MIN_TRACE_TIME);
|
||||||
|
window.TRACES = null;
|
||||||
|
|
||||||
|
this.traces.sort((t1, t2) => {
|
||||||
|
let cmp = t1.startTime - t2.startTime;
|
||||||
|
if (cmp !== 0) {
|
||||||
|
return cmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return t1.endTime - t2.endTime;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.findMinTime();
|
||||||
|
this.normalizeTimes();
|
||||||
|
this.removeIdleTime();
|
||||||
|
this.findMaxTime();
|
||||||
|
|
||||||
|
this.startSelection = 3 * this.maxTime / 8;
|
||||||
|
this.endSelection = 5 * this.maxTime / 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the minimum timestamp.
|
||||||
|
findMinTime() {
|
||||||
|
this.minTime = this.traces.reduce((min, t) => Math.min(min, t.startTime),
|
||||||
|
Infinity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the maximum timestamp.
|
||||||
|
findMaxTime() {
|
||||||
|
this.maxTime = this.traces.reduce((max, t) => Math.max(max, t.endTime),
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize all times to be relative to the minTime and then reset the
|
||||||
|
// minTime to 0.
|
||||||
|
normalizeTimes() {
|
||||||
|
for (let i = 0; i < this.traces.length; i++) {
|
||||||
|
let trace = this.traces[i];
|
||||||
|
trace.startTime -= this.minTime;
|
||||||
|
trace.endTime -= this.minTime;
|
||||||
|
}
|
||||||
|
this.minTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove idle time between traces. It isn't useful to see and makes
|
||||||
|
// visualizing the data more difficult.
|
||||||
|
removeIdleTime() {
|
||||||
|
let totalIdleTime = 0;
|
||||||
|
let lastEndTime = null;
|
||||||
|
|
||||||
|
for (let i = 0; i < this.traces.length; i++) {
|
||||||
|
let trace = this.traces[i];
|
||||||
|
|
||||||
|
if (lastEndTime !== null && trace.startTime > lastEndTime) {
|
||||||
|
totalIdleTime += trace.startTime - lastEndTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastEndTime = trace.endTime;
|
||||||
|
|
||||||
|
trace.startTime -= totalIdleTime;
|
||||||
|
trace.endTime -= totalIdleTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the color for the given category, or assign one if no such color
|
||||||
|
// exists yet.
|
||||||
|
getColorForCategory(category) {
|
||||||
|
let result = this.categoryToColor[category];
|
||||||
|
if (!result) {
|
||||||
|
result = COLORS[this.colorIndex++ % COLORS.length];
|
||||||
|
this.categoryToColor[category] = result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}());
|
||||||
|
|
||||||
|
window.state = new State();
|
||||||
|
|
||||||
|
/*** Utilities ****************************************************************/
|
||||||
|
|
||||||
|
// Get the closest power of ten to the given number.
|
||||||
|
window.closestPowerOfTen = n => {
|
||||||
|
let powerOfTen = 1;
|
||||||
|
let diff = Math.abs(n - powerOfTen);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
let nextPowerOfTen = powerOfTen * 10;
|
||||||
|
let nextDiff = Math.abs(n - nextPowerOfTen);
|
||||||
|
|
||||||
|
if (nextDiff > diff) {
|
||||||
|
return powerOfTen;
|
||||||
|
}
|
||||||
|
|
||||||
|
diff = nextDiff;
|
||||||
|
powerOfTen = nextPowerOfTen;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Select the tick increment for the given range size and maximum number of
|
||||||
|
// ticks to show for that range.
|
||||||
|
window.selectIncrement = (range, maxTicks) => {
|
||||||
|
let increment = closestPowerOfTen(range / 10);
|
||||||
|
while (range / increment > maxTicks) {
|
||||||
|
increment *= 2;
|
||||||
|
}
|
||||||
|
return increment;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the category name for the given trace.
|
||||||
|
window.traceCategory = trace => {
|
||||||
|
return Object.keys(trace.category)[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
/*** Initial Persistent Element Creation **************************************/
|
||||||
|
|
||||||
|
document.body.innerHTML = "";
|
||||||
|
|
||||||
|
window.sliderContainer = document.createElement("div");
|
||||||
|
sliderContainer.id = "slider";
|
||||||
|
document.body.appendChild(sliderContainer);
|
||||||
|
|
||||||
|
window.leftGrabby = document.createElement("span");
|
||||||
|
leftGrabby.className = "grabby";
|
||||||
|
sliderContainer.appendChild(leftGrabby);
|
||||||
|
|
||||||
|
window.sliderViewport = document.createElement("span");
|
||||||
|
sliderViewport.id = "slider-viewport";
|
||||||
|
sliderContainer.appendChild(sliderViewport);
|
||||||
|
|
||||||
|
window.rightGrabby = document.createElement("span");
|
||||||
|
rightGrabby.className = "grabby";
|
||||||
|
sliderContainer.appendChild(rightGrabby);
|
||||||
|
|
||||||
|
window.tracesContainer = document.createElement("div");
|
||||||
|
tracesContainer.id = "traces";
|
||||||
|
document.body.appendChild(tracesContainer);
|
||||||
|
|
||||||
|
/*** Listeners ***************************************************************/
|
||||||
|
|
||||||
|
// Run the given function and render afterwards.
|
||||||
|
window.withRender = fn => (...args) => {
|
||||||
|
fn(...args);
|
||||||
|
render();
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("resize", withRender(() => {
|
||||||
|
state.windowWidth = window.innerWidth;
|
||||||
|
}));
|
||||||
|
|
||||||
|
window.addEventListener("mouseup", () => {
|
||||||
|
state.grabbingSlider = state.grabbingLeft = state.grabbingRight = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
leftGrabby.addEventListener("mousedown", () => {
|
||||||
|
state.grabbingLeft = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
rightGrabby.addEventListener("mousedown", () => {
|
||||||
|
state.grabbingRight = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
sliderViewport.addEventListener("mousedown", () => {
|
||||||
|
state.grabbingSlider = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("mousemove", event => {
|
||||||
|
let ratio = event.clientX / state.windowWidth;
|
||||||
|
let relativeTime = ratio * state.maxTime;
|
||||||
|
let absTime = state.minTime + relativeTime;
|
||||||
|
absTime = Math.min(state.maxTime, absTime);
|
||||||
|
absTime = Math.max(state.minTime, absTime);
|
||||||
|
|
||||||
|
if (state.grabbingSlider) {
|
||||||
|
let delta = event.movementX / state.windowWidth * state.maxTime;
|
||||||
|
if (delta < 0) {
|
||||||
|
delta = Math.max(-state.startSelection, delta);
|
||||||
|
} else {
|
||||||
|
delta = Math.min(state.maxTime - state.endSelection, delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.startSelection += delta;
|
||||||
|
state.endSelection += delta;
|
||||||
|
render();
|
||||||
|
} else if (state.grabbingLeft) {
|
||||||
|
state.startSelection = Math.min(absTime, state.endSelection);
|
||||||
|
render();
|
||||||
|
} else if (state.grabbingRight) {
|
||||||
|
state.endSelection = Math.max(absTime, state.startSelection);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sliderContainer.addEventListener("wheel", withRender(event => {
|
||||||
|
let increment = state.maxTime / 1000;
|
||||||
|
|
||||||
|
state.startSelection -= event.deltaY * increment
|
||||||
|
state.startSelection = Math.max(0, state.startSelection);
|
||||||
|
state.startSelection = Math.min(state.startSelection, state.endSelection);
|
||||||
|
|
||||||
|
state.endSelection += event.deltaY * increment;
|
||||||
|
state.endSelection = Math.min(state.maxTime, state.endSelection);
|
||||||
|
state.endSelection = Math.max(state.startSelection, state.endSelection);
|
||||||
|
}));
|
||||||
|
|
||||||
|
/*** Rendering ***************************************************************/
|
||||||
|
|
||||||
|
// Create a function that calls the given function `fn` only once per animation
|
||||||
|
// frame.
|
||||||
|
window.oncePerAnimationFrame = fn => {
|
||||||
|
let animationId = null;
|
||||||
|
return () => {
|
||||||
|
if (animationId !== null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
animationId = requestAnimationFrame(() => {
|
||||||
|
fn();
|
||||||
|
animationId = null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only call the given function once per window width resize.
|
||||||
|
window.oncePerWindowWidth = fn => {
|
||||||
|
let lastWidth = null;
|
||||||
|
return () => {
|
||||||
|
if (state.windowWidth !== lastWidth) {
|
||||||
|
fn();
|
||||||
|
lastWidth = state.windowWidth;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Top level entry point for rendering. Renders the current `window.state`.
|
||||||
|
window.render = oncePerAnimationFrame(() => {
|
||||||
|
renderSlider();
|
||||||
|
renderTraces();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render the slider at the top of the screen.
|
||||||
|
window.renderSlider = () => {
|
||||||
|
let selectionDelta = state.endSelection - state.startSelection;
|
||||||
|
|
||||||
|
leftGrabby.style.marginLeft = (state.startSelection / state.maxTime) * state.windowWidth + "px";
|
||||||
|
|
||||||
|
// -6px because of the 3px width of each grabby.
|
||||||
|
sliderViewport.style.width = (selectionDelta / state.maxTime) * state.windowWidth - 6 + "px";
|
||||||
|
|
||||||
|
rightGrabby.style.rightMargin = (state.maxTime - state.endSelection) / state.maxTime
|
||||||
|
* state.windowWidth + "px";
|
||||||
|
|
||||||
|
renderSliderTicks();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render the ticks along the slider overview.
|
||||||
|
window.renderSliderTicks = oncePerWindowWidth(() => {
|
||||||
|
let oldTicks = Array.from(document.querySelectorAll(".slider-tick"));
|
||||||
|
for (let tick of oldTicks) {
|
||||||
|
tick.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
let increment = selectIncrement(state.maxTime, 20);
|
||||||
|
let px = increment / state.maxTime * state.windowWidth;
|
||||||
|
let ms = 0;
|
||||||
|
for (let i = 0; i < state.windowWidth; i += px) {
|
||||||
|
let tick = document.createElement("div");
|
||||||
|
tick.className = "slider-tick";
|
||||||
|
tick.textContent = ms + " ms";
|
||||||
|
tick.style.left = i + "px";
|
||||||
|
document.body.appendChild(tick);
|
||||||
|
ms += increment / 1000000;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render the individual traces.
|
||||||
|
window.renderTraces = () => {
|
||||||
|
renderTracesTicks();
|
||||||
|
|
||||||
|
let tracesToRender = [];
|
||||||
|
for (let i = 0; i < state.traces.length; i++) {
|
||||||
|
let trace = state.traces[i];
|
||||||
|
|
||||||
|
if (trace.endTime < state.startSelection || trace.startTime > state.endSelection) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
tracesToRender.push(trace);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that we have enouch traces elements. If we have more elements than
|
||||||
|
// traces we are going to render, then remove some. If we have fewer elements
|
||||||
|
// than traces we are going to render, then add some.
|
||||||
|
let rows = Array.from(tracesContainer.querySelectorAll(".outer"));
|
||||||
|
while (rows.length > tracesToRender.length) {
|
||||||
|
rows.pop().remove();
|
||||||
|
}
|
||||||
|
while (rows.length < tracesToRender.length) {
|
||||||
|
let elem = makeTraceTemplate();
|
||||||
|
tracesContainer.appendChild(elem);
|
||||||
|
rows.push(elem);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < tracesToRender.length; i++) {
|
||||||
|
renderTrace(tracesToRender[i], rows[i]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render the ticks behind the traces.
|
||||||
|
window.renderTracesTicks = () => {
|
||||||
|
let oldTicks = Array.from(tracesContainer.querySelectorAll(".traces-tick"));
|
||||||
|
for (let tick of oldTicks) {
|
||||||
|
tick.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectionDelta = state.endSelection - state.startSelection;
|
||||||
|
let increment = selectIncrement(selectionDelta, 10);
|
||||||
|
let px = increment / selectionDelta * state.windowWidth;
|
||||||
|
let offset = state.startSelection % increment;
|
||||||
|
let time = state.startSelection - offset + increment;
|
||||||
|
|
||||||
|
while (time < state.endSelection) {
|
||||||
|
let tick = document.createElement("div");
|
||||||
|
tick.className = "traces-tick";
|
||||||
|
tick.textContent = Math.round(time / 1000000) + " ms";
|
||||||
|
tick.style.left = (time - state.startSelection) / selectionDelta * state.windowWidth + "px";
|
||||||
|
tracesContainer.appendChild(tick);
|
||||||
|
|
||||||
|
time += increment;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the DOM structure for an individual trace.
|
||||||
|
window.makeTraceTemplate = () => {
|
||||||
|
let outer = document.createElement("div");
|
||||||
|
outer.className = "outer";
|
||||||
|
|
||||||
|
let inner = document.createElement("div");
|
||||||
|
inner.className = "inner";
|
||||||
|
|
||||||
|
let tooltip = document.createElement("div");
|
||||||
|
tooltip.className = "tooltip";
|
||||||
|
|
||||||
|
let header = document.createElement("h3");
|
||||||
|
header.className = "header";
|
||||||
|
tooltip.appendChild(header);
|
||||||
|
|
||||||
|
let duration = document.createElement("h4");
|
||||||
|
duration.className = "duration";
|
||||||
|
tooltip.appendChild(duration);
|
||||||
|
|
||||||
|
let pairs = document.createElement("dl");
|
||||||
|
|
||||||
|
let timeStartLabel = document.createElement("dt");
|
||||||
|
timeStartLabel.textContent = "Start:"
|
||||||
|
pairs.appendChild(timeStartLabel);
|
||||||
|
|
||||||
|
let timeStartValue = document.createElement("dd");
|
||||||
|
timeStartValue.className = "start";
|
||||||
|
pairs.appendChild(timeStartValue);
|
||||||
|
|
||||||
|
let timeEndLabel = document.createElement("dt");
|
||||||
|
timeEndLabel.textContent = "End:"
|
||||||
|
pairs.appendChild(timeEndLabel);
|
||||||
|
|
||||||
|
let timeEndValue = document.createElement("dd");
|
||||||
|
timeEndValue.className = "end";
|
||||||
|
pairs.appendChild(timeEndValue);
|
||||||
|
|
||||||
|
let urlLabel = document.createElement("dt");
|
||||||
|
urlLabel.textContent = "URL:";
|
||||||
|
pairs.appendChild(urlLabel);
|
||||||
|
|
||||||
|
let urlValue = document.createElement("dd");
|
||||||
|
urlValue.className = "url";
|
||||||
|
pairs.appendChild(urlValue);
|
||||||
|
|
||||||
|
let iframeLabel = document.createElement("dt");
|
||||||
|
iframeLabel.textContent = "iframe?";
|
||||||
|
pairs.appendChild(iframeLabel);
|
||||||
|
|
||||||
|
let iframeValue = document.createElement("dd");
|
||||||
|
iframeValue.className = "iframe";
|
||||||
|
pairs.appendChild(iframeValue);
|
||||||
|
|
||||||
|
let incrementalLabel = document.createElement("dt");
|
||||||
|
incrementalLabel.textContent = "Incremental?";
|
||||||
|
pairs.appendChild(incrementalLabel);
|
||||||
|
|
||||||
|
let incrementalValue = document.createElement("dd");
|
||||||
|
incrementalValue.className = "incremental";
|
||||||
|
pairs.appendChild(incrementalValue);
|
||||||
|
|
||||||
|
tooltip.appendChild(pairs);
|
||||||
|
outer.appendChild(tooltip);
|
||||||
|
outer.appendChild(inner);
|
||||||
|
return outer;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render `trace` into the given `elem`. We reuse the trace elements and modify
|
||||||
|
// them with the new trace that will populate this particular `elem` rather than
|
||||||
|
// clearing the DOM out and rebuilding it from scratch. Its a bit of a
|
||||||
|
// performance win when there are a lot of traces being rendered. Funnily
|
||||||
|
// enough, iterating over the complete set of traces hasn't been a performance
|
||||||
|
// problem at all and the bottleneck seems to be purely rendering the subset of
|
||||||
|
// traces we wish to show.
|
||||||
|
window.renderTrace = (trace, elem) => {
|
||||||
|
let inner = elem.querySelector(".inner");
|
||||||
|
inner.style.width = (trace.endTime - trace.startTime) / (state.endSelection - state.startSelection)
|
||||||
|
* state.windowWidth + "px";
|
||||||
|
inner.style.marginLeft = (trace.startTime - state.startSelection)
|
||||||
|
/ (state.endSelection - state.startSelection)
|
||||||
|
* state.windowWidth + "px";
|
||||||
|
|
||||||
|
let category = traceCategory(trace);
|
||||||
|
inner.textContent = category;
|
||||||
|
inner.style.backgroundColor = state.getColorForCategory(category);
|
||||||
|
|
||||||
|
let header = elem.querySelector(".header");
|
||||||
|
header.textContent = category;
|
||||||
|
|
||||||
|
let duration = elem.querySelector(".duration");
|
||||||
|
duration.textContent = (trace.endTime - trace.startTime) / 1000000 + " ms";
|
||||||
|
|
||||||
|
let timeStartValue = elem.querySelector(".start");
|
||||||
|
timeStartValue.textContent = trace.startTime / 1000000 + " ms";
|
||||||
|
|
||||||
|
let timeEndValue = elem.querySelector(".end");
|
||||||
|
timeEndValue.textContent = trace.endTime / 1000000 + " ms";
|
||||||
|
|
||||||
|
if (trace.metadata) {
|
||||||
|
let urlValue = elem.querySelector(".url");
|
||||||
|
urlValue.textContent = trace.metadata.url;
|
||||||
|
urlValue.removeAttribute("hidden");
|
||||||
|
|
||||||
|
let iframeValue = elem.querySelector(".iframe");
|
||||||
|
iframeValue.textContent = trace.metadata.iframe.RootWindow ? "No" : "Yes";
|
||||||
|
iframeValue.removeAttribute("hidden");
|
||||||
|
|
||||||
|
let incrementalValue = elem.querySelector(".incremental");
|
||||||
|
incrementalValue.textContent = trace.metadata.incremental.Incremental ? "Yes" : "No";
|
||||||
|
incrementalValue.removeAttribute("hidden");
|
||||||
|
} else {
|
||||||
|
elem.querySelector(".url").setAttribute("hidden", "");
|
||||||
|
elem.querySelector(".iframe").setAttribute("hidden", "");
|
||||||
|
elem.querySelector(".incremental").setAttribute("hidden", "");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render();
|
79
components/profile/trace_dump.rs
Normal file
79
components/profile/trace_dump.rs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
//! A module for writing time profiler traces out to a self contained HTML file.
|
||||||
|
|
||||||
|
use profile_traits::time::{ProfilerCategory, TimerMetadata};
|
||||||
|
use serde_json::{self};
|
||||||
|
use std::fs;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
/// An RAII class for writing the HTML trace dump.
|
||||||
|
pub struct TraceDump {
|
||||||
|
file: fs::File,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct TraceEntry {
|
||||||
|
category: ProfilerCategory,
|
||||||
|
metadata: Option<TimerMetadata>,
|
||||||
|
|
||||||
|
#[serde(rename = "startTime")]
|
||||||
|
start_time: u64,
|
||||||
|
|
||||||
|
#[serde(rename = "endTime")]
|
||||||
|
end_time: u64,
|
||||||
|
|
||||||
|
#[serde(rename = "startEnergy")]
|
||||||
|
start_energy: u64,
|
||||||
|
|
||||||
|
#[serde(rename = "endEnergy")]
|
||||||
|
end_energy: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TraceDump {
|
||||||
|
/// Create a new TraceDump and write the prologue of the HTML file out to
|
||||||
|
/// disk.
|
||||||
|
pub fn new(mut file: fs::File) -> TraceDump {
|
||||||
|
write_prologue(&mut file);
|
||||||
|
TraceDump { file: file }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write one trace to the trace dump file.
|
||||||
|
pub fn write_one(&mut self,
|
||||||
|
category: &(ProfilerCategory, Option<TimerMetadata>),
|
||||||
|
time: (u64, u64),
|
||||||
|
energy: (u64, u64)) {
|
||||||
|
let entry = TraceEntry {
|
||||||
|
category: category.0,
|
||||||
|
metadata: category.1.clone(),
|
||||||
|
start_time: time.0,
|
||||||
|
end_time: time.1,
|
||||||
|
start_energy: energy.0,
|
||||||
|
end_energy: energy.1,
|
||||||
|
};
|
||||||
|
serde_json::to_writer(&mut self.file, &entry).unwrap();
|
||||||
|
writeln!(&mut self.file, ",").unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TraceDump {
|
||||||
|
/// Write the epilogue of the trace dump HTML file out to disk on
|
||||||
|
/// destruction.
|
||||||
|
fn drop(&mut self) {
|
||||||
|
write_epilogue(&mut self.file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_prologue(file: &mut fs::File) {
|
||||||
|
writeln!(file, "{}", include_str!("./trace-dump-prologue-1.html")).unwrap();
|
||||||
|
writeln!(file, "{}", include_str!("./trace-dump.css")).unwrap();
|
||||||
|
writeln!(file, "{}", include_str!("./trace-dump-prologue-2.html")).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_epilogue(file: &mut fs::File) {
|
||||||
|
writeln!(file, "{}", include_str!("./trace-dump-epilogue-1.html")).unwrap();
|
||||||
|
writeln!(file, "{}", include_str!("./trace-dump.js")).unwrap();
|
||||||
|
writeln!(file, "{}", include_str!("./trace-dump-epilogue-2.html")).unwrap();
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ use energy::read_energy_uj;
|
||||||
use ipc_channel::ipc::IpcSender;
|
use ipc_channel::ipc::IpcSender;
|
||||||
use self::std_time::precise_time_ns;
|
use self::std_time::precise_time_ns;
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, PartialOrd, Eq, Ord, Deserialize, Serialize)]
|
#[derive(PartialEq, Clone, PartialOrd, Eq, Ord, Debug, Deserialize, Serialize)]
|
||||||
pub struct TimerMetadata {
|
pub struct TimerMetadata {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub iframe: TimerMetadataFrameType,
|
pub iframe: TimerMetadataFrameType,
|
||||||
|
@ -35,7 +35,7 @@ pub enum ProfilerMsg {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
#[derive(PartialEq, Clone, PartialOrd, Eq, Ord, Deserialize, Serialize, Debug, Hash)]
|
#[derive(PartialEq, Clone, Copy, PartialOrd, Eq, Ord, Deserialize, Serialize, Debug, Hash)]
|
||||||
pub enum ProfilerCategory {
|
pub enum ProfilerCategory {
|
||||||
Compositing,
|
Compositing,
|
||||||
LayoutPerform,
|
LayoutPerform,
|
||||||
|
@ -78,13 +78,13 @@ pub enum ProfilerCategory {
|
||||||
ApplicationHeartbeat,
|
ApplicationHeartbeat,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
|
||||||
pub enum TimerMetadataFrameType {
|
pub enum TimerMetadataFrameType {
|
||||||
RootWindow,
|
RootWindow,
|
||||||
IFrame,
|
IFrame,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
|
||||||
pub enum TimerMetadataReflowType {
|
pub enum TimerMetadataReflowType {
|
||||||
Incremental,
|
Incremental,
|
||||||
FirstReflow,
|
FirstReflow,
|
||||||
|
@ -123,4 +123,3 @@ pub fn send_profile_data(category: ProfilerCategory,
|
||||||
(start_time, end_time),
|
(start_time, end_time),
|
||||||
(start_energy, end_energy)));
|
(start_energy, end_energy)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
3
components/servo/Cargo.lock
generated
3
components/servo/Cargo.lock
generated
|
@ -1652,6 +1652,9 @@ dependencies = [
|
||||||
"plugins 0.0.1",
|
"plugins 0.0.1",
|
||||||
"profile_traits 0.0.1",
|
"profile_traits 0.0.1",
|
||||||
"regex 0.1.55 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 0.1.55 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_json 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_macros 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"task_info 0.0.1",
|
"task_info 0.0.1",
|
||||||
"time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
"time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"util 0.0.1",
|
"util 0.0.1",
|
||||||
|
|
|
@ -116,7 +116,8 @@ impl Browser {
|
||||||
let (compositor_proxy, compositor_receiver) =
|
let (compositor_proxy, compositor_receiver) =
|
||||||
window.create_compositor_channel();
|
window.create_compositor_channel();
|
||||||
let supports_clipboard = window.supports_clipboard();
|
let supports_clipboard = window.supports_clipboard();
|
||||||
let time_profiler_chan = profile_time::Profiler::create(opts.time_profiler_period);
|
let time_profiler_chan = profile_time::Profiler::create(opts.time_profiler_period,
|
||||||
|
opts.time_profiler_trace_path.clone());
|
||||||
let mem_profiler_chan = profile_mem::Profiler::create(opts.mem_profiler_period);
|
let mem_profiler_chan = profile_mem::Profiler::create(opts.mem_profiler_period);
|
||||||
let devtools_chan = opts.devtools_port.map(|port| {
|
let devtools_chan = opts.devtools_port.map(|port| {
|
||||||
devtools::start_server(port)
|
devtools::start_server(port)
|
||||||
|
|
|
@ -17,7 +17,7 @@ use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering};
|
use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering};
|
||||||
use url::{self, Url};
|
use url::{self, Url};
|
||||||
|
@ -50,6 +50,10 @@ pub struct Opts {
|
||||||
/// cause it to produce output on that interval (`-p`).
|
/// cause it to produce output on that interval (`-p`).
|
||||||
pub time_profiler_period: Option<f64>,
|
pub time_profiler_period: Option<f64>,
|
||||||
|
|
||||||
|
/// When the profiler is enabled, this is an optional path to dump a self-contained HTML file
|
||||||
|
/// visualizing the traces as a timeline.
|
||||||
|
pub time_profiler_trace_path: Option<String>,
|
||||||
|
|
||||||
/// `None` to disable the memory profiler or `Some` with an interval in seconds to enable it
|
/// `None` to disable the memory profiler or `Some` with an interval in seconds to enable it
|
||||||
/// and cause it to produce output on that interval (`-m`).
|
/// and cause it to produce output on that interval (`-m`).
|
||||||
pub mem_profiler_period: Option<f64>,
|
pub mem_profiler_period: Option<f64>,
|
||||||
|
@ -469,6 +473,7 @@ pub fn default_opts() -> Opts {
|
||||||
tile_size: 512,
|
tile_size: 512,
|
||||||
device_pixels_per_px: None,
|
device_pixels_per_px: None,
|
||||||
time_profiler_period: None,
|
time_profiler_period: None,
|
||||||
|
time_profiler_trace_path: None,
|
||||||
mem_profiler_period: None,
|
mem_profiler_period: None,
|
||||||
layout_threads: 1,
|
layout_threads: 1,
|
||||||
nonincremental_layout: false,
|
nonincremental_layout: false,
|
||||||
|
@ -529,6 +534,9 @@ pub fn from_cmdline_args(args: &[String]) -> ArgumentParsingResult {
|
||||||
opts.optopt("", "device-pixel-ratio", "Device pixels per px", "");
|
opts.optopt("", "device-pixel-ratio", "Device pixels per px", "");
|
||||||
opts.optopt("t", "threads", "Number of paint threads", "1");
|
opts.optopt("t", "threads", "Number of paint threads", "1");
|
||||||
opts.optflagopt("p", "profile", "Profiler flag and output interval", "10");
|
opts.optflagopt("p", "profile", "Profiler flag and output interval", "10");
|
||||||
|
opts.optflagopt("", "profiler-trace-path",
|
||||||
|
"Path to dump a self-contained HTML timeline of profiler traces",
|
||||||
|
"");
|
||||||
opts.optflagopt("m", "memory-profile", "Memory profiler flag and output interval", "10");
|
opts.optflagopt("m", "memory-profile", "Memory profiler flag and output interval", "10");
|
||||||
opts.optflag("x", "exit", "Exit after load flag");
|
opts.optflag("x", "exit", "Exit after load flag");
|
||||||
opts.optopt("y", "layout-threads", "Number of threads to use for layout", "1");
|
opts.optopt("y", "layout-threads", "Number of threads to use for layout", "1");
|
||||||
|
@ -656,6 +664,15 @@ pub fn from_cmdline_args(args: &[String]) -> ArgumentParsingResult {
|
||||||
period.parse().unwrap_or_else(|err| args_fail(&format!("Error parsing option: -p ({})", err)))
|
period.parse().unwrap_or_else(|err| args_fail(&format!("Error parsing option: -p ({})", err)))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if let Some(ref time_profiler_trace_path) = opt_match.opt_str("profiler-trace-path") {
|
||||||
|
let mut path = PathBuf::from(time_profiler_trace_path);
|
||||||
|
path.pop();
|
||||||
|
if let Err(why) = fs::create_dir_all(&path) {
|
||||||
|
error!("Couldn't create/open {:?}: {:?}",
|
||||||
|
Path::new(time_profiler_trace_path).to_string_lossy(), why);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mem_profiler_period = opt_match.opt_default("m", "5").map(|period| {
|
let mem_profiler_period = opt_match.opt_default("m", "5").map(|period| {
|
||||||
period.parse().unwrap_or_else(|err| args_fail(&format!("Error parsing option: -m ({})", err)))
|
period.parse().unwrap_or_else(|err| args_fail(&format!("Error parsing option: -m ({})", err)))
|
||||||
});
|
});
|
||||||
|
@ -755,6 +772,7 @@ pub fn from_cmdline_args(args: &[String]) -> ArgumentParsingResult {
|
||||||
tile_size: tile_size,
|
tile_size: tile_size,
|
||||||
device_pixels_per_px: device_pixels_per_px,
|
device_pixels_per_px: device_pixels_per_px,
|
||||||
time_profiler_period: time_profiler_period,
|
time_profiler_period: time_profiler_period,
|
||||||
|
time_profiler_trace_path: opt_match.opt_str("profiler-trace-path"),
|
||||||
mem_profiler_period: mem_profiler_period,
|
mem_profiler_period: mem_profiler_period,
|
||||||
layout_threads: layout_threads,
|
layout_threads: layout_threads,
|
||||||
nonincremental_layout: nonincremental_layout,
|
nonincremental_layout: nonincremental_layout,
|
||||||
|
|
3
ports/cef/Cargo.lock
generated
3
ports/cef/Cargo.lock
generated
|
@ -1530,6 +1530,9 @@ dependencies = [
|
||||||
"plugins 0.0.1",
|
"plugins 0.0.1",
|
||||||
"profile_traits 0.0.1",
|
"profile_traits 0.0.1",
|
||||||
"regex 0.1.55 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 0.1.55 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_json 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_macros 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"task_info 0.0.1",
|
"task_info 0.0.1",
|
||||||
"time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
"time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"util 0.0.1",
|
"util 0.0.1",
|
||||||
|
|
3
ports/gonk/Cargo.lock
generated
3
ports/gonk/Cargo.lock
generated
|
@ -1513,6 +1513,9 @@ dependencies = [
|
||||||
"plugins 0.0.1",
|
"plugins 0.0.1",
|
||||||
"profile_traits 0.0.1",
|
"profile_traits 0.0.1",
|
||||||
"regex 0.1.55 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 0.1.55 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_json 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_macros 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"task_info 0.0.1",
|
"task_info 0.0.1",
|
||||||
"time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
"time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"util 0.0.1",
|
"util 0.0.1",
|
||||||
|
|
|
@ -7,7 +7,7 @@ use profile_traits::time::ProfilerMsg;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn time_profiler_smoke_test() {
|
fn time_profiler_smoke_test() {
|
||||||
let chan = time::Profiler::create(None);
|
let chan = time::Profiler::create(None, None);
|
||||||
assert!(true, "Can create the profiler thread");
|
assert!(true, "Can create the profiler thread");
|
||||||
|
|
||||||
chan.send(ProfilerMsg::Exit);
|
chan.send(ProfilerMsg::Exit);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue