Skip to content

Commit

Permalink
perf - track times in stats object, rewrite some code away from lodash
Browse files Browse the repository at this point in the history
  • Loading branch information
cscheid committed Jan 13, 2025
1 parent e41dfb8 commit 7738833
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 82 deletions.
24 changes: 14 additions & 10 deletions src/core/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,22 @@
*/

import * as ld from "./lodash.ts";
import { makeTimedFunction } from "./performance/function-times.ts";

export function mergeConfigs<T>(config: T, ...configs: Array<unknown>): T {
// copy all configs so we don't mutate them
config = ld.cloneDeep(config);
configs = ld.cloneDeep(configs);
export const mergeConfigs = makeTimedFunction(
"mergeConfigs",
function mergeConfigs<T>(config: T, ...configs: Array<unknown>): T {
// copy all configs so we don't mutate them
config = ld.cloneDeep(config);
configs = ld.cloneDeep(configs);

return ld.mergeWith(
config,
...configs,
mergeArrayCustomizer,
);
}
return ld.mergeWith(
config,
...configs,
mergeArrayCustomizer,
);
},
);

export function mergeArrayCustomizer(objValue: unknown, srcValue: unknown) {
if (ld.isArray(objValue) || ld.isArray(srcValue)) {
Expand Down
20 changes: 12 additions & 8 deletions src/core/deno-dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,24 @@ import { debug } from "../deno_ral/log.ts";
import { HTMLDocument, initParser } from "deno_dom/deno-dom-wasm-noinit.ts";
import { register } from "deno_dom/src/parser.ts";
import { DOMParser } from "deno_dom/src/dom/dom-parser.ts";
import { makeTimedFunctionAsync } from "./performance/function-times.ts";

export async function getDomParser() {
await initDenoDom();
return new DOMParser();
}

export async function parseHtml(src: string): Promise<HTMLDocument> {
await initDenoDom();
const result = (new DOMParser()).parseFromString(src, "text/html");
if (!result) {
throw new Error("Couldn't parse string into HTML");
}
return result;
}
export const parseHtml = makeTimedFunctionAsync(
"parseHtml",
async function parseHtml(src: string): Promise<HTMLDocument> {
await initDenoDom();
const result = (new DOMParser()).parseFromString(src, "text/html");
if (!result) {
throw new Error("Couldn't parse string into HTML");
}
return result;
},
);

export async function writeDomToHtmlFile(
doc: HTMLDocument,
Expand Down
16 changes: 8 additions & 8 deletions src/core/lodash.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
/*
* lodash.ts
*
* piecemeal exports of lodash to make the tree-shaker happier
*
* Copyright (C) 2022 Posit Software, PBC
*
*/
* lodash.ts
*
* piecemeal exports of lodash to make the tree-shaker happier
*
* Copyright (C) 2022 Posit Software, PBC
*/

import ld_cloneDeep from "lodash/cloneDeep.js";
import ld_debounce from "lodash/debounce.js";
Expand All @@ -23,8 +22,9 @@ import ld_isObject from "lodash/isObject.js";
import ld_isEqual from "lodash/isEqual.js";
import ld_orderBy from "lodash/orderBy.js";
import ld_escape from "lodash/escape.js";
import { makeTimedFunction } from "./performance/function-times.ts";

export const cloneDeep = ld_cloneDeep;
export const cloneDeep = makeTimedFunction("ld_cloneDeep", ld_cloneDeep);
export const debounce = ld_debounce;
export const difference = ld_difference;
export const each = ld_each;
Expand Down
6 changes: 3 additions & 3 deletions src/core/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import { parse } from "flags";
import { exitWithCleanup } from "./cleanup.ts";
import {
captureFileReads,
makeTimedFunctionAsync,
type MetricsKeys,
reportPerformanceMetrics,
} from "./performance/metrics.ts";
import { makeTimedFunctionAsync } from "./performance/function-times.ts";
import { isWindows } from "../deno_ral/platform.ts";

type Runner = (args: Args) => Promise<unknown>;
Expand All @@ -31,7 +31,8 @@ export async function mainRunner(runner: Runner) {
Deno.addSignalListener("SIGTERM", abend);
}

if (Deno.env.get("QUARTO_REPORT_PERFORMANCE_METRICS") !== undefined) {
const metricEnv = Deno.env.get("QUARTO_REPORT_PERFORMANCE_METRICS");
if (metricEnv === "true" || metricEnv?.split(",").includes("fileReads")) {
captureFileReads();
}

Expand All @@ -47,7 +48,6 @@ export async function mainRunner(runner: Runner) {
await new Promise((resolve) => setTimeout(resolve, 10000));
}

const metricEnv = Deno.env.get("QUARTO_REPORT_PERFORMANCE_METRICS");
if (metricEnv !== undefined) {
if (metricEnv !== "true") {
reportPerformanceMetrics(metricEnv.split(",") as MetricsKeys[]);
Expand Down
2 changes: 1 addition & 1 deletion src/core/mapped-text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { debug } from "../deno_ral/log.ts";

import * as mt from "./lib/mapped-text.ts";
import { withTiming } from "./timing.ts";
import { makeTimedFunction } from "./performance/metrics.ts";
import { makeTimedFunction } from "./performance/function-times.ts";

export type EitherString = mt.EitherString;
export type MappedString = mt.MappedString;
Expand Down
60 changes: 60 additions & 0 deletions src/core/performance/function-times.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* function-times.ts
*
* Copyright (C) 2025 Posit Software, PBC
*/

import { Stats } from "./stats.ts";

export const functionTimes: Record<string, Stats> = {};

// deno-lint-ignore no-explicit-any
export const makeTimedFunction = <T extends (...args: any[]) => any>(
name: string,
fn: T,
): T => {
if (Deno.env.get("QUARTO_REPORT_PERFORMANCE_METRICS") === undefined) {
return fn;
}
functionTimes[name] = new Stats();
return function (...args: Parameters<T>): ReturnType<T> {
const start = performance.now();
try {
const result = fn(...args);
return result;
} finally {
const end = performance.now();
functionTimes[name].add(end - start);
}
} as T;
};

export const timeCall = <T>(callback: () => T): { result: T; time: number } => {
const start = performance.now();
const result = callback();
const end = performance.now();
return { result, time: end - start };
};

export const makeTimedFunctionAsync = <
// deno-lint-ignore no-explicit-any
T extends (...args: any[]) => Promise<any>,
>(
name: string,
fn: T,
): T => {
if (Deno.env.get("QUARTO_REPORT_PERFORMANCE_METRICS") === undefined) {
return fn;
}
functionTimes[name] = new Stats();
return async function (...args: Parameters<T>): Promise<ReturnType<T>> {
const start = performance.now();
try {
const result = await fn(...args);
return result;
} finally {
const end = performance.now();
functionTimes[name].add(end - start);
}
} as T;
};
52 changes: 4 additions & 48 deletions src/core/performance/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import { inputTargetIndexCacheMetrics } from "../../project/project-index.ts";
import { functionTimes } from "./function-times.ts";
import { Stats } from "./stats.ts";

type FileReadRecord = {
Expand All @@ -30,51 +31,6 @@ export function captureFileReads() {
};
}

const functionTimes: Record<string, Stats> = {};
// deno-lint-ignore no-explicit-any
export const makeTimedFunction = <T extends (...args: any[]) => any>(
name: string,
fn: T,
): T => {
if (Deno.env.get("QUARTO_REPORT_PERFORMANCE_METRICS") === undefined) {
return fn;
}
functionTimes[name] = new Stats();
return function (...args: Parameters<T>): ReturnType<T> {
const start = performance.now();
try {
const result = fn(...args);
return result;
} finally {
const end = performance.now();
functionTimes[name].add(end - start);
}
} as T;
};

export const makeTimedFunctionAsync = <
// deno-lint-ignore no-explicit-any
T extends (...args: any[]) => Promise<any>,
>(
name: string,
fn: T,
): T => {
if (Deno.env.get("QUARTO_REPORT_PERFORMANCE_METRICS") === undefined) {
return fn;
}
functionTimes[name] = new Stats();
return async function (...args: Parameters<T>): Promise<ReturnType<T>> {
const start = performance.now();
try {
const result = await fn(...args);
return result;
} finally {
const end = performance.now();
functionTimes[name].add(end - start);
}
} as T;
};

const metricsObject = {
inputTargetIndexCache: inputTargetIndexCacheMetrics,
fileReads,
Expand Down Expand Up @@ -102,14 +58,14 @@ export function quartoPerformanceMetrics(keys?: MetricsKeys[]) {
}

export function reportPerformanceMetrics(keys?: MetricsKeys[]) {
console.log("---");
console.log("Performance metrics");
console.log("Quarto:");
const content = JSON.stringify(quartoPerformanceMetrics(keys), null, 2);
const outFile = Deno.env.get("QUARTO_REPORT_PERFORMANCE_METRICS_FILE");
if (outFile) {
Deno.writeTextFileSync(outFile, content);
} else {
console.log("---");
console.log("Performance metrics");
console.log("Quarto:");
console.log(content);
}
}
2 changes: 2 additions & 0 deletions src/core/performance/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export class Stats {
if (this.count === 0) {
return {
count: 0,
total: 0,
};
}
return {
Expand All @@ -45,6 +46,7 @@ export class Stats {
count: this.count,
mean: this.mean,
variance: this.m2 / this.count,
total: this.mean * this.count,
};
}
}
9 changes: 5 additions & 4 deletions src/project/project-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ import { computeProjectEnvironment } from "./project-environment.ts";
import { ProjectEnvironment } from "./project-environment-types.ts";
import { NotebookContext } from "../render/notebook/notebook-types.ts";
import { MappedString } from "../core/mapped-text.ts";
import { timeCall } from "../core/performance/function-times.ts";
import { assertEquals } from "testing/asserts";

export async function projectContext(
path: string,
Expand Down Expand Up @@ -826,10 +828,9 @@ export async function projectInputFiles(
inclusion.engineIntermediates
).flat();

const inputFiles = ld.difference(
ld.uniq(files),
ld.uniq(intermediateFiles),
) as string[];
const inputFiles = Array.from(
new Set(files).difference(new Set(intermediateFiles)),
);

return { files: inputFiles, engines };
}
Expand Down

0 comments on commit 7738833

Please sign in to comment.