Bug 1356871 - Add timing related columns. r=Honza

MozReview-Commit-ID: 24Ja9oOjNyN

--HG--
extra : rebase_source : d6dc801796e6e22ae17fb719a28c2ae585f3e00b
This commit is contained in:
Vangelis Katsikaros 2017-05-01 23:02:10 +03:00
Родитель 749f6ddade
Коммит f3c162e4e1
17 изменённых файлов: 451 добавлений и 6 удалений

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

@ -505,6 +505,31 @@ netmonitor.toolbar.setCookies=Set-Cookies
# in the network table toolbar, above the "scheme" column.
netmonitor.toolbar.scheme=Scheme
# LOCALIZATION NOTE (netmonitor.toolbar.startTime): This is the label displayed
# in the network table toolbar, above the "start time" column, which is the time
# from start of 1st request until the start of this request.
netmonitor.toolbar.startTime=Start Time
# LOCALIZATION NOTE (netmonitor.toolbar.endTime): This is the label displayed
# in the network table toolbar, above the "end time" column, which is the time
# from start of 1st request until the end of this response.
netmonitor.toolbar.endTime=End Time
# LOCALIZATION NOTE (netmonitor.toolbar.responseTime): This is the label displayed
# in the network table toolbar, above the "response time" column, which is the time
# from start of 1st request until the beginning of download of this response.
netmonitor.toolbar.responseTime=Response Time
# LOCALIZATION NOTE (netmonitor.toolbar.duration): This is the label displayed
# in the network table toolbar, above the "duration" column, which is the time
# from start of this request until the end of this response.
netmonitor.toolbar.duration=Duration
# LOCALIZATION NOTE (netmonitor.toolbar.latency): This is the label displayed
# in the network table toolbar, above the "latency" column, which is the time
# from end of this request until the beginning of download of this response.
netmonitor.toolbar.latency=Latency
# LOCALIZATION NOTE (netmonitor.toolbar.transferred): This is the label displayed
# in the network table toolbar, above the "transferred" column, which is the
# compressed / encoded size.
@ -611,6 +636,10 @@ netmonitor.toolbar.perf=Toggle performance analysis…
# displayed in the network table header context menu.
netmonitor.toolbar.resetColumns=Reset Columns
# LOCALIZATION NOTE (netmonitor.toolbar.timings): This is the label
# displayed in the network table header context menu for the timing submenu
netmonitor.toolbar.timings=Timings
# LOCALIZATION NOTE (netmonitor.summary.url): This is the label displayed
# in the network details headers tab identifying the URL.
netmonitor.summary.url=Request URL:

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

@ -24,7 +24,9 @@ EventEmitter.decorate(window);
pref("devtools.netmonitor.enabled", true);
pref("devtools.netmonitor.filters", "[\"all\"]");
pref("devtools.netmonitor.hiddenColumns",
"[\"cookies\",\"protocol\",\"remoteip\",\"scheme\",\"setCookies\"]");
"[\"cookies\",\"duration\",\"endTime\",\"latency\"," +
"\"protocol\",\"remoteip\",\"responseTime\",\"scheme\",\"setCookies\",\"startTime\"]"
);
pref("devtools.netmonitor.panes-network-details-width", 550);
pref("devtools.netmonitor.panes-network-details-height", 450);
pref("devtools.netmonitor.har.defaultLogDir", "");

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

@ -459,6 +459,37 @@ body,
width: 13%;
}
/* Start Time column */
.requests-list-start-time {
width: 8%;
}
/* End Time column */
.requests-list-end-time {
width: 8%;
}
/* Response Time column */
.requests-list-response-time {
width: 10%;
}
/* Duration column */
.requests-list-duration {
width: 8%;
}
/* Latency column */
.requests-list-latency {
width: 8%;
}
.requests-list-domain.requests-list-column {
text-align: start;
}

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

