UNPKG

faastjs

Version:

Serverless batch computing made simple.

582 lines 83.1 kB
"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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RUFBRSxJQUFJLElBQUksRUFBRTtvQkFDaEIsYUFBYSxFQUFFLEVBQUUsQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDO29CQUNyQyxrQkFBa0IsRUFBRSxFQUFFLENBQUMsYUFBYSxDQUFDLEtBQUssQ0FBQztvQkFDM0MsVUFBVSxFQUFFLEVBQUUsQ0FBQyxtQkFBbUIsQ0FBQyxJQUFJLENBQUM7b0JBQ3hDLEdBQUcsT0FBTztpQkFDYixDQUFDO2dCQUVGLEVBQUUsSUFBSSxJQUFBLGVBQU0sRUFBQyxHQUFHLENBQUM7cUJBQ1osR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO3FCQUN4QixJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7Z0JBQ2YsRUFBRSxJQUFJLElBQUksQ0FBQztZQUNmLENBQUMsQ0FBQyxDQUFDO1lBQ0gsT0FBTyxFQUFFLENBQUM7UUFDZCxDQUFDO0tBQ0o7SUFyR1ksbUJBQU0sU0FxR2xCLENBQUE7QUFDTCxDQUFDLEVBbmVnQixZQUFZLDRCQUFaLFlBQVksUUFtZTVCIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IExpc3RyIGZyb20gXCJsaXN0clwiO1xuaW1wb3J0IHsgaW5zcGVjdCB9IGZyb20gXCJ1dGlsXCI7XG5pbXBvcnQgeyBmYWFzdCwgRmFhc3RNb2R1bGUgfSBmcm9tIFwiLi4vaW5kZXhcIjtcbmltcG9ydCB7IEF3c09wdGlvbnMgfSBmcm9tIFwiLi9hd3MvYXdzLWZhYXN0XCI7XG5pbXBvcnQgeyBGdW5jdGlvblN0YXRzLCBDb21tb25PcHRpb25zIH0gZnJvbSBcIi4vcHJvdmlkZXJcIjtcbmltcG9ydCB7IGYxLCBrZXlzT2YsIFN0YXRpc3RpY3MsIHN1bSB9IGZyb20gXCIuL3NoYXJlZFwiO1xuaW1wb3J0IHsgdGhyb3R0bGUgfSBmcm9tIFwiLi90aHJvdHRsZVwiO1xuaW1wb3J0IHsgUHJvcGVydGllc0V4Y2VwdCwgQW55RnVuY3Rpb24gfSBmcm9tIFwiLi90eXBlc1wiO1xuXG4vKipcbiAqIEEgbGluZSBpdGVtIGluIHRoZSBjb3N0IGVzdGltYXRlLCBpbmNsdWRpbmcgdGhlIHJlc291cmNlIHVzYWdlIG1ldHJpY1xuICogbWVhc3VyZWQgYW5kIGl0cyBwcmljaW5nLlxuICogQHB1YmxpY1xuICovXG5leHBvcnQgY2xhc3MgQ29zdE1ldHJpYyB7XG4gICAgLyoqIFRoZSBuYW1lIG9mIHRoZSBjb3N0IG1ldHJpYywgZS5nLiBgZnVuY3Rpb25DYWxsRHVyYXRpb25gICovXG4gICAgcmVhZG9ubHkgbmFtZTogc3RyaW5nO1xuICAgIC8qKiBUaGUgcHJpY2UgaW4gVVNEIHBlciB1bml0IG1lYXN1cmVkLiAqL1xuICAgIHJlYWRvbmx5IHByaWNpbmc6IG51bWJlcjtcbiAgICAvKiogVGhlIG5hbWUgb2YgdGhlIHVuaXRzIHRoYXQgcHJpY2luZyBpcyBtZWFzdXJlZCBpbiBmb3IgdGhpcyBtZXRyaWMuICovXG4gICAgcmVhZG9ubHkgdW5pdDogc3RyaW5nO1xuICAgIC8qKiBUaGUgbWVhc3VyZWQgdmFsdWUgb2YgdGhlIGNvc3QgbWV0cmljLCBpbiB1bml0cy4gKi9cbiAgICByZWFkb25seSBtZWFzdXJlZDogbnVtYmVyO1xuICAgIC8qKlxuICAgICAqIFRoZSBwbHVyYWwgZm9ybSBvZiB0aGUgdW5pdCBuYW1lLiBCeSBkZWZhdWx0IHRoZSBwbHVyYWwgZm9ybSB3aWxsIGJlIHRoZVxuICAgICAqIG5hbWUgb2YgdGhlIHVuaXQgd2l0aCBcInNcIiBhcHBlbmRlZCBhdCB0aGUgZW5kLCB1bmxlc3MgdGhlIGxhc3QgbGV0dGVyIGlzXG4gICAgICogY2FwaXRhbGl6ZWQsIGluIHdoaWNoIGNhc2UgdGhlcmUgaXMgbm8gcGx1cmFsIGZvcm0gKGUuZy4gXCJHQlwiKS5cbiAgICAgKi9cbiAgICByZWFkb25seSB1bml0UGx1cmFsPzogc3RyaW5nO1xuICAgIC8qKlxuICAgICAqIEFuIG9wdGlvbmFsIGNvbW1lbnQsIHVzdWFsbHkgcHJvdmlkaW5nIGEgbGluayB0byB0aGUgcHJvdmlkZXIncyBwcmljaW5nXG4gICAgICogcGFnZSBhbmQgb3RoZXIgZGF0YS5cbiAgICAgKi9cbiAgICByZWFkb25seSBjb21tZW50Pzogc3RyaW5nO1xuICAgIC8qKlxuICAgICAqIFRydWUgaWYgdGhpcyBjb3N0IG1ldHJpYyBpcyBvbmx5IGZvciBpbmZvcm1hdGlvbmFsIHB1cnBvc2VzIChlLmcuIEFXUydzXG4gICAgICogYGxvZ0luZ2VzdGlvbmApIGFuZCBkb2VzIG5vdCBjb250cmlidXRlIGNvc3QuXG4gICAgICovXG4gICAgcmVhZG9ubHkgaW5mb3JtYXRpb25hbE9ubHk/OiBib29sZWFuO1xuXG4gICAgLyoqIEBpbnRlcm5hbCAqL1xuICAgIGNvbnN0cnVjdG9yKGFyZzogUHJvcGVydGllc0V4Y2VwdDxDb3N0TWV0cmljLCBBbnlGdW5jdGlvbj4pIHtcbiAgICAgICAgdGhpcy5uYW1lID0gYXJnLm5hbWU7XG4gICAgICAgIHRoaXMucHJpY2luZyA9IGFyZy5wcmljaW5nO1xuICAgICAgICB0aGlzLnVuaXQgPSBhcmcudW5pdDtcbiAgICAgICAgdGhpcy5tZWFzdXJlZCA9IGFyZy5tZWFzdXJlZDtcbiAgICAgICAgdGhpcy51bml0UGx1cmFsID0gYXJnLnVuaXRQbHVyYWw7XG4gICAgICAgIHRoaXMuY29tbWVudCA9IGFyZy5jb21tZW50O1xuICAgICAgICB0aGlzLmluZm9ybWF0aW9uYWxPbmx5ID0gYXJnLmluZm9ybWF0aW9uYWxPbmx5O1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFRoZSBjb3N0IGNvbnRyaWJ1dGlvbiBvZiB0aGlzIGNvc3QgbWV0cmljLiBFcXVhbCB0b1xuICAgICAqIHtAbGluayBDb3N0TWV0cmljLnByaWNpbmd9ICoge0BsaW5rIENvc3RNZXRyaWMubWVhc3VyZWR9LlxuICAgICAqL1xuICAgIGNvc3QoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLnByaWNpbmcgKiB0aGlzLm1lYXN1cmVkO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybiBhIHN0cmluZyB3aXRoIHRoZSBjb3N0IGVzdGltYXRlIGZvciB0aGlzIG1ldHJpYywgb21pdHRpbmdcbiAgICAgKiBjb21tZW50cy5cbiAgICAgKi9cbiAgICBkZXNjcmliZUNvc3RPbmx5KCkge1xuICAgICAgICBjb25zdCBwID0gKG46IG51bWJlciwgcHJlY2lzaW9uID0gOCkgPT5cbiAgICAgICAgICAgIE51bWJlci5pc0ludGVnZXIobikgPyBTdHJpbmcobikgOiBuLnRvRml4ZWQocHJlY2lzaW9uKTtcbiAgICAgICAgY29uc3QgZ2V0VW5pdCA9IChuOiBudW1iZXIpID0+IHtcbiAgICAgICAgICAgIGlmIChuID4gMSkge1xuICAgICAgICAgICAgICAgIHJldHVybiAoXG4gICAgICAgICAgICAgICAgICAgIHRoaXMudW5pdFBsdXJhbCB8fFxuICAgICAgICAgICAgICAgICAgICAoIXRoaXMudW5pdC5tYXRjaCgvW0EtWl0kLykgPyB0aGlzLnVuaXQgKyBcInNcIiA6IHRoaXMudW5pdClcbiAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcy51bml0O1xuICAgICAgICAgICAgfVxuICAgICAgICB9O1xuXG4gICAgICAgIGNvbnN0IGNvc3QgPSBgJCR7cCh0aGlzLmNvc3QoKSl9YDtcbiAgICAgICAgY29uc3QgcHJpY2luZyA9IGAkJHtwKHRoaXMucHJpY2luZyl9LyR7dGhpcy51bml0fWA7XG4gICAgICAgIGNvbnN0IG1ldHJpYyA9IHAodGhpcy5tZWFzdXJlZCwgdGhpcy51bml0ID09PSBcInNlY29uZFwiID8gMSA6IDgpO1xuICAgICAgICBjb25zdCB1bml0ID0gZ2V0VW5pdCh0aGlzLm1lYXN1cmVkKTtcblxuICAgICAgICByZXR1cm4gYCR7dGhpcy5uYW1lLnBhZEVuZCgyMSl9ICR7cHJpY2luZy5wYWRFbmQoMjApfSAke21ldHJpYy5wYWRTdGFydChcbiAgICAgICAgICAgIDEyXG4gICAgICAgICl9ICR7dW5pdC5wYWRFbmQoMTApfSAke2Nvc3QucGFkRW5kKDE0KX1gO1xuICAgIH1cblxuICAgIC8qKiBEZXNjcmliZSB0aGlzIGNvc3QgbWV0cmljLCBpbmNsdWRpbmcgY29tbWVudHMuICovXG4gICAgdG9TdHJpbmcoKSB7XG4gICAgICAgIHJldHVybiBgJHt0aGlzLmRlc2NyaWJlQ29zdE9ubHkoKX0ke1xuICAgICAgICAgICAgKHRoaXMuY29tbWVudCAmJiBgLy8gJHt0aGlzLmNvbW1lbnR9YCkgfHwgXCJcIlxuICAgICAgICB9YDtcbiAgICB9XG59XG5cbi8qKlxuICogQSBzdW1tYXJ5IG9mIHRoZSBjb3N0cyBpbmN1cnJlZCBieSBhIGZhYXN0LmpzIG1vZHVsZSBhdCBhIHBvaW50IGluIHRpbWUuXG4gKiBPdXRwdXQgb2Yge0BsaW5rIEZhYXN0TW9kdWxlLmNvc3RTbmFwc2hvdH0uXG4gKiBAcmVtYXJrc1xuICogQ29zdCBpbmZvcm1hdGlvbiBwcm92aWRlZCBieSBmYWFzdC5qcyBpcyBhbiBlc3RpbWF0ZS4gSXQgaXMgZGVyaXZlZCBmcm9tXG4gKiBpbnRlcm5hbCBmYWFzdC5qcyBtZWFzdXJlbWVudHMgYW5kIG5vdCBieSBjb25zdWx0aW5nIGRhdGEgcHJvdmlkZWQgYnkgeW91clxuICogY2xvdWQgcHJvdmlkZXIuXG4gKlxuICogKipGYWFzdC5qcyBkb2VzIG5vdCBndWFyYW50ZWUgdGhlIGFjY3VyYWN5IG9mIGNvc3QgZXN0aW1hdGVzLioqXG4gKlxuICogKipVc2UgYXQgeW91ciBvd24gcmlzay4qKlxuICpcbiAqIEV4YW1wbGUgdXNpbmcgQVdTOlxuICogYGBgdHlwZXNjcmlwdFxuICogY29uc3QgZmFhc3RNb2R1bGUgPSBhd2FpdCBmYWFzdChcImF3c1wiLCBtKTtcbiAqIHRyeSB7XG4gKiAgICAgLy8gSW52b2tlIGZhYXN0TW9kdWxlLmZ1bmN0aW9ucy4qXG4gKiB9IGZpbmFsbHkge1xuICogICAgIGF3YWl0IGZhYXN0TW9kdWxlLmNsZWFudXAoKTtcbiAqICAgICBjb25zb2xlLmxvZyhgQ29zdCBlc3RpbWF0ZTpgKTtcbiAqICAgICBjb25zb2xlLmxvZyhgJHthd2FpdCBmYWFzdE1vZHVsZS5jb3N0U25hcHNob3QoKX1gKTtcbiAqIH1cbiAqIGBgYFxuICpcbiAqIEFXUyBleGFtcGxlIG91dHB1dDpcbiAqIGBgYHRleHRcbiAqIENvc3QgZXN0aW1hdGU6XG4gKiBmdW5jdGlvbkNhbGxEdXJhdGlvbiAgJDAuMDAwMDI4MTMvc2Vjb25kICAgICAgICAgICAgMC42IHNlY29uZCAgICAgJDAuMDAwMDE2ODggICAgNjguNCUgIFsxXVxuICogc3FzICAgICAgICAgICAgICAgICAgICQwLjAwMDAwMDQwL3JlcXVlc3QgICAgICAgICAgICAgOSByZXF1ZXN0cyAgICQwLjAwMDAwMzYwICAgIDE0LjYlICBbMl1cbiAqIHNucyAgICAgICAgICAgICAgICAgICAkMC4wMDAwMDA1MC9yZXF1ZXN0ICAgICAgICAgICAgIDUgcmVxdWVzdHMgICAkMC4wMDAwMDI1MCAgICAxMC4xJSAgWzNdXG4gKiBmdW5jdGlvbkNhbGxSZXF1ZXN0cyAgJDAuMDAwMDAwMjAvcmVxdWVzdCAgICAgICAgICAgICA1IHJlcXVlc3RzICAgJDAuMDAwMDAxMDAgICAgIDQuMSUgIFs0XVxuICogb3V0Ym91bmREYXRhVHJhbnNmZXIgICQwLjA5MDAwMDAwL0dCICAgICAgICAgMC4wMDAwMDc2OSBHQiAgICAgICAgICQwLjAwMDAwMDY5ICAgICAyLjglICBbNV1cbiAqIGxvZ0luZ2VzdGlvbiAgICAgICAgICAkMC41MDAwMDAwMC9HQiAgICAgICAgICAgICAgICAgIDAgR0IgICAgICAgICAkMCAgICAgICAgICAgICAgMC4wJSAgWzZdXG4gKiAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAkMC4wMDAwMjQ2NyAoVVNEKVxuICpcbiAqICAgKiBFc3RpbWF0ZWQgdXNpbmcgaGlnaGVzdCBwcmljaW5nIHRpZXIgZm9yIGVhY2ggc2VydmljZS4gTGltaXRhdGlvbnMgYXBwbHkuXG4gKiAgKiogRG9lcyBub3QgYWNjb3VudCBmb3IgZnJlZSB0aWVyLlxuICogWzFdOiBodHRwczovL2F3cy5hbWF6b24uY29tL2xhbWJkYS9wcmljaW5nIChyYXRlID0gMC4wMDAwMTY2Ny8oR0Iqc2Vjb25kKSAqIDEuNjg3NSBHQiA9IDAuMDAwMDI4MTMvc2Vjb25kKVxuICogWzJdOiBodHRwczovL2F3cy5hbWF6b24uY29tL3Nxcy9wcmljaW5nXG4gKiBbM106IGh0dHBzOi8vYXdzLmFtYXpvbi5jb20vc25zL3ByaWNpbmdcbiAqIFs0XTogaHR0cHM6Ly9hd3MuYW1hem9uLmNvbS9sYW1iZGEvcHJpY2luZ1xuICogWzVdOiBodHRwczovL2F3cy5hbWF6b24uY29tL2VjMi9wcmljaW5nL29uLWRlbWFuZC8jRGF0YV9UcmFuc2ZlclxuICogWzZdOiBodHRwczovL2F3cy5hbWF6b24uY29tL2Nsb3Vkd2F0Y2gvcHJpY2luZy8gLSBMb2cgaW5nZXN0aW9uIGNvc3RzIG5vdCBjdXJyZW50bHkgaW5jbHVkZWQuXG4gKiBgYGBcbiAqXG4gKiBBIGNvc3Qgc25hcHNob3QgY29udGFpbnMgc2V2ZXJhbCB7QGxpbmsgQ29zdE1ldHJpY30gdmFsdWVzLiBFYWNoIGBDb3N0TWV0cmljYFxuICogc3VtbWFyaXplcyBvbmUgY29tcG9uZW50IG9mIHRoZSBvdmVyYWxsIGNvc3Qgb2YgZXhlY3V0aW5nIHRoZSBmdW5jdGlvbnMgc29cbiAqIGZhci4gU29tZSBjb3N0IG1ldHJpY3MgYXJlIGNvbW1vbiB0byBhbGwgZmFhc3QgcHJvdmlkZXJzLCBhbmQgb3RoZXIgbWV0cmljc1xuICogYXJlIHByb3ZpZGVyLXNwZWNpZmljLiBUaGUgY29tbW9uIG1ldHJpY3MgYXJlOlxuICpcbiAqIC0gYGZ1bmN0aW9uQ2FsbER1cmF0aW9uYDogdGhlIGVzdGltYXRlZCBiaWxsZWQgQ1BVIHRpbWUgKHJvdW5kZWQgdG8gdGhlIG5leHRcbiAqICAgMTAwbXMpIGNvbnN1bWVkIGJ5IGNvbXBsZXRlZCBjbG91ZCBmdW5jdGlvbiBjYWxscy4gVGhpcyBpcyB0aGUgbWV0cmljIHRoYXRcbiAqICAgdXN1YWxseSBkb21pbmF0ZXMgY29zdC5cbiAqXG4gKiAtIGBmdW5jdGlvbkNhbGxSZXF1ZXN0c2A6IHRoZSBudW1iZXIgb2YgaW52b2NhdGlvbiByZXF1ZXN0cyBtYWRlLiBNb3N0XG4gKiAgIHByb3ZpZGVycyBjaGFyZ2UgZm9yIGVhY2ggaW52b2NhdGlvbi5cbiAqXG4gKiBQcm92aWRlci1zcGVjaWZpYyBtZXRyaWNzIHZhcnkuIEZvciBleGFtcGxlLCBBV1MgaGFzIHRoZSBmb2xsb3dpbmcgYWRkaXRpb25hbFxuICogbWV0cmljczpcbiAqXG4gKiAtIGBzcXNgOiBBV1MgU2ltcGxlIFF1ZXVlaW5nIFNlcnZpY2UuIFRoaXMgbWV0cmljIGNhcHR1cmVzIHRoZSBudW1iZXIgb2ZcbiAqICAgcXVldWUgcmVxdWVzdHMgbWFkZSB0byBpbnNlcnQgYW5kIHJldHJpZXZlIHF1ZXVlZCByZXN1bHRzIChlYWNoIDY0a2IgY2h1bmtcbiAqICAgaXMgY291bnRlZCBhcyBhbiBhZGRpdGlvbmFsIHJlcXVlc3QpLiBTUVMgaXMgdXNlZCBldmVuIGlmXG4gKiAgIHtAbGluayBDb21tb25PcHRpb25zLm1vZGV9IGlzIG5vdCBzZXQgdG8gYFwicXVldWVcImAsIGJlY2F1c2UgaXQgaXMgbmVjZXNzYXJ5XG4gKiAgIGZvciBtb25pdG9yaW5nIGNsb3VkIGZ1bmN0aW9uIGludm9jYXRpb25zLlxuICpcbiAqIC0gYHNuc2A6IEFXUyBTaW1wbGUgTm90aWZpY2F0aW9uIFNlcnZpY2UuIFNOUyBpcyB1c2VkIHRvIGludm9rZSBMYW1iZGFcbiAqICAgZnVuY3Rpb25zIHdoZW4ge0BsaW5rIENvbW1vbk9wdGlvbnMubW9kZX0gaXMgYFwicXVldWVcImAuXG4gKlxuICogLSBgb3V0Ym91bmREYXRhVHJhbnNmZXJgOiBhbiBlc3RpbWF0ZSBvZiB0aGUgbmV0d29yayBkYXRhIHRyYW5zZmVycmVkIG91dFxuICogICBmcm9tIHRoZSBjbG91ZCBwcm92aWRlciBmb3IgdGhpcyBmYWFzdC5qcyBtb2R1bGUuIFRoaXMgZXN0aW1hdGUgb25seSBjb3VudHNcbiAqICAgZGF0YSByZXR1cm5lZCBmcm9tIGNsb3VkIGZ1bmN0aW9uIGludm9jYXRpb25zIGFuZCBpbmZyYXN0cnVjdHVyZSB0aGF0XG4gKiAgIGZhYXN0LmpzIHNldHMgdXAuIEl0IGRvZXMgbm90IGNvdW50IGFueSBvdXRib3VuZCBkYXRhIHNlbnQgYnkgeW91ciBjbG91ZFxuICogICBmdW5jdGlvbnMgdGhhdCBhcmUgbm90IGtub3duIHRvIGZhYXN0LmpzLiBOb3RlIHRoYXQgaWYgeW91IHJ1biBmYWFzdC5qcyBvblxuICogICBFQzIgaW4gdGhlIHNhbWUgcmVnaW9uIChzZWUge0BsaW5rIEF3c09wdGlvbnMucmVnaW9ufSksIHRoZW4gdGhlIGRhdGFcbiAqICAgdHJhbnNmZXIgY29zdHMgd2lsbCBiZSB6ZXJvIChob3dldmVyLCB0aGUgY29zdCBzbmFwc2hvdCB3aWxsIG5vdCBpbmNsdWRlXG4gKiAgIEVDMiBjb3N0cykuIEFsc28gbm90ZSB0aGF0IGlmIHlvdXIgY2xvdWQgZnVuY3Rpb24gdHJhbnNmZXJzIGRhdGEgZnJvbS90byBTM1xuICogICBidWNrZXRzIGluIHRoZSBzYW1lIHJlZ2lvbiwgdGhlcmUgaXMgbm8gY29zdCBhcyBsb25nIGFzIHRoYXQgZGF0YSBpcyBub3RcbiAqICAgcmV0dXJuZWQgZnJvbSB0aGUgZnVuY3Rpb24uXG4gKlxuICogLSBgbG9nSW5nZXN0aW9uYDogdGhpcyBjb3N0IG1ldHJpYyBpcyBhbHdheXMgemVybyBmb3IgQVdTLiBJdCBpcyBwcmVzZW50IHRvXG4gKiAgIHJlbWluZCB0aGUgdXNlciB0aGF0IEFXUyBjaGFyZ2VzIGZvciBsb2cgZGF0YSBpbmdlc3RlZCBieSBDbG91ZFdhdGNoIExvZ3NcbiAqICAgdGhhdCBhcmUgbm90IG1lYXN1cmVkIGJ5IGZhYXN0LmpzLiBMb2cgZW50cmllcyBtYXkgYXJyaXZlIHNpZ25pZmljYW50bHlcbiAqICAgYWZ0ZXIgZnVuY3Rpb24gZXhlY3V0aW9uIGNvbXBsZXRlcywgYW5kIHRoZXJlIGlzIG5vIHdheSBmb3IgZmFhc3QuanMgdG9cbiAqICAga25vdyBleGFjdGx5IGhvdyBsb25nIHRvIHdhaXQsIHRoZXJlZm9yZSBpdCBkb2VzIG5vdCBhdHRlbXB0IHRvIG1lYXN1cmVcbiAqICAgdGhpcyBjb3N0LiBJbiBwcmFjdGljZSwgaWYgeW91ciBjbG91ZCBmdW5jdGlvbnMgZG8gbm90IHBlcmZvcm0gZXh0ZW5zaXZlXG4gKiAgIGxvZ2dpbmcgb24gYWxsIGludm9jYXRpb25z