@oaklean/profiler-core
Version:
Part of the @oaklean suite. It provides all basic functions to work with the `.oak` file format. It allows parsing the `.oak` file format as well as tools for analyzing the measurement values. It also provides all necessary capabilities required for prec
311 lines • 28.6 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AccountingHelper = void 0;
const CallIdentifier_1 = require("./CallIdentifier");
const system_1 = require("../../system");
// Types
const types_1 = require("../../types");
const TypescriptParser_1 = require("../TypescriptParser");
class AccountingHelper {
// IMPORTANT to change when new measurement type gets added
// if a node was already visited, set the aggregated measurements to 0
// to avoid double counting of measurements
static sensorValuesForVisitedNode(sensorValues, visited) {
const result = Object.assign({}, sensorValues);
if (visited) {
result.aggregatedCPUTime = 0;
result.aggregatedCPUEnergyConsumption = 0;
result.aggregatedRAMEnergyConsumption = 0;
}
return result;
}
/**
* This method creates a new source node (if it does not exist)
* in the [lang internal] section of the current report.
* And adds the sensor values of the cpu node to the new source node.
*
* It also handles the linking of the newly created source node to the current source node.
*
* @param cpuNode the new cpu node (that should be inserted)
* @param transition the transition (by inserting the cpu node)
*
* @returns the new state
*/
static accountToLangInternal(currentState, cpuNode, transition, callRelationTracker) {
return __awaiter(this, void 0, void 0, function* () {
const sensorValues = cpuNode.sensorValues;
const accountedSourceNode = currentState.callIdentifier.report.addToLangInternal(cpuNode.sourceLocation.rawUrl, cpuNode.sourceLocation
.sourceNodeIdentifier);
const currentCallIdentifier = new CallIdentifier_1.CallIdentifier(currentState.callIdentifier.report, accountedSourceNode, currentState.compensationLayerDepth);
const firstTimeVisited = callRelationTracker.initializeCallNodeIfAbsent(currentCallIdentifier, 'langInternal');
const firstTimeInCurrentCompensationLayer = callRelationTracker.initializeInCompensationLayerIfAbsent(currentCallIdentifier);
accountedSourceNode.sensorValues.profilerHits += cpuNode.profilerHits;
const accountedSensorValues = AccountingHelper.sensorValuesForVisitedNode(sensorValues, !firstTimeVisited);
accountedSourceNode.addToSensorValues(accountedSensorValues);
let accountedSourceNodeReference;
if (transition.options.createLink) {
if (currentState.callIdentifier.sourceNode === null) {
throw new Error('InsertCPUProfileStateMachine.accountToLangInternal: Current state has no source node assigned');
}
const alreadyLinked = callRelationTracker.linkCallToParent(currentCallIdentifier, currentState.callIdentifier);
const accountedSensorValues = AccountingHelper.sensorValuesForVisitedNode(sensorValues, alreadyLinked);
const sourceNodeReference = currentState.callIdentifier.sourceNode.addSensorValuesToLangInternal(accountedSourceNode.globalIdentifier(), accountedSensorValues);
sourceNodeReference.sensorValues.profilerHits += cpuNode.profilerHits;
accountedSourceNodeReference = {
firstTimeVisited: !alreadyLinked,
reference: sourceNodeReference
};
}
else {
accountedSourceNodeReference = null;
}
return {
nextState: {
scope: currentState.scope,
type: 'lang_internal',
headless: transition.options.headless,
callIdentifier: currentCallIdentifier,
compensationLayerDepth: currentState.compensationLayerDepth
},
accountingInfo: {
type: 'accountToLangInternal',
accountedSourceNode: {
node: accountedSourceNode,
firstTimeVisited,
firstTimeInCurrentCompensationLayer
},
accountedSensorValues,
accountedSourceNodeReference
}
};
});
}
/**
* This method creates a new source node (if it does not exist)
* in the [intern] section of the current report.
* And adds the sensor values of the cpu node to the new source node.
*
* It also handles the linking of the newly created source node to the current source node.
*
* @param cpuNode the new cpu node (that should be inserted)
* @param transition the transition (by inserting the cpu node)
*
* @returns the new state
*/
static accountToIntern(currentState, cpuNode, transition, callRelationTracker, awaiterStack) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
const sensorValues = cpuNode.sensorValues;
const sourceNodeLocation = transition.options.sourceNodeLocation;
let accountedSourceNodeReference;
// intern
const accountedSourceNode = currentState.callIdentifier.report.addToIntern(sourceNodeLocation.relativeFilePath.toString(), sourceNodeLocation.functionIdentifier);
accountedSourceNode.presentInOriginalSourceCode =
transition.options.presentInOriginalSourceCode;
const currentCallIdentifier = new CallIdentifier_1.CallIdentifier(currentState.callIdentifier.report, accountedSourceNode, currentState.compensationLayerDepth);
const firstTimeVisited = callRelationTracker.initializeCallNodeIfAbsent(currentCallIdentifier, 'intern');
const firstTimeInCurrentCompensationLayer = callRelationTracker.initializeInCompensationLayerIfAbsent(currentCallIdentifier);
accountedSourceNode.sensorValues.profilerHits += cpuNode.profilerHits;
const accountedSensorValues = AccountingHelper.sensorValuesForVisitedNode(sensorValues, !firstTimeVisited);
accountedSourceNode.addToSensorValues(accountedSensorValues);
if (sourceNodeLocation.functionIdentifier ===
TypescriptParser_1.TypescriptHelper.awaiterSourceNodeIdentifier()) {
currentCallIdentifier.isAwaiterSourceNode = true;
// add the awaiter to the stack and the corresponding async function parent
// if the parentNodeInfo.sourceNode is null or of type lang internal
// the awaiter is the first function in the call tree
// this could happen if the was called from node internal functions for example
awaiterStack.push({
awaiter: accountedSourceNode,
awaiterParent: ((_a = currentState.callIdentifier.sourceNode) === null || _a === void 0 ? void 0 : _a.type) ===
types_1.SourceNodeMetaDataType.SourceNode
? currentState.callIdentifier
.sourceNode
: undefined
});
}
if (transition.options.createLink) {
const alreadyLinked = callRelationTracker.linkCallToParent(currentCallIdentifier, currentState.callIdentifier);
if (currentState.callIdentifier.sourceNode === null) {
throw new Error('InsertCPUProfileStateMachine.accountToIntern: Current state has no source node assigned');
}
if (currentState.callIdentifier.sourceNode !== accountedSourceNode) {
// only create a reference if its not a recursive call
const accountedSensorValues = AccountingHelper.sensorValuesForVisitedNode(sensorValues, alreadyLinked);
const sourceNodeReference = currentState.callIdentifier.sourceNode.addSensorValuesToIntern(accountedSourceNode.globalIdentifier(), accountedSensorValues);
sourceNodeReference.sensorValues.profilerHits += cpuNode.profilerHits;
accountedSourceNodeReference = {
firstTimeVisited: !alreadyLinked,
reference: sourceNodeReference
};
}
else {
accountedSourceNodeReference = {
firstTimeVisited: !alreadyLinked
};
}
}
else {
accountedSourceNodeReference = null;
}
return {
nextState: transition.transition === 'toProject'
? {
scope: 'project',
type: 'intern',
headless: false,
callIdentifier: currentCallIdentifier,
compensationLayerDepth: currentState.compensationLayerDepth
}
: {
scope: 'module',
type: 'intern',
headless: transition.options.headless,
callIdentifier: currentCallIdentifier,
compensationLayerDepth: currentState.compensationLayerDepth
},
accountingInfo: {
type: 'accountToIntern',
accountedSourceNode: {
node: accountedSourceNode,
firstTimeVisited,
firstTimeInCurrentCompensationLayer
},
accountedSensorValues,
accountedSourceNodeReference
}
};
});
}
/**
* This method creates a new source node and node module (if it does not exist) in the current report
* in the [extern] section of the current report.
* And adds the sensor values of the cpu node to the new source node.
*
* It also handles the linking of the newly created source node to the current source node.
*
* @param cpuNode the new cpu node (that should be inserted)
* @param transition the transition (by inserting the cpu node)
*
* @returns the new state
*/
static accountToExtern(currentState, cpuNode, transition, callRelationTracker) {
return __awaiter(this, void 0, void 0, function* () {
const sensorValues = cpuNode.sensorValues;
const sourceNodeLocation = transition.options.sourceNodeLocation;
const nodeModule = transition.options.nodeModule;
let accountedSourceNodeReference;
const globalIdentifier = new system_1.GlobalIdentifier(sourceNodeLocation.relativeFilePath.toString(), sourceNodeLocation.functionIdentifier, nodeModule);
// extern
const { report, sourceNodeMetaData: accountedSourceNode } = currentState.callIdentifier.report.addToExtern(sourceNodeLocation.relativeFilePath, nodeModule, sourceNodeLocation.functionIdentifier);
accountedSourceNode.presentInOriginalSourceCode =
transition.options.presentInOriginalSourceCode;
const currentCallIdentifier = new CallIdentifier_1.CallIdentifier(report, accountedSourceNode, currentState.compensationLayerDepth);
const firstTimeVisited = callRelationTracker.initializeCallNodeIfAbsent(currentCallIdentifier, 'extern');
const firstTimeInCurrentCompensationLayer = callRelationTracker.initializeInCompensationLayerIfAbsent(currentCallIdentifier);
accountedSourceNode.sensorValues.profilerHits += cpuNode.profilerHits;
const accountedSensorValues = AccountingHelper.sensorValuesForVisitedNode(sensorValues, !firstTimeVisited);
accountedSourceNode.addToSensorValues(accountedSensorValues);
if (transition.options.createLink) {
if (currentState.callIdentifier.sourceNode === null) {
throw new Error('InsertCPUProfileStateMachine.accountToIntern: Current state has no source node assigned');
}
const alreadyLinked = callRelationTracker.linkCallToParent(currentCallIdentifier, currentState.callIdentifier);
const accountedSensorValues = AccountingHelper.sensorValuesForVisitedNode(sensorValues, alreadyLinked);
const sourceNodeReference = currentState.callIdentifier.sourceNode.addSensorValuesToExtern(globalIdentifier, accountedSensorValues);
sourceNodeReference.sensorValues.profilerHits += cpuNode.profilerHits;
accountedSourceNodeReference = {
firstTimeVisited: !alreadyLinked,
reference: sourceNodeReference
};
}
else {
accountedSourceNodeReference = null;
}
return {
nextState: {
scope: 'module',
type: 'intern',
headless: transition.options.headless,
callIdentifier: currentCallIdentifier,
compensationLayerDepth: currentState.compensationLayerDepth
},
accountingInfo: {
type: 'accountToExtern',
accountedSourceNode: {
node: accountedSourceNode,
firstTimeVisited,
firstTimeInCurrentCompensationLayer
},
accountedSensorValues,
accountedSourceNodeReference
}
};
});
}
/**
* This method is called when the new cpu node belongs to the project source code
* but was executed by an external function (module or wasm).
*
* It creates a new source node (if it does not exist) in the [intern] section of the current report.
* And adds the sensor values of the cpu node to the new source node.
*
* It does NOT create a link to the parent, since the parent call is from a different report.
* Since Wasm code is treated as external code, it has its own report.
*
* @param originalReport the original report where the cpu profile is inserted
* @param cpuNode the new cpu node (that should be inserted)
* @param transition the transition (by inserting the cpu node)
*
* @returns the new state
*/
static accountOwnCodeGetsExecutedByExternal(currentState, cpuNode, transition, callRelationTracker, originalReport) {
return __awaiter(this, void 0, void 0, function* () {
const sensorValues = cpuNode.sensorValues;
const sourceNodeLocation = transition.options.sourceNodeLocation;
const accountedSourceNode = originalReport.addToIntern(sourceNodeLocation.relativeFilePath.toString(), sourceNodeLocation.functionIdentifier);
accountedSourceNode.presentInOriginalSourceCode =
transition.options.presentInOriginalSourceCode;
const currentCallIdentifier = new CallIdentifier_1.CallIdentifier(originalReport, accountedSourceNode, currentState.compensationLayerDepth + 1);
const firstTimeVisited = callRelationTracker.initializeCallNodeIfAbsent(currentCallIdentifier, 'intern');
const firstTimeInCurrentCompensationLayer = callRelationTracker.initializeInCompensationLayerIfAbsent(currentCallIdentifier);
accountedSourceNode.sensorValues.profilerHits += cpuNode.profilerHits;
const accountedSensorValues = AccountingHelper.sensorValuesForVisitedNode(sensorValues, !firstTimeVisited);
// add measurements to original source code
accountedSourceNode.addToSensorValues(accountedSensorValues);
if (transition.options.createLink) {
throw new Error('InsertCPUProfileStateMachine.accountOwnCodeGetsExecutedByExternal: Cannot create link to parent, since the parent call is from a different report');
}
return {
nextState: {
scope: 'project',
type: 'intern',
headless: false,
callIdentifier: currentCallIdentifier,
compensationLayerDepth: currentState.compensationLayerDepth + 1
},
accountingInfo: {
type: 'accountOwnCodeGetsExecutedByExternal',
accountedSourceNode: {
node: accountedSourceNode,
firstTimeVisited,
firstTimeInCurrentCompensationLayer
},
accountedSensorValues,
accountedSourceNodeReference: null
}
};
});
}
}
exports.AccountingHelper = AccountingHelper;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQWNjb3VudGluZ0hlbHBlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9oZWxwZXIvSW5zZXJ0Q1BVUHJvZmlsZUhlbHBlci9BY2NvdW50aW5nSGVscGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7OztBQUFBLHFEQUFpRDtBQWVqRCx5Q0FBK0M7QUFHL0MsUUFBUTtBQUNSLHVDQU9vQjtBQUNwQiwwREFBc0Q7QUFFdEQsTUFBYSxnQkFBZ0I7SUFDNUIsMkRBQTJEO0lBQzNELHNFQUFzRTtJQUN0RSwyQ0FBMkM7SUFDM0MsTUFBTSxDQUFDLDBCQUEwQixDQUNoQyxZQUEyQixFQUMzQixPQUFnQjtRQUVoQixNQUFNLE1BQU0scUJBQ1IsWUFBWSxDQUNmLENBQUE7UUFFRCxJQUFJLE9BQU8sRUFBRSxDQUFDO1lBQ2IsTUFBTSxDQUFDLGlCQUFpQixHQUFHLENBQXdCLENBQUE7WUFDbkQsTUFBTSxDQUFDLDhCQUE4QixHQUFHLENBQXNCLENBQUE7WUFDOUQsTUFBTSxDQUFDLDhCQUE4QixHQUFHLENBQXNCLENBQUE7UUFDL0QsQ0FBQztRQUVELE9BQU8sTUFBTSxDQUFBO0lBQ2QsQ0FBQztJQUVEOzs7Ozs7Ozs7OztPQVdHO0lBQ0gsTUFBTSxDQUFPLHFCQUFxQixDQUNqQyxZQUFtQixFQUNuQixPQUFnQixFQUNoQixVQUFvQyxFQUNwQyxtQkFBd0M7O1lBUXhDLE1BQU0sWUFBWSxHQUFHLE9BQU8sQ0FBQyxZQUFZLENBQUE7WUFFekMsTUFBTSxtQkFBbUIsR0FDeEIsWUFBWSxDQUFDLGNBQWMsQ0FBQyxNQUFNLENBQUMsaUJBQWlCLENBQ25ELE9BQU8sQ0FBQyxjQUFjLENBQUMsTUFBaUMsRUFDeEQsT0FBTyxDQUFDLGNBQWM7aUJBQ3BCLG9CQUErRCxDQUNqRSxDQUFBO1lBRUYsTUFBTSxxQkFBcUIsR0FBRyxJQUFJLCtCQUFjLENBQy9DLFlBQVksQ0FBQyxjQUFjLENBQUMsTUFBTSxFQUNsQyxtQkFBbUIsRUFDbkIsWUFBWSxDQUFDLHNCQUFzQixDQUNuQyxDQUFBO1lBQ0QsTUFBTSxnQkFBZ0IsR0FBRyxtQkFBbUIsQ0FBQywwQkFBMEIsQ0FDdEUscUJBQXFCLEVBQ3JCLGNBQWMsQ0FDZCxDQUFBO1lBQ0QsTUFBTSxtQ0FBbUMsR0FDeEMsbUJBQW1CLENBQUMscUNBQXFDLENBQ3hELHFCQUFxQixDQUNyQixDQUFBO1lBRUYsbUJBQW1CLENBQUMsWUFBWSxDQUFDLFlBQVksSUFBSSxPQUFPLENBQUMsWUFBWSxDQUFBO1lBQ3JFLE1BQU0scUJBQXFCLEdBQUcsZ0JBQWdCLENBQUMsMEJBQTBCLENBQ3hFLFlBQVksRUFDWixDQUFDLGdCQUFnQixDQUNqQixDQUFBO1lBQ0QsbUJBQW1CLENBQUMsaUJBQWlCLENBQUMscUJBQXFCLENBQUMsQ0FBQTtZQUU1RCxJQUFJLDRCQUE4SCxDQUFBO1lBRWxJLElBQUksVUFBVSxDQUFDLE9BQU8sQ0FBQyxVQUFVLEVBQUUsQ0FBQztnQkFDbkMsSUFBSSxZQUFZLENBQUMsY0FBYyxDQUFDLFVBQVUsS0FBSyxJQUFJLEVBQUUsQ0FBQztvQkFDckQsTUFBTSxJQUFJLEtBQUssQ0FDZCwrRkFBK0YsQ0FDL0YsQ0FBQTtnQkFDRixDQUFDO2dCQUNELE1BQU0sYUFBYSxHQUFHLG1CQUFtQixDQUFDLGdCQUFnQixDQUN6RCxxQkFBcUIsRUFDckIsWUFBWSxDQUFDLGNBQWMsQ0FDM0IsQ0FBQTtnQkFFRCxNQUFNLHFCQUFxQixHQUFHLGdCQUFnQixDQUFDLDBCQUEwQixDQUN4RSxZQUFZLEVBQ1osYUFBYSxDQUNiLENBQUE7Z0JBRUQsTUFBTSxtQkFBbUIsR0FDeEIsWUFBWSxDQUFDLGNBQWMsQ0FBQyxVQUFVLENBQUMsNkJBQTZCLENBQ25FLG1CQUFtQixDQUFDLGdCQUFnQixFQUFFLEVBQ3RDLHFCQUFxQixDQUNyQixDQUFBO2dCQUNGLG1CQUFtQixDQUFDLFlBQVksQ0FBQyxZQUFZLElBQUksT0FBTyxDQUFDLFlBQVksQ0FBQTtnQkFFckUsNEJBQTRCLEdBQUc7b0JBQzlCLGdCQUFnQixFQUFFLENBQUMsYUFBYTtvQkFDaEMsU0FBUyxFQUFFLG1CQUFtQjtpQkFDOUIsQ0FBQTtZQUNGLENBQUM7aUJBQU0sQ0FBQztnQkFDUCw0QkFBNEIsR0FBRyxJQUFJLENBQUE7WUFDcEMsQ0FBQztZQUVELE9BQU87Z0JBQ04sU0FBUyxFQUFFO29CQUNWLEtBQUssRUFBRSxZQUFZLENBQUMsS0FBSztvQkFDekIsSUFBSSxFQUFFLGVBQWU7b0JBQ3JCLFFBQVEsRUFBRSxVQUFVLENBQUMsT0FBTyxDQUFDLFFBQVE7b0JBQ3JDLGNBQWMsRUFBRSxxQkFBcUI7b0JBQ3JDLHNCQUFzQixFQUFFLFlBQVksQ0FBQyxzQkFBc0I7aUJBQzNEO2dCQUNELGNBQWMsRUFBRTtvQkFDZixJQUFJLEVBQUUsdUJBQXVCO29CQUM3QixtQkFBbUIsRUFBRTt3QkFDcEIsSUFBSSxFQUFFLG1CQUFtQjt3QkFDekIsZ0JBQWdCO3dCQUNoQixtQ0FBbUM7cUJBQ25DO29CQUNELHFCQUFxQjtvQkFDckIsNEJBQTRCO2lCQUM1QjthQUNELENBQUE7UUFDRixDQUFDO0tBQUE7SUFFRDs7Ozs7Ozs7Ozs7T0FXRztJQUNILE1BQU0sQ0FBTyxlQUFlLENBQzNCLFlBQW1CLEVBQ25CLE9BQWdCLEVBQ2hCLFVBQW9ELEVBQ3BELG1CQUF3QyxFQUN4QyxZQUEwQjs7O1lBUTFCLE1BQU0sWUFBWSxHQUFHLE9BQU8sQ0FBQyxZQUFZLENBQUE7WUFDekMsTUFBTSxrQkFBa0IsR0FBRyxVQUFVLENBQUMsT0FBTyxDQUFDLGtCQUFrQixDQUFBO1lBRWhFLElBQUksNEJBQXdILENBQUE7WUFFNUgsU0FBUztZQUNULE1BQU0sbUJBQW1CLEdBQUcsWUFBWSxDQUFDLGNBQWMsQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUN6RSxrQkFBa0IsQ0FBQyxnQkFBZ0IsQ0FBQyxRQUFRLEVBQUUsRUFDOUMsa0JBQWtCLENBQUMsa0JBQWtCLENBQ3JDLENBQUE7WUFDRCxtQkFBbUIsQ0FBQywyQkFBMkI7Z0JBQzlDLFVBQVUsQ0FBQyxPQUFPLENBQUMsMkJBQTJCLENBQUE7WUFDL0MsTUFBTSxxQkFBcUIsR0FBRyxJQUFJLCtCQUFjLENBQy9DLFlBQVksQ0FBQyxjQUFjLENBQUMsTUFBTSxFQUNsQyxtQkFBbUIsRUFDbkIsWUFBWSxDQUFDLHNCQUFzQixDQUNuQyxDQUFBO1lBQ0QsTUFBTSxnQkFBZ0IsR0FBRyxtQkFBbUIsQ0FBQywwQkFBMEIsQ0FDdEUscUJBQXFCLEVBQ3JCLFFBQVEsQ0FDUixDQUFBO1lBQ0QsTUFBTSxtQ0FBbUMsR0FDeEMsbUJBQW1CLENBQUMscUNBQXFDLENBQ3hELHFCQUFxQixDQUNyQixDQUFBO1lBRUYsbUJBQW1CLENBQUMsWUFBWSxDQUFDLFlBQVksSUFBSSxPQUFPLENBQUMsWUFBWSxDQUFBO1lBQ3JFLE1BQU0scUJBQXFCLEdBQUcsZ0JBQWdCLENBQUMsMEJBQTBCLENBQ3hFLFlBQVksRUFDWixDQUFDLGdCQUFnQixDQUNqQixDQUFBO1lBQ0QsbUJBQW1CLENBQUMsaUJBQWlCLENBQUMscUJBQXFCLENBQUMsQ0FBQTtZQUU1RCxJQUNDLGtCQUFrQixDQUFDLGtCQUFrQjtnQkFDckMsbUNBQWdCLENBQUMsMkJBQTJCLEVBQUUsRUFDN0MsQ0FBQztnQkFDRixxQkFBcUIsQ0FBQyxtQkFBbUIsR0FBRyxJQUFJLENBQUE7Z0JBRWhELDJFQUEyRTtnQkFDM0Usb0VBQW9FO2dCQUNwRSxxREFBcUQ7Z0JBQ3JELCtFQUErRTtnQkFDL0UsWUFBWSxDQUFDLElBQUksQ0FBQztvQkFDakIsT0FBTyxFQUFFLG1CQUFtQjtvQkFDNUIsYUFBYSxFQUNaLENBQUEsTUFBQSxZQUFZLENBQUMsY0FBYyxDQUFDLFVBQVUsMENBQUUsSUFBSTt3QkFDNUMsOEJBQXNCLENBQUMsVUFBVTt3QkFDaEMsQ0FBQyxDQUFFLFlBQVksQ0FBQyxjQUFjOzZCQUMzQixVQUFvRTt3QkFDdkUsQ0FBQyxDQUFDLFNBQVM7aUJBQ2IsQ0FBQyxDQUFBO1lBQ0gsQ0FBQztZQUVELElBQUksVUFBVSxDQUFDLE9BQU8sQ0FBQyxVQUFVLEVBQUUsQ0FBQztnQkFDbkMsTUFBTSxhQUFhLEdBQUcsbUJBQW1CLENBQUMsZ0JBQWdCLENBQ3pELHFCQUFxQixFQUNyQixZQUFZLENBQUMsY0FBYyxDQUMzQixDQUFBO2dCQUVELElBQUksWUFBWSxDQUFDLGNBQWMsQ0FBQyxVQUFVLEtBQUssSUFBSSxFQUFFLENBQUM7b0JBQ3JELE1BQU0sSUFBSSxLQUFLLENBQ2QseUZBQXlGLENBQ3pGLENBQUE7Z0JBQ0YsQ0FBQztnQkFFRCxJQUFJLFlBQVksQ0FBQyxjQUFjLENBQUMsVUFBVSxLQUFLLG1CQUFtQixFQUFFLENBQUM7b0JBQ3BFLHNEQUFzRDtvQkFDdEQsTUFBTSxxQkFBcUIsR0FDMUIsZ0JBQWdCLENBQUMsMEJBQTBCLENBQzFDLFlBQVksRUFDWixhQUFhLENBQ2IsQ0FBQTtvQkFDRixNQUFNLG1CQUFtQixHQUN4QixZQUFZLENBQUMsY0FBYyxDQUFDLFVBQVUsQ0FBQyx1QkFBdUIsQ0FDN0QsbUJBQW1CLENBQUMsZ0JBQWdCLEVBQUUsRUFDdEMscUJBQXFCLENBQ3JCLENBQUE7b0JBQ0YsbUJBQW1CLENBQUMsWUFBWSxDQUFDLFlBQVksSUFBSSxPQUFPLENBQUMsWUFBWSxDQUFBO29CQUVyRSw0QkFBNEIsR0FBRzt3QkFDOUIsZ0JBQWdCLEVBQUUsQ0FBQyxhQUFhO3dCQUNoQyxTQUFTLEVBQUUsbUJBQW1CO3FCQUM5QixDQUFBO2dCQUNGLENBQUM7cUJBQU0sQ0FBQztvQkFDUCw0QkFBNEIsR0FBRzt3QkFDOUIsZ0JBQWdCLEVBQUUsQ0FBQyxhQUFhO3FCQUNoQyxDQUFBO2dCQUNGLENBQUM7WUFDRixDQUFDO2lCQUFNLENBQUM7Z0JBQ1AsNEJBQTRCLEdBQUcsSUFBSSxDQUFBO1lBQ3BDLENBQUM7WUFFRCxPQUFPO2dCQUNOLFNBQVMsRUFDUixVQUFVLENBQUMsVUFBVSxLQUFLLFdBQVc7b0JBQ3BDLENBQUMsQ0FBQzt3QkFDQSxLQUFLLEVBQUUsU0FBUzt3QkFDaEIsSUFBSSxFQUFFLFFBQVE7d0JBQ2QsUUFBUSxFQUFFLEtBQUs7d0JBQ2YsY0FBYyxFQUFFLHFCQUFxQjt3QkFDckMsc0JBQXNCLEVBQUUsWUFBWSxDQUFDLHNCQUFzQjtxQkFDM0Q7b0JBQ0YsQ0FBQyxDQUFDO3dCQUNBLEtBQUssRUFBRSxRQUFRO3dCQUNmLElBQUksRUFBRSxRQUFRO3dCQUNkLFFBQVEsRUFBRSxVQUFVLENBQUMsT0FBTyxDQUFDLFFBQVE7d0JBQ3JDLGNBQWMsRUFBRSxxQkFBcUI7d0JBQ3JDLHNCQUFzQixFQUFFLFlBQVksQ0FBQyxzQkFBc0I7cUJBQzNEO2dCQUNKLGNBQWMsRUFBRTtvQkFDZixJQUFJLEVBQUUsaUJBQWlCO29CQUN2QixtQkFBbUIsRUFBRTt3QkFDcEIsSUFBSSxFQUFFLG1CQUFtQjt3QkFDekIsZ0JBQWdCO3dCQUNoQixtQ0FBbUM7cUJBQ25DO29CQUNELHFCQUFxQjtvQkFDckIsNEJBQTRCO2lCQUM1QjthQUNELENBQUE7UUFDRixDQUFDO0tBQUE7SUFFRDs7Ozs7Ozs7Ozs7T0FXRztJQUNILE1BQU0sQ0FBTyxlQUFlLENBQzNCLFlBQW1CLEVBQ25CLE9BQWdCLEVBQ2hCLFVBQThCLEVBQzlCLG1CQUF3Qzs7WUFReEMsTUFBTSxZQUFZLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQTtZQUN6QyxNQUFNLGtCQUFrQixHQUFHLFVBQVUsQ0FBQyxPQUFPLENBQUMsa0JBQWtCLENBQUE7WUFDaEUsTUFBTSxVQUFVLEdBQUcsVUFBVSxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUE7WUFDaEQsSUFBSSw0QkFBd0gsQ0FBQTtZQUU1SCxNQUFNLGdCQUFnQixHQUFHLElBQUkseUJBQWdCLENBQzVDLGtCQUFrQixDQUFDLGdCQUFnQixDQUFDLFFBQVEsRUFBRSxFQUM5QyxrQkFBa0IsQ0FBQyxrQkFBa0IsRUFDckMsVUFBVSxDQUNWLENBQUE7WUFFRCxTQUFTO1lBQ1QsTUFBTSxFQUFFLE1BQU0sRUFBRSxrQkFBa0IsRUFBRSxtQkFBbUIsRUFBRSxHQUN4RCxZQUFZLENBQUMsY0FBYyxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQzdDLGtCQUFrQixDQUFDLGdCQUFnQixFQUNuQyxVQUFVLEVBQ1Ysa0JBQWtCLENBQUMsa0JBQWtCLENBQ3JDLENBQUE7WUFDRixtQkFBbUIsQ0FBQywyQkFBMkI7Z0JBQzlDLFVBQVUsQ0FBQyxPQUFPLENBQUMsMkJBQTJCLENBQUE7WUFDL0MsTUFBTSxxQkFBcUIsR0FBRyxJQUFJLCtCQUFjLENBQy9DLE1BQU0sRUFDTixtQkFBbUIsRUFDbkIsWUFBWSxDQUFDLHNCQUFzQixDQUNuQyxDQUFBO1lBQ0QsTUFBTSxnQkFBZ0IsR0FBRyxtQkFBbUIsQ0FBQywwQkFBMEIsQ0FDdEUscUJBQXFCLEVBQ3JCLFFBQVEsQ0FDUixDQUFBO1lBQ0QsTUFBTSxtQ0FBbUMsR0FDeEMsbUJBQW1CLENBQUMscUNBQXFDLENBQ3hELHFCQUFxQixDQUNyQixDQUFBO1lBRUYsbUJBQW1CLENBQUMsWUFBWSxDQUFDLFlBQVksSUFBSSxPQUFPLENBQUMsWUFBWSxDQUFBO1lBQ3JFLE1BQU0scUJBQXFCLEdBQUcsZ0JBQWdCLENBQUMsMEJBQTBCLENBQ3hFLFlBQVksRUFDWixDQUFDLGdCQUFnQixDQUNqQixDQUFBO1lBQ0QsbUJBQW1CLENBQUMsaUJBQWlCLENBQUMscUJBQXFCLENBQUMsQ0FBQTtZQUU1RCxJQUFJLFVBQVUsQ0FBQyxPQUFPLENBQUMsVUFBVSxFQUFFLENBQUM7Z0JBQ25DLElBQUksWUFBWSxDQUFDLGNBQWMsQ0FBQyxVQUFVLEtBQUssSUFBSSxFQUFFLENBQUM7b0JBQ3JELE1BQU0sSUFBSSxLQUFLLENBQ2QseUZBQXlGLENBQ3pGLENBQUE7Z0JBQ0YsQ0FBQztnQkFDRCxNQUFNLGFBQWEsR0FBRyxtQkFBbUIsQ0FBQyxnQkFBZ0IsQ0FDekQscUJBQXFCLEVBQ3JCLFlBQVksQ0FBQyxjQUFjLENBQzNCLENBQUE7Z0JBRUQsTUFBTSxxQkFBcUIsR0FBRyxnQkFBZ0IsQ0FBQywwQkFBMEIsQ0FDeEUsWUFBWSxFQUNaLGFBQWEsQ0FDYixDQUFBO2dCQUVELE1BQU0sbUJBQW1CLEdBQ3hCLFlBQVksQ0FBQyxjQUFjLENBQUMsVUFBVSxDQUFDLHVCQUF1QixDQUM3RCxnQkFBZ0IsRUFDaEIscUJBQXFCLENBQ3JCLENBQUE7Z0JBQ0YsbUJBQW1CLENBQUMsWUFBWSxDQUFDLFlBQVksSUFBSSxPQUFPLENBQUMsWUFBWSxDQUFBO2dCQUVyRSw0QkFBNEIsR0FBRztvQkFDOUIsZ0JBQWdCLEVBQUUsQ0FBQyxhQUFhO29CQUNoQyxTQUFTLEVBQUUsbUJBQW1CO2lCQUM5QixDQUFBO1lBQ0YsQ0FBQztpQkFBTSxDQUFDO2dCQUNQLDRCQUE0QixHQUFHLElBQUksQ0FBQTtZQUNwQyxDQUFDO1lBRUQsT0FBTztnQkFDTixTQUFTLEVBQUU7b0JBQ1YsS0FBSyxFQUFFLFFBQVE7b0JBQ2YsSUFBSSxFQUFFLFFBQVE7b0JBQ2QsUUFBUSxFQUFFLFVBQVUsQ0FBQyxPQUFPLENBQUMsUUFBUTtvQkFDckMsY0FBYyxFQUFFLHFCQUFxQjtvQkFDckMsc0JBQXNCLEVBQUUsWUFBWSxDQUFDLHNCQUFzQjtpQkFDM0Q7Z0JBQ0QsY0FBYyxFQUFFO29CQUNmLElBQUksRUFBRSxpQkFBaUI7b0JBQ3ZCLG1CQUFtQixFQUFFO3dCQUNwQixJQUFJLEVBQUUsbUJBQW1CO3dCQUN6QixnQkFBZ0I7d0JBQ2hCLG1DQUFtQztxQkFDbkM7b0JBQ0QscUJBQXFCO29CQUNyQiw0QkFBNEI7aUJBQzVCO2FBQ0QsQ0FBQTtRQUNGLENBQUM7S0FBQTtJQUVEOzs7Ozs7Ozs7Ozs7Ozs7T0FlRztJQUNILE1BQU0sQ0FBTyxvQ0FBb0MsQ0FDaEQsWUFBbUIsRUFDbkIsT0FBZ0IsRUFDaEIsVUFBK0IsRUFDL0IsbUJBQXdDLEVBQ3hDLGNBQTZCOztZQVE3QixNQUFNLFlBQVksR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFBO1lBQ3pDLE1BQU0sa0JBQWtCLEdBQUcsVUFBVSxDQUFDLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBQTtZQUVoRSxNQUFNLG1CQUFtQixHQUFHLGNBQWMsQ0FBQyxXQUFXLENBQ3JELGtCQUFrQixDQUFDLGdCQUFnQixDQUFDLFFBQVEsRUFBRSxFQUM5QyxrQkFBa0IsQ0FBQyxrQkFBa0IsQ0FDckMsQ0FBQTtZQUNELG1CQUFtQixDQUFDLDJCQUEyQjtnQkFDOUMsVUFBVSxDQUFDLE9BQU8sQ0FBQywyQkFBMkIsQ0FBQTtZQUMvQyxNQUFNLHFCQUFxQixHQUFHLElBQUksK0JBQWMsQ0FDL0MsY0FBYyxFQUNkLG1CQUFtQixFQUNuQixZQUFZLENBQUMsc0JBQXNCLEdBQUcsQ0FBQyxDQUN2QyxDQUFBO1lBQ0QsTUFBTSxnQkFBZ0IsR0FBRyxtQkFBbUIsQ0FBQywwQkFBMEIsQ0FDdEUscUJBQXFCLEVBQ3JCLFFBQVEsQ0FDUixDQUFBO1lBQ0QsTUFBTSxtQ0FBbUMsR0FDeEMsbUJBQW1CLENBQUMscUNBQXFDLENBQ3hELHFCQUFxQixDQUNyQixDQUFBO1lBRUYsbUJBQW1CLENBQUMsWUFBWSxDQUFDLFlBQVksSUFBSSxPQUFPLENBQUMsWUFBWSxDQUFBO1lBQ3JFLE1BQU0scUJBQXFCLEdBQUcsZ0JBQWdCLENBQUMsMEJBQTBCLENBQ3hFLFlBQVksRUFDWixDQUFDLGdCQUFnQixDQUNqQixDQUFBO1lBQ0QsMkNBQTJDO1lBQzNDLG1CQUFtQixDQUFDLGlCQUFpQixDQUFDLHFCQUFxQixDQUFDLENBQUE7WUFFNUQsSUFBSSxVQUFVLENBQUMsT0FBTyxDQUFDLFVBQVUsRUFBRSxDQUFDO2dCQUNuQyxNQUFNLElBQUksS0FBSyxDQUNkLG1KQUFtSixDQUNuSixDQUFBO1lBQ0YsQ0FBQztZQUVELE9BQU87Z0JBQ04sU0FBUyxFQUFFO29CQUNWLEtBQUssRUFBRSxTQUFTO29CQUNoQixJQUFJLEVBQUUsUUFBUTtvQkFDZCxRQUFRLEVBQUUsS0FBSztvQkFDZixjQUFjLEVBQUUscUJBQXFCO29CQUNyQyxzQkFBc0IsRUFBRSxZQUFZLENBQUMsc0JBQXNCLEdBQUcsQ0FBQztpQkFDL0Q7Z0JBQ0QsY0FBYyxFQUFFO29CQUNmLElBQUksRUFBRSxzQ0FBc0M7b0JBQzVDLG1CQUFtQixFQUFFO3dCQUNwQixJQUFJLEVBQUUsbUJBQW1CO3dCQUN6QixnQkFBZ0I7d0JBQ2hCLG1DQUFtQztxQkFDbkM7b0JBQ0QscUJBQXFCO29CQUNyQiw0QkFBNEIsRUFBRSxJQUFJO2lCQUNsQzthQUNELENBQUE7UUFDRixDQUFDO0tBQUE7Q0FDRDtBQWhlRCw0Q0FnZUMifQ==