@ -16,12 +16,17 @@ DevToolsModules(
'request-list-column-content-size.js',
'request-list-column-cookies.js',
'request-list-column-domain.js',
'request-list-column-duration.js',
'request-list-column-end-time.js',
'request-list-column-file.js',
'request-list-column-latency.js',
'request-list-column-method.js',
'request-list-column-protocol.js',
'request-list-column-remote-ip.js',
'request-list-column-response-time.js',
'request-list-column-scheme.js',
'request-list-column-set-cookies.js',
'request-list-column-start-time.js',
'request-list-column-status.js',
'request-list-column-transferred-size.js',
'request-list-column-type.js',

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

@ -0,0 +1,41 @@
/* 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 {
createClass,
DOM,
PropTypes,
} = require("devtools/client/shared/vendor/react");
const { getFormattedTime } = require("../utils/format-utils");
const { div } = DOM;
const RequestListColumnDuration = createClass({
displayName: "RequestListColumnDuration",
propTypes: {
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return this.props.item.totalTime !== nextProps.item.totalTime;
},
render() {
let { totalTime } = this.props.item;
let duration = getFormattedTime(totalTime);
return (
div({
className: "requests-list-column requests-list-duration",
title: duration,
},
duration
)
);
}
});
module.exports = RequestListColumnDuration;

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

@ -0,0 +1,44 @@
/* 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 {
createClass,
DOM,
PropTypes,
} = require("devtools/client/shared/vendor/react");
const { getFormattedTime } = require("../utils/format-utils");
const { getEndTime } = require("../utils/request-utils");
const { div } = DOM;
const RequestListColumnEndTime = createClass({
displayName: "RequestListColumnEndTime",
propTypes: {
firstRequestStartedMillis: PropTypes.number.isRequired,
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return getEndTime(this.props.item) !== getEndTime(nextProps.item);
},
render() {
let { firstRequestStartedMillis, item } = this.props;
let endTime = getFormattedTime(getEndTime(item, firstRequestStartedMillis));
return (
div({
className: "requests-list-column requests-list-end-time",
title: endTime,
},
endTime
)
);
}
});
module.exports = RequestListColumnEndTime;

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

@ -0,0 +1,44 @@
/* 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 {
createClass,
DOM,
PropTypes,
} = require("devtools/client/shared/vendor/react");
const { getFormattedTime } = require("../utils/format-utils");
const { div } = DOM;
const RequestListColumnLatency = createClass({
displayName: "RequestListColumnLatency",
propTypes: {
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
let { eventTimings: currEventTimings = { timings: {} } } = this.props.item;
let { eventTimings: nextEventTimings = { timings: {} } } = nextProps.item;
return currEventTimings.timings.wait !== nextEventTimings.timings.wait;
},
render() {
let { eventTimings = { timings: {} } } = this.props.item;
let latency = getFormattedTime(eventTimings.timings.wait);
return (
div({
className: "requests-list-column requests-list-latency",
title: latency,
},
latency
)
);
}
});
module.exports = RequestListColumnLatency;

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

@ -0,0 +1,45 @@
/* 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 {
createClass,
DOM,
PropTypes,
} = require("devtools/client/shared/vendor/react");
const { getFormattedTime } = require("../utils/format-utils");
const { getResponseTime } = require("../utils/request-utils");
const { div } = DOM;
const RequestListColumnResponseTime = createClass({
displayName: "RequestListColumnResponseTime",
propTypes: {
firstRequestStartedMillis: PropTypes.number.isRequired,
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return getResponseTime(this.props.item) !== getResponseTime(nextProps.item);
},
render() {
let { firstRequestStartedMillis, item } = this.props;
let responseTime = getFormattedTime(
getResponseTime(item, firstRequestStartedMillis));
return (
div({
className: "requests-list-column requests-list-response-time",
title: responseTime,
},
responseTime
)
);
}
});
module.exports = RequestListColumnResponseTime;

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

@ -0,0 +1,45 @@
/* 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 {
createClass,
DOM,
PropTypes,
} = require("devtools/client/shared/vendor/react");
const { getFormattedTime } = require("../utils/format-utils");
const { getStartTime } = require("../utils/request-utils");
const { div } = DOM;
const RequestListColumnStartTime = createClass({
displayName: "RequestListColumnStartTime",
propTypes: {
firstRequestStartedMillis: PropTypes.number.isRequired,
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return getStartTime(this.props.item, this.props.firstRequestStartedMillis)
!== getStartTime(nextProps.item, nextProps.firstRequestStartedMillis);
},
render() {
let { firstRequestStartedMillis, item } = this.props;
let startTime = getFormattedTime(getStartTime(item, firstRequestStartedMillis));
return (
div({
className: "requests-list-column requests-list-start-time",
title: startTime,
},
startTime
)
);
}
});
module.exports = RequestListColumnStartTime;

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

@ -18,12 +18,17 @@ const RequestListColumnCause = createFactory(require("./request-list-column-caus
const RequestListColumnContentSize = createFactory(require("./request-list-column-content-size"));
const RequestListColumnCookies = createFactory(require("./request-list-column-cookies"));
const RequestListColumnDomain = createFactory(require("./request-list-column-domain"));
const RequestListColumnDuration = createFactory(require("./request-list-column-duration"));
const RequestListColumnEndTime = createFactory(require("./request-list-column-end-time"));
const RequestListColumnFile = createFactory(require("./request-list-column-file"));
const RequestListColumnLatency = createFactory(require("./request-list-column-latency"));
const RequestListColumnMethod = createFactory(require("./request-list-column-method"));
const RequestListColumnProtocol = createFactory(require("./request-list-column-protocol"));
const RequestListColumnRemoteIP = createFactory(require("./request-list-column-remote-ip"));
const RequestListColumnResponseTime = createFactory(require("./request-list-column-response-time"));
const RequestListColumnScheme = createFactory(require("./request-list-column-scheme"));
const RequestListColumnSetCookies = createFactory(require("./request-list-column-set-cookies"));
const RequestListColumnStartTime = createFactory(require("./request-list-column-start-time"));
const RequestListColumnStatus = createFactory(require("./request-list-column-status"));
const RequestListColumnTransferredSize = createFactory(require("./request-list-column-transferred-size"));
const RequestListColumnType = createFactory(require("./request-list-column-type"));
@ -148,6 +153,14 @@ const RequestListItem = createClass({
columns.get("setCookies") && RequestListColumnSetCookies({ item }),
columns.get("transferred") && RequestListColumnTransferredSize({ item }),
columns.get("contentSize") && RequestListColumnContentSize({ item }),
columns.get("startTime") &&
RequestListColumnStartTime({ item, firstRequestStartedMillis }),
columns.get("endTime") &&
RequestListColumnEndTime({ item, firstRequestStartedMillis }),
columns.get("responseTime") &&
RequestListColumnResponseTime({ item, firstRequestStartedMillis }),
columns.get("duration") && RequestListColumnDuration({ item }),
columns.get("latency") && RequestListColumnLatency({ item }),
columns.get("waterfall") &&
RequestListColumnWaterfall({ item, firstRequestStartedMillis }),
)

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

@ -152,6 +152,34 @@ const HEADERS = [
filterKey: "size",
canFilter: true,
},
{
name: "startTime",
boxName: "start-time",
canFilter: false,
subMenu: "timings",
},
{
name: "endTime",
boxName: "end-time",
canFilter: false,
subMenu: "timings",
},
{
name: "responseTime",
boxName: "response-time",
canFilter: false,
subMenu: "timings",
},
{
name: "duration",
canFilter: false,
subMenu: "timings",
},
{
name: "latency",
canFilter: false,
subMenu: "timings",
},
{
name: "waterfall",
canFilter: false,

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

@ -32,6 +32,11 @@ const Columns = I.Record({
setCookies: false,
transferred: true,
contentSize: true,
startTime: false,
endTime: false,
responseTime: false,
duration: false,
latency: false,
waterfall: true,
});

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

@ -12,6 +12,10 @@ const stringMap = HEADERS
.filter((header) => header.hasOwnProperty("label"))
.reduce((acc, { name, label }) => Object.assign(acc, { [name]: label }), {});
const subMenuMap = HEADERS
.filter((header) => header.hasOwnProperty("subMenu"))
.reduce((acc, { name, subMenu }) => Object.assign(acc, { [name]: subMenu }), {});
class RequestListHeaderContextMenu {
constructor({ toggleColumn, resetColumns }) {
this.toggleColumn = toggleColumn;
@ -33,10 +37,11 @@ class RequestListHeaderContextMenu {
*/
open(event = {}) {
let menu = [];
let subMenu = { timings: [] };
let onlyOneColumn = this.visibleColumns.length === 1;
for (let [column, shown] of this.columns) {
menu.push({
let entry = {
id: `request-list-header-${column}-toggle`,
label: L10N.getStr(`netmonitor.toolbar.${stringMap[column] || column}`),
type: "checkbox",
@ -44,11 +49,19 @@ class RequestListHeaderContextMenu {
click: () => this.toggleColumn(column),
// We don't want to allow hiding the last visible column
disabled: onlyOneColumn && shown,
});
};
subMenuMap.hasOwnProperty(column) ?
subMenu[subMenuMap[column]].push(entry) :
menu.push(entry);
}
menu.push({ type: "separator" });
menu.push({
label: L10N.getStr("netmonitor.toolbar.timings"),
submenu: subMenu.timings,
});
menu.push({ type: "separator" });
menu.push({
id: "request-list-header-reset-columns",
label: L10N.getStr("netmonitor.toolbar.resetColumns"),

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

@ -301,6 +301,45 @@ function propertiesEqual(props, item1, item2) {
return item1 === item2 || props.every(p => item1[p] === item2[p]);
}
/**
* Calculate the start time of a request, which is the time from start
* of 1st request until the start of this request.
*
* Without a firstRequestStartedMillis argument the wrong time will be returned.
* However, it can be omitted when comparing two start times and neither supplies
* a firstRequestStartedMillis.
*/
function getStartTime(item, firstRequestStartedMillis = 0) {
return item.startedMillis - firstRequestStartedMillis;
}
/**
* Calculate the end time of a request, which is the time from start
* of 1st request until the end of this response.
*
* Without a firstRequestStartedMillis argument the wrong time will be returned.
* However, it can be omitted when comparing two end times and neither supplies
* a firstRequestStartedMillis.
*/
function getEndTime(item, firstRequestStartedMillis = 0) {
let { startedMillis, totalTime } = item;
return startedMillis + totalTime - firstRequestStartedMillis;
}
/**
* Calculate the response time of a request, which is the time from start
* of 1st request until the beginning of download of this response.
*
* Without a firstRequestStartedMillis argument the wrong time will be returned.
* However, it can be omitted when comparing two response times and neither supplies
* a firstRequestStartedMillis.
*/
function getResponseTime(item, firstRequestStartedMillis = 0) {
let { startedMillis, totalTime, eventTimings = { timings: {} } } = item;
return startedMillis + totalTime - firstRequestStartedMillis -
eventTimings.timings.receive;
}
module.exports = {
getFormDataSections,
fetchHeaders,
@ -308,6 +347,9 @@ module.exports = {
writeHeaderText,
decodeUnicodeUrl,
getAbbreviatedMimeType,
getEndTime,
getResponseTime,
getStartTime,
getUrlBaseName,
getUrlBaseNameWithQuery,
getUrlDetails,

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

@ -6,6 +6,9 @@
const {
getAbbreviatedMimeType,
getEndTime,
getResponseTime,
getStartTime,
ipToLong,
} = require("./request-utils");
@ -61,6 +64,34 @@ function scheme(first, second) {
return result || waterfall(first, second);
}
function startTime(first, second) {
const result = compareValues(getStartTime(first), getStartTime(second));
return result || waterfall(first, second);
}
function endTime(first, second) {
const result = compareValues(getEndTime(first), getEndTime(second));
return result || waterfall(first, second);
}
function responseTime(first, second) {
const result = compareValues(getResponseTime(first), getResponseTime(second));
return result || waterfall(first, second);
}
function duration(first, second) {
const result = compareValues(first.totalTime, second.totalTime);
return result || waterfall(first, second);
}
function latency(first, second) {
let { eventTimings: firstEventTimings = { timings: {} } } = first;
let { eventTimings: secondEventTimings = { timings: {} } } = second;
const result = compareValues(firstEventTimings.timings.wait,
secondEventTimings.timings.wait);
return result || waterfall(first, second);
}
function domain(first, second) {
const firstDomain = first.urlDetails.host.toLowerCase();
const secondDomain = second.urlDetails.host.toLowerCase();
@ -133,5 +164,10 @@ exports.Sorters = {
type,
transferred,
contentSize,
startTime,
endTime,
responseTime,
duration,
latency,
waterfall,
};

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

@ -15,7 +15,8 @@ Services.scriptloader.loadSubScript(
const { EVENTS } = require("devtools/client/netmonitor/src/constants");
const {
getFormattedIPAndPort
getFormattedIPAndPort,
getFormattedTime,
} = require("devtools/client/netmonitor/src/utils/format-utils");
const {
decodeUnicodeUrl,
@ -384,9 +385,17 @@ function verifyRequestItemTarget(document, requestList, requestItem, method,
let query = getUrlQuery(url);
let host = getUrlHost(url);
let scheme = getUrlScheme(url);
let { httpVersion = "", remoteAddress, remotePort } = requestItem;
let {
httpVersion = "",
remoteAddress,
remotePort,
totalTime,
eventTimings = { timings: {} },
} = requestItem;
let formattedIPPort = getFormattedIPAndPort(remoteAddress, remotePort);
let remoteIP = remoteAddress ? `${formattedIPPort}` : "unknown";
let duration = getFormattedTime(totalTime);
let latency = getFormattedTime(eventTimings.timings.wait);
if (fuzzyUrl) {
ok(requestItem.method.startsWith(method), "The attached method is correct.");
@ -437,6 +446,18 @@ function verifyRequestItemTarget(document, requestList, requestItem, method,
is(target.querySelector(".requests-list-scheme").getAttribute("title"),
scheme, "The tooltip scheme is correct.");
is(target.querySelector(".requests-list-duration").textContent,
duration, "The displayed duration is correct.");
is(target.querySelector(".requests-list-duration").getAttribute("title"),
duration, "The tooltip duration is correct.");
is(target.querySelector(".requests-list-latency").textContent,
latency, "The displayed latency is correct.");
is(target.querySelector(".requests-list-latency").getAttribute("title"),
latency, "The tooltip latency is correct.");
if (status !== undefined) {
let value = target.querySelector(".requests-list-status-icon")
.getAttribute("data-code");

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

@ -151,7 +151,8 @@ pref("devtools.netmonitor.panes-network-details-width", 550);
pref("devtools.netmonitor.panes-network-details-height", 450);
pref("devtools.netmonitor.filters", "[\"all\"]");
pref("devtools.netmonitor.hiddenColumns",
"[\"cookies\",\"protocol\",\"remoteip\",\"scheme\",\"setCookies\"]");
"[\"cookies\",\"duration\",\"endTime\",\"latency\",\"protocol\",\"remoteip\",\"responseTime\",\"scheme\",\"setCookies\",\"startTime\"]"
);
// The default Network monitor HAR export setting
pref("devtools.netmonitor.har.defaultLogDir", "");