v0.0.6: Include fixed tick processor. Add regression analysis.

This commit is contained in:
Jason Gore 2019-08-22 14:37:09 -07:00
Родитель 39bc2243bb
Коммит c4fd88f0a6
22 изменённых файлов: 4683 добавлений и 129 удалений

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

@ -47,10 +47,13 @@ help message
## Examples
```
$ flamegrill cook -s http://localhost:4322
$ flamegrill cook -n SplitButton -s "http://fabricweb.z5.web.core.windows.net/pr-deploy-site/refs/heads/master/perf-test/index.html?scenario=SplitButtonNew&iterations=5000"
$ flamegrill cook -s http://localhost:4322 -r http://some.url.com
$ flamegrill cook -n SplitButton -s "http://fabricweb.z5.web.core.windows.net/pr-deploy-site/refs/heads/master/perf-test/index.html?scenario=SplitButtonNew&iterations=5000" -r "http://fabricweb.z5.web.core.windows.net/pr-deploy-site/refs/heads/master/perf-test/index.html?scenario=SplitButton&iterations=5000"
$ flamegrill cook -n SplitButtonNew -s "http://fabricweb.z5.web.core.windows.net/pr-deploy-site/refs/heads/master/perf-test/index.html?scenario=SplitButtonNew&iterations=5000" -o out -t temp
```
## Open Source Credits
[Flamebearer](https://github.com/mapbox/flamebearer) is an inspiration for this project and is used to generate flamegraphs. Parts of Flamebearer have been modified and expanded upon to add more functionality to the flamegraphs.

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

@ -47,10 +47,13 @@ help message
## Examples
```
$ flamegrill cook -s http://localhost:4322
$ flamegrill cook -n SplitButton -s "http://fabricweb.z5.web.core.windows.net/pr-deploy-site/refs/heads/master/perf-test/index.html?scenario=SplitButtonNew&iterations=5000"
$ flamegrill cook -s http://localhost:4322 -r http://some.url.com
$ flamegrill cook -n SplitButton -s "http://fabricweb.z5.web.core.windows.net/pr-deploy-site/refs/heads/master/perf-test/index.html?scenario=SplitButtonNew&iterations=5000" -r "http://fabricweb.z5.web.core.windows.net/pr-deploy-site/refs/heads/master/perf-test/index.html?scenario=SplitButton&iterations=5000"
$ flamegrill cook -n SplitButtonNew -s "http://fabricweb.z5.web.core.windows.net/pr-deploy-site/refs/heads/master/perf-test/index.html?scenario=SplitButtonNew&iterations=5000" -o out -t temp
```
## Open Source Credits
[Flamebearer](https://github.com/mapbox/flamebearer) is an inspiration for this project and is used to generate flamegraphs. Parts of Flamebearer have been modified and expanded upon to add more functionality to the flamegraphs.

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

@ -0,0 +1,78 @@
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
class BaseArgumentsProcessor {
constructor(args) {
this.args_ = args;
this.result_ = this.getDefaultResults();
console.assert(this.result_ !== undefined)
console.assert(this.result_.logFileName !== undefined);
this.argsDispatch_ = this.getArgsDispatch();
console.assert(this.argsDispatch_ !== undefined);
}
getDefaultResults() {
throw "Implement in getDefaultResults in subclass";
}
getArgsDispatch() {
throw "Implement getArgsDispatch in subclass";
}
result() { return this.result_ }
printUsageAndExit() {
print('Cmdline args: [options] [log-file-name]\n' +
'Default log file name is "' +
this.result_.logFileName + '".\n');
print('Options:');
for (var arg in this.argsDispatch_) {
var synonyms = [arg];
var dispatch = this.argsDispatch_[arg];
for (var synArg in this.argsDispatch_) {
if (arg !== synArg && dispatch === this.argsDispatch_[synArg]) {
synonyms.push(synArg);
delete this.argsDispatch_[synArg];
}
}
print(' ' + synonyms.join(', ').padEnd(20) + " " + dispatch[2]);
}
quit(2);
}
parse() {
while (this.args_.length) {
var arg = this.args_.shift();
if (arg.charAt(0) != '-') {
this.result_.logFileName = arg;
continue;
}
var userValue = null;
var eqPos = arg.indexOf('=');
if (eqPos != -1) {
userValue = arg.substr(eqPos + 1);
arg = arg.substr(0, eqPos);
}
if (arg in this.argsDispatch_) {
var dispatch = this.argsDispatch_[arg];
var property = dispatch[0];
var defaultValue = dispatch[1];
if (typeof defaultValue == "function") {
userValue = defaultValue(userValue);
} else if (userValue == null) {
userValue = defaultValue;
}
this.result_[property] = userValue;
} else {
return false;
}
}
return true;
}
}
function parseBool(str) {
if (str == "true" || str == "1") return true;
return false;
}

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

@ -0,0 +1,326 @@
// Copyright 2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/**
* Constructs a mapper that maps addresses into code entries.
*
* @constructor
*/
function CodeMap() {
/**
* Dynamic code entries. Used for JIT compiled code.
*/
this.dynamics_ = new SplayTree();
/**
* Name generator for entries having duplicate names.
*/
this.dynamicsNameGen_ = new CodeMap.NameGenerator();
/**
* Static code entries. Used for statically compiled code.
*/
this.statics_ = new SplayTree();
/**
* Libraries entries. Used for the whole static code libraries.
*/
this.libraries_ = new SplayTree();
/**
* Map of memory pages occupied with static code.
*/
this.pages_ = [];
};
/**
* The number of alignment bits in a page address.
*/
CodeMap.PAGE_ALIGNMENT = 12;
/**
* Page size in bytes.
*/
CodeMap.PAGE_SIZE =
1 << CodeMap.PAGE_ALIGNMENT;
/**
* Adds a dynamic (i.e. moveable and discardable) code entry.
*
* @param {number} start The starting address.
* @param {CodeMap.CodeEntry} codeEntry Code entry object.
*/
CodeMap.prototype.addCode = function(start, codeEntry) {
this.deleteAllCoveredNodes_(this.dynamics_, start, start + codeEntry.size);
this.dynamics_.insert(start, codeEntry);
};
/**
* Moves a dynamic code entry. Throws an exception if there is no dynamic
* code entry with the specified starting address.
*
* @param {number} from The starting address of the entry being moved.
* @param {number} to The destination address.
*/
CodeMap.prototype.moveCode = function(from, to) {
var removedNode = this.dynamics_.remove(from);
this.deleteAllCoveredNodes_(this.dynamics_, to, to + removedNode.value.size);
this.dynamics_.insert(to, removedNode.value);
};
/**
* Discards a dynamic code entry. Throws an exception if there is no dynamic
* code entry with the specified starting address.
*
* @param {number} start The starting address of the entry being deleted.
*/
CodeMap.prototype.deleteCode = function(start) {
var removedNode = this.dynamics_.remove(start);
};
/**
* Adds a library entry.
*
* @param {number} start The starting address.
* @param {CodeMap.CodeEntry} codeEntry Code entry object.
*/
CodeMap.prototype.addLibrary = function(
start, codeEntry) {
this.markPages_(start, start + codeEntry.size);
this.libraries_.insert(start, codeEntry);
};
/**
* Adds a static code entry.
*
* @param {number} start The starting address.
* @param {CodeMap.CodeEntry} codeEntry Code entry object.
*/
CodeMap.prototype.addStaticCode = function(
start, codeEntry) {
this.statics_.insert(start, codeEntry);
};
/**
* @private
*/
CodeMap.prototype.markPages_ = function(start, end) {
for (var addr = start; addr <= end; addr += CodeMap.PAGE_SIZE) {
// JS >>> is zero-fill 32-bit rotation, which is truncating higher bits.
// https://www.w3schools.com/js/js_bitwise.asp
// https://bugs.chromium.org/p/v8/issues/detail?id=9578
const page = enableFix ? Math.trunc(addr / 4096) : addr >>> CodeMap.PAGE_ALIGNMENT;
this.pages_[page] = 1;
}
};
/**
* @private
*/
CodeMap.prototype.deleteAllCoveredNodes_ = function(tree, start, end) {
var to_delete = [];
var addr = end - 1;
while (addr >= start) {
var node = tree.findGreatestLessThan(addr);
if (!node) break;
var start2 = node.key, end2 = start2 + node.value.size;
if (start2 < end && start < end2) to_delete.push(start2);
addr = start2 - 1;
}
for (var i = 0, l = to_delete.length; i < l; ++i) tree.remove(to_delete[i]);
};
/**
* @private
*/
CodeMap.prototype.isAddressBelongsTo_ = function(addr, node) {
return addr >= node.key && addr < (node.key + node.value.size);
};
/**
* @private
*/
CodeMap.prototype.findInTree_ = function(tree, addr) {
var node = tree.findGreatestLessThan(addr);
return node && this.isAddressBelongsTo_(addr, node) ? node : null;
};
/**
* Finds a code entry that contains the specified address. Both static and
* dynamic code entries are considered. Returns the code entry and the offset
* within the entry.
*
* @param {number} addr Address.
*/
CodeMap.prototype.findAddress = function(addr) {
// JS >>> is zero-fill 32-bit rotation, which is truncating higher bits.
// https://www.w3schools.com/js/js_bitwise.asp
// https://bugs.chromium.org/p/v8/issues/detail?id=9578
var pageAddr = enableFix ? Math.trunc(addr / 4096) : addr >>> CodeMap.PAGE_ALIGNMENT;
if (pageAddr in this.pages_) {
// Static code entries can contain "holes" of unnamed code.
// In this case, the whole library is assigned to this address.
var result = this.findInTree_(this.statics_, addr);
if (!result) {
result = this.findInTree_(this.libraries_, addr);
if (!result) return null;
}
return { entry : result.value, offset : addr - result.key };
}
var min = this.dynamics_.findMin();
var max = this.dynamics_.findMax();
if (max != null && addr < (max.key + max.value.size) && addr >= min.key) {
var dynaEntry = this.findInTree_(this.dynamics_, addr);
if (dynaEntry == null) return null;
// Dedupe entry name.
var entry = dynaEntry.value;
if (!entry.nameUpdated_) {
entry.name = this.dynamicsNameGen_.getName(entry.name);
entry.nameUpdated_ = true;
}
return { entry : entry, offset : addr - dynaEntry.key };
}
return null;
};
/**
* Finds a code entry that contains the specified address. Both static and
* dynamic code entries are considered.
*
* @param {number} addr Address.
*/
CodeMap.prototype.findEntry = function(addr) {
var result = this.findAddress(addr);
return result ? result.entry : null;
};
/**
* Returns a dynamic code entry using its starting address.
*
* @param {number} addr Address.
*/
CodeMap.prototype.findDynamicEntryByStartAddress =
function(addr) {
var node = this.dynamics_.find(addr);
return node ? node.value : null;
};
/**
* Returns an array of all dynamic code entries.
*/
CodeMap.prototype.getAllDynamicEntries = function() {
return this.dynamics_.exportValues();
};
/**
* Returns an array of pairs of all dynamic code entries and their addresses.
*/
CodeMap.prototype.getAllDynamicEntriesWithAddresses = function() {
return this.dynamics_.exportKeysAndValues();
};
/**
* Returns an array of all static code entries.
*/
CodeMap.prototype.getAllStaticEntries = function() {
return this.statics_.exportValues();
};
/**
* Returns an array of pairs of all static code entries and their addresses.
*/
CodeMap.prototype.getAllStaticEntriesWithAddresses = function() {
return this.statics_.exportKeysAndValues();
};
/**
* Returns an array of all libraries entries.
*/
CodeMap.prototype.getAllLibrariesEntries = function() {
return this.libraries_.exportValues();
};
/**
* Creates a code entry object.
*
* @param {number} size Code entry size in bytes.
* @param {string} opt_name Code entry name.
* @param {string} opt_type Code entry type, e.g. SHARED_LIB, CPP.
* @constructor
*/
CodeMap.CodeEntry = function(size, opt_name, opt_type) {
this.size = size;
this.name = opt_name || '';
this.type = opt_type || '';
this.nameUpdated_ = false;
};
CodeMap.CodeEntry.prototype.getName = function() {
return this.name;
};
CodeMap.CodeEntry.prototype.toString = function() {
return this.name + ': ' + this.size.toString(16);
};
CodeMap.NameGenerator = function() {
this.knownNames_ = {};
};
CodeMap.NameGenerator.prototype.getName = function(name) {
if (!(name in this.knownNames_)) {
this.knownNames_[name] = 0;
return name;
}
var count = ++this.knownNames_[name];
return name + ' {' + count + '}';
};

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

@ -0,0 +1,92 @@
// Copyright 2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/**
* Constructs a ConsArray object. It is used mainly for tree traversal.
* In this use case we have lots of arrays that we need to iterate
* sequentally. The internal Array implementation is horribly slow
* when concatenating on large (10K items) arrays due to memory copying.
* That's why we avoid copying memory and insead build a linked list
* of arrays to iterate through.
*
* @constructor
*/
function ConsArray() {
this.tail_ = new ConsArray.Cell(null, null);
this.currCell_ = this.tail_;
this.currCellPos_ = 0;
};
/**
* Concatenates another array for iterating. Empty arrays are ignored.
* This operation can be safely performed during ongoing ConsArray
* iteration.
*
* @param {Array} arr Array to concatenate.
*/
ConsArray.prototype.concat = function(arr) {
if (arr.length > 0) {
this.tail_.data = arr;
this.tail_ = this.tail_.next = new ConsArray.Cell(null, null);
}
};
/**
* Whether the end of iteration is reached.
*/
ConsArray.prototype.atEnd = function() {
return this.currCell_ === null ||
this.currCell_.data === null ||
this.currCellPos_ >= this.currCell_.data.length;
};
/**
* Returns the current item, moves to the next one.
*/
ConsArray.prototype.next = function() {
var result = this.currCell_.data[this.currCellPos_++];
if (this.currCellPos_ >= this.currCell_.data.length) {
this.currCell_ = this.currCell_.next;
this.currCellPos_ = 0;
}
return result;
};
/**
* A cell object used for constructing a list in ConsArray.
*
* @constructor
*/
ConsArray.Cell = function(data, next) {
this.data = data;
this.next = next;
};

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

@ -0,0 +1,105 @@
// Copyright 2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/**
* Creates a CSV lines parser.
*/
class CsvParser {
/**
* Converts \x00 and \u0000 escape sequences in the given string.
*
* @param {string} input field.
**/
escapeField(string) {
let nextPos = string.indexOf("\\");
if (nextPos === -1) return string;
let result = string.substring(0, nextPos);
// Escape sequences of the form \x00 and \u0000;
let endPos = string.length;
let pos = 0;
while (nextPos !== -1) {
let escapeIdentifier = string.charAt(nextPos + 1);
pos = nextPos + 2;
if (escapeIdentifier == 'n') {
result += '\n';
nextPos = pos;
} else if (escapeIdentifier == '\\') {
result += '\\';
nextPos = pos;
} else {
if (escapeIdentifier == 'x') {
// \x00 ascii range escapes consume 2 chars.
nextPos = pos + 2;
} else {
// \u0000 unicode range escapes consume 4 chars.
nextPos = pos + 4;
}
// Convert the selected escape sequence to a single character.
let escapeChars = string.substring(pos, nextPos);
result += String.fromCharCode(parseInt(escapeChars, 16));
}
// Continue looking for the next escape sequence.
pos = nextPos;
nextPos = string.indexOf("\\", pos);
// If there are no more escape sequences consume the rest of the string.
if (nextPos === -1) {
result += string.substr(pos);
} else if (pos != nextPos) {
result += string.substring(pos, nextPos);
}
}
return result;
}
/**
* Parses a line of CSV-encoded values. Returns an array of fields.
*
* @param {string} line Input line.
*/
parseLine(line) {
var pos = 0;
var endPos = line.length;
var fields = [];
if (endPos == 0) return fields;
let nextPos = 0;
while(nextPos !== -1) {
nextPos = line.indexOf(',', pos);
let field;
if (nextPos === -1) {
field = line.substr(pos);
} else {
field = line.substring(pos, nextPos);
}
fields.push(this.escapeField(field));
pos = nextPos + 1;
};
return fields
}
}

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

@ -0,0 +1,258 @@
// Copyright 2011 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/**
* @fileoverview Log Reader is used to process log file produced by V8.
*/
/**
* Base class for processing log files.
*
* @param {Array.<Object>} dispatchTable A table used for parsing and processing
* log records.
* @param {boolean} timedRange Ignore ticks outside timed range.
* @param {boolean} pairwiseTimedRange Ignore ticks outside pairs of timer
* markers.
* @constructor
*/
function LogReader(dispatchTable, timedRange, pairwiseTimedRange) {
/**
* @type {Array.<Object>}
*/
this.dispatchTable_ = dispatchTable;
/**
* @type {boolean}
*/
this.timedRange_ = timedRange;
/**
* @type {boolean}
*/
this.pairwiseTimedRange_ = pairwiseTimedRange;
if (pairwiseTimedRange) {
this.timedRange_ = true;
}
/**
* Current line.
* @type {number}
*/
this.lineNum_ = 0;
/**
* CSV lines parser.
* @type {CsvParser}
*/
this.csvParser_ = new CsvParser();
/**
* Keeps track of whether we've seen a "current-time" tick yet.
* @type {boolean}
*/
this.hasSeenTimerMarker_ = false;
/**
* List of log lines seen since last "current-time" tick.
* @type {Array.<String>}
*/
this.logLinesSinceLastTimerMarker_ = [];
};
/**
* Used for printing error messages.
*
* @param {string} str Error message.
*/
LogReader.prototype.printError = function(str) {
// Do nothing.
};
/**
* Processes a portion of V8 profiler event log.
*
* @param {string} chunk A portion of log.
*/
LogReader.prototype.processLogChunk = function(chunk) {
this.processLog_(chunk.split('\n'));
};
/**
* Processes a line of V8 profiler event log.
*
* @param {string} line A line of log.
*/
LogReader.prototype.processLogLine = function(line) {
if (!this.timedRange_) {
this.processLogLine_(line);
return;
}
if (line.startsWith("current-time")) {
if (this.hasSeenTimerMarker_) {
this.processLog_(this.logLinesSinceLastTimerMarker_);
this.logLinesSinceLastTimerMarker_ = [];
// In pairwise mode, a "current-time" line ends the timed range.
if (this.pairwiseTimedRange_) {
this.hasSeenTimerMarker_ = false;
}
} else {
this.hasSeenTimerMarker_ = true;
}
} else {
if (this.hasSeenTimerMarker_) {
this.logLinesSinceLastTimerMarker_.push(line);
} else if (!line.startsWith("tick")) {
this.processLogLine_(line);
}
}
};
/**
* Processes stack record.
*
* @param {number} pc Program counter.
* @param {number} func JS Function.
* @param {Array.<string>} stack String representation of a stack.
* @return {Array.<number>} Processed stack.
*/
LogReader.prototype.processStack = function(pc, func, stack) {
var fullStack = func ? [pc, func] : [pc];
var prevFrame = pc;
for (var i = 0, n = stack.length; i < n; ++i) {
var frame = stack[i];
var firstChar = frame.charAt(0);
if (firstChar == '+' || firstChar == '-') {
// An offset from the previous frame.
prevFrame += parseInt(frame, 16);
fullStack.push(prevFrame);
// Filter out possible 'overflow' string.
} else if (firstChar != 'o') {
fullStack.push(parseInt(frame, 16));
} else {
this.printError("dropping: " + frame);
}
}
return fullStack;
};
/**
* Returns whether a particular dispatch must be skipped.
*
* @param {!Object} dispatch Dispatch record.
* @return {boolean} True if dispatch must be skipped.
*/
LogReader.prototype.skipDispatch = function(dispatch) {
return false;
};
// Parses dummy variable for readability;
const parseString = 'parse-string';
const parseVarArgs = 'parse-var-args';
/**
* Does a dispatch of a log record.
*
* @param {Array.<string>} fields Log record.
* @private
*/
LogReader.prototype.dispatchLogRow_ = function(fields) {
// Obtain the dispatch.
var command = fields[0];
var dispatch = this.dispatchTable_[command];
if (dispatch === undefined) return;
if (dispatch === null || this.skipDispatch(dispatch)) {
return;
}
// Parse fields.
var parsedFields = [];
for (var i = 0; i < dispatch.parsers.length; ++i) {
var parser = dispatch.parsers[i];
if (parser === parseString) {
parsedFields.push(fields[1 + i]);
} else if (typeof parser == 'function') {
// Check for numbers we can't handle. Breaking point is 53-bit:
// dispatchLogRow_: Can't parse 0x2fffffffffffff, integer too large.
if (parser === parseInt) {
const field = fields[1 + i];
if (!Number.isSafeInteger(parseInt(field))) {
throw Error(`dispatchLogRow_: Can't parse ${field}, integer too large.`);
}
}
parsedFields.push(parser(fields[1 + i]));
} else if (parser === parseVarArgs) {
// var-args
parsedFields.push(fields.slice(1 + i));
break;
} else {
throw new Error("Invalid log field parser: " + parser);
}
}
// Run the processor.
dispatch.processor.apply(this, parsedFields);
};
/**
* Processes log lines.
*
* @param {Array.<string>} lines Log lines.
* @private
*/
LogReader.prototype.processLog_ = function(lines) {
for (var i = 0, n = lines.length; i < n; ++i) {
this.processLogLine_(lines[i]);
}
}
/**
* Processes a single log line.
*
* @param {String} a log line
* @private
*/
LogReader.prototype.processLogLine_ = function(line) {
if (line.length > 0) {
try {
var fields = this.csvParser_.parseLine(line);
this.dispatchLogRow_(fields);
} catch (e) {
this.printError('line ' + (this.lineNum_ + 1) + ': ' + (e.message || e));
// Report error back to flamegrill.
throw e;
}
}
this.lineNum_++;
};

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -0,0 +1,201 @@
// Copyright 2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/**
* Creates a Profile View builder object.
*
* @param {number} samplingRate Number of ms between profiler ticks.
* @constructor
*/
function ViewBuilder(samplingRate) {
this.samplingRate = samplingRate;
};
/**
* Builds a profile view for the specified call tree.
*
* @param {CallTree} callTree A call tree.
* @param {boolean} opt_bottomUpViewWeights Whether remapping
* of self weights for a bottom up view is needed.
*/
ViewBuilder.prototype.buildView = function(
callTree, opt_bottomUpViewWeights) {
var head;
var samplingRate = this.samplingRate;
var createViewNode = this.createViewNode;
callTree.traverse(function(node, viewParent) {
var totalWeight = node.totalWeight * samplingRate;
var selfWeight = node.selfWeight * samplingRate;
if (opt_bottomUpViewWeights === true) {
if (viewParent === head) {
selfWeight = totalWeight;
} else {
selfWeight = 0;
}
}
var viewNode = createViewNode(node.label, totalWeight, selfWeight, head);
if (viewParent) {
viewParent.addChild(viewNode);
} else {
head = viewNode;
}
return viewNode;
});
var view = this.createView(head);
return view;
};
/**
* Factory method for a profile view.
*
* @param {ProfileView.Node} head View head node.
* @return {ProfileView} Profile view.
*/
ViewBuilder.prototype.createView = function(head) {
return new ProfileView(head);
};
/**
* Factory method for a profile view node.
*
* @param {string} internalFuncName A fully qualified function name.
* @param {number} totalTime Amount of time that application spent in the
* corresponding function and its descendants (not that depending on
* profile they can be either callees or callers.)
* @param {number} selfTime Amount of time that application spent in the
* corresponding function only.
* @param {ProfileView.Node} head Profile view head.
* @return {ProfileView.Node} Profile view node.
*/
ViewBuilder.prototype.createViewNode = function(
funcName, totalTime, selfTime, head) {
return new ProfileView.Node(
funcName, totalTime, selfTime, head);
};
/**
* Creates a Profile View object. It allows to perform sorting
* and filtering actions on the profile.
*
* @param {ProfileView.Node} head Head (root) node.
* @constructor
*/
function ProfileView(head) {
this.head = head;
};
/**
* Sorts the profile view using the specified sort function.
*
* @param {function(ProfileView.Node,
* ProfileView.Node):number} sortFunc A sorting
* functions. Must comply with Array.sort sorting function requirements.
*/
ProfileView.prototype.sort = function(sortFunc) {
this.traverse(function (node) {
node.sortChildren(sortFunc);
});
};
/**
* Traverses profile view nodes in preorder.
*
* @param {function(ProfileView.Node)} f Visitor function.
*/
ProfileView.prototype.traverse = function(f) {
var nodesToTraverse = new ConsArray();
nodesToTraverse.concat([this.head]);
while (!nodesToTraverse.atEnd()) {
var node = nodesToTraverse.next();
f(node);
nodesToTraverse.concat(node.children);
}
};
/**
* Constructs a Profile View node object. Each node object corresponds to
* a function call.
*
* @param {string} internalFuncName A fully qualified function name.
* @param {number} totalTime Amount of time that application spent in the
* corresponding function and its descendants (not that depending on
* profile they can be either callees or callers.)
* @param {number} selfTime Amount of time that application spent in the
* corresponding function only.
* @param {ProfileView.Node} head Profile view head.
* @constructor
*/
ProfileView.Node = function(
internalFuncName, totalTime, selfTime, head) {
this.internalFuncName = internalFuncName;
this.totalTime = totalTime;
this.selfTime = selfTime;
this.head = head;
this.parent = null;
this.children = [];
};
/**
* Returns a share of the function's total time in its parent's total time.
*/
ProfileView.Node.prototype.__defineGetter__(
'parentTotalPercent',
function() { return this.totalTime /
(this.parent ? this.parent.totalTime : this.totalTime) * 100.0; });
/**
* Adds a child to the node.
*
* @param {ProfileView.Node} node Child node.
*/
ProfileView.Node.prototype.addChild = function(node) {
node.parent = this;
this.children.push(node);
};
/**
* Sorts all the node's children recursively.
*
* @param {function(ProfileView.Node,
* ProfileView.Node):number} sortFunc A sorting
* functions. Must comply with Array.sort sorting function requirements.
*/
ProfileView.Node.prototype.sortChildren = function(
sortFunc) {
this.children.sort(sortFunc);
};

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

@ -0,0 +1,327 @@
// Copyright 2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/**
* Constructs a Splay tree. A splay tree is a self-balancing binary
* search tree with the additional property that recently accessed
* elements are quick to access again. It performs basic operations
* such as insertion, look-up and removal in O(log(n)) amortized time.
*
* @constructor
*/
function SplayTree() {
};
/**
* Pointer to the root node of the tree.
*
* @type {SplayTree.Node}
* @private
*/
SplayTree.prototype.root_ = null;
/**
* @return {boolean} Whether the tree is empty.
*/
SplayTree.prototype.isEmpty = function() {
return !this.root_;
};
/**
* Inserts a node into the tree with the specified key and value if
* the tree does not already contain a node with the specified key. If
* the value is inserted, it becomes the root of the tree.
*
* @param {number} key Key to insert into the tree.
* @param {*} value Value to insert into the tree.
*/
SplayTree.prototype.insert = function(key, value) {
if (this.isEmpty()) {
this.root_ = new SplayTree.Node(key, value);
return;
}
// Splay on the key to move the last node on the search path for
// the key to the root of the tree.
this.splay_(key);
if (this.root_.key == key) {
return;
}
var node = new SplayTree.Node(key, value);
if (key > this.root_.key) {
node.left = this.root_;
node.right = this.root_.right;
this.root_.right = null;
} else {
node.right = this.root_;
node.left = this.root_.left;
this.root_.left = null;
}
this.root_ = node;
};
/**
* Removes a node with the specified key from the tree if the tree
* contains a node with this key. The removed node is returned. If the
* key is not found, an exception is thrown.
*
* @param {number} key Key to find and remove from the tree.
* @return {SplayTree.Node} The removed node.
*/
SplayTree.prototype.remove = function(key) {
if (this.isEmpty()) {
throw Error('Key not found: ' + key);
}
this.splay_(key);
if (this.root_.key != key) {
throw Error('Key not found: ' + key);
}
var removed = this.root_;
if (!this.root_.left) {
this.root_ = this.root_.right;
} else {
var right = this.root_.right;
this.root_ = this.root_.left;
// Splay to make sure that the new root has an empty right child.
this.splay_(key);
// Insert the original right child as the right child of the new
// root.
this.root_.right = right;
}
return removed;
};
/**
* Returns the node having the specified key or null if the tree doesn't contain
* a node with the specified key.
*
* @param {number} key Key to find in the tree.
* @return {SplayTree.Node} Node having the specified key.
*/
SplayTree.prototype.find = function(key) {
if (this.isEmpty()) {
return null;
}
this.splay_(key);
return this.root_.key == key ? this.root_ : null;
};
/**
* @return {SplayTree.Node} Node having the minimum key value.
*/
SplayTree.prototype.findMin = function() {
if (this.isEmpty()) {
return null;
}
var current = this.root_;
while (current.left) {
current = current.left;
}
return current;
};
/**
* @return {SplayTree.Node} Node having the maximum key value.
*/
SplayTree.prototype.findMax = function(opt_startNode) {
if (this.isEmpty()) {
return null;
}
var current = opt_startNode || this.root_;
while (current.right) {
current = current.right;
}
return current;
};
/**
* @return {SplayTree.Node} Node having the maximum key value that
* is less or equal to the specified key value.
*/
SplayTree.prototype.findGreatestLessThan = function(key) {
if (this.isEmpty()) {
return null;
}
// Splay on the key to move the node with the given key or the last
// node on the search path to the top of the tree.
this.splay_(key);
// Now the result is either the root node or the greatest node in
// the left subtree.
if (this.root_.key <= key) {
return this.root_;
} else if (this.root_.left) {
return this.findMax(this.root_.left);
} else {
return null;
}
};
/**
* @return {Array<*>} An array containing all the values of tree's nodes paired
* with keys.
*/
SplayTree.prototype.exportKeysAndValues = function() {
var result = [];
this.traverse_(function(node) { result.push([node.key, node.value]); });
return result;
};
/**
* @return {Array<*>} An array containing all the values of tree's nodes.
*/
SplayTree.prototype.exportValues = function() {
var result = [];
this.traverse_(function(node) { result.push(node.value); });
return result;
};
/**
* Perform the splay operation for the given key. Moves the node with
* the given key to the top of the tree. If no node has the given
* key, the last node on the search path is moved to the top of the
* tree. This is the simplified top-down splaying algorithm from:
* "Self-adjusting Binary Search Trees" by Sleator and Tarjan
*
* @param {number} key Key to splay the tree on.
* @private
*/
SplayTree.prototype.splay_ = function(key) {
if (this.isEmpty()) {
return;
}
// Create a dummy node. The use of the dummy node is a bit
// counter-intuitive: The right child of the dummy node will hold
// the L tree of the algorithm. The left child of the dummy node
// will hold the R tree of the algorithm. Using a dummy node, left
// and right will always be nodes and we avoid special cases.
var dummy, left, right;
dummy = left = right = new SplayTree.Node(null, null);
var current = this.root_;
while (true) {
if (key < current.key) {
if (!current.left) {
break;
}
if (key < current.left.key) {
// Rotate right.
var tmp = current.left;
current.left = tmp.right;
tmp.right = current;
current = tmp;
if (!current.left) {
break;
}
}
// Link right.
right.left = current;
right = current;
current = current.left;
} else if (key > current.key) {
if (!current.right) {
break;
}
if (key > current.right.key) {
// Rotate left.
var tmp = current.right;
current.right = tmp.left;
tmp.left = current;
current = tmp;
if (!current.right) {
break;
}
}
// Link left.
left.right = current;
left = current;
current = current.right;
} else {
break;
}
}
// Assemble.
left.right = current.left;
right.left = current.right;
current.left = dummy.right;
current.right = dummy.left;
this.root_ = current;
};
/**
* Performs a preorder traversal of the tree.
*
* @param {function(SplayTree.Node)} f Visitor function.
* @private
*/
SplayTree.prototype.traverse_ = function(f) {
var nodesToVisit = [this.root_];
while (nodesToVisit.length > 0) {
var node = nodesToVisit.shift();
if (node == null) {
continue;
}
f(node);
nodesToVisit.push(node.left);
nodesToVisit.push(node.right);
}
};
/**
* Constructs a Splay tree node.
*
* @param {number} key Key.
* @param {*} value Value.
*/
SplayTree.Node = function(key, value) {
this.key = key;
this.value = value;
};
/**
* @type {SplayTree.Node}
*/
SplayTree.Node.prototype.left = null;
/**
* @type {SplayTree.Node}
*/
SplayTree.Node.prototype.right = null;

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

@ -0,0 +1,83 @@
// Copyright 2012 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Tick Processor's code flow.
function processArguments(args) {
var processor = new ArgumentsProcessor(args);
if (processor.parse()) {
return processor.result();
} else {
processor.printUsageAndExit();
}
}
function initSourceMapSupport() {
// Pull dev tools source maps into our name space.
SourceMap = WebInspector.SourceMap;
// Overwrite the load function to load scripts synchronously.
SourceMap.load = function(sourceMapURL) {
var content = readFile(sourceMapURL);
var sourceMapObject = (JSON.parse(content));
return new SourceMap(sourceMapURL, sourceMapObject);
};
}
var entriesProviders = {
'unix': UnixCppEntriesProvider,
'windows': WindowsCppEntriesProvider,
'mac': MacCppEntriesProvider
};
var params = processArguments(arguments);
var sourceMap = null;
if (params.sourceMap) {
initSourceMapSupport();
sourceMap = SourceMap.load(params.sourceMap);
}
var tickProcessor = new TickProcessor(
new (entriesProviders[params.platform])(params.nm, params.targetRootFS,
params.apkEmbeddedLibrary),
params.separateIc,
params.separateBytecodes,
params.separateBuiltins,
params.separateStubs,
params.callGraphSize,
params.ignoreUnknown,
params.stateFilter,
params.distortion,
params.range,
sourceMap,
params.timedRange,
params.pairwiseTimedRange,
params.onlySummary,
params.runtimeTimerFilter,
params.preprocessJson);
tickProcessor.processLogFile(params.logFileName);
tickProcessor.printStatistics();

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

@ -0,0 +1,962 @@
// Copyright 2012 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
function inherits(childCtor, parentCtor) {
childCtor.prototype.__proto__ = parentCtor.prototype;
};
function V8Profile(separateIc, separateBytecodes, separateBuiltins,
separateStubs) {
Profile.call(this);
var regexps = [];
if (!separateIc) regexps.push(V8Profile.IC_RE);
if (!separateBytecodes) regexps.push(V8Profile.BYTECODES_RE);
if (!separateBuiltins) regexps.push(V8Profile.BUILTINS_RE);
if (!separateStubs) regexps.push(V8Profile.STUBS_RE);
if (regexps.length > 0) {
this.skipThisFunction = function(name) {
for (var i=0; i<regexps.length; i++) {
if (regexps[i].test(name)) return true;
}
return false;
};
}
};
inherits(V8Profile, Profile);
V8Profile.IC_RE =
/^(LoadGlobalIC: )|(Handler: )|(?:CallIC|LoadIC|StoreIC)|(?:Builtin: (?:Keyed)?(?:Load|Store)IC_)/;
V8Profile.BYTECODES_RE = /^(BytecodeHandler: )/
V8Profile.BUILTINS_RE = /^(Builtin: )/
V8Profile.STUBS_RE = /^(Stub: )/
/**
* A thin wrapper around shell's 'read' function showing a file name on error.
*/
function readFile(fileName) {
try {
return read(fileName);
} catch (e) {
printErr(fileName + ': ' + (e.message || e));
throw e;
}
}
/**
* Parser for dynamic code optimization state.
*/
function parseState(s) {
switch (s) {
case "": return Profile.CodeState.COMPILED;
case "~": return Profile.CodeState.OPTIMIZABLE;
case "*": return Profile.CodeState.OPTIMIZED;
}
throw new Error("unknown code state: " + s);
}
function TickProcessor(
cppEntriesProvider,
separateIc,
separateBytecodes,
separateBuiltins,
separateStubs,
callGraphSize,
ignoreUnknown,
stateFilter,
distortion,
range,
sourceMap,
timedRange,
pairwiseTimedRange,
onlySummary,
runtimeTimerFilter,
preprocessJson) {
this.preprocessJson = preprocessJson;
LogReader.call(this, {
'shared-library': { parsers: [parseString, parseInt, parseInt, parseInt],
processor: this.processSharedLibrary },
'code-creation': {
parsers: [parseString, parseInt, parseInt, parseInt, parseInt,
parseString, parseVarArgs],
processor: this.processCodeCreation },
'code-deopt': {
parsers: [parseInt, parseInt, parseInt, parseInt, parseInt,
parseString, parseString, parseString],
processor: this.processCodeDeopt },
'code-move': { parsers: [parseInt, parseInt, ],
processor: this.processCodeMove },
'code-delete': { parsers: [parseInt],
processor: this.processCodeDelete },
'code-source-info': {
parsers: [parseInt, parseInt, parseInt, parseInt, parseString,
parseString, parseString],
processor: this.processCodeSourceInfo },
'script-source': {
parsers: [parseInt, parseString, parseString],
processor: this.processScriptSource },
'sfi-move': { parsers: [parseInt, parseInt],
processor: this.processFunctionMove },
'active-runtime-timer': {
parsers: [parseString],
processor: this.processRuntimeTimerEvent },
'tick': {
parsers: [parseInt, parseInt, parseInt,
parseInt, parseInt, parseVarArgs],
processor: this.processTick },
'heap-sample-begin': { parsers: [parseString, parseString, parseInt],
processor: this.processHeapSampleBegin },
'heap-sample-end': { parsers: [parseString, parseString],
processor: this.processHeapSampleEnd },
'timer-event-start' : { parsers: [parseString, parseString, parseString],
processor: this.advanceDistortion },
'timer-event-end' : { parsers: [parseString, parseString, parseString],
processor: this.advanceDistortion },
// Ignored events.
'profiler': null,
'function-creation': null,
'function-move': null,
'function-delete': null,
'heap-sample-item': null,
'current-time': null, // Handled specially, not parsed.
// Obsolete row types.
'code-allocate': null,
'begin-code-region': null,
'end-code-region': null },
timedRange,
pairwiseTimedRange);
this.cppEntriesProvider_ = cppEntriesProvider;
this.callGraphSize_ = callGraphSize;
this.ignoreUnknown_ = ignoreUnknown;
this.stateFilter_ = stateFilter;
this.runtimeTimerFilter_ = runtimeTimerFilter;
this.sourceMap = sourceMap;
var ticks = this.ticks_ =
{ total: 0, unaccounted: 0, excluded: 0, gc: 0 };
distortion = parseInt(distortion);
// Convert picoseconds to nanoseconds.
this.distortion_per_entry = isNaN(distortion) ? 0 : (distortion / 1000);
this.distortion = 0;
var rangelimits = range ? range.split(",") : [];
var range_start = parseInt(rangelimits[0]);
var range_end = parseInt(rangelimits[1]);
// Convert milliseconds to nanoseconds.
this.range_start = isNaN(range_start) ? -Infinity : (range_start * 1000);
this.range_end = isNaN(range_end) ? Infinity : (range_end * 1000)
V8Profile.prototype.handleUnknownCode = function(
operation, addr, opt_stackPos) {
var op = Profile.Operation;
switch (operation) {
case op.MOVE:
printErr('Code move event for unknown code: 0x' + addr.toString(16));
break;
case op.DELETE:
printErr('Code delete event for unknown code: 0x' + addr.toString(16));
break;
case op.TICK:
// Only unknown PCs (the first frame) are reported as unaccounted,
// otherwise tick balance will be corrupted (this behavior is compatible
// with the original tickprocessor.py script.)
if (opt_stackPos == 0) {
ticks.unaccounted++;
}
break;
}
};
if (preprocessJson) {
this.profile_ = new JsonProfile();
} else {
this.profile_ = new V8Profile(separateIc, separateBytecodes,
separateBuiltins, separateStubs);
}
this.codeTypes_ = {};
// Count each tick as a time unit.
this.viewBuilder_ = new ViewBuilder(1);
this.lastLogFileName_ = null;
this.generation_ = 1;
this.currentProducerProfile_ = null;
this.onlySummary_ = onlySummary;
};
inherits(TickProcessor, LogReader);
TickProcessor.VmStates = {
JS: 0,
GC: 1,
PARSER: 2,
BYTECODE_COMPILER: 3,
COMPILER: 4,
OTHER: 5,
EXTERNAL: 6,
IDLE: 7,
};
TickProcessor.CodeTypes = {
CPP: 0,
SHARED_LIB: 1
};
// Otherwise, this is JS-related code. We are not adding it to
// codeTypes_ map because there can be zillions of them.
TickProcessor.CALL_PROFILE_CUTOFF_PCT = 1.0;
TickProcessor.CALL_GRAPH_SIZE = 5;
/**
* @override
*/
TickProcessor.prototype.printError = function(str) {
printErr(str);
};
TickProcessor.prototype.setCodeType = function(name, type) {
this.codeTypes_[name] = TickProcessor.CodeTypes[type];
};
TickProcessor.prototype.isSharedLibrary = function(name) {
return this.codeTypes_[name] == TickProcessor.CodeTypes.SHARED_LIB;
};
TickProcessor.prototype.isCppCode = function(name) {
return this.codeTypes_[name] == TickProcessor.CodeTypes.CPP;
};
TickProcessor.prototype.isJsCode = function(name) {
return name !== "UNKNOWN" && !(name in this.codeTypes_);
};
TickProcessor.prototype.processLogFile = function(fileName) {
this.lastLogFileName_ = fileName;
var line;
while (line = readline()) {
this.processLogLine(line);
}
};
TickProcessor.prototype.processLogFileInTest = function(fileName) {
// Hack file name to avoid dealing with platform specifics.
this.lastLogFileName_ = 'v8.log';
var contents = readFile(fileName);
this.processLogChunk(contents);
};
TickProcessor.prototype.processSharedLibrary = function(
name, startAddr, endAddr, aslrSlide) {
var entry = this.profile_.addLibrary(name, startAddr, endAddr, aslrSlide);
this.setCodeType(entry.getName(), 'SHARED_LIB');
var self = this;
var libFuncs = this.cppEntriesProvider_.parseVmSymbols(
name, startAddr, endAddr, aslrSlide, function(fName, fStart, fEnd) {
self.profile_.addStaticCode(fName, fStart, fEnd);
self.setCodeType(fName, 'CPP');
});
};
TickProcessor.prototype.processCodeCreation = function(
type, kind, timestamp, start, size, name, maybe_func) {
if (maybe_func.length) {
var funcAddr = parseInt(maybe_func[0]);
var state = parseState(maybe_func[1]);
this.profile_.addFuncCode(type, name, timestamp, start, size, funcAddr, state);
} else {
this.profile_.addCode(type, name, timestamp, start, size);
}
};
TickProcessor.prototype.processCodeDeopt = function(
timestamp, size, code, inliningId, scriptOffset, bailoutType,
sourcePositionText, deoptReasonText) {
this.profile_.deoptCode(timestamp, code, inliningId, scriptOffset,
bailoutType, sourcePositionText, deoptReasonText);
};
TickProcessor.prototype.processCodeMove = function(from, to) {
this.profile_.moveCode(from, to);
};
TickProcessor.prototype.processCodeDelete = function(start) {
this.profile_.deleteCode(start);
};
TickProcessor.prototype.processCodeSourceInfo = function(
start, script, startPos, endPos, sourcePositions, inliningPositions,
inlinedFunctions) {
this.profile_.addSourcePositions(start, script, startPos,
endPos, sourcePositions, inliningPositions, inlinedFunctions);
};
TickProcessor.prototype.processScriptSource = function(script, url, source) {
this.profile_.addScriptSource(script, url, source);
};
TickProcessor.prototype.processFunctionMove = function(from, to) {
this.profile_.moveFunc(from, to);
};
TickProcessor.prototype.includeTick = function(vmState) {
if (this.stateFilter_ !== null) {
return this.stateFilter_ == vmState;
} else if (this.runtimeTimerFilter_ !== null) {
return this.currentRuntimeTimer == this.runtimeTimerFilter_;
}
return true;
};
TickProcessor.prototype.processRuntimeTimerEvent = function(name) {
this.currentRuntimeTimer = name;
}
TickProcessor.prototype.processTick = function(pc,
ns_since_start,
is_external_callback,
tos_or_external_callback,
vmState,
stack) {
this.distortion += this.distortion_per_entry;
ns_since_start -= this.distortion;
if (ns_since_start < this.range_start || ns_since_start > this.range_end) {
return;
}
this.ticks_.total++;
if (vmState == TickProcessor.VmStates.GC) this.ticks_.gc++;
if (!this.includeTick(vmState)) {
this.ticks_.excluded++;
return;
}
if (is_external_callback) {
// Don't use PC when in external callback code, as it can point
// inside callback's code, and we will erroneously report
// that a callback calls itself. Instead we use tos_or_external_callback,
// as simply resetting PC will produce unaccounted ticks.
pc = tos_or_external_callback;
tos_or_external_callback = 0;
} else if (tos_or_external_callback) {
// Find out, if top of stack was pointing inside a JS function
// meaning that we have encountered a frameless invocation.
var funcEntry = this.profile_.findEntry(tos_or_external_callback);
if (!funcEntry || !funcEntry.isJSFunction || !funcEntry.isJSFunction()) {
tos_or_external_callback = 0;
}
}
this.profile_.recordTick(
ns_since_start, vmState,
this.processStack(pc, tos_or_external_callback, stack));
};
TickProcessor.prototype.advanceDistortion = function() {
this.distortion += this.distortion_per_entry;
}
TickProcessor.prototype.processHeapSampleBegin = function(space, state, ticks) {
if (space != 'Heap') return;
this.currentProducerProfile_ = new CallTree();
};
TickProcessor.prototype.processHeapSampleEnd = function(space, state) {
if (space != 'Heap' || !this.currentProducerProfile_) return;
print('Generation ' + this.generation_ + ':');
var tree = this.currentProducerProfile_;
tree.computeTotalWeights();
var producersView = this.viewBuilder_.buildView(tree);
// Sort by total time, desc, then by name, desc.
producersView.sort(function(rec1, rec2) {
return rec2.totalTime - rec1.totalTime ||
(rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); });
this.printHeavyProfile(producersView.head.children);
this.currentProducerProfile_ = null;
this.generation_++;
};
TickProcessor.prototype.printStatistics = function() {
if (this.preprocessJson) {
this.profile_.writeJson();
return;
}
print('Statistical profiling result from ' + this.lastLogFileName_ +
', (' + this.ticks_.total +
' ticks, ' + this.ticks_.unaccounted + ' unaccounted, ' +
this.ticks_.excluded + ' excluded).');
if (this.ticks_.total == 0) return;
var flatProfile = this.profile_.getFlatProfile();
var flatView = this.viewBuilder_.buildView(flatProfile);
// Sort by self time, desc, then by name, desc.
flatView.sort(function(rec1, rec2) {
return rec2.selfTime - rec1.selfTime ||
(rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); });
var totalTicks = this.ticks_.total;
if (this.ignoreUnknown_) {
totalTicks -= this.ticks_.unaccounted;
}
var printAllTicks = !this.onlySummary_;
// Count library ticks
var flatViewNodes = flatView.head.children;
var self = this;
var libraryTicks = 0;
if(printAllTicks) this.printHeader('Shared libraries');
this.printEntries(flatViewNodes, totalTicks, null,
function(name) { return self.isSharedLibrary(name); },
function(rec) { libraryTicks += rec.selfTime; }, printAllTicks);
var nonLibraryTicks = totalTicks - libraryTicks;
var jsTicks = 0;
if(printAllTicks) this.printHeader('JavaScript');
this.printEntries(flatViewNodes, totalTicks, nonLibraryTicks,
function(name) { return self.isJsCode(name); },
function(rec) { jsTicks += rec.selfTime; }, printAllTicks);
var cppTicks = 0;
if(printAllTicks) this.printHeader('C++');
this.printEntries(flatViewNodes, totalTicks, nonLibraryTicks,
function(name) { return self.isCppCode(name); },
function(rec) { cppTicks += rec.selfTime; }, printAllTicks);
this.printHeader('Summary');
this.printLine('JavaScript', jsTicks, totalTicks, nonLibraryTicks);
this.printLine('C++', cppTicks, totalTicks, nonLibraryTicks);
this.printLine('GC', this.ticks_.gc, totalTicks, nonLibraryTicks);
this.printLine('Shared libraries', libraryTicks, totalTicks, null);
if (!this.ignoreUnknown_ && this.ticks_.unaccounted > 0) {
this.printLine('Unaccounted', this.ticks_.unaccounted,
this.ticks_.total, null);
}
if(printAllTicks) {
print('\n [C++ entry points]:');
print(' ticks cpp total name');
var c_entry_functions = this.profile_.getCEntryProfile();
var total_c_entry = c_entry_functions[0].ticks;
for (var i = 1; i < c_entry_functions.length; i++) {
c = c_entry_functions[i];
this.printLine(c.name, c.ticks, total_c_entry, totalTicks);
}
this.printHeavyProfHeader();
var heavyProfile = this.profile_.getBottomUpProfile();
var heavyView = this.viewBuilder_.buildView(heavyProfile);
// To show the same percentages as in the flat profile.
heavyView.head.totalTime = totalTicks;
// Sort by total time, desc, then by name, desc.
heavyView.sort(function(rec1, rec2) {
return rec2.totalTime - rec1.totalTime ||
(rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); });
this.printHeavyProfile(heavyView.head.children);
}
};
function padLeft(s, len) {
s = s.toString();
if (s.length < len) {
var padLength = len - s.length;
if (!(padLength in padLeft)) {
padLeft[padLength] = new Array(padLength + 1).join(' ');
}
s = padLeft[padLength] + s;
}
return s;
};
TickProcessor.prototype.printHeader = function(headerTitle) {
print('\n [' + headerTitle + ']:');
print(' ticks total nonlib name');
};
TickProcessor.prototype.printLine = function(
entry, ticks, totalTicks, nonLibTicks) {
var pct = ticks * 100 / totalTicks;
var nonLibPct = nonLibTicks != null
? padLeft((ticks * 100 / nonLibTicks).toFixed(1), 5) + '% '
: ' ';
print(' ' + padLeft(ticks, 5) + ' ' +
padLeft(pct.toFixed(1), 5) + '% ' +
nonLibPct +
entry);
}
TickProcessor.prototype.printHeavyProfHeader = function() {
print('\n [Bottom up (heavy) profile]:');
print(' Note: percentage shows a share of a particular caller in the ' +
'total\n' +
' amount of its parent calls.');
print(' Callers occupying less than ' +
TickProcessor.CALL_PROFILE_CUTOFF_PCT.toFixed(1) +
'% are not shown.\n');
print(' ticks parent name');
};
TickProcessor.prototype.processProfile = function(
profile, filterP, func) {
for (var i = 0, n = profile.length; i < n; ++i) {
var rec = profile[i];
if (!filterP(rec.internalFuncName)) {
continue;
}
func(rec);
}
};
TickProcessor.prototype.getLineAndColumn = function(name) {
var re = /:([0-9]+):([0-9]+)$/;
var array = re.exec(name);
if (!array) {
return null;
}
return {line: array[1], column: array[2]};
}
TickProcessor.prototype.hasSourceMap = function() {
return this.sourceMap != null;
};
TickProcessor.prototype.formatFunctionName = function(funcName) {
if (!this.hasSourceMap()) {
return funcName;
}
var lc = this.getLineAndColumn(funcName);
if (lc == null) {
return funcName;
}
// in source maps lines and columns are zero based
var lineNumber = lc.line - 1;
var column = lc.column - 1;
var entry = this.sourceMap.findEntry(lineNumber, column);
var sourceFile = entry[2];
var sourceLine = entry[3] + 1;
var sourceColumn = entry[4] + 1;
return sourceFile + ':' + sourceLine + ':' + sourceColumn + ' -> ' + funcName;
};
TickProcessor.prototype.printEntries = function(
profile, totalTicks, nonLibTicks, filterP, callback, printAllTicks) {
var that = this;
this.processProfile(profile, filterP, function (rec) {
if (rec.selfTime == 0) return;
callback(rec);
var funcName = that.formatFunctionName(rec.internalFuncName);
if(printAllTicks) {
that.printLine(funcName, rec.selfTime, totalTicks, nonLibTicks);
}
});
};
TickProcessor.prototype.printHeavyProfile = function(profile, opt_indent) {
var self = this;
var indent = opt_indent || 0;
var indentStr = padLeft('', indent);
this.processProfile(profile, function() { return true; }, function (rec) {
// Cut off too infrequent callers.
if (rec.parentTotalPercent < TickProcessor.CALL_PROFILE_CUTOFF_PCT) return;
var funcName = self.formatFunctionName(rec.internalFuncName);
print(' ' + padLeft(rec.totalTime, 5) + ' ' +
padLeft(rec.parentTotalPercent.toFixed(1), 5) + '% ' +
indentStr + funcName);
// Limit backtrace depth.
if (indent < 2 * self.callGraphSize_) {
self.printHeavyProfile(rec.children, indent + 2);
}
// Delimit top-level functions.
if (indent == 0) {
print('');
}
});
};
function CppEntriesProvider() {
};
CppEntriesProvider.prototype.parseVmSymbols = function(
libName, libStart, libEnd, libASLRSlide, processorFunc) {
this.loadSymbols(libName);
var lastUnknownSize;
var lastAdded;
function inRange(funcInfo, start, end) {
return funcInfo.start >= start && funcInfo.end <= end;
}
function addEntry(funcInfo) {
// Several functions can be mapped onto the same address. To avoid
// creating zero-sized entries, skip such duplicates.
// Also double-check that function belongs to the library address space.
if (lastUnknownSize &&
lastUnknownSize.start < funcInfo.start) {
// Try to update lastUnknownSize based on new entries start position.
lastUnknownSize.end = funcInfo.start;
if ((!lastAdded || !inRange(lastUnknownSize, lastAdded.start,
lastAdded.end)) &&
inRange(lastUnknownSize, libStart, libEnd)) {
processorFunc(lastUnknownSize.name, lastUnknownSize.start,
lastUnknownSize.end);
lastAdded = lastUnknownSize;
}
}
lastUnknownSize = undefined;
if (funcInfo.end) {
// Skip duplicates that have the same start address as the last added.
if ((!lastAdded || lastAdded.start != funcInfo.start) &&
inRange(funcInfo, libStart, libEnd)) {
processorFunc(funcInfo.name, funcInfo.start, funcInfo.end);
lastAdded = funcInfo;
}
} else {
// If a funcInfo doesn't have an end, try to match it up with then next
// entry.
lastUnknownSize = funcInfo;
}
}
while (true) {
var funcInfo = this.parseNextLine();
if (funcInfo === null) {
continue;
} else if (funcInfo === false) {
break;
}
if (funcInfo.start < libStart - libASLRSlide &&
funcInfo.start < libEnd - libStart) {
funcInfo.start += libStart;
} else {
funcInfo.start += libASLRSlide;
}
if (funcInfo.size) {
funcInfo.end = funcInfo.start + funcInfo.size;
}
addEntry(funcInfo);
}
addEntry({name: '', start: libEnd});
};
CppEntriesProvider.prototype.loadSymbols = function(libName) {
};
CppEntriesProvider.prototype.parseNextLine = function() {
return false;
};
function UnixCppEntriesProvider(nmExec, targetRootFS, apkEmbeddedLibrary) {
this.symbols = [];
this.parsePos = 0;
this.nmExec = nmExec;
this.targetRootFS = targetRootFS;
this.apkEmbeddedLibrary = apkEmbeddedLibrary;
this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ([0-9a-fA-F]{8,16} )?[tTwW] (.*)$/;
};
inherits(UnixCppEntriesProvider, CppEntriesProvider);
UnixCppEntriesProvider.prototype.loadSymbols = function(libName) {
this.parsePos = 0;
if (this.apkEmbeddedLibrary && libName.endsWith('.apk')) {
libName = this.apkEmbeddedLibrary;
}
if (this.targetRootFS) {
libName = libName.substring(libName.lastIndexOf('/') + 1);
libName = this.targetRootFS + libName;
}
try {
this.symbols = [
os.system(this.nmExec, ['-C', '-n', '-S', libName], -1, -1),
os.system(this.nmExec, ['-C', '-n', '-S', '-D', libName], -1, -1)
];
} catch (e) {
// If the library cannot be found on this system let's not panic.
this.symbols = ['', ''];
}
};
UnixCppEntriesProvider.prototype.parseNextLine = function() {
if (this.symbols.length == 0) {
return false;
}
var lineEndPos = this.symbols[0].indexOf('\n', this.parsePos);
if (lineEndPos == -1) {
this.symbols.shift();
this.parsePos = 0;
return this.parseNextLine();
}
var line = this.symbols[0].substring(this.parsePos, lineEndPos);
this.parsePos = lineEndPos + 1;
var fields = line.match(this.FUNC_RE);
var funcInfo = null;
if (fields) {
funcInfo = { name: fields[3], start: parseInt(fields[1], 16) };
if (fields[2]) {
funcInfo.size = parseInt(fields[2], 16);
}
}
return funcInfo;
};
function MacCppEntriesProvider(nmExec, targetRootFS, apkEmbeddedLibrary) {
UnixCppEntriesProvider.call(this, nmExec, targetRootFS, apkEmbeddedLibrary);
// Note an empty group. It is required, as UnixCppEntriesProvider expects 3 groups.
this.FUNC_RE = /^([0-9a-fA-F]{8,16})() (.*)$/;
};
inherits(MacCppEntriesProvider, UnixCppEntriesProvider);
MacCppEntriesProvider.prototype.loadSymbols = function(libName) {
this.parsePos = 0;
libName = this.targetRootFS + libName;
// It seems that in OS X `nm` thinks that `-f` is a format option, not a
// "flat" display option flag.
try {
this.symbols = [os.system(this.nmExec, ['-n', libName], -1, -1), ''];
} catch (e) {
// If the library cannot be found on this system let's not panic.
this.symbols = '';
}
};
function WindowsCppEntriesProvider(_ignored_nmExec, targetRootFS,
_ignored_apkEmbeddedLibrary) {
this.targetRootFS = targetRootFS;
this.symbols = '';
this.parsePos = 0;
};
inherits(WindowsCppEntriesProvider, CppEntriesProvider);
WindowsCppEntriesProvider.FILENAME_RE = /^(.*)\.([^.]+)$/;
WindowsCppEntriesProvider.FUNC_RE =
/^\s+0001:[0-9a-fA-F]{8}\s+([_\?@$0-9a-zA-Z]+)\s+([0-9a-fA-F]{8}).*$/;
WindowsCppEntriesProvider.IMAGE_BASE_RE =
/^\s+0000:00000000\s+___ImageBase\s+([0-9a-fA-F]{8}).*$/;
// This is almost a constant on Windows.
WindowsCppEntriesProvider.EXE_IMAGE_BASE = 0x00400000;
WindowsCppEntriesProvider.prototype.loadSymbols = function(libName) {
libName = this.targetRootFS + libName;
var fileNameFields = libName.match(WindowsCppEntriesProvider.FILENAME_RE);
if (!fileNameFields) return;
var mapFileName = fileNameFields[1] + '.map';
this.moduleType_ = fileNameFields[2].toLowerCase();
try {
this.symbols = read(mapFileName);
} catch (e) {
// If .map file cannot be found let's not panic.
this.symbols = '';
}
};
WindowsCppEntriesProvider.prototype.parseNextLine = function() {
var lineEndPos = this.symbols.indexOf('\r\n', this.parsePos);
if (lineEndPos == -1) {
return false;
}
var line = this.symbols.substring(this.parsePos, lineEndPos);
this.parsePos = lineEndPos + 2;
// Image base entry is above all other symbols, so we can just
// terminate parsing.
var imageBaseFields = line.match(WindowsCppEntriesProvider.IMAGE_BASE_RE);
if (imageBaseFields) {
var imageBase = parseInt(imageBaseFields[1], 16);
if ((this.moduleType_ == 'exe') !=
(imageBase == WindowsCppEntriesProvider.EXE_IMAGE_BASE)) {
return false;
}
}
var fields = line.match(WindowsCppEntriesProvider.FUNC_RE);
return fields ?
{ name: this.unmangleName(fields[1]), start: parseInt(fields[2], 16) } :
null;
};
/**
* Performs very simple unmangling of C++ names.
*
* Does not handle arguments and template arguments. The mangled names have
* the form:
*
* ?LookupInDescriptor@JSObject@internal@v8@@...arguments info...
*/
WindowsCppEntriesProvider.prototype.unmangleName = function(name) {
// Empty or non-mangled name.
if (name.length < 1 || name.charAt(0) != '?') return name;
var nameEndPos = name.indexOf('@@');
var components = name.substring(1, nameEndPos).split('@');
components.reverse();
return components.join('::');
};
class ArgumentsProcessor extends BaseArgumentsProcessor {
getArgsDispatch() {
let dispatch = {
'-j': ['stateFilter', TickProcessor.VmStates.JS,
'Show only ticks from JS VM state'],
'-g': ['stateFilter', TickProcessor.VmStates.GC,
'Show only ticks from GC VM state'],
'-p': ['stateFilter', TickProcessor.VmStates.PARSER,
'Show only ticks from PARSER VM state'],
'-b': ['stateFilter', TickProcessor.VmStates.BYTECODE_COMPILER,
'Show only ticks from BYTECODE_COMPILER VM state'],
'-c': ['stateFilter', TickProcessor.VmStates.COMPILER,
'Show only ticks from COMPILER VM state'],
'-o': ['stateFilter', TickProcessor.VmStates.OTHER,
'Show only ticks from OTHER VM state'],
'-e': ['stateFilter', TickProcessor.VmStates.EXTERNAL,
'Show only ticks from EXTERNAL VM state'],
'--filter-runtime-timer': ['runtimeTimerFilter', null,
'Show only ticks matching the given runtime timer scope'],
'--call-graph-size': ['callGraphSize', TickProcessor.CALL_GRAPH_SIZE,
'Set the call graph size'],
'--ignore-unknown': ['ignoreUnknown', true,
'Exclude ticks of unknown code entries from processing'],
'--separate-ic': ['separateIc', parseBool,
'Separate IC entries'],
'--separate-bytecodes': ['separateBytecodes', parseBool,
'Separate Bytecode entries'],
'--separate-builtins': ['separateBuiltins', parseBool,
'Separate Builtin entries'],
'--separate-stubs': ['separateStubs', parseBool,
'Separate Stub entries'],
'--unix': ['platform', 'unix',
'Specify that we are running on *nix platform'],
'--windows': ['platform', 'windows',
'Specify that we are running on Windows platform'],
'--mac': ['platform', 'mac',
'Specify that we are running on Mac OS X platform'],
'--nm': ['nm', 'nm',
'Specify the \'nm\' executable to use (e.g. --nm=/my_dir/nm)'],
'--target': ['targetRootFS', '',
'Specify the target root directory for cross environment'],
'--apk-embedded-library': ['apkEmbeddedLibrary', '',
'Specify the path of the embedded library for Android traces'],
'--range': ['range', 'auto,auto',
'Specify the range limit as [start],[end]'],
'--distortion': ['distortion', 0,
'Specify the logging overhead in picoseconds'],
'--source-map': ['sourceMap', null,
'Specify the source map that should be used for output'],
'--timed-range': ['timedRange', true,
'Ignore ticks before first and after last Date.now() call'],
'--pairwise-timed-range': ['pairwiseTimedRange', true,
'Ignore ticks outside pairs of Date.now() calls'],
'--only-summary': ['onlySummary', true,
'Print only tick summary, exclude other information'],
'--preprocess': ['preprocessJson', true,
'Preprocess for consumption with web interface']
};
dispatch['--js'] = dispatch['-j'];
dispatch['--gc'] = dispatch['-g'];
dispatch['--compiler'] = dispatch['-c'];
dispatch['--other'] = dispatch['-o'];
dispatch['--external'] = dispatch['-e'];
dispatch['--ptr'] = dispatch['--pairwise-timed-range'];
return dispatch;
}
getDefaultResults() {
return {
logFileName: 'v8.log',
platform: 'unix',
stateFilter: null,
callGraphSize: 5,
ignoreUnknown: false,
separateIc: true,
separateBytecodes: false,
separateBuiltins: true,
separateStubs: true,
preprocessJson: null,
targetRootFS: '',
nm: 'nm',
range: 'auto,auto',
distortion: 0,
timedRange: false,
pairwiseTimedRange: false,
onlySummary: false,
runtimeTimerFilter: null,
};
}
}

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

@ -0,0 +1,13 @@
const { tscTask } = require('just-scripts');
const { logger, series, task } = require('just-task');
const tickprocessorGenerator = require('./tasks/tickprocessorGenerator');
task('sayhello', function() {
logger.error('intentional logger error');
// throw new Error('an intentionally thrown error');
});
task('ts', tscTask());
task('generate', tickprocessorGenerator);
task('build', series('ts', 'sayhello', 'generate'));

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

@ -1,6 +1,6 @@
{
"name": "flamegrill",
"version": "0.0.5",
"version": "0.0.6",
"main": "lib/flamegrill.js",
"module": "lib/flamegrill.js",
"license": "MIT",
@ -12,12 +12,13 @@
"url": "https://github.com/JasonGore/flamegrill"
},
"scripts": {
"build": "tsc",
"build": "just-scripts build",
"start": "tsc -w --preserveWatchOutput"
},
"dependencies": {
"concat-stream": "^2.0.0",
"flamebearer": "^1.1.3",
"n-readlines": "^1.0.0",
"puppeteer": "^1.13.0",
"yargs-parser": "^13.1.0"
},
@ -25,6 +26,7 @@
"@types/concat-stream": "^1.6.0",
"@types/puppeteer": "^1.12.4",
"@types/yargs-parser": "^13.0.0",
"just-scripts": "0.28.0",
"typescript": "^3.5.1"
}
}

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

