diff --git a/scripts/generate-perf-trace-events.sh b/scripts/generate-perf-trace-events.sh new file mode 100755 index 0000000000..c84cd7e447 --- /dev/null +++ b/scripts/generate-perf-trace-events.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# This file is not run often, so we don't need to make it part of the build system. +# We do this because the event names have to be compile-time constants. + + +export TRACE_EVENTS=$(rg 'bun\.perf\.trace\("([^"]*)"\)' -t zig --json \ + | jq -r 'select(.type == "match")' \ + | jq -r '.data.submatches[].match.text' \ + | cut -d'"' -f2 \ + | sort \ + | uniq) + +echo "// Generated with scripts/generate-perf-trace-events.sh" > src/bun.js/bindings/generated_perf_trace_events.h +echo "// clang-format off" >> src/bun.js/bindings/generated_perf_trace_events.h +echo "#define FOR_EACH_TRACE_EVENT(macro) \\" >> src/bun.js/bindings/generated_perf_trace_events.h +i=0 +for event in $TRACE_EVENTS; do + echo " macro($event, $((i++))) \\" >> src/bun.js/bindings/generated_perf_trace_events.h +done +echo " // end" >> src/bun.js/bindings/generated_perf_trace_events.h + +echo "Generated src/bun.js/bindings/generated_perf_trace_events.h" + +echo "// Generated with scripts/generate-perf-trace-events.sh" > src/generated_perf_trace_events.zig +echo "pub const PerfEvent = enum(i32) {" >> src/generated_perf_trace_events.zig +for event in $TRACE_EVENTS; do + echo " @\"$event\"," >> src/generated_perf_trace_events.zig +done +echo "};" >> src/generated_perf_trace_events.zig + +echo "Generated src/generated_perf_trace_events.zig" \ No newline at end of file diff --git a/src/StandaloneModuleGraph.zig b/src/StandaloneModuleGraph.zig index 64ca13d111..807ecf520b 100644 --- a/src/StandaloneModuleGraph.zig +++ b/src/StandaloneModuleGraph.zig @@ -326,7 +326,7 @@ pub const StandaloneModuleGraph = struct { } pub fn toBytes(allocator: std.mem.Allocator, prefix: []const u8, output_files: []const bun.options.OutputFile, output_format: bun.options.Format) ![]u8 { - var serialize_trace = bun.tracy.traceNamed(@src(), "StandaloneModuleGraph.serialize"); + var serialize_trace = bun.perf.trace("StandaloneModuleGraph.serialize"); defer serialize_trace.end(); var entry_point_id: ?usize = null; diff --git a/src/bun.js/RuntimeTranspilerCache.zig b/src/bun.js/RuntimeTranspilerCache.zig index d754282eef..22d93515e2 100644 --- a/src/bun.js/RuntimeTranspilerCache.zig +++ b/src/bun.js/RuntimeTranspilerCache.zig @@ -160,7 +160,7 @@ pub const RuntimeTranspilerCache = struct { output_code: OutputCode, exports_kind: bun.JSAst.ExportsKind, ) !void { - var tracer = bun.tracy.traceNamed(@src(), "RuntimeTranspilerCache.save"); + var tracer = bun.perf.trace("RuntimeTranspilerCache.save"); defer tracer.end(); // atomically write to a tmpfile and then move it to the final destination @@ -457,7 +457,7 @@ pub const RuntimeTranspilerCache = struct { sourcemap_allocator: std.mem.Allocator, output_code_allocator: std.mem.Allocator, ) !Entry { - var tracer = bun.tracy.traceNamed(@src(), "RuntimeTranspilerCache.fromFile"); + var tracer = bun.perf.trace("RuntimeTranspilerCache.fromFile"); defer tracer.end(); var cache_file_path_buf: bun.PathBuffer = undefined; @@ -531,7 +531,7 @@ pub const RuntimeTranspilerCache = struct { source_code: bun.String, exports_kind: bun.JSAst.ExportsKind, ) !void { - var tracer = bun.tracy.traceNamed(@src(), "RuntimeTranspilerCache.toFile"); + var tracer = bun.perf.trace("RuntimeTranspilerCache.toFile"); defer tracer.end(); var cache_file_path_buf: bun.PathBuffer = undefined; diff --git a/src/bun.js/api/JSBundler.zig b/src/bun.js/api/JSBundler.zig index 8a4571523f..ebf5b15dd7 100644 --- a/src/bun.js/api/JSBundler.zig +++ b/src/bun.js/api/JSBundler.zig @@ -952,7 +952,7 @@ pub const JSBundler = struct { is_onLoad: bool, ) bool { JSC.markBinding(@src()); - const tracer = bun.tracy.traceNamed(@src(), "JSBundler.hasAnyMatches"); + const tracer = bun.perf.trace("JSBundler.hasAnyMatches"); defer tracer.end(); const namespace_string = if (path.isFile()) @@ -974,7 +974,7 @@ pub const JSBundler = struct { is_server_side: bool, ) void { JSC.markBinding(@src()); - const tracer = bun.tracy.traceNamed(@src(), "JSBundler.matchOnLoad"); + const tracer = bun.perf.trace("JSBundler.matchOnLoad"); defer tracer.end(); debug("JSBundler.matchOnLoad(0x{x}, {s}, {s})", .{ @intFromPtr(this), namespace, path }); const namespace_string = if (namespace.len == 0) @@ -996,7 +996,7 @@ pub const JSBundler = struct { import_record_kind: bun.ImportKind, ) void { JSC.markBinding(@src()); - const tracer = bun.tracy.traceNamed(@src(), "JSBundler.matchOnResolve"); + const tracer = bun.perf.trace("JSBundler.matchOnResolve"); defer tracer.end(); const namespace_string = if (strings.eqlComptime(namespace, "file")) bun.String.empty @@ -1019,7 +1019,7 @@ pub const JSBundler = struct { is_bake: bool, ) !JSValue { JSC.markBinding(@src()); - const tracer = bun.tracy.traceNamed(@src(), "JSBundler.addPlugin"); + const tracer = bun.perf.trace("JSBundler.addPlugin"); defer tracer.end(); return JSBundlerPlugin__runSetupFunction( this, diff --git a/src/bun.js/bindings/JSGlobalObject.zig b/src/bun.js/bindings/JSGlobalObject.zig index 7b96a16a62..9c6efd280a 100644 --- a/src/bun.js/bindings/JSGlobalObject.zig +++ b/src/bun.js/bindings/JSGlobalObject.zig @@ -739,6 +739,9 @@ pub const JSGlobalObject = opaque { eval_mode: bool, worker_ptr: ?*anyopaque, ) *JSGlobalObject { + const trace = bun.perf.trace("JSGlobalObject.create"); + defer trace.end(); + v.eventLoop().ensureWaker(); const global = Zig__GlobalObject__create(console, context_id, mini_mode, eval_mode, worker_ptr); diff --git a/src/bun.js/bindings/c-bindings.cpp b/src/bun.js/bindings/c-bindings.cpp index bcd9b56dce..8675973c05 100644 --- a/src/bun.js/bindings/c-bindings.cpp +++ b/src/bun.js/bindings/c-bindings.cpp @@ -882,6 +882,28 @@ extern "C" const char* BUN_DEFAULT_PATH_FOR_SPAWN = "/usr/bin:/bin"; #endif #if OS(DARWIN) +#include +#include "generated_perf_trace_events.h" + +// The event names have to be compile-time constants. +// So we trick the compiler into thinking they are by using a macro. +extern "C" void Bun__signpost_emit(os_log_t log, os_signpost_type_t type, os_signpost_id_t spid, int trace_event_id) +{ +#define EMIT_SIGNPOST(name, id) \ + case id: \ + os_signpost_emit_with_type(log, type, spid, #name, ""); \ + break; + + switch (trace_event_id) { + FOR_EACH_TRACE_EVENT(EMIT_SIGNPOST) + default: { + ASSERT_NOT_REACHED_WITH_MESSAGE("Invalid trace event id. Please run scripts/generate-perf-trace-events.sh to update the list of trace events."); + } + } +} + +#undef EMIT_SIGNPOST +#undef FOR_EACH_TRACE_EVENT #define BLOB_HEADER_ALIGNMENT 16 * 1024 diff --git a/src/bun.js/bindings/generated_perf_trace_events.h b/src/bun.js/bindings/generated_perf_trace_events.h new file mode 100644 index 0000000000..c6a166fee1 --- /dev/null +++ b/src/bun.js/bindings/generated_perf_trace_events.h @@ -0,0 +1,62 @@ +// Generated with scripts/generate-perf-trace-events.sh +// clang-format off +#define FOR_EACH_TRACE_EVENT(macro) \ + macro(Bundler.BindImportsToExports, 0) \ + macro(Bundler.CloneLinkerGraph, 1) \ + macro(Bundler.CreateNamespaceExports, 2) \ + macro(Bundler.FigureOutCommonJS, 3) \ + macro(Bundler.MatchImportsWithExports, 4) \ + macro(Bundler.ParseJS, 5) \ + macro(Bundler.ParseJSON, 6) \ + macro(Bundler.ParseTOML, 7) \ + macro(Bundler.ResolveExportStarStatements, 8) \ + macro(Bundler.Worker.create, 9) \ + macro(Bundler.WrapDependencies, 10) \ + macro(Bundler.breakOutputIntoPieces, 11) \ + macro(Bundler.cloneAST, 12) \ + macro(Bundler.computeChunks, 13) \ + macro(Bundler.findAllImportedPartsInJSOrder, 14) \ + macro(Bundler.findReachableFiles, 15) \ + macro(Bundler.generateChunksInParallel, 16) \ + macro(Bundler.generateCodeForFileInChunkCss, 17) \ + macro(Bundler.generateCodeForFileInChunkJS, 18) \ + macro(Bundler.generateIsolatedHash, 19) \ + macro(Bundler.generateSourceMapForChunk, 20) \ + macro(Bundler.markFileLiveForTreeShaking, 21) \ + macro(Bundler.markFileReachableForCodeSplitting, 22) \ + macro(Bundler.onParseTaskComplete, 23) \ + macro(Bundler.postProcessJSChunk, 24) \ + macro(Bundler.readFile, 25) \ + macro(Bundler.renameSymbolsInChunk, 26) \ + macro(Bundler.scanImportsAndExports, 27) \ + macro(Bundler.treeShakingAndCodeSplitting, 28) \ + macro(Bundler.writeChunkToDisk, 29) \ + macro(Bundler.writeOutputFilesToDisk, 30) \ + macro(ExtractTarball.extract, 31) \ + macro(FolderResolver.readPackageJSONFromDisk.folder, 32) \ + macro(FolderResolver.readPackageJSONFromDisk.workspace, 33) \ + macro(JSBundler.addPlugin, 34) \ + macro(JSBundler.hasAnyMatches, 35) \ + macro(JSBundler.matchOnLoad, 36) \ + macro(JSBundler.matchOnResolve, 37) \ + macro(JSGlobalObject.create, 38) \ + macro(JSParser.analyze, 39) \ + macro(JSParser.parse, 40) \ + macro(JSParser.postvisit, 41) \ + macro(JSParser.visit, 42) \ + macro(JSPrinter.print, 43) \ + macro(JSPrinter.printWithSourceMap, 44) \ + macro(ModuleResolver.resolve, 45) \ + macro(PackageInstaller.install, 46) \ + macro(PackageInstaller.installPatch, 47) \ + macro(PackageManifest.Serializer.loadByFile, 48) \ + macro(PackageManifest.Serializer.save, 49) \ + macro(RuntimeTranspilerCache.fromFile, 50) \ + macro(RuntimeTranspilerCache.save, 51) \ + macro(RuntimeTranspilerCache.toFile, 52) \ + macro(StandaloneModuleGraph.serialize, 53) \ + macro(Symbols.followAll, 54) \ + macro(TestCommand.printCodeCoverageLCov, 55) \ + macro(TestCommand.printCodeCoverageLCovAndText, 56) \ + macro(TestCommand.printCodeCoverageText, 57) \ + // end diff --git a/src/bun.js/bindings/linux_perf_tracing.cpp b/src/bun.js/bindings/linux_perf_tracing.cpp new file mode 100644 index 0000000000..0c7e0b4e24 --- /dev/null +++ b/src/bun.js/bindings/linux_perf_tracing.cpp @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include + +extern "C" { + +// This is a simple implementation for Linux tracing using ftrace +// It writes to /sys/kernel/debug/tracing/trace_marker +// +// To use with perf: +// 1. Ensure kernel.perf_event_paranoid is set to a value that allows tracing +// echo 1 > /proc/sys/kernel/perf_event_paranoid +// 2. Run perf record -e ftrace:print -a -- your_program +// 3. Run perf report + +// Bun trace events will appear in the trace as: +// C|PID|EventName|DurationInNs +// +// Where 'C' means counter/complete events with end timestamps + +#define TRACE_MARKER_PATH "/sys/kernel/debug/tracing/trace_marker" +#define MAX_EVENT_NAME_LENGTH 128 + +static int trace_fd = -1; + +// Initialize the tracing system +int Bun__linux_trace_init() +{ + if (trace_fd != -1) { + return 1; // Already initialized + } + + trace_fd = open(TRACE_MARKER_PATH, O_WRONLY); + return (trace_fd != -1) ? 1 : 0; +} + +// Close the trace file descriptor +void Bun__linux_trace_close() +{ + if (trace_fd != -1) { + close(trace_fd); + trace_fd = -1; + } +} + +// Write a trace event to the trace marker +// Format: "C|PID|EventName|DurationInNs" +int Bun__linux_trace_emit(const char* event_name, int64_t duration_ns) +{ + if (trace_fd == -1) { + return 0; + } + + char buffer[MAX_EVENT_NAME_LENGTH + 64]; + int len = snprintf(buffer, sizeof(buffer), + "C|%d|%s|%lld\n", + getpid(), event_name, (long long)duration_ns); + + if (len <= 0) { + return 0; + } + + ssize_t written = write(trace_fd, buffer, len); + return (written == len) ? 1 : 0; +} + +} // end extern "C" diff --git a/src/bun.zig b/src/bun.zig index 7a8260ad60..f7a344d64b 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -4127,6 +4127,7 @@ pub inline fn writeAnyToHasher(hasher: anytype, thing: anytype) void { hasher.update(std.mem.asBytes(&thing)); } +pub const perf = @import("./perf.zig"); pub inline fn isComptimeKnown(x: anytype) bool { return comptime @typeInfo(@TypeOf(.{x})).@"struct".fields[0].is_comptime; } diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index f8225a6d9f..d21428a214 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -135,11 +135,6 @@ const DataURL = @import("../resolver/resolver.zig").DataURL; const logPartDependencyTree = Output.scoped(.part_dep_tree, false); pub const MangledProps = std.AutoArrayHashMapUnmanaged(Ref, []const u8); - -fn tracer(comptime src: std.builtin.SourceLocation, comptime name: [:0]const u8) bun.tracy.Ctx { - return bun.tracy.traceNamed(src, "Transpiler." ++ name); -} - pub const ThreadPool = struct { /// macOS holds an IORWLock on every file open. /// This causes massive contention after about 4 threads as of macOS 15.2 @@ -268,9 +263,6 @@ pub const ThreadPool = struct { } pub fn getWorker(this: *ThreadPool, id: std.Thread.Id) *Worker { - const trace = tracer(@src(), "getWorker"); - defer trace.end(); - var worker: *Worker = undefined; { this.workers_assignments_lock.lock(); @@ -370,7 +362,7 @@ pub const ThreadPool = struct { } fn create(this: *Worker, ctx: *BundleV2) void { - const trace = tracer(@src(), "Worker.create"); + const trace = bun.perf.trace("Bundler.Worker.create"); defer trace.end(); this.has_created = true; @@ -678,7 +670,7 @@ pub const BundleV2 = struct { }; pub fn findReachableFiles(this: *BundleV2) ![]Index { - const trace = tracer(@src(), "findReachableFiles"); + const trace = bun.perf.trace("Bundler.findReachableFiles"); defer trace.end(); // Create a quick index for server-component boundaries. @@ -1333,7 +1325,7 @@ pub const BundleV2 = struct { } fn cloneAST(this: *BundleV2) !void { - const trace = tracer(@src(), "cloneAST"); + const trace = bun.perf.trace("Bundler.cloneAST"); defer trace.end(); this.linker.allocator = this.transpiler.allocator; this.linker.graph.allocator = this.transpiler.allocator; @@ -3392,7 +3384,7 @@ pub const BundleV2 = struct { } pub fn onParseTaskComplete(parse_result: *ParseTask.Result, this: *BundleV2) void { - const trace = tracer(@src(), "onParseTaskComplete"); + const trace = bun.perf.trace("Bundler.onParseTaskComplete"); defer trace.end(); if (parse_result.external.function != null) { const source = switch (parse_result.value) { @@ -4244,7 +4236,7 @@ pub const ParseTask = struct { ) !JSAst { switch (loader) { .jsx, .tsx, .js, .ts => { - const trace = tracer(@src(), "ParseJS"); + const trace = bun.perf.trace("Bundler.ParseJS"); defer trace.end(); return if (try resolver.caches.js.parse( transpiler.allocator, @@ -4266,13 +4258,13 @@ pub const ParseTask = struct { }; }, .json, .jsonc => |v| { - const trace = tracer(@src(), "ParseJSON"); + const trace = bun.perf.trace("Bundler.ParseJSON"); defer trace.end(); const root = (try resolver.caches.json.parseJSON(log, source, allocator, if (v == .jsonc) .jsonc else .json, true)) orelse Expr.init(E.Object, E.Object{}, Logger.Loc.Empty); return JSAst.init((try js_parser.newLazyExportAST(allocator, transpiler.options.define, opts, log, root, &source, "")).?); }, .toml => { - const trace = tracer(@src(), "ParseTOML"); + const trace = bun.perf.trace("Bundler.ParseTOML"); defer trace.end(); var temp_log = bun.logger.Log.init(allocator); defer { @@ -4555,7 +4547,7 @@ pub const ParseTask = struct { ) !CacheEntry { return switch (task.contents_or_fd) { .fd => |contents| brk: { - const trace = tracer(@src(), "readFile"); + const trace = bun.perf.trace("Bundler.readFile"); defer trace.end(); if (strings.eqlComptime(file_path.namespace, "node")) lookup_builtin: { @@ -6501,7 +6493,7 @@ pub const LinkerContext = struct { server_component_boundaries: ServerComponentBoundary.List, reachable: []Index, ) !void { - const trace = tracer(@src(), "CloneLinkerGraph"); + const trace = bun.perf.trace("Bundler.CloneLinkerGraph"); defer trace.end(); this.parse_graph = &bundle.graph; @@ -6676,7 +6668,7 @@ pub const LinkerContext = struct { this: *LinkerContext, unique_key: u64, ) ![]Chunk { - const trace = tracer(@src(), "computeChunks"); + const trace = bun.perf.trace("Bundler.computeChunks"); defer trace.end(); bun.assert(this.dev_server == null); // use @@ -7023,7 +7015,7 @@ pub const LinkerContext = struct { } pub fn findAllImportedPartsInJSOrder(this: *LinkerContext, temp_allocator: std.mem.Allocator, chunks: []Chunk) !void { - const trace = tracer(@src(), "findAllImportedPartsInJSOrder"); + const trace = bun.perf.trace("Bundler.findAllImportedPartsInJSOrder"); defer trace.end(); var part_ranges_shared = std.ArrayList(PartRange).init(temp_allocator); @@ -8395,7 +8387,7 @@ pub const LinkerContext = struct { } pub fn scanImportsAndExports(this: *LinkerContext) !void { - const outer_trace = tracer(@src(), "scanImportsAndExports"); + const outer_trace = bun.perf.trace("Bundler.scanImportsAndExports"); defer outer_trace.end(); const reachable = this.graph.reachable_files; const output_format = this.options.output_format; @@ -8425,7 +8417,7 @@ pub const LinkerContext = struct { // Step 1: Figure out what modules must be CommonJS for (reachable) |source_index_| { - const trace = tracer(@src(), "FigureOutCommonJS"); + const trace = bun.perf.trace("Bundler.FigureOutCommonJS"); defer trace.end(); const id = source_index_.get(); @@ -8594,7 +8586,7 @@ pub const LinkerContext = struct { // bundle time. { - const trace = tracer(@src(), "WrapDependencies"); + const trace = bun.perf.trace("Bundler.WrapDependencies"); defer trace.end(); var dependency_wrapper = DependencyWrapper{ .linker = this, @@ -8645,7 +8637,7 @@ pub const LinkerContext = struct { // are ignored for those modules. { var export_star_ctx: ?ExportStarContext = null; - const trace = tracer(@src(), "ResolveExportStarStatements"); + const trace = bun.perf.trace("Bundler.ResolveExportStarStatements"); defer trace.end(); defer { if (export_star_ctx) |*export_ctx| { @@ -8706,7 +8698,7 @@ pub const LinkerContext = struct { // export stars because imports can bind to export star re-exports. { this.cycle_detector.clearRetainingCapacity(); - const trace = tracer(@src(), "MatchImportsWithExports"); + const trace = bun.perf.trace("Bundler.MatchImportsWithExports"); defer trace.end(); const wrapper_part_indices = this.graph.meta.items(.wrapper_part_index); const imports_to_bind = this.graph.meta.items(.imports_to_bind); @@ -8777,7 +8769,7 @@ pub const LinkerContext = struct { // parts that declare the export to all parts that use the import. Also // generate wrapper parts for wrapped files. { - const trace = tracer(@src(), "BindImportsToExports"); + const trace = bun.perf.trace("Bundler.BindImportsToExports"); defer trace.end(); // const needs_export_symbol_from_runtime: []const bool = this.graph.meta.items(.needs_export_symbol_from_runtime); @@ -9735,7 +9727,7 @@ pub const LinkerContext = struct { /// imported using an import star statement. pub fn doStep5(c: *LinkerContext, source_index_: Index, _: usize) void { const source_index = source_index_.get(); - const trace = tracer(@src(), "CreateNamespaceExports"); + const trace = bun.perf.trace("Bundler.CreateNamespaceExports"); defer trace.end(); const id = source_index; @@ -9977,7 +9969,7 @@ pub const LinkerContext = struct { } pub fn treeShakingAndCodeSplitting(c: *LinkerContext) !void { - const trace = tracer(@src(), "treeShakingAndCodeSplitting"); + const trace = bun.perf.trace("Bundler.treeShakingAndCodeSplitting"); defer trace.end(); const parts = c.graph.ast.items(.parts); @@ -9989,7 +9981,7 @@ pub const LinkerContext = struct { const distances = c.graph.files.items(.distance_from_entry_point); { - const trace2 = tracer(@src(), "markFileLiveForTreeShaking"); + const trace2 = bun.perf.trace("Bundler.markFileLiveForTreeShaking"); defer trace2.end(); // Tree shaking: Each entry point marks all files reachable from itself @@ -10006,7 +9998,7 @@ pub const LinkerContext = struct { } { - const trace2 = tracer(@src(), "markFileReachableForCodeSplitting"); + const trace2 = bun.perf.trace("Bundler.markFileReachableForCodeSplitting"); defer trace2.end(); const file_entry_bits: []AutoBitSet = c.graph.files.items(.entry_bits); @@ -10492,7 +10484,7 @@ pub const LinkerContext = struct { chunk: *Chunk, files_in_order: []const u32, ) !renamer.Renamer { - const trace = tracer(@src(), "renameSymbolsInChunk"); + const trace = bun.perf.trace("Bundler.renameSymbolsInChunk"); defer trace.end(); const all_module_scopes = c.graph.ast.items(.module_scope); const all_flags: []const JSMeta.Flags = c.graph.meta.items(.flags); @@ -10789,7 +10781,7 @@ pub const LinkerContext = struct { } fn generateCompileResultForCssChunkImpl(worker: *ThreadPool.Worker, c: *LinkerContext, chunk: *Chunk, imports_in_chunk_index: u32) CompileResult { - const trace = tracer(@src(), "generateCodeForFileInChunkCss"); + const trace = bun.perf.trace("Bundler.generateCodeForFileInChunkCss"); defer trace.end(); var arena = &worker.temporary_arena; @@ -10942,7 +10934,7 @@ pub const LinkerContext = struct { } fn generateCompileResultForJSChunkImpl(worker: *ThreadPool.Worker, c: *LinkerContext, chunk: *Chunk, part_range: PartRange) CompileResult { - const trace = tracer(@src(), "generateCodeForFileInChunkJS"); + const trace = bun.perf.trace("Bundler.generateCodeForFileInChunkJS"); defer trace.end(); // Client bundles for Bake must be globally allocated, @@ -11639,7 +11631,7 @@ pub const LinkerContext = struct { // This runs after we've already populated the compile results fn postProcessJSChunk(ctx: GenerateChunkCtx, worker: *ThreadPool.Worker, chunk: *Chunk, chunk_index: usize) !void { - const trace = tracer(@src(), "postProcessJSChunk"); + const trace = bun.perf.trace("Bundler.postProcessJSChunk"); defer trace.end(); _ = chunk_index; @@ -12065,7 +12057,7 @@ pub const LinkerContext = struct { chunk_abs_dir: string, can_have_shifts: bool, ) !sourcemap.SourceMapPieces { - const trace = tracer(@src(), "generateSourceMapForChunk"); + const trace = bun.perf.trace("Bundler.generateSourceMapForChunk"); defer trace.end(); var j = StringJoiner{ .allocator = worker.allocator }; @@ -12210,7 +12202,7 @@ pub const LinkerContext = struct { } pub fn generateIsolatedHash(c: *LinkerContext, chunk: *const Chunk) u64 { - const trace = tracer(@src(), "generateIsolatedHash"); + const trace = bun.perf.trace("Bundler.generateIsolatedHash"); defer trace.end(); var hasher = ContentHasher{}; @@ -14538,7 +14530,7 @@ pub const LinkerContext = struct { } pub fn generateChunksInParallel(c: *LinkerContext, chunks: []Chunk, comptime is_dev_server: bool) !if (is_dev_server) void else std.ArrayList(options.OutputFile) { - const trace = tracer(@src(), "generateChunksInParallel"); + const trace = bun.perf.trace("Bundler.generateChunksInParallel"); defer trace.end(); c.mangleLocalCss(); @@ -15151,7 +15143,7 @@ pub const LinkerContext = struct { chunks: []Chunk, output_files: *std.ArrayList(options.OutputFile), ) !void { - const trace = tracer(@src(), "writeOutputFilesToDisk"); + const trace = bun.perf.trace("Bundler.writeOutputFilesToDisk"); defer trace.end(); var root_dir = std.fs.cwd().makeOpenPath(root_path, .{}) catch |err| { if (err == error.NotDir) { @@ -15188,7 +15180,7 @@ pub const LinkerContext = struct { var pathbuf: bun.PathBuffer = undefined; for (chunks) |*chunk| { - const trace2 = tracer(@src(), "writeChunkToDisk"); + const trace2 = bun.perf.trace("Bundler.writeChunkToDisk"); defer trace2.end(); defer max_heap_allocator.reset(); @@ -16641,7 +16633,7 @@ pub const LinkerContext = struct { j: *StringJoiner, count: u32, ) !Chunk.IntermediateOutput { - const trace = tracer(@src(), "breakOutputIntoPieces"); + const trace = bun.perf.trace("Bundler.breakOutputIntoPieces"); defer trace.end(); const OutputPiece = Chunk.OutputPiece; diff --git a/src/c.zig b/src/c.zig index fac44409c4..e05bddf14e 100644 --- a/src/c.zig +++ b/src/c.zig @@ -433,27 +433,20 @@ pub fn dlsymWithHandle(comptime Type: type, comptime name: [:0]const u8, comptim const Wrapper = struct { pub var function: Type = undefined; - pub var loaded: LazyStatus = LazyStatus.pending; - }; - - if (Wrapper.loaded == .pending) { - const result = _dlsym(@call(bun.callmod_inline, handle_getter, .{}), name); - - if (result) |ptr| { - Wrapper.function = bun.cast(Type, ptr); - Wrapper.loaded = .loaded; - return Wrapper.function; - } else { - Wrapper.loaded = .failed; - return null; + var failed = false; + pub var once = std.once(loadOnce); + fn loadOnce() void { + function = bun.cast(Type, _dlsym(@call(bun.callmod_inline, handle_getter, .{}), name) orelse { + failed = true; + return; + }); } + }; + Wrapper.once.call(); + if (Wrapper.failed) { + return null; } - - if (Wrapper.loaded == .loaded) { - return Wrapper.function; - } - - return null; + return Wrapper.function; } pub fn dlsym(comptime Type: type, comptime name: [:0]const u8) ?Type { diff --git a/src/cli/test_command.zig b/src/cli/test_command.zig index 47340e584a..1994d3f23c 100644 --- a/src/cli/test_command.zig +++ b/src/cli/test_command.zig @@ -760,21 +760,15 @@ pub const CommandLineReporter = struct { comptime reporters: TestCommand.Reporters, comptime enable_ansi_colors: bool, ) !void { - const trace = bun.tracy.traceNamed(@src(), comptime brk: { - if (reporters.text and reporters.lcov) { - break :brk "TestCommand.printCodeCoverageLCovAndText"; - } - - if (reporters.text) { - break :brk "TestCommand.printCodeCoverageText"; - } - - if (reporters.lcov) { - break :brk "TestCommand.printCodeCoverageLCov"; - } - + const trace = if (reporters.text and reporters.lcov) + bun.perf.trace("TestCommand.printCodeCoverageLCovAndText") + else if (reporters.text) + bun.perf.trace("TestCommand.printCodeCoverageText") + else if (reporters.lcov) + bun.perf.trace("TestCommand.printCodeCoverageLCov") + else @compileError("No reporters enabled"); - }); + defer trace.end(); if (comptime !reporters.text and !reporters.lcov) { @@ -1335,7 +1329,7 @@ pub const TestCommand = struct { strings.startsWith(arg, "./") or strings.startsWith(arg, "../") or (Environment.isWindows and (strings.startsWith(arg, ".\\") or - strings.startsWith(arg, "..\\")))) break true; + strings.startsWith(arg, "..\\")))) break true; } else false) { // One of the files is a filepath. Instead of treating the arguments as filters, treat them as filepaths for (ctx.positionals[1..]) |arg| { @@ -1453,9 +1447,9 @@ pub const TestCommand = struct { if (has_file_like == null and (strings.hasSuffixComptime(filter, ".ts") or - strings.hasSuffixComptime(filter, ".tsx") or - strings.hasSuffixComptime(filter, ".js") or - strings.hasSuffixComptime(filter, ".jsx"))) + strings.hasSuffixComptime(filter, ".tsx") or + strings.hasSuffixComptime(filter, ".js") or + strings.hasSuffixComptime(filter, ".jsx"))) { has_file_like = i; } diff --git a/src/darwin_c.zig b/src/darwin_c.zig index 9a67df2bdf..a64faba152 100644 --- a/src/darwin_c.zig +++ b/src/darwin_c.zig @@ -87,7 +87,6 @@ pub const stat = blk: { const T = *const fn (?[*:0]const u8, ?*bun.Stat) callconv(.C) c_int; break :blk @extern(T, .{ .name = if (bun.Environment.isAarch64) "stat" else "stat64" }); }; - // benchmarking this did nothing on macOS // i verified it wasn't returning -1 pub fn preallocate_file(_: posix.fd_t, _: off_t, _: off_t) !void { @@ -696,3 +695,75 @@ pub const CLOCK_THREAD_CPUTIME_ID = 1; pub extern fn memset_pattern4(buf: [*]u8, pattern: [*]const u8, len: usize) void; pub extern fn memset_pattern8(buf: [*]u8, pattern: [*]const u8, len: usize) void; pub extern fn memset_pattern16(buf: [*]u8, pattern: [*]const u8, len: usize) void; + +pub const OSLog = opaque { + pub const Category = enum(u8) { + PointsOfInterest = 0, + Dynamicity = 1, + SizeAndThroughput = 2, + TimeProfile = 3, + SystemReporting = 4, + UserCustom = 5, + }; + + // Common subsystems that Instruments recognizes + pub const Subsystem = struct { + pub const Network = "com.apple.network"; + pub const FileIO = "com.apple.disk_io"; + pub const Graphics = "com.apple.graphics"; + pub const Memory = "com.apple.memory"; + pub const Performance = "com.apple.performance"; + }; + + extern "C" fn os_log_create(subsystem: ?[*:0]const u8, category: ?[*:0]const u8) ?*OSLog; + + pub fn init() ?*OSLog { + return os_log_create("com.bun.bun", "PointsOfInterest"); + } + + // anything except 0 and ~0 is a valid signpost id + var signpost_id_counter = std.atomic.Value(u64).init(1); + + pub fn signpost(log: *OSLog, name: i32) Signpost { + return .{ + .id = signpost_id_counter.fetchAdd(1, .monotonic), + .name = name, + .log = log, + }; + } + + const SignpostType = enum(u8) { + Event = 0, + IntervalBegin = 1, + IntervalEnd = 2, + }; + + pub extern "C" fn Bun__signpost_emit(log: *OSLog, id: u64, signpost_type: SignpostType, name: i32, category: u8) void; + + pub const Signpost = struct { + id: u64, + name: i32, + log: *OSLog, + + pub fn emit(this: *const Signpost, category: Category) void { + Bun__signpost_emit(this.log, this.id, .Event, this.name, @intFromEnum(category)); + } + + pub const Interval = struct { + signpost: Signpost, + category: Category, + + pub fn end(this: *const Interval) void { + Bun__signpost_emit(this.signpost.log, this.signpost.id, .IntervalEnd, this.signpost.name, @intFromEnum(this.category)); + } + }; + + pub fn interval(this: Signpost, category: Category) Interval { + Bun__signpost_emit(this.log, this.id, .IntervalBegin, this.name, @intFromEnum(category)); + return Interval{ + .signpost = this, + .category = category, + }; + } + }; +}; diff --git a/src/env_loader.zig b/src/env_loader.zig index a8e224b49e..ff54acd599 100644 --- a/src/env_loader.zig +++ b/src/env_loader.zig @@ -92,27 +92,7 @@ pub const Loader = struct { } pub fn loadTracy(this: *const Loader) void { - tracy: { - if (this.get("BUN_TRACY") != null) { - if (!bun.tracy.init()) { - Output.prettyErrorln("Failed to load Tracy. Is it installed in your include path?", .{}); - Output.flush(); - break :tracy; - } - - bun.tracy.start(); - - if (!bun.tracy.isConnected()) { - std.time.sleep(std.time.ns_per_ms * 10); - } - - if (!bun.tracy.isConnected()) { - Output.prettyErrorln("Tracy is not connected. Is Tracy running on your computer?", .{}); - Output.flush(); - break :tracy; - } - } - } + _ = this; // autofix } pub fn getS3Credentials(this: *Loader) s3.S3Credentials { diff --git a/src/generated_perf_trace_events.zig b/src/generated_perf_trace_events.zig new file mode 100644 index 0000000000..e062d2623e --- /dev/null +++ b/src/generated_perf_trace_events.zig @@ -0,0 +1,61 @@ +// Generated with scripts/generate-perf-trace-events.sh +pub const PerfEvent = enum(i32) { + @"Bundler.BindImportsToExports", + @"Bundler.CloneLinkerGraph", + @"Bundler.CreateNamespaceExports", + @"Bundler.FigureOutCommonJS", + @"Bundler.MatchImportsWithExports", + @"Bundler.ParseJS", + @"Bundler.ParseJSON", + @"Bundler.ParseTOML", + @"Bundler.ResolveExportStarStatements", + @"Bundler.Worker.create", + @"Bundler.WrapDependencies", + @"Bundler.breakOutputIntoPieces", + @"Bundler.cloneAST", + @"Bundler.computeChunks", + @"Bundler.findAllImportedPartsInJSOrder", + @"Bundler.findReachableFiles", + @"Bundler.generateChunksInParallel", + @"Bundler.generateCodeForFileInChunkCss", + @"Bundler.generateCodeForFileInChunkJS", + @"Bundler.generateIsolatedHash", + @"Bundler.generateSourceMapForChunk", + @"Bundler.markFileLiveForTreeShaking", + @"Bundler.markFileReachableForCodeSplitting", + @"Bundler.onParseTaskComplete", + @"Bundler.postProcessJSChunk", + @"Bundler.readFile", + @"Bundler.renameSymbolsInChunk", + @"Bundler.scanImportsAndExports", + @"Bundler.treeShakingAndCodeSplitting", + @"Bundler.writeChunkToDisk", + @"Bundler.writeOutputFilesToDisk", + @"ExtractTarball.extract", + @"FolderResolver.readPackageJSONFromDisk.folder", + @"FolderResolver.readPackageJSONFromDisk.workspace", + @"JSBundler.addPlugin", + @"JSBundler.hasAnyMatches", + @"JSBundler.matchOnLoad", + @"JSBundler.matchOnResolve", + @"JSGlobalObject.create", + @"JSParser.analyze", + @"JSParser.parse", + @"JSParser.postvisit", + @"JSParser.visit", + @"JSPrinter.print", + @"JSPrinter.printWithSourceMap", + @"ModuleResolver.resolve", + @"PackageInstaller.install", + @"PackageInstaller.installPatch", + @"PackageManifest.Serializer.loadByFile", + @"PackageManifest.Serializer.save", + @"RuntimeTranspilerCache.fromFile", + @"RuntimeTranspilerCache.save", + @"RuntimeTranspilerCache.toFile", + @"StandaloneModuleGraph.serialize", + @"Symbols.followAll", + @"TestCommand.printCodeCoverageLCov", + @"TestCommand.printCodeCoverageLCovAndText", + @"TestCommand.printCodeCoverageText", +}; diff --git a/src/install/extract_tarball.zig b/src/install/extract_tarball.zig index 0278611a5d..1c39805991 100644 --- a/src/install/extract_tarball.zig +++ b/src/install/extract_tarball.zig @@ -128,6 +128,9 @@ threadlocal var folder_name_buf: bun.PathBuffer = undefined; threadlocal var json_path_buf: bun.PathBuffer = undefined; fn extract(this: *const ExtractTarball, tgz_bytes: []const u8) !Install.ExtractData { + const tracer = bun.perf.trace("ExtractTarball.extract"); + defer tracer.end(); + const tmpdir = this.temp_dir; var tmpname_buf: if (Environment.isWindows) bun.WPathBuffer else bun.PathBuffer = undefined; const name = if (this.name.slice().len > 0) this.name.slice() else brk: { diff --git a/src/install/install.zig b/src/install/install.zig index 417ef23396..3c58e29373 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -2394,18 +2394,29 @@ pub fn NewPackageInstall(comptime kind: PkgInstallKind) type { } pub fn install(this: *@This(), skip_delete: bool, destination_dir: std.fs.Dir, resolution_tag: Resolution.Tag) Result { - const result = this.installImpl(skip_delete, destination_dir, this.getInstallMethod(), resolution_tag); - if (comptime kind == .regular) return result; - if (result == .fail) return result; - const fd = bun.toFD(destination_dir.fd); - const subpath = bun.path.joinZ(&[_][]const u8{ this.destination_dir_subpath, ".bun-patch-tag" }); - const tag_fd = switch (bun.sys.openat(fd, subpath, bun.O.CREAT | bun.O.WRONLY, 0o666)) { - .err => |e| return .fail(bun.errnoToZigErr(e.getErrno()), .patching, @errorReturnTrace()), - .result => |f| f, - }; - defer _ = bun.sys.close(tag_fd); + switch (comptime kind) { + .regular => { + const tracer = bun.perf.trace("PackageInstaller.install"); + defer tracer.end(); + return this.installImpl(skip_delete, destination_dir, this.getInstallMethod(), resolution_tag); + }, + .patch => { + const tracer = bun.perf.trace("PackageInstaller.installPatch"); + defer tracer.end(); - if (bun.sys.File.writeAll(.{ .handle = tag_fd }, this.package_version).asErr()) |e| return .fail(bun.errnoToZigErr(e.getErrno()), .patching, @errorReturnTrace()); + const result = this.installImpl(skip_delete, destination_dir, this.getInstallMethod(), resolution_tag); + if (result == .fail) return result; + const fd = bun.toFD(destination_dir.fd); + const subpath = bun.path.joinZ(&[_][]const u8{ this.destination_dir_subpath, ".bun-patch-tag" }); + const tag_fd = switch (bun.sys.openat(fd, subpath, bun.O.CREAT | bun.O.WRONLY, 0o666)) { + .err => |e| return .fail(bun.errnoToZigErr(e.getErrno()), .patching, @errorReturnTrace()), + .result => |f| f, + }; + defer _ = bun.sys.close(tag_fd); + if (bun.sys.File.writeAll(.{ .handle = tag_fd }, this.package_version).asErr()) |e| return .fail(bun.errnoToZigErr(e.getErrno()), .patching, @errorReturnTrace()); + return result; + }, + } } pub fn installImpl(this: *@This(), skip_delete: bool, destination_dir: std.fs.Dir, method_: Method, resolution_tag: Resolution.Tag) Result { diff --git a/src/install/npm.zig b/src/install/npm.zig index f6c4703121..baa076e5e4 100644 --- a/src/install/npm.zig +++ b/src/install/npm.zig @@ -1230,6 +1230,9 @@ pub const PackageManifest = struct { pub usingnamespace bun.New(@This()); pub fn run(task: *bun.ThreadPool.Task) void { + const tracer = bun.perf.trace("PackageManifest.Serializer.save"); + defer tracer.end(); + const save_task: *@This() = @fieldParentPtr("task", task); defer { save_task.destroy(); @@ -1295,6 +1298,8 @@ pub const PackageManifest = struct { } pub fn loadByFile(allocator: std.mem.Allocator, scope: *const Registry.Scope, manifest_file: File) !?PackageManifest { + const tracer = bun.perf.trace("PackageManifest.Serializer.loadByFile"); + defer tracer.end(); const bytes = try manifest_file.readToEnd(allocator).unwrap(); errdefer allocator.free(bytes); @@ -2164,7 +2169,7 @@ pub const PackageManifest = struct { if (count > 0 and ((comptime !is_peer) or - optional_peer_dep_names.items.len == 0)) + optional_peer_dep_names.items.len == 0)) { const name_map_hash = name_hasher.final(); const version_map_hash = version_hasher.final(); diff --git a/src/install/resolvers/folder_resolver.zig b/src/install/resolvers/folder_resolver.zig index 287e9a4fc9..5b5d27a785 100644 --- a/src/install/resolvers/folder_resolver.zig +++ b/src/install/resolvers/folder_resolver.zig @@ -181,6 +181,9 @@ pub const FolderResolution = union(Tag) { var package = Lockfile.Package{}; if (comptime ResolverType == WorkspaceResolver) { + const tracer = bun.perf.trace("FolderResolver.readPackageJSONFromDisk.workspace"); + defer tracer.end(); + const json = try manager.workspace_package_json_cache.getWithPath(manager.allocator, manager.log, abs, .{}).unwrap(); try package.parseWithJSON( @@ -195,6 +198,9 @@ pub const FolderResolution = union(Tag) { features, ); } else { + const tracer = bun.perf.trace("FolderResolver.readPackageJSONFromDisk.folder"); + defer tracer.end(); + const source = brk: { var file = bun.sys.File.from(try bun.sys.openatA( bun.FD.cwd(), diff --git a/src/js_ast.zig b/src/js_ast.zig index 9dcefb5e11..11da3a4ae3 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -1410,7 +1410,7 @@ pub const Symbol = struct { } pub fn followAll(symbols: *Map) void { - const trace = bun.tracy.traceNamed(@src(), "Symbols.followAll"); + const trace = bun.perf.trace("Symbols.followAll"); defer trace.end(); for (symbols.symbols_for_source.slice()) |list| { for (list.slice()) |*symbol| { diff --git a/src/js_parser.zig b/src/js_parser.zig index 081e6ae040..949d064c70 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -3159,7 +3159,7 @@ pub const Parser = struct { // Parse the file in the first pass, but do not bind symbols var opts = ParseStatementOptions{ .is_module_scope = true }; - const parse_tracer = bun.tracy.traceNamed(@src(), "JSParser.parse"); + const parse_tracer = bun.perf.trace("JSParser.parse"); const stmts = p.parseStmtsUpTo(js_lexer.T.t_end_of_file, &opts) catch |err| { if (comptime Environment.isWasm) { @@ -3197,7 +3197,7 @@ pub const Parser = struct { return error.SyntaxError; } - const visit_tracer = bun.tracy.traceNamed(@src(), "JSParser.visit"); + const visit_tracer = bun.perf.trace("JSParser.visit"); try p.prepareForVisitPass(); var parts = ListManaged(js_ast.Part).init(p.allocator); @@ -3206,7 +3206,7 @@ pub const Parser = struct { try p.appendPart(&parts, stmts); visit_tracer.end(); - const analyze_tracer = bun.tracy.traceNamed(@src(), "JSParser.analyze"); + const analyze_tracer = bun.perf.trace("JSParser.analyze"); try callback(context, &p, parts.items); analyze_tracer.end(); } @@ -3292,7 +3292,7 @@ pub const Parser = struct { // Parse the file in the first pass, but do not bind symbols var opts = ParseStatementOptions{ .is_module_scope = true }; - const parse_tracer = bun.tracy.traceNamed(@src(), "JSParser.parse"); + const parse_tracer = bun.perf.trace("JSParser.parse"); // Parsing seems to take around 2x as much time as visiting. // Which makes sense. @@ -3324,7 +3324,7 @@ pub const Parser = struct { bun.crash_handler.current_action = .{ .visit = self.source.path.text }; - const visit_tracer = bun.tracy.traceNamed(@src(), "JSParser.visit"); + const visit_tracer = bun.perf.trace("JSParser.visit"); try p.prepareForVisitPass(); var before = ListManaged(js_ast.Part).init(p.allocator); @@ -3531,7 +3531,7 @@ pub const Parser = struct { return error.SyntaxError; } - const postvisit_tracer = bun.tracy.traceNamed(@src(), "JSParser.postvisit"); + const postvisit_tracer = bun.perf.trace("JSParser.postvisit"); defer postvisit_tracer.end(); var uses_dirname = p.symbols.items[p.dirname_ref.innerIndex()].use_count_estimate > 0; diff --git a/src/js_printer.zig b/src/js_printer.zig index 4609f86083..db27f3675a 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -5935,7 +5935,7 @@ pub fn print( renamer: bun.renamer.Renamer, comptime generate_source_maps: bool, ) PrintResult { - const trace = bun.tracy.traceNamed(@src(), "JSPrinter.print"); + const trace = bun.perf.trace("JSPrinter.print"); defer trace.end(); const buffer_writer = BufferWriter.init(allocator) catch |err| return .{ .err = err }; diff --git a/src/perf.zig b/src/perf.zig new file mode 100644 index 0000000000..196f1a83a9 --- /dev/null +++ b/src/perf.zig @@ -0,0 +1,160 @@ +const bun = @import("root").bun; +const std = @import("std"); + +pub const Ctx = union(enum) { + disabled: Disabled, + enabled: switch (bun.Environment.os) { + .mac => Darwin, + .linux => Linux, + else => Disabled, + }, + + pub const Disabled = struct { + pub inline fn end(_: *const @This()) void {} + }; + + pub fn end(this: *const @This()) void { + switch (this.*) { + inline else => |*ctx| ctx.end(), + } + } +}; +var is_enabled_once = std.once(isEnabledOnce); +var is_enabled = std.atomic.Value(bool).init(false); +fn isEnabledOnMacOSOnce() void { + if (bun.getenvZ("DYLD_ROOT_PATH") != null or bun.getRuntimeFeatureFlag("BUN_INSTRUMENTS")) { + is_enabled.store(true, .seq_cst); + } +} + +fn isEnabledOnLinuxOnce() void { + if (bun.getRuntimeFeatureFlag("BUN_TRACE")) { + is_enabled.store(true, .seq_cst); + } +} + +fn isEnabledOnce() void { + if (comptime bun.Environment.isMac) { + isEnabledOnMacOSOnce(); + if (Darwin.get() == null) { + is_enabled.store(false, .seq_cst); + } + } else if (comptime bun.Environment.isLinux) { + isEnabledOnLinuxOnce(); + if (!Linux.isSupported()) { + is_enabled.store(false, .seq_cst); + } + } +} + +pub fn isEnabled() bool { + is_enabled_once.call(); + return is_enabled.load(.seq_cst); +} + +const PerfEvent = @import("./generated_perf_trace_events.zig").PerfEvent; + +/// Trace an event using the system profiler (Instruments). +/// +/// When instruments is not connected, this is a no-op. +/// +/// When adding a new event, you must run `scripts/generate-perf-trace-events.sh` to update the list of trace events. +/// +/// Tip: Make sure you write bun.perf.trace() with a string literal exactly instead of passing a variable. +/// +/// It has to be compile-time known this way because they need to become string literals in C. +pub fn trace(comptime name: [:0]const u8) Ctx { + comptime { + if (!@hasField(PerfEvent, name)) { + @compileError(std.fmt.comptimePrint( + \\"{s}" is missing from generated_perf_trace_events.zig + \\ + \\Please run this command in your terminal and commit the result: + \\ + \\ bash scripts/generate-perf-trace-events.sh + \\ + \\Tip: Make sure you write bun.perf.trace as a string literal exactly instead of passing a variable. + , + .{ + name, + }, + )); + } + } + + if (!isEnabled()) { + @branchHint(.likely); + return .{ .disabled = .{} }; + } + + if (comptime bun.Environment.isMac) { + return .{ .enabled = Darwin.init(@intFromEnum(@field(PerfEvent, name))) }; + } else if (comptime bun.Environment.isLinux) { + return .{ .enabled = Linux.init(@field(PerfEvent, name)) }; + } + + return .{ .disabled = .{} }; +} + +pub const Darwin = struct { + const OSLog = bun.C.OSLog; + interval: OSLog.Signpost.Interval, + + pub fn init(comptime name: i32) @This() { + return .{ + .interval = os_log.?.signpost(name).interval(.PointsOfInterest), + }; + } + + pub fn end(this: *const @This()) void { + this.interval.end(); + } + + var os_log: ?*OSLog = null; + var os_log_once = std.once(getOnce); + fn getOnce() void { + os_log = OSLog.init(); + } + + pub fn get() ?*OSLog { + os_log_once.call(); + return os_log; + } +}; + +pub const Linux = struct { + start_time: u64, + event: PerfEvent, + + var is_initialized = std.atomic.Value(bool).init(false); + var init_once = std.once(initOnce); + + extern "c" fn Bun__linux_trace_init() c_int; + extern "c" fn Bun__linux_trace_close() void; + extern "c" fn Bun__linux_trace_emit(event_name: [*:0]const u8, duration_ns: i64) c_int; + + fn initOnce() void { + const result = Bun__linux_trace_init(); + is_initialized.store(result != 0, .monotonic); + } + + pub fn isSupported() bool { + init_once.call(); + return is_initialized.load(.monotonic); + } + + pub fn init(event: PerfEvent) @This() { + return .{ + .start_time = bun.timespec.now().ns(), + .event = event, + }; + } + + pub fn end(this: *const @This()) void { + if (!isSupported()) return; + + const duration = bun.timespec.now().ns() -| this.start_time; + + _ = Bun__linux_trace_emit(@tagName(this.event).ptr, @intCast(duration)); + } +}; diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index d8e01b1896..d4ba9cce07 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -660,7 +660,7 @@ pub const Resolver = struct { kind: ast.ImportKind, global_cache: GlobalCache, ) Result.Union { - const tracer = bun.tracy.traceNamed(@src(), "ModuleResolver.resolve"); + const tracer = bun.perf.trace("ModuleResolver.resolve"); defer tracer.end(); // Only setting 'current_action' in debug mode because module resolution diff --git a/src/transpiler.zig b/src/transpiler.zig index 60470cfdf7..5dbe50e1b8 100644 --- a/src/transpiler.zig +++ b/src/transpiler.zig @@ -822,7 +822,10 @@ pub const Transpiler = struct { source_map_context: ?js_printer.SourceMapHandler, runtime_transpiler_cache: ?*bun.JSC.RuntimeTranspilerCache, ) !usize { - const tracer = bun.tracy.traceNamed(@src(), if (enable_source_map) "JSPrinter.printWithSourceMap" else "JSPrinter.print"); + const tracer = if (enable_source_map) + bun.perf.trace("JSPrinter.printWithSourceMap") + else + bun.perf.trace("JSPrinter.print"); defer tracer.end(); const symbols = js_ast.Symbol.NestedList.init(&[_]js_ast.Symbol.List{ast.symbols}); diff --git a/test/internal/ban-words.test.ts b/test/internal/ban-words.test.ts index 9334d99768..ff08d7cd51 100644 --- a/test/internal/ban-words.test.ts +++ b/test/internal/ban-words.test.ts @@ -27,7 +27,7 @@ const words: Record "alloc.ptr !=": { reason: "The std.mem.Allocator context pointer can be undefined, which makes this comparison undefined behavior" }, "== alloc.ptr": { reason: "The std.mem.Allocator context pointer can be undefined, which makes this comparison undefined behavior" }, "!= alloc.ptr": { reason: "The std.mem.Allocator context pointer can be undefined, which makes this comparison undefined behavior" }, - [String.raw`: [a-zA-Z0-9_\.\*\?\[\]\(\)]+ = undefined,`]: { reason: "Do not default a struct field to undefined", limit: 244, regex: true }, + [String.raw`: [a-zA-Z0-9_\.\*\?\[\]\(\)]+ = undefined,`]: { reason: "Do not default a struct field to undefined", limit: 246, regex: true }, "usingnamespace": { reason: "This brings Bun away from incremental / faster compile times.", limit: 492 }, }; const words_keys = [...Object.keys(words)];