v0.0.6: Include fixed tick processor. Add regression analysis.
This commit is contained in:
Родитель
39bc2243bb
Коммит
c4fd88f0a6
|
@ -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
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==
|
||||
|
|
Загрузка…
Ссылка в новой задаче