diff --git a/packages/zapshot/src/index.js b/packages/zapshot/src/index.ts similarity index 94% rename from packages/zapshot/src/index.js rename to packages/zapshot/src/index.ts index 32c8835..49cd5c2 100644 --- a/packages/zapshot/src/index.js +++ b/packages/zapshot/src/index.ts @@ -1,5 +1,3 @@ -// @flow - export { Metric } from "./metric"; export { Metrics } from "./metrics"; export { measure } from "./measure"; diff --git a/packages/zapshot/src/measure.js b/packages/zapshot/src/measure.ts similarity index 70% rename from packages/zapshot/src/measure.js rename to packages/zapshot/src/measure.ts index fc461ee..ea8e22b 100644 --- a/packages/zapshot/src/measure.js +++ b/packages/zapshot/src/measure.ts @@ -1,31 +1,28 @@ -// @flow -import path from "path"; -// $FlowFixMe(perf_hooks-exists) -import { performance, PerformanceObserver } from "perf_hooks"; -import { Progress, type Logger } from "./progress"; -import { Metric, type Call } from "./metric"; +import path from "path"; // $FlowFixMe(perf_hooks-exists) -// FIXME: Move to lib -type Option = {| - +step: number, - +progressLogger: Logger -|}; +import { performance, PerformanceObserver } from "perf_hooks"; +import { Progress, Logger } from "./progress"; +import { Metric, Call } from "./metric"; // FIXME: Move to lib +type Option = { + readonly step: number; + readonly progressLogger: Logger; +}; type SetupFunction = ( timerify: typeof performance.timerify ) => any | Promise; type TestFunction = (arg: any) => void | Promise; -type Queue = Array<{| - name: string, - setup: SetupFunction, - fn: TestFunction -|}>; - +type Queue = Array<{ + name: string; + setup: SetupFunction; + fn: TestFunction; +}>; export const measure = ( target: string, option: Option ): Promise> => { const queue: Queue = []; + global.bench = (name: string, setup: SetupFunction, fn: TestFunction) => { queue.push({ name, @@ -36,21 +33,29 @@ export const measure = ( const run = async (queue: Queue): Promise> => { const metrics: Array = []; + for (let { name, fn, setup } of queue) { const calls: Array = []; - const markedCalls: { [string]: Array } = {}; - + const markedCalls: { + [x: string]: Array; + } = {}; const observer = new PerformanceObserver(list => { list.getEntries().forEach(entry => { if (entry.entryType === "measure") { - calls.push({ duration: entry.duration }); + calls.push({ + duration: entry.duration + }); } else { markedCalls[entry.name] = markedCalls[entry.name] || []; - markedCalls[entry.name].push({ duration: entry.duration }); + markedCalls[entry.name].push({ + duration: entry.duration + }); } }); }); - observer.observe({ entryTypes: ["function", "measure"] }); + observer.observe({ + entryTypes: ["function", "measure"] + }); const arg = setup(performance.timerify); for (let i = 0; i < option.step; i++) { @@ -63,7 +68,6 @@ export const measure = ( } observer.disconnect(); - metrics.push( new Metric({ name, @@ -74,15 +78,13 @@ export const measure = ( } return metrics; - }; + }; // $FlowFixMe(allow-dynamic-require) - // $FlowFixMe(allow-dynamic-require) require(target); const progress = new Progress(option.progressLogger); progress.setTotal(queue.length * option.step); progress.setTitle(path.relative(process.cwd(), target)); progress.tick(0); - return run(queue); }; diff --git a/packages/zapshot/src/metric.js b/packages/zapshot/src/metric.ts similarity index 75% rename from packages/zapshot/src/metric.js rename to packages/zapshot/src/metric.ts index 16e06ad..bd3d55d 100644 --- a/packages/zapshot/src/metric.js +++ b/packages/zapshot/src/metric.ts @@ -1,22 +1,22 @@ -// @flow import sumBy from "lodash/sumBy"; import meanBy from "lodash/meanBy"; import sortBy from "lodash/sortBy"; - -export type Call = {| - +duration: number -|}; - -export type MetricProps = {| - +name: string, - +calls: Array, - +markedCalls: { [name: string]: Array } -|}; - +export type Call = { + readonly duration: number; +}; +export type MetricProps = { + readonly name: string; + readonly calls: Array; + readonly markedCalls: { + [name: string]: Array; + }; +}; export class Metric { - +name: string; - +calls: Array; - +markedCalls: { [marker: string]: Array }; + name: string; + calls: Array; + markedCalls: { + [marker: string]: Array; + }; constructor({ name, calls, markedCalls }: MetricProps) { this.name = name; @@ -39,34 +39,41 @@ export class Metric { ); } - getMarked(mark: string): ?Array { + getMarked(mark: string): Array | undefined | null { if (!this.markedCalls[mark]) { return null; } + return this.markedCalls[mark]; } getMarkedCount(mark: string): number { const marked = this.getMarked(mark); + if (!marked) { return -1; } + return marked.length; } getMarkedTotal(mark: string): number { const marked = this.getMarked(mark); + if (!marked) { return -1; } + return sumBy(marked, "duration"); } getMarkedMean(mark: string): number { const marked = this.getMarked(mark); + if (!marked) { return -1; } + return meanBy(marked, "duration"); } } diff --git a/packages/zapshot/src/metrics.js b/packages/zapshot/src/metrics.ts similarity index 64% rename from packages/zapshot/src/metrics.js rename to packages/zapshot/src/metrics.ts index db28afb..3b64fa5 100644 --- a/packages/zapshot/src/metrics.js +++ b/packages/zapshot/src/metrics.ts @@ -1,20 +1,30 @@ -// @flow -import { Metric, type MetricProps } from "./metric"; - +import { Metric, MetricProps } from "./metric"; export class Metrics { - metrics: { [name: string]: Metric }; + metrics: { + [name: string]: Metric; + }; static fromArray(metrics: Array) { const metricsMap = {}; + for (let metric of metrics) { metricsMap[metric.name] = JSON.parse(JSON.stringify(metric)); } - return new Metrics({ metrics: metricsMap }); + return new Metrics({ + metrics: metricsMap + }); } - constructor({ metrics }: { metrics: { [name: string]: MetricProps } }) { + constructor({ + metrics + }: { + metrics: { + [name: string]: MetricProps; + }; + }) { this.metrics = {}; + for (let name in metrics) { this.metrics[name] = new Metric(metrics[name]); } @@ -29,7 +39,7 @@ export class Metrics { return Object.values(this.metrics); } - getByName(name: string): ?Metric { + getByName(name: string): Metric | undefined | null { return this.metrics[name] || null; } } diff --git a/packages/zapshot/src/progress.js b/packages/zapshot/src/progress.ts similarity index 98% rename from packages/zapshot/src/progress.js rename to packages/zapshot/src/progress.ts index bf86b7a..dddf9f7 100644 --- a/packages/zapshot/src/progress.js +++ b/packages/zapshot/src/progress.ts @@ -1,10 +1,7 @@ -// @flow - export interface Logger { log: (title: string, tick: number, total: number, eta: number) => void; tearDown: (spent: number) => void; } - export class Progress { logger: Logger; current: number; diff --git a/packages/zapshot/src/report.js b/packages/zapshot/src/report.js deleted file mode 100644 index c79b42a..0000000 --- a/packages/zapshot/src/report.js +++ /dev/null @@ -1,83 +0,0 @@ -// @flow -import sortBy from "lodash/sortBy"; -import { type Metric } from "./metric"; -import { type Metrics } from "./metrics"; - -export type Case = { - total: number, - mean: number, - diff: ?number, - diffPercentage: ?number, - marks: Array<{ - name: string, - total: number, - times: number, - diff: ?number, - diffPercentage: ?number - }> -}; - -export class Report { - cases: { [caseName: string]: Case }; - - static fromMetricses(metrics: Metrics, beforeMetrics: ?Metrics): Report { - const cases: { [caseName: string]: Case } = metrics - .getItems() - .reduce((acc, metric) => { - const before: ?Metric = beforeMetrics - ? beforeMetrics.getByName(metric.name) - : null; - const total = metric.getTotal(); - const totalDiff = diff(metric, before, m => m.getTotal()); - const marks = metric.getMarks().reduce((marks, mark) => { - const subTotal = metric.getMarkedTotal(mark); - const subTotalDiff = diff(metric, before, m => - m.getMarkedTotal(mark) - ); - - return marks.concat([ - { - name: mark, - total: subTotal, - times: metric.getMarkedCount(mark), - diff: subTotalDiff, - diffPercentage: subTotalDiff - ? subTotalDiff / subTotal * 100 - : null - } - ]); - }, []); - - return { - ...acc, - [metric.name]: { - total: total, - mean: metric.getMean(), - diff: totalDiff, - diffPercentage: totalDiff ? totalDiff / total * 100 : null, - marks: sortBy(marks, mark => -mark.total) - } - }; - }, {}); - - return new Report(cases); - } - - constructor(cases: { [caseName: string]: Case }) { - this.cases = cases; - } -} - -export const diff = ( - a: Metric, - b: ?Metric, - accessor: Metric => number -): ?number => { - if (!b) { - return null; - } - - const aValue = accessor(a); - const bValue = accessor(b); - return aValue - bValue; -}; diff --git a/packages/zapshot/src/report.ts b/packages/zapshot/src/report.ts new file mode 100644 index 0000000..2e2d7eb --- /dev/null +++ b/packages/zapshot/src/report.ts @@ -0,0 +1,77 @@ +import sortBy from "lodash/sortBy"; +import { Metric } from "./metric"; +import { Metrics } from "./metrics"; +export type Case = { + total: number; + mean: number; + diff: number | undefined | null; + diffPercentage: number | undefined | null; + marks: Array<{ + name: string; + total: number; + times: number; + diff: number | undefined | null; + diffPercentage: number | undefined | null; + }>; +}; +export class Report { + cases: { + [caseName: string]: Case; + }; + + static fromMetricses( + metrics: Metrics, + beforeMetrics: Metrics | undefined | null + ): Report { + const cases: { + [caseName: string]: Case; + } = metrics.getItems().reduce((acc, metric) => { + const before: Metric | undefined | null = beforeMetrics + ? beforeMetrics.getByName(metric.name) + : null; + const total = metric.getTotal(); + const totalDiff = diff(metric, before, m => m.getTotal()); + const marks = metric.getMarks().reduce((marks, mark) => { + const subTotal = metric.getMarkedTotal(mark); + const subTotalDiff = diff(metric, before, m => m.getMarkedTotal(mark)); + return marks.concat([ + { + name: mark, + total: subTotal, + times: metric.getMarkedCount(mark), + diff: subTotalDiff, + diffPercentage: subTotalDiff ? subTotalDiff / subTotal * 100 : null + } + ]); + }, []); + return { + ...acc, + [metric.name]: { + total: total, + mean: metric.getMean(), + diff: totalDiff, + diffPercentage: totalDiff ? totalDiff / total * 100 : null, + marks: sortBy(marks, mark => -mark.total) + } + }; + }, {}); + return new Report(cases); + } + + constructor(cases: { [caseName: string]: Case }) { + this.cases = cases; + } +} +export const diff = ( + a: Metric, + b: Metric | undefined | null, + accessor: (a: Metric) => number +): number | undefined | null => { + if (!b) { + return null; + } + + const aValue = accessor(a); + const bValue = accessor(b); + return aValue - bValue; +}; diff --git a/packages/zapshot/src/reporter.js b/packages/zapshot/src/reporter.ts similarity index 88% rename from packages/zapshot/src/reporter.js rename to packages/zapshot/src/reporter.ts index acf658b..8da4a01 100644 --- a/packages/zapshot/src/reporter.js +++ b/packages/zapshot/src/reporter.ts @@ -1,15 +1,12 @@ -// @flow /* eslint-disable no-console */ import maxBy from "lodash/maxBy"; import chalk from "chalk"; -import { type Report, type Case } from "./report"; - -export type ReportOptions = {| - +precision?: number, - +quiet: boolean, - +threshold: number -|}; - +import { Report, Case } from "./report"; +export type ReportOptions = { + readonly precision?: number; + readonly quiet: boolean; + readonly threshold: number; +}; export const report = ( report: Report, { precision = 2, quiet, threshold }: ReportOptions @@ -28,7 +25,6 @@ export const report = ( ); } else { const legend = `${indent(4)}Total`; - console.log(`\n${indent(2)}${caseName}`); console.log( `${legend}: ${c.total.toFixed(precision)}ms (mean: ${c.mean.toFixed( @@ -38,11 +34,11 @@ export const report = ( threshold })}` ); - const longestMark = maxBy(c.marks, mark => mark.name.length); const longestMarkWidth = longestMark ? longestMark.name.length : 0; const totalMaxWidth = c.marks.length > 0 ? c.marks[0].total.toFixed(precision).length : 0; + for (let mark of c.marks) { const percentage = (mark.total / c.total * 100).toFixed(0).padStart(5); console.log( @@ -61,14 +57,18 @@ export const report = ( return Promise.resolve(); }; - export const indent = (num: number): string => " ".repeat(num); - export const humanizeDiff = ( - diff: ?number, - diffPercentage: ?number, + diff: number | undefined | null, + diffPercentage: number | undefined | null, unit?: string, - { precision, threshold }: { precision: number, threshold: number } + { + precision, + threshold + }: { + precision: number; + threshold: number; + } ) => { const unitStr = unit ? ` ${unit}` : ""; @@ -81,6 +81,7 @@ export const humanizeDiff = ( const sign = diff > 0 ? "+" : ""; let humanizedDiff = sign + diff.toFixed(precision); let humanizedPercentage = sign + diffPercentage.toFixed(precision); + if (diffPercentage >= threshold) { humanizedDiff = chalk.red(humanizedDiff); humanizedPercentage = chalk.red(humanizedPercentage);