@ -0,0 +1,332 @@
import path from 'path';
const { isFrameworkName, isSystemName } = require('../../assets/helpers');
// TODO: Improvements to Flamegraphs:
// - Add ability to filter out Framework and System calls.
// - Always show descendants regardless of width.
// - Always make descendants show hover info and clickable regardless of width.
// - Recalculate percentages when changing the top level function displayed.
// These types are modeled after the data output from the perf test, currently dictated by flamegraph implementation.
export interface PerfTestOutput {
numTicks: number;
names: string[];
levels: number[][];
};
export interface FunctionsMap {
[key: string]: FunctionData;
};
export interface FunctionsLevel {
[key: string]: FunctionInstance;
};
export interface FunctionData {
name: string;
displayName: string;
index: number;
instances: FunctionInstance[];
filtered?: boolean;
};
export interface FunctionInstance {
level: number;
name: string;
ticks: number;
ticksNormalized: number;
}
// TODO: get rid of name if not used
export interface FunctionRegression {
name: string;
displayName: string;
// level: number;
}
export interface ProcessedData {
functions: FunctionData[];
functionsMap: FunctionsMap;
// functionsByLevel: FunctionsLevel[];
numLevels: number;
numTicks: number;
}
export interface RegressionAnalysis {
summary: string;
isRegression: boolean;
}
/**
* Analyzes processed perf.
*
* @param {string} datafileBefore Perf data input.
*/
export function checkForRegressions(datafileBefore: string, datafileAfter: string): RegressionAnalysis {
let summary = '';
let isRegression = false;
const dataBefore = processFile(datafileBefore);
const dataAfter = processFile(datafileAfter);
// TODO: UNIT TESTS FOR EVERYONE!
// * function removed making other functions seem to be regressions when overall perf improves
// * combined with variance this may not be possible to detect.
// * output regression analysis showing detection of functions removed?
// * function present multiple times at same level but part of different call hierarchies
// * recursive cases (improvements and regressions)
// * system/framework regressions
// TODO: analysis. sort by samples, level, alphabetical? multiple?
// TODO: make analysis dynamic? (part of "summary page"). dynamic sliders for tick and diff thresholds.
// TODO: at least have options to see unfiltered results.
// TODO: show only diffs instead of all? also could be dynamic / selectable
// TODO: side-by-side flamegraphs?
// TODO: flamegraph server that regenerates on code change?
// TODO: need to account for similarities that only differ by level
// TODO: ignore or filter out functions with varying indices, like fabric_icons_16_initializeIcons
// TODO: Should calculate difference thresholds as a function of:
// Total samples: i.e. 26 total samples means one tick accounts for almost 4% variation
// Iterations: 1 tick in 5,000 iterations is meaningless
const regressionNewBase = 0.05; // required percentage to report new functions
const regressionDiffBase = 0.10; // required percentage difference after normalization
// TODO: Can't simply accumulate total ticks without double-counting ticks up the stack, particularly for recursive functions like extractRules.
// TODO: should be able to print out results with call hierarchy, not just level number
const newFunctions: FunctionRegression[] = [];
const regressions: FunctionRegression[] = [];
// Analyze data to find new functions and regressions.
Object.keys(dataAfter.functionsMap).forEach(name => {
// In some scenarios (such as native button), a tick can represent more time than our base thresholds.
// In these cases, elevate the base threshold to the tick base * 2.
// For example, if base threshold reports new functions at 2% time consumed, but 1 tick is 4% time,
// then new functions will only be reported when > 8%.
const minTicks = Math.min(dataBefore.numTicks, dataAfter.numTicks);
const regressionNew = Math.max(1 / minTicks * 2, regressionNewBase);
const regressionDiff = Math.max(1 / minTicks * 2, regressionDiffBase);
// TODO: make this part of a unit test
// if (regressionNew !== regressionNewBase || regressionDiff !== regressionDiffBase) {
// console.log(`Modified base thresholds: regressionNew = ${regressionNew}, regressionDiff = ${regressionDiff}`);
// }
const after = dataAfter.functionsMap[name];
const before = dataBefore.functionsMap[name];
// TODO: This analysis should really account for the entire call hierarchy when comparing rather than just averaging.
// For now just ignore spurious 1 tick instances by using the same number of instances for both.
const instances = before ? Math.min(before.instances.length, after.instances.length) : after.instances.length;
const afterTicksNormalized = calcTotalTicksNormalized(after.instances) / instances;
if (!before) {
if(afterTicksNormalized > regressionNew) {
newFunctions.push({ name, displayName: after.displayName });
}
} else {
const beforeTicksNormalized = calcTotalTicksNormalized(before.instances) / instances;
const percDiff = afterTicksNormalized - beforeTicksNormalized;
if (percDiff > regressionDiff) {
regressions.push({ name, displayName: after.displayName });
}
}
});
summary += `Results for ${datafileBefore} => ${datafileAfter}\n\n`;
summary += `numTicks: ${dataBefore.numTicks} => ${dataAfter.numTicks}\n\n`;
if (regressions.length === 0 && newFunctions.length === 0) {
console.log('OK!');
}
if (regressions.length > 0) {
isRegression = true;
summary += 'Potential Regressions: \n';
regressions.forEach(regression => {
const after = dataAfter.functionsMap[regression.displayName];
const before = dataBefore.functionsMap[regression.displayName];
// Averaging prevents overexaggeration of recurisve functions, but can also underexaggerate their impact.
// Averaging can also cause spurious (1 tick samples of obj.computed, what causes these?) of functions to underexaggerate impact.
// TODO: Remove averaging when analysis takes into account call hierarchy.
// For now just ignore spurious 1 tick instances by using the same number of instances for both.
const instances = before ? Math.min(before.instances.length, after.instances.length) : after.instances.length;
const afterTicksNormalized = calcTotalTicksNormalized(after.instances) / instances;
const beforeTicksNormalized = before && calcTotalTicksNormalized(before.instances) / instances;
const beforeTicksDisplay = beforeTicksNormalized ? (beforeTicksNormalized * 100).toFixed(0) : 'not present';
const afterTicksDisplay = afterTicksNormalized && (afterTicksNormalized * 100).toFixed(0);
summary += ` ${regression.displayName}` +
`, time consumed: ${beforeTicksDisplay}% => ` +
`${afterTicksDisplay}% \n`;
});
}
if (newFunctions.length > 0) {
isRegression = true;
summary += '\nNew Functions: \n';
newFunctions.forEach(newFunction => {
const ticksNormalized = calcTotalTicksNormalized(dataAfter.functionsMap[newFunction.displayName].instances);
summary += ` ${newFunction.displayName}, time consumed = ${(ticksNormalized * 100).toFixed(0)}%\n`;
});
}
return { summary, isRegression };
}
function processFile(datafile: string): ProcessedData {
// Levels:
// Index - level of flamegraph, from top down
// Data - [0, 1, 0, 0, 758, 5]
// Sets of 3:
// 0 - start sample to show horizontally where sample = 0 to numTicks
// 758 - number of samples
// 5 - index into names array
const { names, levels, numTicks } = require(datafile) as PerfTestOutput;
// TODO: if cleaning names, write checkDuplicateNames utility to make sure duplicates don't appear in filtered name set.
// TODO: also clean out ~
const cleanedNames = names.map(name => {
const matchedName = name.match(/^(.*) /)
return matchedName && matchedName[1] || name;
});
let functions: FunctionData[] = names.map((name, index) => ({
name,
displayName: cleanedNames[index],
index,
instances: [],
totalTicks: 0,
totalTicksNormalized: 0
}));
// Find functions in all of the levels and write them into the functions structure for analysis.
levels.forEach((level, levelIndex) => {
for (let i = 0; i < level.length; i += 3) {
let ticks = level[i + 1];
// TODO: account for missing functions in both directions when calculating normalized values.
// i.e. determine actual variance due to missing functions and use the new "total ticks" to normalize values.
// probably have to do this later, not here.
let ticksNormalized = ticks / numTicks;
let functionIndex = level[i + 2];
// levels refers to functions in the names (and by extension, funcions) array by index,
// making functionIndex a valid lookup on the functions array.
functions[functionIndex].instances.push({ ticks, ticksNormalized, level: levelIndex, name: names[functionIndex] });
}
});
functions = filterMinifiedNames(functions);
functions = filterSystemNames(functions);
// TODO: is there a need to keep sorting?
const sortKey = 'displayName';
functions.sort(function (a, b) {
if (a[sortKey] < b[sortKey]) { return -1; }
if (a[sortKey] > b[sortKey]) { return 1; }
return 0;
});
const functionsMap: FunctionsMap = {};
// const functionsByLevel: FunctionsLevel[] = [];
functions.forEach(entry => {
if (!entry.filtered) {
// Some functions like ~result will appear from multiple components at different line numbers, resulting in multiple entries in names
// like "~result :35:27" and "~result :77:27". Merge them for now and treat them as the same function.
// We have to use displayName as a key because line numbers can change before and after, which would appear as different functions.
if (functionsMap[entry.displayName]) {
functionsMap[entry.displayName].instances = functionsMap[entry.displayName].instances.concat(entry.instances);
} else {
functionsMap[entry.displayName] = entry;
}
// entry.instances.forEach(instance => {
// if (!functionsByLevel[instance.level]) {
// functionsByLevel[instance.level] = {};
// }
// // TODO: need to support multiple function instances per level
// if(functionsByLevel[instance.level][entry.name]) {
// console.error(`Function already exists in functionsByLevel: ${entry.name}`);
// }
// functionsByLevel[instance.level][entry.name] = instance;
// })
}
});
return { numLevels: levels.length, numTicks, functions, functionsMap };
}
function calcTotalTicks(levels: FunctionInstance[]): number {
return levels.map(el => el.ticks).reduce((acc, ticks) => acc + ticks);
}
function calcTotalTicksNormalized(levels: FunctionInstance[]): number {
return levels.map(el => el.ticksNormalized).reduce((acc, ticks) => acc + ticks);
}
function filterMinifiedNames(functions: FunctionData[]): FunctionData[] {
functions = functions.map(entry => {
entry.filtered = entry.filtered || isFrameworkName(entry.name);
return entry;
});
return functions;
}
function filterSystemNames(functions: FunctionData[]): FunctionData[] {
functions = functions.map(entry => {
entry.filtered = entry.filtered || isSystemName(entry.name);
return entry;
});
return functions;
}
if (require.main === module) {
// From directory with output:
// node ..\..\..\node_modules\flamegrill\lib\analysis\processData.js > ../analysis.txt
// const coreScenarios = [
// 'BaseButton',
// 'BaseButtonNew',
// 'button',
// 'DefaultButton',
// 'DefaultButtonNew',
// 'DetailsRow',
// 'DetailsRowNoStyles',
// 'DocumentCardTitle',
// 'MenuButton',
// 'MenuButtonNew',
// 'PrimaryButton',
// 'PrimaryButtonNew',
// 'SplitButton',
// 'SplitButtonNew',
// 'Stack',
// 'StackWithIntrinsicChildren',
// 'StackWithTextChildren',
// 'Text',
// 'Toggle',
// 'ToggleNew'
// ];
const coreScenarios = ['Toggle'];
const scenarios: string[] = [];
coreScenarios.forEach((scenario, index) => {
// Array.from({ length: 20 }, (entry, index) => {
// scenarios.push(scenario + index);
// });
scenarios.push(scenario);
});
scenarios.forEach(scenario => {
const analysis = checkForRegressions(path.join(process.cwd(), `${scenario}_ref.data.js`), path.join(process.cwd(), `${scenario}.data.js`));
console.log(JSON.stringify(analysis));
// processPerfData(path.join(process.cwd(), `${scenario}_ref.js`), path.join(process.cwd(), `${scenario}.js`));
});
}

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

