Skip to content

Commit 7738833

Browse files
committed
perf - track times in stats object, rewrite some code away from lodash
1 parent e41dfb8 commit 7738833

File tree

9 files changed

+109
-82
lines changed

9 files changed

+109
-82
lines changed

src/core/config.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,22 @@
55
*/
66

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

9-
export function mergeConfigs<T>(config: T, ...configs: Array<unknown>): T {
10-
// copy all configs so we don't mutate them
11-
config = ld.cloneDeep(config);
12-
configs = ld.cloneDeep(configs);
10+
export const mergeConfigs = makeTimedFunction(
11+
"mergeConfigs",
12+
function mergeConfigs<T>(config: T, ...configs: Array<unknown>): T {
13+
// copy all configs so we don't mutate them
14+
config = ld.cloneDeep(config);
15+
configs = ld.cloneDeep(configs);
1316

14-
return ld.mergeWith(
15-
config,
16-
...configs,
17-
mergeArrayCustomizer,
18-
);
19-
}
17+
return ld.mergeWith(
18+
config,
19+
...configs,
20+
mergeArrayCustomizer,
21+
);
22+
},
23+
);
2024

2125
export function mergeArrayCustomizer(objValue: unknown, srcValue: unknown) {
2226
if (ld.isArray(objValue) || ld.isArray(srcValue)) {

src/core/deno-dom.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,24 @@ import { debug } from "../deno_ral/log.ts";
99
import { HTMLDocument, initParser } from "deno_dom/deno-dom-wasm-noinit.ts";
1010
import { register } from "deno_dom/src/parser.ts";
1111
import { DOMParser } from "deno_dom/src/dom/dom-parser.ts";
12+
import { makeTimedFunctionAsync } from "./performance/function-times.ts";
1213

1314
export async function getDomParser() {
1415
await initDenoDom();
1516
return new DOMParser();
1617
}
1718

18-
export async function parseHtml(src: string): Promise<HTMLDocument> {
19-
await initDenoDom();
20-
const result = (new DOMParser()).parseFromString(src, "text/html");
21-
if (!result) {
22-
throw new Error("Couldn't parse string into HTML");
23-
}
24-
return result;
25-
}
19+
export const parseHtml = makeTimedFunctionAsync(
20+
"parseHtml",
21+
async function parseHtml(src: string): Promise<HTMLDocument> {
22+
await initDenoDom();
23+
const result = (new DOMParser()).parseFromString(src, "text/html");
24+
if (!result) {
25+
throw new Error("Couldn't parse string into HTML");
26+
}
27+
return result;
28+
},
29+
);
2630

2731
export async function writeDomToHtmlFile(
2832
doc: HTMLDocument,

src/core/lodash.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
/*
2-
* lodash.ts
3-
*
4-
* piecemeal exports of lodash to make the tree-shaker happier
5-
*
6-
* Copyright (C) 2022 Posit Software, PBC
7-
*
8-
*/
2+
* lodash.ts
3+
*
4+
* piecemeal exports of lodash to make the tree-shaker happier
5+
*
6+
* Copyright (C) 2022 Posit Software, PBC
7+
*/
98

109
import ld_cloneDeep from "lodash/cloneDeep.js";
1110
import ld_debounce from "lodash/debounce.js";
@@ -23,8 +22,9 @@ import ld_isObject from "lodash/isObject.js";
2322
import ld_isEqual from "lodash/isEqual.js";
2423
import ld_orderBy from "lodash/orderBy.js";
2524
import ld_escape from "lodash/escape.js";
25+
import { makeTimedFunction } from "./performance/function-times.ts";
2626

27-
export const cloneDeep = ld_cloneDeep;
27+
export const cloneDeep = makeTimedFunction("ld_cloneDeep", ld_cloneDeep);
2828
export const debounce = ld_debounce;
2929
export const difference = ld_difference;
3030
export const each = ld_each;

src/core/main.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import { parse } from "flags";
1212
import { exitWithCleanup } from "./cleanup.ts";
1313
import {
1414
captureFileReads,
15-
makeTimedFunctionAsync,
1615
type MetricsKeys,
1716
reportPerformanceMetrics,
1817
} from "./performance/metrics.ts";
18+
import { makeTimedFunctionAsync } from "./performance/function-times.ts";
1919
import { isWindows } from "../deno_ral/platform.ts";
2020

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

34-
if (Deno.env.get("QUARTO_REPORT_PERFORMANCE_METRICS") !== undefined) {
34+
const metricEnv = Deno.env.get("QUARTO_REPORT_PERFORMANCE_METRICS");
35+
if (metricEnv === "true" || metricEnv?.split(",").includes("fileReads")) {
3536
captureFileReads();
3637
}
3738

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

50-
const metricEnv = Deno.env.get("QUARTO_REPORT_PERFORMANCE_METRICS");
5151
if (metricEnv !== undefined) {
5252
if (metricEnv !== "true") {
5353
reportPerformanceMetrics(metricEnv.split(",") as MetricsKeys[]);

src/core/mapped-text.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { debug } from "../deno_ral/log.ts";
1515

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

2020
export type EitherString = mt.EitherString;
2121
export type MappedString = mt.MappedString;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* function-times.ts
3+
*
4+
* Copyright (C) 2025 Posit Software, PBC
5+
*/
6+
7+
import { Stats } from "./stats.ts";
8+
9+
export const functionTimes: Record<string, Stats> = {};
10+
11+
// deno-lint-ignore no-explicit-any
12+
export const makeTimedFunction = <T extends (...args: any[]) => any>(
13+
name: string,
14+
fn: T,
15+
): T => {
16+
if (Deno.env.get("QUARTO_REPORT_PERFORMANCE_METRICS") === undefined) {
17+
return fn;
18+
}
19+
functionTimes[name] = new Stats();
20+
return function (...args: Parameters<T>): ReturnType<T> {
21+
const start = performance.now();
22+
try {
23+
const result = fn(...args);
24+
return result;
25+
} finally {
26+
const end = performance.now();
27+
functionTimes[name].add(end - start);
28+
}
29+
} as T;
30+
};
31+
32+
export const timeCall = <T>(callback: () => T): { result: T; time: number } => {
33+
const start = performance.now();
34+
const result = callback();
35+
const end = performance.now();
36+
return { result, time: end - start };
37+
};
38+
39+
export const makeTimedFunctionAsync = <
40+
// deno-lint-ignore no-explicit-any
41+
T extends (...args: any[]) => Promise<any>,
42+
>(
43+
name: string,
44+
fn: T,
45+
): T => {
46+
if (Deno.env.get("QUARTO_REPORT_PERFORMANCE_METRICS") === undefined) {
47+
return fn;
48+
}
49+
functionTimes[name] = new Stats();
50+
return async function (...args: Parameters<T>): Promise<ReturnType<T>> {
51+
const start = performance.now();
52+
try {
53+
const result = await fn(...args);
54+
return result;
55+
} finally {
56+
const end = performance.now();
57+
functionTimes[name].add(end - start);
58+
}
59+
} as T;
60+
};

src/core/performance/metrics.ts

Lines changed: 4 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
import { inputTargetIndexCacheMetrics } from "../../project/project-index.ts";
8+
import { functionTimes } from "./function-times.ts";
89
import { Stats } from "./stats.ts";
910

1011
type FileReadRecord = {
@@ -30,51 +31,6 @@ export function captureFileReads() {
3031
};
3132
}
3233

33-
const functionTimes: Record<string, Stats> = {};
34-
// deno-lint-ignore no-explicit-any
35-
export const makeTimedFunction = <T extends (...args: any[]) => any>(
36-
name: string,
37-
fn: T,
38-
): T => {
39-
if (Deno.env.get("QUARTO_REPORT_PERFORMANCE_METRICS") === undefined) {
40-
return fn;
41-
}
42-
functionTimes[name] = new Stats();
43-
return function (...args: Parameters<T>): ReturnType<T> {
44-
const start = performance.now();
45-
try {
46-
const result = fn(...args);
47-
return result;
48-
} finally {
49-
const end = performance.now();
50-
functionTimes[name].add(end - start);
51-
}
52-
} as T;
53-
};
54-
55-
export const makeTimedFunctionAsync = <
56-
// deno-lint-ignore no-explicit-any
57-
T extends (...args: any[]) => Promise<any>,
58-
>(
59-
name: string,
60-
fn: T,
61-
): T => {
62-
if (Deno.env.get("QUARTO_REPORT_PERFORMANCE_METRICS") === undefined) {
63-
return fn;
64-
}
65-
functionTimes[name] = new Stats();
66-
return async function (...args: Parameters<T>): Promise<ReturnType<T>> {
67-
const start = performance.now();
68-
try {
69-
const result = await fn(...args);
70-
return result;
71-
} finally {
72-
const end = performance.now();
73-
functionTimes[name].add(end - start);
74-
}
75-
} as T;
76-
};
77-
7834
const metricsObject = {
7935
inputTargetIndexCache: inputTargetIndexCacheMetrics,
8036
fileReads,
@@ -102,14 +58,14 @@ export function quartoPerformanceMetrics(keys?: MetricsKeys[]) {
10258
}
10359

10460
export function reportPerformanceMetrics(keys?: MetricsKeys[]) {
105-
console.log("---");
106-
console.log("Performance metrics");
107-
console.log("Quarto:");
10861
const content = JSON.stringify(quartoPerformanceMetrics(keys), null, 2);
10962
const outFile = Deno.env.get("QUARTO_REPORT_PERFORMANCE_METRICS_FILE");
11063
if (outFile) {
11164
Deno.writeTextFileSync(outFile, content);
11265
} else {
66+
console.log("---");
67+
console.log("Performance metrics");
68+
console.log("Quarto:");
11369
console.log(content);
11470
}
11571
}

src/core/performance/stats.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export class Stats {
3737
if (this.count === 0) {
3838
return {
3939
count: 0,
40+
total: 0,
4041
};
4142
}
4243
return {
@@ -45,6 +46,7 @@ export class Stats {
4546
count: this.count,
4647
mean: this.mean,
4748
variance: this.m2 / this.count,
49+
total: this.mean * this.count,
4850
};
4951
}
5052
}

src/project/project-context.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ import { computeProjectEnvironment } from "./project-environment.ts";
9999
import { ProjectEnvironment } from "./project-environment-types.ts";
100100
import { NotebookContext } from "../render/notebook/notebook-types.ts";
101101
import { MappedString } from "../core/mapped-text.ts";
102+
import { timeCall } from "../core/performance/function-times.ts";
103+
import { assertEquals } from "testing/asserts";
102104

103105
export async function projectContext(
104106
path: string,
@@ -826,10 +828,9 @@ export async function projectInputFiles(
826828
inclusion.engineIntermediates
827829
).flat();
828830

829-
const inputFiles = ld.difference(
830-
ld.uniq(files),
831-
ld.uniq(intermediateFiles),
832-
) as string[];
831+
const inputFiles = Array.from(
832+
new Set(files).difference(new Set(intermediateFiles)),
833+
);
833834

834835
return { files: inputFiles, engines };
835836
}

0 commit comments

Comments
 (0)