Skip to content

Performance Reporting

The TypeSpec compiler can report performance statistics after compilation, helping you identify bottlenecks and optimize your build process. Enable this feature by passing the --stats flag to the CLI:

Terminal window
tsp compile . --stats
Terminal window
tsp compile . --stats
TypeSpec compiler v1.9.0
Compilation completed successfully.
Compiler statistics:
Complexity:
Created types: 494
Finished types: 319
Performance:
loader: 19ms
resolver: 6ms
checker: 8ms
validation: 0ms
compiler: 0ms
linter: 0ms

The report includes:

  • Complexity metrics: Number of types created and finished during compilation
  • Performance breakdown: Time spent in each compilation phase (loading, resolving, type-checking, validation, and linting)

Emitters can report their own performance statistics, which are displayed alongside the compiler metrics in the same report.

Use the EmitContext.perf API to instrument your emitter code. The API provides several methods depending on your use case.

Best for when the start and stop points are in different parts of your code, or when you need conditional timing:

const timer = context.perf.startTimer("my-task");
// ... do some work across multiple statements
timer.stop();

Best for wrapping synchronous code blocks. Returns the result of the callback function:

const result = context.perf.time("my-task", () => {
// ... do some work
return computedValue;
});

Best for wrapping async operations. Returns a promise with the callback’s result:

const result = await context.perf.timeAsync("my-task", async () => {
// ... do some async work
return await fetchData();
});

Best when you already have timing data from another source (e.g., a child process or external tool):

emit.ts
const { duration } = runTask();
context.perf.reportTime("my-task", duration);

You can use the standalone perf utilities to measure duration in code that doesn’t have access to the emit context:

task-runner.ts
import { perf } from "@typespec/compiler/utils";
function runTask(): { duration: number } {
const timer = perf.startTimer();
// ... do some work
return { duration: timer.end() };
}

Here’s how to instrument a typical emitter with multiple phases:

import { EmitContext } from "@typespec/compiler";
export async function $onEmit(context: EmitContext) {
// Manual timer for the preparation phase
const timer = context.perf.startTimer("prepare");
prepare();
timer.stop();
// Wrap synchronous rendering with automatic timing
const renderResult = context.perf.time("render", () => render());
// Wrap async file writing with automatic timing
await context.perf.timeAsync("write", async () => writeOutput(renderResult));
}

Running tsp compile . --stats with this instrumented emitter produces:

Terminal window
tsp compile . --stats
TypeSpec compiler v1.9.0
Compilation completed successfully.
Compiler statistics:
Complexity:
Created types: 494
Finished types: 319
Performance:
loader: 19ms
resolver: 6ms
checker: 8ms
validation: 0ms
compiler: 0ms
linter: 0ms
emit: 128ms
my-emitter: 128ms
prepare: 39ms
render: 28ms
write: 51ms

The emitter’s custom metrics (prepare, render, write) appear nested under the emitter name, giving you a clear breakdown of where time is spent during code generation.