@ -1,6 +1,6 @@
// TODO: use approach similar to jest.. config file with URL targets
// TODO: recheck and remove unnecessary depedencies
// TODO: recheck and remove unnecessary dependencies
// TODO: filters should be custom defined with names and regex... can't assume using minified React. get rid of helpers?
// Iterations:
// The way perf-test is set up now, iterations are baked into page under target
@ -8,11 +8,24 @@
// TODO: standardize URL interface for passing iteration count?
// TODO: leave it up to consumer?
// TODO: documentation should include
// * development vs. production considerations (React, app, etc.)
// * insight vs. additional overhead
// * webpack production builds that do not minify for additional visibility
// * approaches for making flamegrill output readable via URL config
// * Analyzing React perf and impact of component / app code on React perf (or other generic framework as defined by user)
// * Layout and Recalculate Style (browser perf hits that don't seem to be part of JS domain)
// * development vs. production considerations (React, app, etc.)
// * insight vs. additional overhead
// * webpack production builds that do not minify for additional visibility
// * build configs vs. source maps?
// * can users use full production builds but still get meaningful results using sourcemaps?
// https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Use_a_source_map
// https://stackoverflow.com/questions/30870830/how-do-i-generate-sourcemaps-when-using-babel-and-webpack
// * filters
// * iterations / rollup
// TODO: alternatives / improvements to flamegraphs:
// * https://github.com/jlfwong/speedscope
// TODO: possible bugs to fix:
// * https://github.com/mapbox/flamebearer/issues
// * https://github.com/mapbox/flamebearer/issues/8
// TODO: add support for both displayName and identifier?
import { CliOptions } from './CliOptions';
import flamegrill from './flamegrill';
@ -101,8 +114,8 @@ Options:
Examples:
$ flamegrill cook -n LocalHostTest -s http://localhost:4322
$ flamegrill cook -n LocalHostTest -s http://localhost:4322 -r http://some.url.com
$ flamegrill cook -n SplitButton -s "http://fabricweb.z5.web.core.windows.net/pr-deploy-site/refs/heads/master/perf-test/index.html?scenario=SplitButtonNew&iterations=5000"
$ flamegrill cook -n SplitButton -s "http://fabricweb.z5.web.core.windows.net/pr-deploy-site/refs/heads/master/perf-test/index.html?scenario=SplitButtonNew&iterations=5000" -r "http://fabricweb.z5.web.core.windows.net/pr-deploy-site/refs/heads/master/perf-test/index.html?scenario=SplitButton&iterations=5000"
$ flamegrill cook -n SplitButtonNew -s "http://fabricweb.z5.web.core.windows.net/pr-deploy-site/refs/heads/master/perf-test/index.html?scenario=SplitButtonNew&iterations=5000" -o out -t temp
`);
}

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

@ -1,7 +1,7 @@
import fs from 'fs';
const flamebearerModule = require('flamebearer');
function flamebearer(buf: Buffer) {
function flamebearer(buf: Buffer): string[] {
console.time('Parsed JSON in');
let json = {} as { code: {}, ticks: {} };
try {
@ -12,7 +12,7 @@ function flamebearer(buf: Buffer) {
if (!json.code || !json.ticks) {
console.log('Invalid input; expected a V8 log in JSON format. Produce one with:');
console.log('node --prof-process --preprocess -j isolate*.log');
return;
return [];
}
console.timeEnd('Parsed JSON in');
@ -30,6 +30,8 @@ function flamebearer(buf: Buffer) {
data += `levels = ${JSON.stringify(levels)};\n`;
data += `numTicks = ${stacks.length};\n`;
// This code constructs a flamegraph using HTML template and shared JS helper files.
// It uses split points in those files to keep and remove source, as needed.
const flamegraph = fs
.readFileSync(require.resolve('../../assets/index.html'), 'utf8')
.toString()
@ -38,15 +40,15 @@ function flamebearer(buf: Buffer) {
.split('/* BIN_SPLIT */')
.filter((str, i) => i % 2 === 0)
.join('')
// Remove exports that cause errors in generated index.html file.
.split('/* MODULE_EXPORT */')
.filter((str, i) => i % 2 === 0)
.join('')
.split('/* BIN_PLACEHOLDER */')
.join(data);
// data += 'module.exports = { names, levels, numTicks };';
// return [flamegraph, data];
return flamegraph;
data += 'module.exports = { names, levels, numTicks };';
return [flamegraph, data];
};
export default flamebearer;

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

@ -0,0 +1,104 @@
import fs from 'fs';
import path from 'path';
import cp from 'child_process';
import { Transform } from 'stream';
import concat from 'concat-stream';
import flamebearer from './flamebearer';
export interface GeneratedFiles {
dataFile?: string;
errorFile?: string;
flamegraphFile?: string;
}
const tickprocessor = require.resolve('../tickprocessor');
// TODO: can remove this if not using Node
function jsonCleanUp() {
return new Transform({
transform: (data, _, next) => {
if (data.indexOf('Testing v8 version different from logging version') > -1) {
next();
return;
}
next(null, data);
}
});
}
/**
* Takes in a v8 or node generated profile log and turn into flamegraph with flamebearer.
* Generates files based on scenarioName: flamegraph (.html), JS data (.data.js), and error output (.err)
*
* @param {string} logFile Log file path and base name input.
* @param {string} outFile File path and base name for file output. Extensions will be added for each type of file output.
* @returns {Promise<OutputFiles>} Promise resolving to generated files.
*/
// TODO: is file output good enough here as an API? should this return programmatic results?
export function generateFlamegraph(logFile: string, outFile: string): Promise<GeneratedFiles> {
// TODO: account for --windows, --unix and --mac arguments? what is the output difference with and without these args?
// TODO: use cli
const preprocessProc = cp.spawn(process.execPath, [tickprocessor, '--preprocess', '-j', logFile]);
const flamegraphFile = outFile + '.html'
const dataFile = outFile + '.data.js';
const errorFile = outFile + '.err';
// TODO: return early?
if (!fs.existsSync(path.dirname(outFile))) {
console.error(`Directory does not exist: ${path.dirname(outFile)}`);
}
// TODO: this just silently fails if the output directory doesn't exist
const flamebearerPromise = new Promise<GeneratedFiles>((resolve, reject) => {
const outStream = concat(preprocessed => {
if (preprocessed.length > 0) {
// This line writes intermediate streaming output for troubleshooting:
// fs.writeFileSync(flamegraphfile.split('.html').join('.preprocessed.log'), preprocessed);
const [flamegraph, data] = flamebearer(preprocessed);
fs.writeFileSync(flamegraphFile, flamegraph);
fs.writeFileSync(dataFile, data);
resolve({ dataFile, flamegraphFile });
}
});
const errStream = concat(err => {
if (err.length > 0) {
fs.writeFileSync(errorFile, err);
resolve({ errorFile });
}
});
preprocessProc.stderr.pipe(jsonCleanUp()).pipe(errStream);
preprocessProc.stdout.pipe(jsonCleanUp()).pipe(outStream);
});
return flamebearerPromise;
}
/**
* Takes in a v8 or node generated profile log and generate a summary text file (.summary.log)
*
* @param {string} logfile Log file input.
* @param {string} outfile File path and base name for file output. Extensions will be added for each type of file output.
* @returns {string} Summary file path.
*/
export function generateSummary(logfile: string, outfile: string): Promise<string> {
// TODO: account for --windows, --unix and --mac arguments? what is the output difference with and without these args?
// TODO: use cli
const summaryProc = cp.spawn(process.execPath, [tickprocessor, logfile]);
const summaryFile = outfile + '.summary.log';
const flamebearerPromise = new Promise<string>((resolve, reject) => {
const concatStream = concat(preprocessed => {
// TODO: this code just blindly assumes flamegraphfile has an html extension
fs.writeFileSync(summaryFile, preprocessed);
resolve(summaryFile);
});
summaryProc.stdout.pipe(jsonCleanUp()).pipe(concatStream);
});
return flamebearerPromise;
}

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

@ -1,47 +0,0 @@
import fs from 'fs';
import path from 'path';
import { spawn } from 'child_process';
import { Transform } from 'stream';
import concat from 'concat-stream';
import flamebearer from './flamebearer';
function jsonCleanUp() {
return new Transform({
transform: (data, _, next) => {
if (data.indexOf('Testing v8 version different from logging version') > -1) {
next();
return;
}
next(null, data);
}
});
}
/**
* Takes in a v8 or node generated profile log and turn into flamegraph with flamebearer
*
* @param {string} logfile Log file input.
* @param {string} outfile Flamegraph output.
*/
function generateFlamegraph(logfile: string, outfile: string) {
const preprocessProc = spawn(process.execPath, ['--prof-process', '--preprocess', '-j', logfile]);
if(!fs.existsSync(path.dirname(outfile))) {
console.error(`Directory does not exist: ${path.dirname(outfile)}`);
}
// TODO: this just silently fails if the output directory doesn't exist
const flamebearerPromise = new Promise((resolve, reject) => {
const concatStream = concat(preprocessed => {
const src = flamebearer(preprocessed);
fs.writeFileSync(outfile, src);
resolve();
});
preprocessProc.stdout.pipe(jsonCleanUp()).pipe(concatStream);
});
return flamebearerPromise;
}
export default generateFlamegraph;

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

@ -1,11 +1,14 @@
import fs from 'fs';
import path from 'path';
import puppeteer, { Browser } from 'puppeteer';
import generateFlamegraph from './flamegraph/generateFlamegraph';
import { generateFlamegraph, GeneratedFiles } from './flamegraph/generate';
import { checkForRegressions } from './analysis/processData';
// Chrome command for running similarly configured instance of Chrome as puppeteer is configured here:
// "C:\Program Files (x86)\Google\Chrome\Application\chrome" --no-sandbox --js-flags=" --logfile=C:\git\perf\output\chrome.log --prof --jitless --no-opt" --user-data-dir="C:\git\perf\user" http://localhost:4322
// TODO: overhaul types and reconcile with processData types
// TODO: get rid of !'s in this file
export type Scenario = {
name: string;
scenario: string;
@ -17,16 +20,35 @@ export type ScenarioConfig = {
tempDir?: string;
};
export interface ScenarioTest extends Scenario {
logfile: string;
outfile: string;
logfileReference?: string;
outfileReference: string;
export interface PerfTest {
logFile: string;
outFile: string;
reference: {
logFile?: string;
outFile: string;
}
};
export interface ScenarioAnalysis extends ScenarioTest {
numTicksReference?: number;
export interface PerfTests {
[key: string]: PerfTest;
};
export interface OutputFiles extends GeneratedFiles {
regressionFile?: string;
};
export interface Analysis {
numTicks?: number;
files?: OutputFiles;
isRegression?: boolean;
reference?: {
files?: OutputFiles;
numTicks?: number;
}
};
export interface Analyses {
[key: string]: Analysis;
};
export async function cook(scenarios: Scenario[], config: ScenarioConfig) {
@ -36,14 +58,16 @@ export async function cook(scenarios: Scenario[], config: ScenarioConfig) {
const outDir = config.outDir ? path.resolve(config.outDir) : process.cwd();
const tempDir = config.tempDir ? path.resolve(config.tempDir) : process.cwd();
const logFilePath = path.join(tempDir, '/puppeteer.log');
console.log(`logFilePath: ${logFilePath}`);
const logFile = path.join(tempDir, '/puppeteer.log');
console.log(`logFile: ${logFile}`);
const browser = await puppeteer.launch({
headless: true,
args: [
'--flag-switches-begin',
'--no-sandbox',
'--js-flags=--logfile=' + logFilePath + ' --prof --jitless --no-opt ' + extraV8Flags,
'--js-flags=--logfile=' + logFile + ' --prof --jitless --no-opt ' + extraV8Flags,
'--flag-switches-end'
]
});
@ -56,43 +80,57 @@ export async function cook(scenarios: Scenario[], config: ScenarioConfig) {
// TODO: Need to decide whether it's ok to run tests in parallel. Variance from results seems to indicate
// not, but then again other things outside of our control will also affect CPU load and results.
// Run tests sequentially for now, at least as a chance of getting more consistent results when run locally.
const testResults: ScenarioTest[] = [];
const perfTests: PerfTests = {};
for (const scenario of scenarios) {
let logfile = await runPerfTest(browser, scenario.scenario, scenario.name, tempDir);
let logfileReference;
let logFile = await runPerfTest(browser, scenario.scenario, scenario.name, tempDir);
let logFileRef;
if (scenario.reference) {
logfileReference = await runPerfTest(browser, scenario.reference, scenario.name, tempDir);
logFileRef = await runPerfTest(browser, scenario.reference, scenario.name, tempDir);
}
let outfileReference = path.join(outDir, `${scenario.name}_ref.html`);
let outfile = path.join(outDir, `${scenario.name}.html`);
let outFileRef = path.join(outDir, `${scenario.name}_ref`);
let outFile = path.join(outDir, `${scenario.name}`);
testResults.push({
...scenario,
logfileReference,
outfileReference,
logfile,
outfile
});
perfTests[scenario.name] = {
logFile,
outFile,
reference: {
logFile: logFileRef,
outFile: outFileRef
}
};
}
console.log('testResults: ' + JSON.stringify(testResults));
console.log('perfTests: ' + JSON.stringify(perfTests));
// Clean up
await browser.close();
const analyses: Analyses = {}
// Serialize a bunch of async generation of flamegraphs
for (const result of testResults) {
await generateFlamegraph(result.logfile, result.outfile);
if(result.logfileReference) {
await generateFlamegraph(result.logfileReference, result.outfileReference);
// TODO: need an API story here. how will users get output? data structures? files? both?
for (const scenario of scenarios) {
const result = perfTests[scenario.name];
const analysis: Analysis = {};
analysis.files = await generateFlamegraph(result.logFile, result.outFile);
// await generateSummary(result.logFile, result.outFile);
if(result.reference.logFile) {
analysis.reference = {};
analysis.reference.files = await generateFlamegraph(result.reference.logFile, result.reference.outFile);
analysis.files.regressionFile = path.join(outDir, `${scenario.name}.regression.txt`);
// await generateSummary(result.logFileRef, result.outFileRef);
}
processResults(analysis);
analyses[scenario.name] = analysis;
}
const scenarioAnalysis = processResults(testResults);
console.log('analyses: ' + JSON.stringify(analyses));
return scenarioAnalysis;
return analyses;
};
/**
@ -100,10 +138,11 @@ export async function cook(scenarios: Scenario[], config: ScenarioConfig) {
* @param {*} browser Launched puppeteer instance.
* @param {string} testUrl Base URL supporting 'scenario' and 'iterations' query parameters.
* @param {string} scenarioName Name of scenario that will be used with baseUrl.
* @param {string} logPath Absolute path to output log profiles.
* @param {string} logDir Absolute path to output log profiles.
* @returns {string} Log file path associated with test.
*/
async function runPerfTest(browser: Browser, testUrl: string, scenarioName: string, logPath: string) {
const logFilesBefore = fs.readdirSync(logPath);
async function runPerfTest(browser: Browser, testUrl: string, scenarioName: string, logDir: string): Promise<string> {
const logFilesBefore = fs.readdirSync(logDir);
const page = await browser.newPage();
@ -112,7 +151,7 @@ async function runPerfTest(browser: Browser, testUrl: string, scenarioName: stri
// TODO: argument? should probably default to 30 seconds
page.setDefaultTimeout(0);
const logFilesAfter = fs.readdirSync(logPath);
const logFilesAfter = fs.readdirSync(logDir);
const testLogFile = arr_diff(logFilesBefore, logFilesAfter);
@ -134,30 +173,33 @@ async function runPerfTest(browser: Browser, testUrl: string, scenarioName: stri
await page.close();
return path.join(logPath, testLogFile[0]);
return path.join(logDir, testLogFile[0]);
}
/**
* Create test summary based on test results.
*/
function processResults(scenarioConfigs: ScenarioTest[]): ScenarioAnalysis[] {
const scenarioResults: ScenarioAnalysis[] = [];
function processResults(analysis: Analysis): Analysis {
const dataFileAfter = analysis.files && analysis.files.dataFile;
const dataFileBefore = analysis.reference && analysis.reference.files && analysis.reference.files.dataFile;
scenarioConfigs.forEach(scenarioConfig => {
let numTicksReference;
if (scenarioConfig.reference) {
numTicksReference = getTicks(scenarioConfig.outfileReference);
}
let numTicks = getTicks(scenarioConfig.outfile);
let scenarioResult: ScenarioAnalysis = {
...scenarioConfig,
numTicksReference,
numTicks
}
scenarioResults.push(scenarioResult);
});
if (dataFileAfter) {
analysis.numTicks = getTicks(dataFileAfter);
}
if (dataFileBefore) {
analysis.reference!.numTicks = getTicks(dataFileBefore);
return scenarioResults;
if (dataFileAfter) {
const regressionAnalysis = checkForRegressions(dataFileBefore, dataFileAfter);
analysis.isRegression = regressionAnalysis.isRegression;
if (regressionAnalysis.isRegression) {
console.log(`regressionFile: ${JSON.stringify(analysis)}`);
fs.writeFileSync(analysis.files!.regressionFile!, regressionAnalysis.summary);
}
}
}
return analysis;
}
/**

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

@ -0,0 +1,81 @@
const fs = require('fs');
const path = require('path');
// V8's tick processor uses 32-bit rotation which breaks resolution of Fabric functions resulting in excessive "unknowns".
// More details here: https://bugs.chromium.org/p/v8/issues/detail?id=9578
// This file generates a patched V8 processor file which is used by flamegrill to eliminate the "unknowns".
// In V8's repo, these files are "merged" together in V8's tick process batch files and passed to the D8 executable.
// This file approximates the behavior by importing all of the files together and creating an entry point for Node.
// Other Fix Considerations
// V8 fix would have to be absorbed by Node before node --prof-process can be used.
// Node's absorption of V8 changes seems to be a couple of months behind:
// https://github.com/nodejs/node/tree/master/deps/v8/tools vs. https://github.com/v8/v8/releases
// Even if a fix could be used from node, can we use newer versions of node?
// Monkey Patch Node:
// Is there any way to pass a tick processor implementation to Node?
// Absorb via puppeteer:
// Is D8 available in puppeteer? Or any method for processing log?
// If D8 is used to profile and process logs, can tick processor impl / process flags be passed to v8 via chrome?
// Is there any way to "monkey patch" the Node implementation without duplicating code?
// Ways to overload ">>>", patch functions, use a higher order parser, etc.
// https://github.com/nodejs/node/blob/master/lib/internal/v8_prof_processor.js
// https://github.com/nodejs/node/blob/master/lib/internal/main/prof_process.js
// https://github.com/nodejs/node/blob/master/lib/internal/v8_prof_polyfill.js
// TODO: account for --windows, --unix and --mac arguments? what is the output difference with and without these args?
// TODO: pin version of puppeteer and version of V8 tick processor together.
// TODO: compare version of V8 in puppeteer against V8 tick processor source somehow?
// TODO: does it make sense to convert repo to just monorepo config?
// TODO: Once it's working, push simple example script to just-stack-monorepo (and maybe lerna-template)
// https://github.com/kenotron/lerna-template
// https://github.com/kenotron/single-template
const scriptNames = fs.readdirSync(path.join(__dirname, '../assets/v8'));
// Entry point script must come after all other scripts.
scriptNames.push(scriptNames.splice(scriptNames.indexOf('tickprocessor-driver.js'), 1)[0]);
// The V8 tick processor script expects certain functions to be made available by d8.
// In order to easily adopt new versions of tick processor code and use it unmodified, we provide these
// functions here:
// readline: blocking function which returns log file info line by line.
// print: general purpose logging function.
// printErr: error logging function.
// quit: quit with error.
// write: write output.
// Additionally, the patched V8 processor uses these definitions:
// enableFix: enables tick processing fixes made to original V8 code.
// printDebug: debug print utility.
let scriptHeader = '';
scriptHeader += 'arguments = process.argv;\n';
scriptHeader += 'enableFix = true;\n';
scriptHeader += 'print = console.log;\n';
scriptHeader += 'printDebug = () => {};\n';
scriptHeader += 'printErr = () => {};\n\n';
scriptHeader += 'treesPrinted = false;\n';
scriptHeader += 'write = print;\n';
scriptHeader += `
const lineByLine = require('n-readlines');
const liner = new lineByLine(arguments[arguments.length - 1]);
function readline() {
const line = liner.next();
if (line) {
return line.toString('ascii');
} else {
return false;
}
}
`;
const scripts = scriptNames.map(scriptName => {
return fs.readFileSync(require.resolve(`../assets/v8/${scriptName}`), 'utf8');
});
module.exports = function generateTickProcessor() {
// TODO: way to pass output argument from just task?
fs.writeFileSync('./lib/tickprocessor.js', scriptHeader.concat(scripts.join("")));
}

487
yarn.lock
Просмотреть файл

@ -752,6 +752,11 @@
npmlog "^4.1.2"
write-file-atomic "^2.3.0"
"@microsoft/package-deps-hash@^2.2.153":
version "2.2.169"
resolved "https://registry.yarnpkg.com/@microsoft/package-deps-hash/-/package-deps-hash-2.2.169.tgz#60e214b2adb3cd926b6da38ec641d0c6e8ec6a39"
integrity sha512-ZW3mBD7snYwOb0prrq7EedGwa/caYMAG1r6rGg7Vf9RCV6KVQ/anEea0K3yYv8b0LlLm33WpuDSKKNWKBBKYwA==
"@mrmlnc/readdir-enhanced@^2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
@ -851,6 +856,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.6.8.tgz#e469b4bf9d1c9832aee4907ba8a051494357c12c"
integrity sha512-aX+gFgA5GHcDi89KG5keey2zf0WfZk/HAQotEamsK2kbey+8yGKcson0hbK8E+v0NArlCJQCqMP161YhV6ZXLg==
"@types/node@^10.12.18":
version "10.14.15"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.15.tgz#e8f7729b631be1b02ae130ff0b61f3e018000640"
integrity sha512-CBR5avlLcu0YCILJiDIXeU2pTw7UK/NIxfC63m7d7CVamho1qDEzXKkOtEauQRPMy6MI8mLozth+JJkas7HY6g==
"@types/puppeteer@^1.12.4":
version "1.12.4"
resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-1.12.4.tgz#8388efdb0b30a54a7e7c4831ca0d709191d77ff1"
@ -916,7 +926,7 @@ ajv@^6.5.5:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ansi-escapes@^3.2.0:
ansi-escapes@^3.1.0, ansi-escapes@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
@ -943,6 +953,11 @@ ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
ansicolors@~0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979"
integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=
any-promise@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
@ -978,11 +993,25 @@ arr-diff@^4.0.0:
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=
arr-flatten@^1.1.0:
arr-filter@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/arr-filter/-/arr-filter-1.1.2.tgz#43fdddd091e8ef11aa4c45d9cdc18e2dff1711ee"
integrity sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=
dependencies:
make-iterator "^1.0.0"
arr-flatten@^1.0.1, arr-flatten@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==
arr-map@^2.0.0, arr-map@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/arr-map/-/arr-map-2.0.2.tgz#3a77345ffc1cf35e2a91825601f9e58f2e24cac4"
integrity sha1-Onc0X/wc814qkYJWAfnljy4kysQ=
dependencies:
make-iterator "^1.0.0"
arr-union@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
@ -993,6 +1022,11 @@ array-differ@^2.0.3:
resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-2.1.0.tgz#4b9c1c3f14b906757082925769e8ab904f4801b1"
integrity sha512-KbUpJgx909ZscOc/7CLATBFam7P1Z1QRQInvgT0UztM9Q72aGKCunKASAl7WNW0tnPmPyEMeMhdsfWhfmW037w==
array-each@^1.0.0, array-each@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f"
integrity sha1-p5SvDAWrF1KEbudTofIRoFugxE8=
array-find-index@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
@ -1003,6 +1037,26 @@ array-ify@^1.0.0:
resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece"
integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=
array-initial@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/array-initial/-/array-initial-1.1.0.tgz#2fa74b26739371c3947bd7a7adc73be334b3d795"
integrity sha1-L6dLJnOTccOUe9enrcc74zSz15U=
dependencies:
array-slice "^1.0.0"
is-number "^4.0.0"
array-last@^1.1.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/array-last/-/array-last-1.3.0.tgz#7aa77073fec565ddab2493f5f88185f404a9d336"
integrity sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==
dependencies:
is-number "^4.0.0"
array-slice@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4"
integrity sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==
array-union@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
@ -1047,11 +1101,28 @@ assign-symbols@^1.0.0:
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
async-done@^1.2.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/async-done/-/async-done-1.3.2.tgz#5e15aa729962a4b07414f528a88cdf18e0b290a2"
integrity sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==
dependencies:
end-of-stream "^1.1.0"
once "^1.3.2"
process-nextick-args "^2.0.0"
stream-exhaust "^1.0.1"
async-limiter@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==
async-settle@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/async-settle/-/async-settle-1.0.0.tgz#1d0a914bb02575bec8a8f3a74e5080f72b2c0c6b"
integrity sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=
dependencies:
async-done "^1.2.2"
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@ -1077,6 +1148,21 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
bach@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/bach/-/bach-1.2.0.tgz#4b3ce96bf27134f79a1b414a51c14e34c3bd9880"
integrity sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=
dependencies:
arr-filter "^1.1.1"
arr-flatten "^1.0.1"
arr-map "^2.0.0"
array-each "^1.0.0"
array-initial "^1.0.0"
array-last "^1.1.1"
async-done "^1.2.2"
async-settle "^1.0.0"
now-and-later "^2.0.0"
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
@ -1161,6 +1247,26 @@ byte-size@^5.0.1:
resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-5.0.1.tgz#4b651039a5ecd96767e71a3d7ed380e48bed4191"
integrity sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw==
cacache@^11.3.3:
version "11.3.3"
resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.3.tgz#8bd29df8c6a718a6ebd2d010da4d7972ae3bbadc"
integrity sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==
dependencies:
bluebird "^3.5.5"
chownr "^1.1.1"
figgy-pudding "^3.5.1"
glob "^7.1.4"
graceful-fs "^4.1.15"
lru-cache "^5.1.1"
mississippi "^3.0.0"
mkdirp "^0.5.1"
move-concurrently "^1.0.1"
promise-inflight "^1.0.1"
rimraf "^2.6.3"
ssri "^6.0.1"
unique-filename "^1.1.1"
y18n "^4.0.0"
cacache@^12.0.0:
version "12.0.2"
resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.2.tgz#8db03205e36089a3df6954c66ce92541441ac46c"
@ -1253,12 +1359,20 @@ camelcase@^5.0.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
cardinal@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-2.1.1.tgz#7cc1055d822d212954d07b085dea251cc7bc5505"
integrity sha1-fMEFXYItISlU0HsIXeolHMe8VQU=
dependencies:
ansicolors "~0.3.2"
redeyed "~2.1.0"
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
chalk@^2.3.1, chalk@^2.4.2:
chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@ -1299,6 +1413,13 @@ cli-cursor@^2.1.0:
dependencies:
restore-cursor "^2.0.0"
cli-table@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23"
integrity sha1-9TsFJmqLGguTSz0IIebi3FkUriM=
dependencies:
colors "1.0.3"
cli-width@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
@ -1323,6 +1444,15 @@ code-point-at@^1.0.0:
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
collection-map@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/collection-map/-/collection-map-1.0.0.tgz#aea0f06f8d26c780c2b75494385544b2255af18c"
integrity sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=
dependencies:
arr-map "^2.0.2"
for-own "^1.0.0"
make-iterator "^1.0.0"
collection-visit@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
@ -1343,6 +1473,11 @@ color-name@1.1.3:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
colors@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=
columnify@^1.5.4:
version "1.5.4"
resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb"
@ -1552,6 +1687,14 @@ cyclist@~0.2.2:
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=
d@1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==
dependencies:
es5-ext "^0.10.50"
type "^1.0.1"
dargs@^4.0.1:
version "4.1.0"
resolved "https://registry.yarnpkg.com/dargs/-/dargs-4.1.0.tgz#03a9dbb4b5c2f139bf14ae53f0b8a2a6a86f4e17"
@ -1632,6 +1775,11 @@ deepmerge@4.0.0:
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.0.0.tgz#3e3110ca29205f120d7cb064960a39c3d2087c09"
integrity sha512-YZ1rOP5+kHor4hMAH+HRQnBQHg+wvS1un1hAOuIcxcBy0hzcUf6Jg2a1w65kpoOUnurOfZbERwjI1TfZxNjcww==
default-resolution@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/default-resolution/-/default-resolution-2.0.0.tgz#bcb82baa72ad79b426a76732f1a81ad6df26d684"
integrity sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=
defaults@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d"
@ -1696,6 +1844,11 @@ dezalgo@^1.0.0:
asap "^2.0.0"
wrappy "1"
diff-match-patch@1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.4.tgz#6ac4b55237463761c4daf0dc603eb869124744b1"
integrity sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg==
dir-glob@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4"
@ -1792,6 +1945,24 @@ es-to-primitive@^1.2.0:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@~0.10.14:
version "0.10.50"
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.50.tgz#6d0e23a0abdb27018e5ac4fd09b412bc5517a778"
integrity sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==
dependencies:
es6-iterator "~2.0.3"
es6-symbol "~3.1.1"
next-tick "^1.0.0"
es6-iterator@^2.0.3, es6-iterator@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c=
dependencies:
d "1"
es5-ext "^0.10.35"
es6-symbol "^3.1.1"
es6-promise@^4.0.3:
version "4.2.8"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
@ -1804,12 +1975,30 @@ es6-promisify@^5.0.0:
dependencies:
es6-promise "^4.0.3"
es6-symbol@^3.1.1, es6-symbol@~3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77"
integrity sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=
dependencies:
d "1"
es5-ext "~0.10.14"
es6-weak-map@^2.0.1:
version "2.0.3"
resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53"
integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==
dependencies:
d "1"
es5-ext "^0.10.46"
es6-iterator "^2.0.3"
es6-symbol "^3.1.1"
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
esprima@^4.0.0:
esprima@^4.0.0, esprima@~4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
@ -1997,11 +2186,18 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
for-in@^1.0.2:
for-in@^1.0.1, for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=
for-own@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b"
integrity sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=
dependencies:
for-in "^1.0.1"
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
@ -2031,6 +2227,15 @@ from2@^2.1.0:
inherits "^2.0.1"
readable-stream "^2.0.0"
fs-extra@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==
dependencies:
graceful-fs "^4.1.2"
jsonfile "^4.0.0"
universalify "^0.1.0"
fs-extra@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
@ -2231,7 +2436,7 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b"
integrity sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==
handlebars@^4.1.0:
handlebars@^4.0.12, handlebars@^4.1.0:
version "4.1.2"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67"
integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==
@ -2255,6 +2460,11 @@ har-validator@~5.1.0:
ajv "^6.5.5"
har-schema "^2.0.0"
has-flag@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
integrity sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
@ -2608,6 +2818,11 @@ is-number@^3.0.0:
dependencies:
kind-of "^3.0.2"
is-number@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff"
integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==
is-obj@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
@ -2722,6 +2937,11 @@ isstream@~0.1.2:
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
jju@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a"
integrity sha1-o6vicYryQaKykE+EpiWXDzia4yo=
js-yaml@^3.13.1:
version "3.13.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
@ -2777,6 +2997,61 @@ jsprim@^1.2.2:
json-schema "0.2.3"
verror "1.10.0"
"just-scripts-utils@>=0.8.3 <1.0.0":
version "0.8.3"
resolved "https://registry.yarnpkg.com/just-scripts-utils/-/just-scripts-utils-0.8.3.tgz#9af85102b110976c983476a1d4481205c63861ce"
integrity sha512-J8HhkNUdMJtpwT4+Grp+z3Cf1rVJNXCg6SENZEtDyDcLD3pleg6W9V3bZT+oJ7WC6UR/g3XkPg+l3nBx+eABzg==
dependencies:
fs-extra "^7.0.1"
glob "^7.1.3"
handlebars "^4.0.12"
jju "^1.4.0"
just-task-logger ">=0.3.0 <1.0.0"
marked "^0.6.0"
marked-terminal "^3.2.0"
semver "^5.6.0"
tar "^4.4.8"
yargs "^12.0.5"
just-scripts@0.28.0:
version "0.28.0"
resolved "https://registry.yarnpkg.com/just-scripts/-/just-scripts-0.28.0.tgz#26075296e30dd27983be57c50175c5f24456bcd5"
integrity sha512-1NFAim0bffBDUW7j6wddhgQwqtKQyvb5kATOdQmzhk9GiyAWKId3gFm5mIfhA5twVsGtBUPosSRBZmXrdfIfZw==
dependencies:
"@types/node" "^10.12.18"
chalk "^2.4.1"
diff-match-patch "1.0.4"
fs-extra "^7.0.1"
glob "^7.1.3"
just-scripts-utils ">=0.8.3 <1.0.0"
just-task ">=0.13.1 <1.0.0"
npm-registry-fetch "^3.9.0"
prompts "^2.0.1"
run-parallel-limit "^1.0.5"
webpack-merge "^4.2.1"
"just-task-logger@>=0.3.0 <1.0.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/just-task-logger/-/just-task-logger-0.3.0.tgz#48f33a928a7acd6cf2d769bc00dc9cde4c1d871b"
integrity sha512-IA5M26L9Qv2aD5V+Wjkd5ILUTerr00RHKNxAg22JZRRHF89KeUVJ6LYNUaYBnxH7cGXlC37OftPO/eYwgqCpaQ==
dependencies:
chalk "^2.4.1"
yargs "^12.0.5"
"just-task@>=0.13.1 <1.0.0":
version "0.13.1"
resolved "https://registry.yarnpkg.com/just-task/-/just-task-0.13.1.tgz#18fee82364d4a29d1e3c09863ce2f1efa053436f"
integrity sha512-i/mYabRo01haIUzUZH1sh9Znbzf7fu04d8p6YzLV3v2TySaDLAJRZVE1QJBqBxV08UHTJZ67awzVPuoQk8Wvew==
dependencies:
"@microsoft/package-deps-hash" "^2.2.153"
chalk "^2.4.1"
fs-extra "^7.0.1"
just-task-logger ">=0.3.0 <1.0.0"
resolve "^1.8.1"
undertaker "^1.2.0"
undertaker-registry "^1.0.1"
yargs "^12.0.5"
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
version "3.2.2"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
@ -2801,6 +3076,19 @@ kind-of@^6.0.0, kind-of@^6.0.2:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==
kleur@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
last-run@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/last-run/-/last-run-1.1.1.tgz#45b96942c17b1c79c772198259ba943bebf8ca5b"
integrity sha1-RblpQsF7HHnHchmCWbqUO+v4yls=
dependencies:
default-resolution "^2.0.0"
es6-weak-map "^2.0.1"
lcid@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf"
@ -2924,12 +3212,17 @@ lodash.templatesettings@^4.0.0:
dependencies:
lodash._reinterpolate "^3.0.0"
lodash.toarray@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561"
integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE=
lodash.uniq@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
lodash@^4.17.12, lodash@^4.17.14, lodash@^4.2.1:
lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.5, lodash@^4.2.1:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
@ -2969,6 +3262,23 @@ make-dir@^2.1.0:
pify "^4.0.1"
semver "^5.6.0"
make-fetch-happen@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-4.0.2.tgz#2d156b11696fb32bffbafe1ac1bc085dd6c78a79"
integrity sha512-YMJrAjHSb/BordlsDEcVcPyTbiJKkzqMf48N8dAJZT9Zjctrkb6Yg4TY9Sq2AwSIQJFn5qBBKVTYt3vP5FMIHA==
dependencies:
agentkeepalive "^3.4.1"
cacache "^11.3.3"
http-cache-semantics "^3.8.1"
http-proxy-agent "^2.1.0"
https-proxy-agent "^2.2.1"
lru-cache "^5.1.1"
mississippi "^3.0.0"
node-fetch-npm "^2.0.2"
promise-retry "^1.1.1"
socks-proxy-agent "^4.0.0"
ssri "^6.0.0"
make-fetch-happen@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-5.0.0.tgz#a8e3fe41d3415dd656fe7b8e8172e1fb4458b38d"
@ -2986,6 +3296,13 @@ make-fetch-happen@^5.0.0:
socks-proxy-agent "^4.0.0"
ssri "^6.0.0"
make-iterator@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6"
integrity sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==
dependencies:
kind-of "^6.0.2"
map-age-cleaner@^0.1.1:
version "0.1.3"
resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a"
@ -3015,6 +3332,23 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
marked-terminal@^3.2.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/marked-terminal/-/marked-terminal-3.3.0.tgz#25ce0c0299285998c7636beaefc87055341ba1bd"
integrity sha512-+IUQJ5VlZoAFsM5MHNT7g3RHSkA3eETqhRCdXv4niUMAKHQ7lb1yvAcuGPmm4soxhmtX13u4Li6ZToXtvSEH+A==
dependencies:
ansi-escapes "^3.1.0"
cardinal "^2.1.1"
chalk "^2.4.1"
cli-table "^0.3.1"
node-emoji "^1.4.1"
supports-hyperlinks "^1.0.1"
marked@^0.6.0:
version "0.6.3"
resolved "https://registry.yarnpkg.com/marked/-/marked-0.6.3.tgz#79babad78af638ba4d522a9e715cdfdd2429e946"
integrity sha512-Fqa7eq+UaxfMriqzYLayfqAE40WN03jf+zHjT18/uXNuzjq3TY0XTbrAoPeqSJrAmPz11VuUA+kBPYOhHt9oOQ==
mem@^4.0.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178"
@ -3245,6 +3579,11 @@ mz@^2.5.0:
object-assign "^4.0.1"
thenify-all "^1.0.0"
n-readlines@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/n-readlines/-/n-readlines-1.0.0.tgz#c353797f216c253fdfef7e91da4e8b17c29a91a6"
integrity sha512-ISDqGcspVu6U3VKqtJZG1uR55SmNNF9uK0EMq1IvNVVZOui6MW6VR0+pIZhqz85ORAGp+4zW+5fJ/SE7bwEibA==
nanomatch@^1.2.9:
version "1.2.13"
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
@ -3272,11 +3611,23 @@ neo-async@^2.6.0:
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
next-tick@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
nice-try@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
node-emoji@^1.4.1:
version "1.10.0"
resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.10.0.tgz#8886abd25d9c7bb61802a658523d1f8d2a89b2da"
integrity sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==
dependencies:
lodash.toarray "^4.4.0"
node-fetch-npm@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz#7258c9046182dca345b4208eda918daf33697ff7"
@ -3330,6 +3681,13 @@ normalize-url@^3.3.0:
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559"
integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==
now-and-later@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.1.tgz#8e579c8685764a7cc02cb680380e94f43ccb1f7c"
integrity sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==
dependencies:
once "^1.3.2"
npm-bundled@^1.0.1:
version "1.0.6"
resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd"
@ -3376,6 +3734,18 @@ npm-pick-manifest@^2.2.3:
npm-package-arg "^6.0.0"
semver "^5.4.1"
npm-registry-fetch@^3.9.0:
version "3.9.1"
resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-3.9.1.tgz#00ff6e4e35d3f75a172b332440b53e93f4cb67de"
integrity sha512-VQCEZlydXw4AwLROAXWUR7QDfe2Y8Id/vpAgp6TI1/H78a4SiQ1kQrKZALm5/zxM5n4HIi+aYb+idUAV/RuY0Q==
dependencies:
JSONStream "^1.3.4"
bluebird "^3.5.1"
figgy-pudding "^3.4.1"
lru-cache "^5.1.1"
make-fetch-happen "^4.0.2"
npm-package-arg "^6.1.0"
npm-run-path@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@ -3429,6 +3799,16 @@ object-visit@^1.0.0:
dependencies:
isobject "^3.0.0"
object.defaults@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf"
integrity sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=
dependencies:
array-each "^1.0.1"
array-slice "^1.0.0"
for-own "^1.0.0"
isobject "^3.0.0"
object.getownpropertydescriptors@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16"
@ -3444,12 +3824,20 @@ object.pick@^1.3.0:
dependencies:
isobject "^3.0.1"
object.reduce@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/object.reduce/-/object.reduce-1.0.1.tgz#6fe348f2ac7fa0f95ca621226599096825bb03ad"
integrity sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=
dependencies:
for-own "^1.0.0"
make-iterator "^1.0.0"
octokit-pagination-methods@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4"
integrity sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ==
once@^1.3.0, once@^1.3.1, once@^1.4.0:
once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
@ -3751,7 +4139,7 @@ posix-character-classes@^0.1.0:
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
process-nextick-args@~2.0.0:
process-nextick-args@^2.0.0, process-nextick-args@~2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
@ -3774,6 +4162,14 @@ promise-retry@^1.1.1:
err-code "^1.0.0"
retry "^0.10.0"
prompts@^2.0.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.2.1.tgz#f901dd2a2dfee080359c0e20059b24188d75ad35"
integrity sha512-VObPvJiWPhpZI6C5m60XOzTfnYg/xc/an+r9VYymj9WJW3B/DIH+REzjpAACPf8brwPeP+7vz3bIim3S+AaMjw==
dependencies:
kleur "^3.0.3"
sisteransi "^1.0.3"
promzard@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee"
@ -3989,6 +4385,13 @@ redent@^2.0.0:
indent-string "^3.0.0"
strip-indent "^2.0.0"
redeyed@~2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-2.1.1.tgz#8984b5815d99cb220469c99eeeffe38913e6cc0b"
integrity sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs=
dependencies:
esprima "~4.0.0"
regex-not@^1.0.0, regex-not@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
@ -4079,6 +4482,13 @@ resolve@^1.10.0:
dependencies:
path-parse "^1.0.6"
resolve@^1.8.1:
version "1.12.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6"
integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==
dependencies:
path-parse "^1.0.6"
restore-cursor@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
@ -4111,6 +4521,11 @@ run-async@^2.2.0:
dependencies:
is-promise "^2.1.0"
run-parallel-limit@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/run-parallel-limit/-/run-parallel-limit-1.0.5.tgz#c29a4fd17b4df358cb52a8a697811a63c984f1b7"
integrity sha512-NsY+oDngvrvMxKB3G8ijBzIema6aYbQMD2bHOamvN52BysbIGTnEY2xsNyfrcr9GhY995/t/0nQN3R3oZvaDlg==
run-queue@^1.0.0, run-queue@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47"
@ -4194,6 +4609,11 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
sisteransi@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.3.tgz#98168d62b79e3a5e758e27ae63c4a053d748f4eb"
integrity sha512-SbEG75TzH8G7eVXFSN5f9EExILKfly7SUvVY5DhhYLvfhKqhDFY0OzevWa/zwak0RLRfWS5AvfMWpd9gJvr5Yg==
slash@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
@ -4383,6 +4803,11 @@ stream-each@^1.1.0:
end-of-stream "^1.1.0"
stream-shift "^1.0.0"
stream-exhaust@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/stream-exhaust/-/stream-exhaust-1.0.2.tgz#acdac8da59ef2bc1e17a2c0ccf6c320d120e555d"
integrity sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==
stream-shift@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
@ -4478,13 +4903,21 @@ strong-log-transformer@^2.0.0:
minimist "^1.2.0"
through "^2.3.4"
supports-color@^5.3.0:
supports-color@^5.0.0, supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
dependencies:
has-flag "^3.0.0"
supports-hyperlinks@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-1.0.1.tgz#71daedf36cc1060ac5100c351bb3da48c29c0ef7"
integrity sha512-HHi5kVSefKaJkGYXbDuKbUGRVxqnWGn3J2e39CYcNJEfWciGq2zYtOhXLTlvrOZW1QU7VX67w7fMmWafHX9Pfw==
dependencies:
has-flag "^2.0.0"
supports-color "^5.0.0"
tar@^4.4.10, tar@^4.4.8:
version "4.4.10"
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1"
@ -4638,6 +5071,11 @@ type-fest@^0.3.0:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1"
integrity sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==
type@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/type/-/type-1.0.3.tgz#16f5d39f27a2d28d86e48f8981859e9d3296c179"
integrity sha512-51IMtNfVcee8+9GJvj0spSuFcZHe9vSib6Xtgsny1Km9ugyz2mbS08I3rsUIRYgJohFRFU1160sgRodYz378Hg==
typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
@ -4666,6 +5104,26 @@ umask@^1.1.0:
resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d"
integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=
undertaker-registry@^1.0.0, undertaker-registry@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/undertaker-registry/-/undertaker-registry-1.0.1.tgz#5e4bda308e4a8a2ae584f9b9a4359a499825cc50"
integrity sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=
undertaker@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/undertaker/-/undertaker-1.2.1.tgz#701662ff8ce358715324dfd492a4f036055dfe4b"
integrity sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==
dependencies:
arr-flatten "^1.0.1"
arr-map "^2.0.0"
bach "^1.0.0"
collection-map "^1.0.0"
es6-weak-map "^2.0.1"
last-run "^1.1.0"
object.defaults "^1.0.0"
object.reduce "^1.0.0"
undertaker-registry "^1.0.0"
union-value@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"
@ -4785,6 +5243,13 @@ webidl-conversions@^4.0.2:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
webpack-merge@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.1.tgz#5e923cf802ea2ace4fd5af1d3247368a633489b4"
integrity sha512-4p8WQyS98bUJcCvFMbdGZyZmsKuWjWVnVHnAS3FFg0HDaRVrPbkivx2RYCre8UiemD67RsiFFLfn4JhLAin8Vw==
dependencies:
lodash "^4.17.5"
whatwg-url@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.0.0.tgz#fde926fa54a599f3adf82dff25a9f7be02dc6edd"
@ -4917,7 +5382,7 @@ yargs-parser@^13.1.0:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs@^12.0.1:
yargs@^12.0.1, yargs@^12.0.5:
version "12.0.5"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==