From a5ef8e84a7e7da9bce5f8339a242af0915943178 Mon Sep 17 00:00:00 2001 From: Carlos Scheidegger Date: Wed, 21 Feb 2024 13:29:02 -0700 Subject: [PATCH 1/4] cleanup --- src/quarto.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/quarto.ts b/src/quarto.ts index 9ae705968c..f40ac0a968 100644 --- a/src/quarto.ts +++ b/src/quarto.ts @@ -13,13 +13,7 @@ import { } from "cliffy/command/mod.ts"; import { commands } from "./command/command.ts"; -import { - appendLogOptions, - cleanupLogger, - initializeLogger, - logError, - logOptions, -} from "./core/log.ts"; +import { appendLogOptions } from "./core/log.ts"; import { debug } from "log/mod.ts"; import { cleanupSessionTempDir, initSessionTempDir } from "./core/temp.ts"; @@ -36,9 +30,8 @@ import { reconfigureQuarto, } from "./core/devconfig.ts"; import { typstBinaryPath } from "./core/typst.ts"; -import { exitWithCleanup, onCleanup } from "./core/cleanup.ts"; +import { onCleanup } from "./core/cleanup.ts"; -import { parse } from "flags/mod.ts"; import { runScript } from "./command/run/run.ts"; // ensures run handlers are registered From b657553a371860fdc9a78d7a9e267298980a741d Mon Sep 17 00:00:00 2001 From: Carlos Scheidegger Date: Wed, 21 Feb 2024 13:29:41 -0700 Subject: [PATCH 2/4] perf - add the ability to capture file reads --- src/core/performance/metrics.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/core/performance/metrics.ts b/src/core/performance/metrics.ts index 16c0601c0d..9551375a59 100644 --- a/src/core/performance/metrics.ts +++ b/src/core/performance/metrics.ts @@ -6,9 +6,33 @@ import { inputTargetIndexCacheMetrics } from "../../project/project-index.ts"; +type FileReadRecord = { + path: string; + stack: string; +}; + +let fileReads: FileReadRecord[] | undefined = undefined; + +export function captureFileReads() { + fileReads = []; + + const originalReadTextFileSync = Deno.readTextFileSync; + + Deno.readTextFileSync = function (path: string | URL) { + try { + throw new Error("File read"); + } catch (e) { + const stack = e.stack!.split("\n").slice(2); + fileReads!.push({ path: String(path), stack }); + } + return originalReadTextFileSync(path); + }; +} + export function quartoPerformanceMetrics() { return { inputTargetIndexCache: inputTargetIndexCacheMetrics, + fileReads, }; } From ae6bc3452131e276c73bee641d734846c605733e Mon Sep 17 00:00:00 2001 From: Carlos Scheidegger Date: Wed, 21 Feb 2024 13:30:36 -0700 Subject: [PATCH 3/4] capture file reads when QUARTO_CAPTURE_PERFORMANCE_METRICS is set --- src/core/main.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/core/main.ts b/src/core/main.ts index e56d6965b5..bf9ffaeffc 100644 --- a/src/core/main.ts +++ b/src/core/main.ts @@ -10,6 +10,10 @@ import { initializeLogger, logError, logOptions } from "../../src/core/log.ts"; import { Args } from "flags/mod.ts"; import { parse } from "flags/mod.ts"; import { exitWithCleanup } from "./cleanup.ts"; +import { + captureFileReads, + reportPeformanceMetrics, +} from "./performance/metrics.ts"; type Runner = (args: Args) => Promise; export async function mainRunner(runner: Runner) { @@ -24,6 +28,10 @@ export async function mainRunner(runner: Runner) { Deno.addSignalListener("SIGTERM", abend); } + if (Deno.env.get("QUARTO_REPORT_PERFORMANCE_METRICS") !== undefined) { + captureFileReads(); + } + await runner(args); // if profiling, wait for 10 seconds before quitting @@ -33,6 +41,10 @@ export async function mainRunner(runner: Runner) { await new Promise((resolve) => setTimeout(resolve, 10000)); } + if (Deno.env.get("QUARTO_REPORT_PERFORMANCE_METRICS") !== undefined) { + reportPeformanceMetrics(); + } + exitWithCleanup(0); } catch (e) { if (e) { From 36fc51e540d32d6b673f79eaad4ef9a7b0e705b7 Mon Sep 17 00:00:00 2001 From: Carlos Scheidegger Date: Wed, 21 Feb 2024 13:32:00 -0700 Subject: [PATCH 4/4] perf - reuse extension context to avoid traversing the file system multiple times --- src/command/preview/cmd.ts | 6 +++++- src/command/render/project.ts | 2 +- src/command/render/render-shared.ts | 8 ++------ src/project/project-context.ts | 18 ++++++++++++------ src/project/serve/serve.ts | 19 ++++++++++++++----- src/project/serve/watch.ts | 8 +++++--- 6 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/command/preview/cmd.ts b/src/command/preview/cmd.ts index 921c3d7e10..3c4373653e 100644 --- a/src/command/preview/cmd.ts +++ b/src/command/preview/cmd.ts @@ -391,7 +391,11 @@ export const previewCommand = new Command() // see if we are serving a project or a file if (Deno.statSync(file).isDirectory) { // project preview - await serveProject(projectTarget, flags, args, { + const renderOptions = { + services: renderServices(notebookContext()), + flags, + }; + await serveProject(projectTarget, renderOptions, args, { port: options.port, host: options.host, browser: (options.browser === false || options.browse === false) diff --git a/src/command/render/project.ts b/src/command/render/project.ts index 5ac035f65d..db86f7f826 100644 --- a/src/command/render/project.ts +++ b/src/command/render/project.ts @@ -294,7 +294,7 @@ export async function renderProject( context = await projectContextForDirectory( context.dir, context.notebookContext, - projectRenderConfig.options.flags, + projectRenderConfig.options, ); // Validate that certain project properties haven't been mutated diff --git a/src/command/render/render-shared.ts b/src/command/render/render-shared.ts index 509e80639a..ee119692bd 100644 --- a/src/command/render/render-shared.ts +++ b/src/command/render/render-shared.ts @@ -18,10 +18,6 @@ import { renderProject } from "./project.ts"; import { renderFiles } from "./render-files.ts"; import { resourceFilesFromRenderedFile } from "./resources.ts"; import { RenderFlags, RenderOptions, RenderResult } from "./types.ts"; -import { - fileExecutionEngine, - fileExecutionEngineAndTarget, -} from "../../execute/engine.ts"; import { isProjectInputFile, @@ -49,12 +45,12 @@ export async function render( const nbContext = notebookContext(); // determine target context/files - let context = await projectContext(path, nbContext, options.flags); + let context = await projectContext(path, nbContext, options); // if there is no project parent and an output-dir was passed, then force a project if (!context && options.flags?.outputDir) { // recompute context - context = await projectContextForDirectory(path, nbContext, options.flags); + context = await projectContextForDirectory(path, nbContext, options); // force clean as --output-dir implies fully overwrite the target options.forceClean = options.flags.clean !== false; diff --git a/src/project/project-context.ts b/src/project/project-context.ts index 91b7695e17..ce6d6e660e 100644 --- a/src/project/project-context.ts +++ b/src/project/project-context.ts @@ -69,7 +69,11 @@ import { projectResolveFullMarkdownForFile, projectVarsFile, } from "./project-shared.ts"; -import { RenderFlags, RenderServices } from "../command/render/types.ts"; +import { + RenderFlags, + RenderOptions, + RenderServices, +} from "../command/render/types.ts"; import { kWebsite } from "./types/website/website-constants.ts"; import { readAndValidateYamlFromFile } from "../core/schema/validated-yaml.ts"; @@ -95,16 +99,18 @@ import { MappedString } from "../core/mapped-text.ts"; export async function projectContext( path: string, notebookContext: NotebookContext, - flags?: RenderFlags, + renderOptions?: RenderOptions, force = false, ): Promise { + const flags = renderOptions?.flags; let dir = normalizePath( Deno.statSync(path).isDirectory ? path : dirname(path), ); const originalDir = dir; - // create a shared extension context - const extensionContext = createExtensionContext(); + // create an extension context if one doesn't exist + const extensionContext = renderOptions?.services.extension || + createExtensionContext(); // first pass uses the config file resolve const configSchema = await getProjectConfigSchema(); @@ -636,9 +642,9 @@ async function resolveLanguageTranslations( export function projectContextForDirectory( path: string, notebookContext: NotebookContext, - flags?: RenderFlags, + renderOptions?: RenderOptions, ): Promise { - return projectContext(path, notebookContext, flags, true) as Promise< + return projectContext(path, notebookContext, renderOptions, true) as Promise< ProjectContext >; } diff --git a/src/project/serve/serve.ts b/src/project/serve/serve.ts index aa15c3e70e..0997615fb3 100644 --- a/src/project/serve/serve.ts +++ b/src/project/serve/serve.ts @@ -82,7 +82,11 @@ import { htmlResourceResolverPostprocessor } from "../../project/types/website/w import { inputFilesDir } from "../../core/render.ts"; import { kResources, kTargetFormat } from "../../config/constants.ts"; import { resourcesFromMetadata } from "../../command/render/resources.ts"; -import { RenderFlags, RenderResult } from "../../command/render/types.ts"; +import { + RenderFlags, + RenderOptions, + RenderResult, +} from "../../command/render/types.ts"; import { kPdfJsInitialPath, pdfJsBaseDir, @@ -124,18 +128,23 @@ export const kRenderDefault = "default"; export async function serveProject( target: string | ProjectContext, - flags: RenderFlags, + renderOptions: RenderOptions, pandocArgs: string[], options: ServeOptions, noServe: boolean, ) { let project: ProjectContext | undefined; - const nbContext = notebookContext(); + let flags = renderOptions.flags; + const nbContext = renderOptions.services.notebook; if (typeof target === "string") { if (target === ".") { target = Deno.cwd(); } - project = await projectContext(target, nbContext, flags); + project = await projectContext( + target, + nbContext, + renderOptions, + ); if (!project || !project?.config) { throw new Error(`${target} is not a project`); } @@ -301,7 +310,7 @@ export async function serveProject( project, extensionDirs, resourceFiles, - flags, + { ...renderOptions, flags }, pandocArgs, options, !pdfOutput, // we don't render on reload for pdf output diff --git a/src/project/serve/watch.ts b/src/project/serve/watch.ts index ca8a468bb8..4b230e262d 100644 --- a/src/project/serve/watch.ts +++ b/src/project/serve/watch.ts @@ -25,7 +25,7 @@ import { projectContext } from "../../project/project-context.ts"; import { ProjectWatcher, ServeOptions } from "./types.ts"; import { httpDevServer } from "../../core/http-devserver.ts"; -import { RenderFlags } from "../../command/render/types.ts"; +import { RenderOptions } from "../../command/render/types.ts"; import { renderProject } from "../../command/render/project.ts"; import { render } from "../../command/render/render-shared.ts"; import { renderServices } from "../../command/render/render-services.ts"; @@ -53,7 +53,7 @@ export function watchProject( project: ProjectContext, extensionDirs: string[], resourceFiles: string[], - flags: RenderFlags, + renderOptions: RenderOptions, pandocArgs: string[], options: ServeOptions, renderingOnReload: boolean, @@ -61,9 +61,11 @@ export function watchProject( stopServer: VoidFunction, ): Promise { const nbContext = notebookContext(); + const flags = renderOptions.flags; // helper to refresh project config const refreshProjectConfig = async () => { - project = (await projectContext(project.dir, nbContext, flags, false))!; + project = + (await projectContext(project.dir, nbContext, renderOptions, false))!; }; // See if we're in draft mode