faastjs
Version:
Serverless batch computing made simple.
582 lines • 82.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CostAnalyzer = exports.CostSnapshot = exports.CostMetric = void 0;
const tslib_1 = require("tslib");
const listr_1 = tslib_1.__importDefault(require("listr"));
const util_1 = require("util");
const index_1 = require("../index");
const shared_1 = require("./shared");
const throttle_1 = require("./throttle");
/**
* A line item in the cost estimate, including the resource usage metric
* measured and its pricing.
* @public
*/
class CostMetric {
/** @internal */
constructor(arg) {
this.name = arg.name;
this.pricing = arg.pricing;
this.unit = arg.unit;
this.measured = arg.measured;
this.unitPlural = arg.unitPlural;
this.comment = arg.comment;
this.informationalOnly = arg.informationalOnly;
}
/**
* The cost contribution of this cost metric. Equal to
* {@link CostMetric.pricing} * {@link CostMetric.measured}.
*/
cost() {
return this.pricing * this.measured;
}
/**
* Return a string with the cost estimate for this metric, omitting
* comments.
*/
describeCostOnly() {
const p = (n, precision = 8) => Number.isInteger(n) ? String(n) : n.toFixed(precision);
const getUnit = (n) => {
if (n > 1) {
return (this.unitPlural ||
(!this.unit.match(/[A-Z]$/) ? this.unit + "s" : this.unit));
}
else {
return this.unit;
}
};
const cost = `$${p(this.cost())}`;
const pricing = `$${p(this.pricing)}/${this.unit}`;
const metric = p(this.measured, this.unit === "second" ? 1 : 8);
const unit = getUnit(this.measured);
return `${this.name.padEnd(21)} ${pricing.padEnd(20)} ${metric.padStart(12)} ${unit.padEnd(10)} ${cost.padEnd(14)}`;
}
/** Describe this cost metric, including comments. */
toString() {
return `${this.describeCostOnly()}${(this.comment && `// ${this.comment}`) || ""}`;
}
}
exports.CostMetric = CostMetric;
/**
* A summary of the costs incurred by a faast.js module at a point in time.
* Output of {@link FaastModule.costSnapshot}.
* @remarks
* Cost information provided by faast.js is an estimate. It is derived from
* internal faast.js measurements and not by consulting data provided by your
* cloud provider.
*
* **Faast.js does not guarantee the accuracy of cost estimates.**
*
* **Use at your own risk.**
*
* Example using AWS:
* ```typescript
* const faastModule = await faast("aws", m);
* try {
* // Invoke faastModule.functions.*
* } finally {
* await faastModule.cleanup();
* console.log(`Cost estimate:`);
* console.log(`${await faastModule.costSnapshot()}`);
* }
* ```
*
* AWS example output:
* ```text
* Cost estimate:
* functionCallDuration $0.00002813/second 0.6 second $0.00001688 68.4% [1]
* sqs $0.00000040/request 9 requests $0.00000360 14.6% [2]
* sns $0.00000050/request 5 requests $0.00000250 10.1% [3]
* functionCallRequests $0.00000020/request 5 requests $0.00000100 4.1% [4]
* outboundDataTransfer $0.09000000/GB 0.00000769 GB $0.00000069 2.8% [5]
* logIngestion $0.50000000/GB 0 GB $0 0.0% [6]
* ---------------------------------------------------------------------------------------
* $0.00002467 (USD)
*
* * Estimated using highest pricing tier for each service. Limitations apply.
* ** Does not account for free tier.
* [1]: https://aws.amazon.com/lambda/pricing (rate = 0.00001667/(GB*second) * 1.6875 GB = 0.00002813/second)
* [2]: https://aws.amazon.com/sqs/pricing
* [3]: https://aws.amazon.com/sns/pricing
* [4]: https://aws.amazon.com/lambda/pricing
* [5]: https://aws.amazon.com/ec2/pricing/on-demand/#Data_Transfer
* [6]: https://aws.amazon.com/cloudwatch/pricing/ - Log ingestion costs not currently included.
* ```
*
* A cost snapshot contains several {@link CostMetric} values. Each `CostMetric`
* summarizes one component of the overall cost of executing the functions so
* far. Some cost metrics are common to all faast providers, and other metrics
* are provider-specific. The common metrics are:
*
* - `functionCallDuration`: the estimated billed CPU time (rounded to the next
* 100ms) consumed by completed cloud function calls. This is the metric that
* usually dominates cost.
*
* - `functionCallRequests`: the number of invocation requests made. Most
* providers charge for each invocation.
*
* Provider-specific metrics vary. For example, AWS has the following additional
* metrics:
*
* - `sqs`: AWS Simple Queueing Service. This metric captures the number of
* queue requests made to insert and retrieve queued results (each 64kb chunk
* is counted as an additional request). SQS is used even if
* {@link CommonOptions.mode} is not set to `"queue"`, because it is necessary
* for monitoring cloud function invocations.
*
* - `sns`: AWS Simple Notification Service. SNS is used to invoke Lambda
* functions when {@link CommonOptions.mode} is `"queue"`.
*
* - `outboundDataTransfer`: an estimate of the network data transferred out
* from the cloud provider for this faast.js module. This estimate only counts
* data returned from cloud function invocations and infrastructure that
* faast.js sets up. It does not count any outbound data sent by your cloud
* functions that are not known to faast.js. Note that if you run faast.js on
* EC2 in the same region (see {@link AwsOptions.region}), then the data
* transfer costs will be zero (however, the cost snapshot will not include
* EC2 costs). Also note that if your cloud function transfers data from/to S3
* buckets in the same region, there is no cost as long as that data is not
* returned from the function.
*
* - `logIngestion`: this cost metric is always zero for AWS. It is present to
* remind the user that AWS charges for log data ingested by CloudWatch Logs
* that are not measured by faast.js. Log entries may arrive significantly
* after function execution completes, and there is no way for faast.js to
* know exactly how long to wait, therefore it does not attempt to measure
* this cost. In practice, if your cloud functions do not perform extensive
* logging on all invocations, log ingestion costs from faast.js are likely to
* be low or fall within the free tier.
*
* The Local provider has no extra metrics.
*
* Prices are retrieved dynamically from AWS and cached locally.
* Cached prices expire after 24h. For each cost metric, faast.js uses the
* highest price tier to compute estimated pricing.
*
* Cost estimates do not take free tiers into account.
* @public
*/
class CostSnapshot {
/** @internal */
constructor(
/** The {@link Provider}, e.g. "aws" */
provider,
/**
* The options used to initialize the faast.js module where this cost
* snapshot was generated.
*/
options, stats, costMetrics = []) {
this.provider = provider;
this.options = options;
/**
* The cost metric components for this cost snapshot. See
* {@link CostMetric}.
*/
this.costMetrics = [];
this.stats = stats.clone();
this.costMetrics = [...costMetrics];
}
/** Sum of cost metrics. */
total() {
return (0, shared_1.sum)(this.costMetrics.map(metric => metric.cost()));
}
/** A summary of all cost metrics and prices in this cost snapshot. */
toString() {
let rv = "";
this.costMetrics.sort((a, b) => b.cost() - a.cost());
const total = this.total();
const comments = [];
const percent = (entry) => ((entry.cost() / total) * 100).toFixed(1).padStart(5) + "% ";
for (const entry of this.costMetrics) {
let commentIndex = "";
if (entry.comment) {
comments.push(entry.comment);
commentIndex = ` [${comments.length}]`;
}
rv += `${entry.describeCostOnly()}${percent(entry)}${commentIndex}\n`;
}
rv +=
"---------------------------------------------------------------------------------------\n";
rv += `$${this.total().toFixed(8)}`.padStart(78) + " (USD)\n\n";
rv += ` * Estimated using highest pricing tier for each service. Limitations apply.\n`;
rv += ` ** Does not account for free tier.\n`;
rv += comments.map((c, i) => `[${i + 1}]: ${c}`).join("\n");
return rv;
}
/**
* Comma separated value output for a cost snapshot.
* @remarks
* The format is "metric,unit,pricing,measured,cost,percentage,comment".
*
* Example output:
* ```text
* metric,unit,pricing,measured,cost,percentage,comment
* functionCallDuration,second,0.00002813,0.60000000,0.00001688,64.1% ,"https://aws.amazon.com/lambda/pricing (rate = 0.00001667/(GB*second) * 1.6875 GB = 0.00002813/second)"
* functionCallRequests,request,0.00000020,5,0.00000100,3.8% ,"https://aws.amazon.com/lambda/pricing"
* outboundDataTransfer,GB,0.09000000,0.00000844,0.00000076,2.9% ,"https://aws.amazon.com/ec2/pricing/on-demand/#Data_Transfer"
* sqs,request,0.00000040,13,0.00000520,19.7% ,"https://aws.amazon.com/sqs/pricing"
* sns,request,0.00000050,5,0.00000250,9.5% ,"https://aws.amazon.com/sns/pricing"
* logIngestion,GB,0.50000000,0,0,0.0% ,"https://aws.amazon.com/cloudwatch/pricing/ - Log ingestion costs not currently included."
* ```
*/
csv() {
let rv = "";
rv += "metric,unit,pricing,measured,cost,percentage,comment\n";
const total = this.total();
const p = (n) => (Number.isInteger(n) ? n : n.toFixed(8));
const percent = (entry) => ((entry.cost() / total) * 100).toFixed(1) + "% ";
for (const entry of this.costMetrics) {
rv += `${entry.name},${entry.unit},${p(entry.pricing)},${p(entry.measured)},${p(entry.cost())},${percent(entry)},"${(entry.comment || "").replace('"', '""')}"\n`;
}
return rv;
}
/** @internal */
push(metric) {
this.costMetrics.push(metric);
}
/**
* Find a specific cost metric by name.
* @returns a {@link CostMetric} if found, otherwise `undefined`.
*/
find(name) {
return this.costMetrics.find(m => m.name === name);
}
}
exports.CostSnapshot = CostSnapshot;
/**
* Analyze the cost of a workload across many provider configurations.
* @public
*/
var CostAnalyzer;
(function (CostAnalyzer) {
/**
* Default AWS cost analyzer configurations include all memory sizes for AWS
* Lambda.
* @remarks
* The default AWS cost analyzer configurations include every memory size
* from 128MB to 3008MB in 64MB increments. Each configuration has the
* following settings:
*
* ```typescript
* {
* provider: "aws",
* options: {
* mode: "https",
* memorySize,
* timeout: 300,
* gc: "off",
* childProcess: true
* }
* }
* ```
*
* Use `Array.map` to change or `Array.filter` to remove some of these
* configurations. For example:
*
* ```typescript
* const configsWithAtLeast1GB = awsConfigurations.filter(c => c.memorySize > 1024)
* const shorterTimeout = awsConfigurations.map(c => ({...c, timeout: 60 }));
* ```
* @public
*/
CostAnalyzer.awsConfigurations = (() => {
const rv = [];
for (let memorySize = 128; memorySize <= 3008; memorySize += 64) {
rv.push({
provider: "aws",
options: {
mode: "https",
memorySize,
timeout: 300,
gc: "off",
childProcess: true
}
});
}
return rv;
})();
const workloadDefaults = {
configurations: CostAnalyzer.awsConfigurations,
summarize: summarizeMean,
format: defaultFormat,
formatCSV: defaultFormatCSV,
silent: false,
repetitions: 10,
concurrency: 64
};
function defaultFormat(attr, value) {
return `${attr}:${(0, shared_1.f1)(value)}`;
}
function defaultFormatCSV(_, value) {
return (0, shared_1.f1)(value);
}
const ps = (n) => (n / 1000).toFixed(3);
function summarizeMean(attributes) {
const stats = {};
attributes.forEach(a => (0, shared_1.keysOf)(a).forEach(attr => {
if (!(attr in stats)) {
stats[attr] = new shared_1.Statistics();
}
stats[attr].update(a[attr]);
}));
const result = {};
(0, shared_1.keysOf)(stats).forEach(attr => {
result[attr] = stats[attr].mean;
});
return result;
}
async function estimate(workload, config) {
const { provider, options } = config;
const faastModule = await (0, index_1.faast)(provider, workload.funcs, options);
const { repetitions, concurrency: repetitionConcurrency } = workload;
const doWork = (0, throttle_1.throttle)({ concurrency: repetitionConcurrency }, workload.work);
const results = [];
for (let i = 0; i < repetitions; i++) {
results.push(doWork(faastModule).catch(_ => { }));
}
const rv = (await Promise.all(results)).filter(r => r);
await faastModule.cleanup();
const costSnapshot = await faastModule.costSnapshot();
const extraMetrics = workload.summarize(rv);
return { costSnapshot, config, extraMetrics };
}
/**
* Estimate the cost of a workload using multiple configurations and
* providers.
* @param userWorkload - a {@link CostAnalyzer.Workload} object specifying
* the workload to run and additional parameters.
* @returns A promise for a {@link CostAnalyzer.Result}
* @public
* @remarks
* It can be deceptively difficult to set optimal parameters for AWS Lambda
* and similar services. On the surface there appears to be only one
* parameter: memory size. Choosing more memory also gives more CPU
* performance, but it's unclear how much. It's also unclear where single
* core performance stops getting better. The workload cost analyzer solves
* these problems by making it easy to run cost experiments.
* ```text
* (AWS)
* ┌───────┐
* ┌────▶│ 128MB │
* │ └───────┘
* │ ┌───────┐
* ┌─────────────────┐ ├────▶│ 256MB │
* ┌──────────────┐ │ │ │ └───────┘
* │ workload │───▶│ │ │ ...
* └──────────────┘ │ │ │ ┌───────┐
* │ cost analyzer │─────┼────▶│3008MB │
* ┌──────────────┐ │ │ └───────┘
* │configurations│───▶│ │
* └──────────────┘ │ │
* └─────────────────┘
*
* ```
* `costAnalyzer` is the entry point. It automatically runs this workload
* against multiple configurations in parallel. Then it uses faast.js' cost
* snapshot mechanism to automatically determine the price of running the
* workload with each configuration.
*
* Example:
*
* ```typescript
* // functions.ts
* export function randomNumbers(n: number) {
* let sum = 0;
* for (let i = 0; i < n; i++) {
* sum += Math.random();
* }
* return sum;
* }
*
* // cost-analyzer-example.ts
* import { writeFileSync } from "fs";
* import { CostAnalyzer, FaastModule } from "faastjs";
* import * as funcs from "./functions";
*
* async function work(faastModule: FaastModule<typeof funcs>) {
* await faastModule.functions.randomNumbers(100000000);
* }
*
* async function main() {
* const results = await CostAnalyzer.analyze({ funcs, work });
* writeFileSync("cost.csv", results.csv());
* }
*
* main();
* ```
*
* Example output (this is printed to `console.log` unless the
* {@link CostAnalyzer.Workload.silent} is `true`):
* ```text
* ✔ aws 128MB queue 15.385s 0.274σ $0.00003921
* ✔ aws 192MB queue 10.024s 0.230σ $0.00003576
* ✔ aws 256MB queue 8.077s 0.204σ $0.00003779
* ▲ ▲ ▲ ▲ ▲ ▲
* │ │ │ │ │ │
* provider │ mode │ stdev average
* │ │ execution estimated
* memory │ time cost
* size │
* average cloud
* execution time
* ```
*
* The output lists the provider, memory size, ({@link CommonOptions.mode}),
* average time of a single execution of the workload, the standard
* deviation (in seconds) of the execution time, and average estimated cost
* for a single run of the workload.
*
* The "execution time" referenced here is not wall clock time, but rather
* execution time in the cloud function. The execution time does not include
* any time the workload spends waiting locally. If the workload invokes
* multiple cloud functions, their execution times will be summed even if
* they happen concurrently. This ensures the execution time and cost are
* aligned.
*/
async function analyze(userWorkload) {
const scheduleEstimate = (0, throttle_1.throttle)({
concurrency: 8,
rate: 4,
burst: 1,
retry: 3
}, estimate);
const { concurrency = workloadDefaults.concurrency } = userWorkload;
const workload = {
...workloadDefaults,
...userWorkload,
work: (0, throttle_1.throttle)({ concurrency }, userWorkload.work)
};
const { configurations } = workload;
const promises = configurations.map(config => scheduleEstimate(workload, config));
const format = workload.format || defaultFormat;
const renderer = workload.silent ? "silent" : "default";
const list = new listr_1.default(promises.map((promise, i) => {
const { provider, options } = configurations[i];
const { memorySize, mode } = options;
return {
title: `${provider} ${memorySize}MB ${mode}`,
task: async (_, task) => {
const { costSnapshot, extraMetrics } = await promise;
const total = (costSnapshot.total() / workload.repetitions).toFixed(8);
const { errors } = costSnapshot.stats;
const { executionTime } = costSnapshot.stats;
const message = `${ps(executionTime.mean)}s ${ps(executionTime.stdev)}σ $${total}`;
const errMessage = errors > 0 ? ` (${errors} errors)` : "";
const extra = (0, shared_1.keysOf)(extraMetrics)
.map(k => format(k, extraMetrics[k]))
.join(" ");
task.title = `${task.title} ${message}${errMessage} ${extra}`;
}
};
}), { concurrent: 8, nonTTYRenderer: renderer, renderer });
await list.run();
const results = await Promise.all(promises);
results.sort((a, b) => a.costSnapshot.options.memorySize - b.costSnapshot.options.memorySize);
return new Result(workload, results);
}
CostAnalyzer.analyze = analyze;
/**
* Cost analyzer results for each workload and configuration.
* @remarks
* The `estimates` property has the cost estimates for each configuration.
* See {@link CostAnalyzer.Estimate}.
* @public
*/
class Result {
/** @internal */
constructor(
/** The workload analyzed. */
workload,
/**
* Cost estimates for each configuration of the workload. See
* {@link CostAnalyzer.Estimate}.
*/
estimates) {
this.workload = workload;
this.estimates = estimates;
}
/**
* Comma-separated output of cost analyzer. One line per cost analyzer
* configuration.
* @remarks
* The columns are:
*
* - `memory`: The memory size allocated.
*
* - `cloud`: The cloud provider.
*
* - `mode`: See {@link CommonOptions.mode}.
*
* - `options`: A string summarizing other faast.js options applied to the
* `workload`. See {@link CommonOptions}.
*
* - `completed`: Number of repetitions that successfully completed.
*
* - `errors`: Number of invocations that failed.
*
* - `retries`: Number of retries that were attempted.
*
* - `cost`: The average cost of executing the workload once.
*
* - `executionTime`: the aggregate time spent executing on the provider for
* all cloud function invocations in the workload. This is averaged across
* repetitions.
*
* - `executionTimeStdev`: The standard deviation of `executionTime`.
*
* - `billedTime`: the same as `exectionTime`, except rounded up to the next
* 100ms for each invocation. Usually very close to `executionTime`.
*/
csv() {
const attributes = new Set();
this.estimates.forEach(est => (0, shared_1.keysOf)(est.extraMetrics).forEach(key => attributes.add(key)));
const columns = [
"memory",
"cloud",
"mode",
"options",
"completed",
"errors",
"retries",
"cost",
"executionTime",
"executionTimeStdev",
"billedTime",
...attributes
];
let rv = columns.join(",") + "\n";
this.estimates.forEach(({ costSnapshot, extraMetrics }) => {
const { memorySize, mode, ...rest } = costSnapshot.options;
const options = `"${(0, util_1.inspect)(rest).replace('"', '""')}"`;
const { completed, errors, retries, executionTime, estimatedBilledTime } = costSnapshot.stats;
const cost = (costSnapshot.total() / this.workload.repetitions).toFixed(8);
const formatter = this.workload.formatCSV || defaultFormatCSV;
const metrics = {};
for (const attr of attributes) {
metrics[attr] = formatter(attr, extraMetrics[attr]);
}
const row = {
memory: memorySize,
cloud: costSnapshot.provider,
mode,
options,
completed,
errors,
retries,
cost: `$${cost}`,
executionTime: ps(executionTime.mean),
executionTimeStdev: ps(executionTime.stdev),
billedTime: ps(estimatedBilledTime.mean),
...metrics
};
rv += (0, shared_1.keysOf)(row)
.map(k => String(row[k]))
.join(",");
rv += "\n";
});
return rv;
}
}
CostAnalyzer.Result = Result;
})(CostAnalyzer || (exports.CostAnalyzer = CostAnalyzer = {}));
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29zdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jb3N0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7QUFBQSwwREFBMEI7QUFDMUIsK0JBQStCO0FBQy9CLG9DQUE4QztBQUc5QyxxQ0FBdUQ7QUFDdkQseUNBQXNDO0FBR3RDOzs7O0dBSUc7QUFDSCxNQUFhLFVBQVU7SUEwQm5CLGdCQUFnQjtJQUNoQixZQUFZLEdBQThDO1FBQ3RELElBQUksQ0FBQyxJQUFJLEdBQUcsR0FBRyxDQUFDLElBQUksQ0FBQztRQUNyQixJQUFJLENBQUMsT0FBTyxHQUFHLEdBQUcsQ0FBQyxPQUFPLENBQUM7UUFDM0IsSUFBSSxDQUFDLElBQUksR0FBRyxHQUFHLENBQUMsSUFBSSxDQUFDO1FBQ3JCLElBQUksQ0FBQyxRQUFRLEdBQUcsR0FBRyxDQUFDLFFBQVEsQ0FBQztRQUM3QixJQUFJLENBQUMsVUFBVSxHQUFHLEdBQUcsQ0FBQyxVQUFVLENBQUM7UUFDakMsSUFBSSxDQUFDLE9BQU8sR0FBRyxHQUFHLENBQUMsT0FBTyxDQUFDO1FBQzNCLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxHQUFHLENBQUMsaUJBQWlCLENBQUM7SUFDbkQsQ0FBQztJQUVEOzs7T0FHRztJQUNILElBQUk7UUFDQSxPQUFPLElBQUksQ0FBQyxPQUFPLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQztJQUN4QyxDQUFDO0lBRUQ7OztPQUdHO0lBQ0gsZ0JBQWdCO1FBQ1osTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFTLEVBQUUsU0FBUyxHQUFHLENBQUMsRUFBRSxFQUFFLENBQ25DLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUMzRCxNQUFNLE9BQU8sR0FBRyxDQUFDLENBQVMsRUFBRSxFQUFFO1lBQzFC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Q0FBQyxhQUFhLENBQUMsS0FBSyxDQUFDO29CQUMzQyxVQUFVLEVBQUUsRUFBRSxDQUFDLG1CQUFtQixDQUFDLElBQUksQ0FBQztvQkFDeEMsR0FBRyxPQUFPO2lCQUNiLENBQUM7Z0JBRUYsRUFBRSxJQUFJLElBQUEsZUFBTSxFQUFDLEdBQUcsQ0FBQztxQkFDWixHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7cUJBQ3hCLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztnQkFDZixFQUFFLElBQUksSUFBSSxDQUFDO1lBQ2YsQ0FBQyxDQUFDLENBQUM7WUFDSCxPQUFPLEVBQUUsQ0FBQztRQUNkLENBQUM7S0FDSjtJQXJHWSxtQkFBTSxTQXFHbEIsQ0FBQTtBQUNMLENBQUMsRUFuZWdCLFlBQVksNEJBQVosWUFBWSxRQW1lNUIiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgTGlzdHIgZnJvbSBcImxpc3RyXCI7XG5pbXBvcnQgeyBpbnNwZWN0IH0gZnJvbSBcInV0aWxcIjtcbmltcG9ydCB7IGZhYXN0LCBGYWFzdE1vZHVsZSB9IGZyb20gXCIuLi9pbmRleFwiO1xuaW1wb3J0IHsgQXdzT3B0aW9ucyB9IGZyb20gXCIuL2F3cy9hd3MtZmFhc3RcIjtcbmltcG9ydCB7IEZ1bmN0aW9uU3RhdHMsIENvbW1vbk9wdGlvbnMgfSBmcm9tIFwiLi9wcm92aWRlclwiO1xuaW1wb3J0IHsgZjEsIGtleXNPZiwgU3RhdGlzdGljcywgc3VtIH0gZnJvbSBcIi4vc2hhcmVkXCI7XG5pbXBvcnQgeyB0aHJvdHRsZSB9IGZyb20gXCIuL3Rocm90dGxlXCI7XG5pbXBvcnQgeyBQcm9wZXJ0aWVzRXhjZXB0LCBBbnlGdW5jdGlvbiB9IGZyb20gXCIuL3R5cGVzXCI7XG5cbi8qKlxuICogQSBsaW5lIGl0ZW0gaW4gdGhlIGNvc3QgZXN0aW1hdGUsIGluY2x1ZGluZyB0aGUgcmVzb3VyY2UgdXNhZ2UgbWV0cmljXG4gKiBtZWFzdXJlZCBhbmQgaXRzIHByaWNpbmcuXG4gKiBAcHVibGljXG4gKi9cbmV4cG9ydCBjbGFzcyBDb3N0TWV0cmljIHtcbiAgICAvKiogVGhlIG5hbWUgb2YgdGhlIGNvc3QgbWV0cmljLCBlLmcuIGBmdW5jdGlvbkNhbGxEdXJhdGlvbmAgKi9cbiAgICByZWFkb25seSBuYW1lOiBzdHJpbmc7XG4gICAgLyoqIFRoZSBwcmljZSBpbiBVU0QgcGVyIHVuaXQgbWVhc3VyZWQuICovXG4gICAgcmVhZG9ubHkgcHJpY2luZzogbnVtYmVyO1xuICAgIC8qKiBUaGUgbmFtZSBvZiB0aGUgdW5pdHMgdGhhdCBwcmljaW5nIGlzIG1lYXN1cmVkIGluIGZvciB0aGlzIG1ldHJpYy4gKi9cbiAgICByZWFkb25seSB1bml0OiBzdHJpbmc7XG4gICAgLyoqIFRoZSBtZWFzdXJlZCB2YWx1ZSBvZiB0aGUgY29zdCBtZXRyaWMsIGluIHVuaXRzLiAqL1xuICAgIHJlYWRvbmx5IG1lYXN1cmVkOiBudW1iZXI7XG4gICAgLyoqXG4gICAgICogVGhlIHBsdXJhbCBmb3JtIG9mIHRoZSB1bml0IG5hbWUuIEJ5IGRlZmF1bHQgdGhlIHBsdXJhbCBmb3JtIHdpbGwgYmUgdGhlXG4gICAgICogbmFtZSBvZiB0aGUgdW5pdCB3aXRoIFwic1wiIGFwcGVuZGVkIGF0IHRoZSBlbmQsIHVubGVzcyB0aGUgbGFzdCBsZXR0ZXIgaXNcbiAgICAgKiBjYXBpdGFsaXplZCwgaW4gd2hpY2ggY2FzZSB0aGVyZSBpcyBubyBwbHVyYWwgZm9ybSAoZS5nLiBcIkdCXCIpLlxuICAgICAqL1xuICAgIHJlYWRvbmx5IHVuaXRQbHVyYWw/OiBzdHJpbmc7XG4gICAgLyoqXG4gICAgICogQW4gb3B0aW9uYWwgY29tbWVudCwgdXN1YWxseSBwcm92aWRpbmcgYSBsaW5rIHRvIHRoZSBwcm92aWRlcidzIHByaWNpbmdcbiAgICAgKiBwYWdlIGFuZCBvdGhlciBkYXRhLlxuICAgICAqL1xuICAgIHJlYWRvbmx5IGNvbW1lbnQ/OiBzdHJpbmc7XG4gICAgLyoqXG4gICAgICogVHJ1ZSBpZiB0aGlzIGNvc3QgbWV0cmljIGlzIG9ubHkgZm9yIGluZm9ybWF0aW9uYWwgcHVycG9zZXMgKGUuZy4gQVdTJ3NcbiAgICAgKiBgbG9nSW5nZXN0aW9uYCkgYW5kIGRvZXMgbm90IGNvbnRyaWJ1dGUgY29zdC5cbiAgICAgKi9cbiAgICByZWFkb25seSBpbmZvcm1hdGlvbmFsT25seT86IGJvb2xlYW47XG5cbiAgICAvKiogQGludGVybmFsICovXG4gICAgY29uc3RydWN0b3IoYXJnOiBQcm9wZXJ0aWVzRXhjZXB0PENvc3RNZXRyaWMsIEFueUZ1bmN0aW9uPikge1xuICAgICAgICB0aGlzLm5hbWUgPSBhcmcubmFtZTtcbiAgICAgICAgdGhpcy5wcmljaW5nID0gYXJnLnByaWNpbmc7XG4gICAgICAgIHRoaXMudW5pdCA9IGFyZy51bml0O1xuICAgICAgICB0aGlzLm1lYXN1cmVkID0gYXJnLm1lYXN1cmVkO1xuICAgICAgICB0aGlzLnVuaXRQbHVyYWwgPSBhcmcudW5pdFBsdXJhbDtcbiAgICAgICAgdGhpcy5jb21tZW50ID0gYXJnLmNvbW1lbnQ7XG4gICAgICAgIHRoaXMuaW5mb3JtYXRpb25hbE9ubHkgPSBhcmcuaW5mb3JtYXRpb25hbE9ubHk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogVGhlIGNvc3QgY29udHJpYnV0aW9uIG9mIHRoaXMgY29zdCBtZXRyaWMuIEVxdWFsIHRvXG4gICAgICoge0BsaW5rIENvc3RNZXRyaWMucHJpY2luZ30gKiB7QGxpbmsgQ29zdE1ldHJpYy5tZWFzdXJlZH0uXG4gICAgICovXG4gICAgY29zdCgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMucHJpY2luZyAqIHRoaXMubWVhc3VyZWQ7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJuIGEgc3RyaW5nIHdpdGggdGhlIGNvc3QgZXN0aW1hdGUgZm9yIHRoaXMgbWV0cmljLCBvbWl0dGluZ1xuICAgICAqIGNvbW1lbnRzLlxuICAgICAqL1xuICAgIGRlc2NyaWJlQ29zdE9ubHkoKSB7XG4gICAgICAgIGNvbnN0IHAgPSAobjogbnVtYmVyLCBwcmVjaXNpb24gPSA4KSA9PlxuICAgICAgICAgICAgTnVtYmVyLmlzSW50ZWdlcihuKSA/IFN0cmluZyhuKSA6IG4udG9GaXhlZChwcmVjaXNpb24pO1xuICAgICAgICBjb25zdCBnZXRVbml0ID0gKG46IG51bWJlcikgPT4ge1xuICAgICAgICAgICAgaWYgKG4gPiAxKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIChcbiAgICAgICAgICAgICAgICAgICAgdGhpcy51bml0UGx1cmFsIHx8XG4gICAgICAgICAgICAgICAgICAgICghdGhpcy51bml0Lm1hdGNoKC9bQS1aXSQvKSA/IHRoaXMudW5pdCArIFwic1wiIDogdGhpcy51bml0KVxuICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzLnVuaXQ7XG4gICAgICAgICAgICB9XG4gICAgICAgIH07XG5cbiAgICAgICAgY29uc3QgY29zdCA9IGAkJHtwKHRoaXMuY29zdCgpKX1gO1xuICAgICAgICBjb25zdCBwcmljaW5nID0gYCQke3AodGhpcy5wcmljaW5nKX0vJHt0aGlzLnVuaXR9YDtcbiAgICAgICAgY29uc3QgbWV0cmljID0gcCh0aGlzLm1lYXN1cmVkLCB0aGlzLnVuaXQgPT09IFwic2Vjb25kXCIgPyAxIDogOCk7XG4gICAgICAgIGNvbnN0IHVuaXQgPSBnZXRVbml0KHRoaXMubWVhc3VyZWQpO1xuXG4gICAgICAgIHJldHVybiBgJHt0aGlzLm5hbWUucGFkRW5kKDIxKX0gJHtwcmljaW5nLnBhZEVuZCgyMCl9ICR7bWV0cmljLnBhZFN0YXJ0KFxuICAgICAgICAgICAgMTJcbiAgICAgICAgKX0gJHt1bml0LnBhZEVuZCgxMCl9ICR7Y29zdC5wYWRFbmQoMTQpfWA7XG4gICAgfVxuXG4gICAgLyoqIERlc2NyaWJlIHRoaXMgY29zdCBtZXRyaWMsIGluY2x1ZGluZyBjb21tZW50cy4gKi9cbiAgICB0b1N0cmluZygpIHtcbiAgICAgICAgcmV0dXJuIGAke3RoaXMuZGVzY3JpYmVDb3N0T25seSgpfSR7XG4gICAgICAgICAgICAodGhpcy5jb21tZW50ICYmIGAvLyAke3RoaXMuY29tbWVudH1gKSB8fCBcIlwiXG4gICAgICAgIH1gO1xuICAgIH1cbn1cblxuLyoqXG4gKiBBIHN1bW1hcnkgb2YgdGhlIGNvc3RzIGluY3VycmVkIGJ5IGEgZmFhc3QuanMgbW9kdWxlIGF0IGEgcG9pbnQgaW4gdGltZS5cbiAqIE91dHB1dCBvZiB7QGxpbmsgRmFhc3RNb2R1bGUuY29zdFNuYXBzaG90fS5cbiAqIEByZW1hcmtzXG4gKiBDb3N0IGluZm9ybWF0aW9uIHByb3ZpZGVkIGJ5IGZhYXN0LmpzIGlzIGFuIGVzdGltYXRlLiBJdCBpcyBkZXJpdmVkIGZyb21cbiAqIGludGVybmFsIGZhYXN0LmpzIG1lYXN1cmVtZW50cyBhbmQgbm90IGJ5IGNvbnN1bHRpbmcgZGF0YSBwcm92aWRlZCBieSB5b3VyXG4gKiBjbG91ZCBwcm92aWRlci5cbiAqXG4gKiAqKkZhYXN0LmpzIGRvZXMgbm90IGd1YXJhbnRlZSB0aGUgYWNjdXJhY3kgb2YgY29zdCBlc3RpbWF0ZXMuKipcbiAqXG4gKiAqKlVzZSBhdCB5b3VyIG93biByaXNrLioqXG4gKlxuICogRXhhbXBsZSB1c2luZyBBV1M6XG4gKiBgYGB0eXBlc2NyaXB0XG4gKiBjb25zdCBmYWFzdE1vZHVsZSA9IGF3YWl0IGZhYXN0KFwiYXdzXCIsIG0pO1xuICogdHJ5IHtcbiAqICAgICAvLyBJbnZva2UgZmFhc3RNb2R1bGUuZnVuY3Rpb25zLipcbiAqIH0gZmluYWxseSB7XG4gKiAgICAgYXdhaXQgZmFhc3RNb2R1bGUuY2xlYW51cCgpO1xuICogICAgIGNvbnNvbGUubG9nKGBDb3N0IGVzdGltYXRlOmApO1xuICogICAgIGNvbnNvbGUubG9nKGAke2F3YWl0IGZhYXN0TW9kdWxlLmNvc3RTbmFwc2hvdCgpfWApO1xuICogfVxuICogYGBgXG4gKlxuICogQVdTIGV4YW1wbGUgb3V0cHV0OlxuICogYGBgdGV4dFxuICogQ29zdCBlc3RpbWF0ZTpcbiAqIGZ1bmN0aW9uQ2FsbER1cmF0aW9uICAkMC4wMDAwMjgxMy9zZWNvbmQgICAgICAgICAgICAwLjYgc2Vjb25kICAgICAkMC4wMDAwMTY4OCAgICA2OC40JSAgWzFdXG4gKiBzcXMgICAgICAgICAgICAgICAgICAgJDAuMDAwMDAwNDAvcmVxdWVzdCAgICAgICAgICAgICA5IHJlcXVlc3RzICAgJDAuMDAwMDAzNjAgICAgMTQuNiUgIFsyXVxuICogc25zICAgICAgICAgICAgICAgICAgICQwLjAwMDAwMDUwL3JlcXVlc3QgICAgICAgICAgICAgNSByZXF1ZXN0cyAgICQwLjAwMDAwMjUwICAgIDEwLjElICBbM11cbiAqIGZ1bmN0aW9uQ2FsbFJlcXVlc3RzICAkMC4wMDAwMDAyMC9yZXF1ZXN0ICAgICAgICAgICAgIDUgcmVxdWVzdHMgICAkMC4wMDAwMDEwMCAgICAgNC4xJSAgWzRdXG4gKiBvdXRib3VuZERhdGFUcmFuc2ZlciAgJDAuMDkwMDAwMDAvR0IgICAgICAgICAwLjAwMDAwNzY5IEdCICAgICAgICAgJDAuMDAwMDAwNjkgICAgIDIuOCUgIFs1XVxuICogbG9nSW5nZXN0aW9uICAgICAgICAgICQwLjUwMDAwMDAwL0dCICAgICAgICAgICAgICAgICAgMCBHQiAgICAgICAgICQwICAgICAgICAgICAgICAwLjAlICBbNl1cbiAqIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICQwLjAwMDAyNDY3IChVU0QpXG4gKlxuICogICAqIEVzdGltYXRlZCB1c2luZyBoaWdoZXN0IHByaWNpbmcgdGllciBmb3IgZWFjaCBzZXJ2aWNlLiBMaW1pdGF0aW9ucyBhcHBseS5cbiAqICAqKiBEb2VzIG5vdCBhY2NvdW50IGZvciBmcmVlIHRpZXIuXG4gKiBbMV06IGh0dHBzOi8vYXdzLmFtYXpvbi5jb20vbGFtYmRhL3ByaWNpbmcgKHJhdGUgPSAwLjAwMDAxNjY3LyhHQipzZWNvbmQpICogMS42ODc1IEdCID0gMC4wMDAwMjgxMy9zZWNvbmQpXG4gKiBbMl06IGh0dHBzOi8vYXdzLmFtYXpvbi5jb20vc3FzL3ByaWNpbmdcbiAqIFszXTogaHR0cHM6Ly9hd3MuYW1hem9uLmNvbS9zbnMvcHJpY2luZ1xuICogWzRdOiBodHRwczovL2F3cy5hbWF6b24uY29tL2xhbWJkYS9wcmljaW5nXG4gKiBbNV06IGh0dHBzOi8vYXdzLmFtYXpvbi5jb20vZWMyL3ByaWNpbmcvb24tZGVtYW5kLyNEYXRhX1RyYW5zZmVyXG4gKiBbNl06IGh0dHBzOi8vYXdzLmFtYXpvbi5jb20vY2xvdWR3YXRjaC9wcmljaW5nLyAtIExvZyBpbmdlc3Rpb24gY29zdHMgbm90IGN1cnJlbnRseSBpbmNsdWRlZC5cbiAqIGBgYFxuICpcbiAqIEEgY29zdCBzbmFwc2hvdCBjb250YWlucyBzZXZlcmFsIHtAbGluayBDb3N0TWV0cmljfSB2YWx1ZXMuIEVhY2ggYENvc3RNZXRyaWNgXG4gKiBzdW1tYXJpemVzIG9uZSBjb21wb25lbnQgb2YgdGhlIG92ZXJhbGwgY29zdCBvZiBleGVjdXRpbmcgdGhlIGZ1bmN0aW9ucyBzb1xuICogZmFyLiBTb21lIGNvc3QgbWV0cmljcyBhcmUgY29tbW9uIHRvIGFsbCBmYWFzdCBwcm92aWRlcnMsIGFuZCBvdGhlciBtZXRyaWNzXG4gKiBhcmUgcHJvdmlkZXItc3BlY2lmaWMuIFRoZSBjb21tb24gbWV0cmljcyBhcmU6XG4gKlxuICogLSBgZnVuY3Rpb25DYWxsRHVyYXRpb25gOiB0aGUgZXN0aW1hdGVkIGJpbGxlZCBDUFUgdGltZSAocm91bmRlZCB0byB0aGUgbmV4dFxuICogICAxMDBtcykgY29uc3VtZWQgYnkgY29tcGxldGVkIGNsb3VkIGZ1bmN0aW9uIGNhbGxzLiBUaGlzIGlzIHRoZSBtZXRyaWMgdGhhdFxuICogICB1c3VhbGx5IGRvbWluYXRlcyBjb3N0LlxuICpcbiAqIC0gYGZ1bmN0aW9uQ2FsbFJlcXVlc3RzYDogdGhlIG51bWJlciBvZiBpbnZvY2F0aW9uIHJlcXVlc3RzIG1hZGUuIE1vc3RcbiAqICAgcHJvdmlkZXJzIGNoYXJnZSBmb3IgZWFjaCBpbnZvY2F0aW9uLlxuICpcbiAqIFByb3ZpZGVyLXNwZWNpZmljIG1ldHJpY3MgdmFyeS4gRm9yIGV4YW1wbGUsIEFXUyBoYXMgdGhlIGZvbGxvd2luZyBhZGRpdGlvbmFsXG4gKiBtZXRyaWNzOlxuICpcbiAqIC0gYHNxc2A6IEFXUyBTaW1wbGUgUXVldWVpbmcgU2VydmljZS4gVGhpcyBtZXRyaWMgY2FwdHVyZXMgdGhlIG51bWJlciBvZlxuICogICBxdWV1ZSByZXF1ZXN0cyBtYWRlIHRvIGluc2VydCBhbmQgcmV0cmlldmUgcXVldWVkIHJlc3VsdHMgKGVhY2ggNjRrYiBjaHVua1xuICogICBpcyBjb3VudGVkIGFzIGFuIGFkZGl0aW9uYWwgcmVxdWVzdCkuIFNRUyBpcyB1c2VkIGV2ZW4gaWZcbiAqICAge0BsaW5rIENvbW1vbk9wdGlvbnMubW9kZX0gaXMgbm90IHNldCB0byBgXCJxdWV1ZVwiYCwgYmVjYXVzZSBpdCBpcyBuZWNlc3NhcnlcbiAqICAgZm9yIG1vbml0b3JpbmcgY2xvdWQgZnVuY3Rpb24gaW52b2NhdGlvbnMuXG4gKlxuICogLSBgc25zYDogQVdTIFNpbXBsZSBOb3RpZmljYXRpb24gU2VydmljZS4gU05TIGlzIHVzZWQgdG8gaW52b2tlIExhbWJkYVxuICogICBmdW5jdGlvbnMgd2hlbiB7QGxpbmsgQ29tbW9uT3B0aW9ucy5tb2RlfSBpcyBgXCJxdWV1ZVwiYC5cbiAqXG4gKiAtIGBvdXRib3VuZERhdGFUcmFuc2ZlcmA6IGFuIGVzdGltYXRlIG9mIHRoZSBuZXR3b3JrIGRhdGEgdHJhbnNmZXJyZWQgb3V0XG4gKiAgIGZyb20gdGhlIGNsb3VkIHByb3ZpZGVyIGZvciB0aGlzIGZhYXN0LmpzIG1vZHVsZS4gVGhpcyBlc3RpbWF0ZSBvbmx5IGNvdW50c1xuICogICBkYXRhIHJldHVybmVkIGZyb20gY2xvdWQgZnVuY3Rpb24gaW52b2NhdGlvbnMgYW5kIGluZnJhc3RydWN0dXJlIHRoYXRcbiAqICAgZmFhc3QuanMgc2V0cyB1cC4gSXQgZG9lcyBub3QgY291bnQgYW55IG91dGJvdW5kIGRhdGEgc2VudCBieSB5b3VyIGNsb3VkXG4gKiAgIGZ1bmN0aW9ucyB0aGF0IGFyZSBub3Qga25vd24gdG8gZmFhc3QuanMuIE5vdGUgdGhhdCBpZiB5b3UgcnVuIGZhYXN0LmpzIG9uXG4gKiAgIEVDMiBpbiB0aGUgc2FtZSByZWdpb24gKHNlZSB7QGxpbmsgQXdzT3B0aW9ucy5yZWdpb259KSwgdGhlbiB0aGUgZGF0YVxuICogICB0cmFuc2ZlciBjb3N0cyB3aWxsIGJlIHplcm8gKGhvd2V2ZXIsIHRoZSBjb3N0IHNuYXBzaG90IHdpbGwgbm90IGluY2x1ZGVcbiAqICAgRUMyIGNvc3RzKS4gQWxzbyBub3RlIHRoYXQgaWYgeW91ciBjbG91ZCBmdW5jdGlvbiB0cmFuc2ZlcnMgZGF0YSBmcm9tL3RvIFMzXG4gKiAgIGJ1Y2tldHMgaW4gdGhlIHNhbWUgcmVnaW9uLCB0aGVyZSBpcyBubyBjb3N0IGFzIGxvbmcgYXMgdGhhdCBkYXRhIGlzIG5vdFxuICogICByZXR1cm5lZCBmcm9tIHRoZSBmdW5jdGlvbi5cbiAqXG4gKiAtIGBsb2dJbmdlc3Rpb25gOiB0aGlzIGNvc3QgbWV0cmljIGlzIGFsd2F5cyB6ZXJvIGZvciBBV1MuIEl0IGlzIHByZXNlbnQgdG9cbiAqICAgcmVtaW5kIHRoZSB1c2VyIHRoYXQgQVdTIGNoYXJnZXMgZm9yIGxvZyBkYXRhIGluZ2VzdGVkIGJ5IENsb3VkV2F0Y2ggTG9nc1xuICogICB0aGF0IGFyZSBub3QgbWVhc3VyZWQgYnkgZmFhc3QuanMuIExvZyBlbnRyaWVzIG1heSBhcnJpdmUgc2lnbmlmaWNhbnRseVxuICogICBhZnRlciBmdW5jdGlvbiBleGVjdXRpb24gY29tcGxldGVzLCBhbmQgdGhlcmUgaXMgbm8gd2F5IGZvciBmYWFzdC5qcyB0b1xuICogICBrbm93IGV4YWN0bHkgaG93IGxvbmcgdG8gd2FpdCwgdGhlcmVmb3JlIGl0IGRvZXMgbm90IGF0dGVtcHQgdG8gbWVhc3VyZVxuICogICB0aGlzIGNvc3QuIEluIHByYWN0aWNlLCBpZiB5b3VyIGNsb3VkIGZ1bmN0aW9ucyBkbyBub3QgcGVyZm9ybSBleHRlbnNpdmVcbiAqICAgbG9nZ2luZyBvbiBhbGwgaW52b2NhdGlvbnMsIGxvZyBpbmdlc3Rpb24gY29zdHMgZnJvbSBmYWFzdC5qcyBhcmUgbGlrZWx5IHRvXG4gKiAgIGJlIGxvdyBvciBmYWxsIHdpdGhpbiB0aGUgZnJlZSB0aWVy