Skip to content

Commit

Permalink
feat(next-swc): introduce experimental tracing support for swc (#35803)
Browse files Browse the repository at this point in the history
* feat(next-swc/napi): expose initcustomtracesubscriber

* feat(next/config): enable experimental swcTrace

* feat(trace): add trace for emotion transform

* refactor(swc): use .next for the default trace output

* refactor(swc): teardown subscriber via drop

* refactor(swc/trace): simplify config

* refactor(swc/trace): adjust teardown
  • Loading branch information
kwonoj committed May 2, 2022
1 parent 3692a5e commit 837e0a6
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 8 deletions.
53 changes: 53 additions & 0 deletions packages/next-swc/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions packages/next-swc/crates/core/Cargo.toml
Expand Up @@ -27,8 +27,7 @@ swc_common = { version = "0.17.25", features = ["concurrent", "sourcemap"] }
swc_ecma_loader = { version = "0.29.1", features = ["node", "lru"] }
swc_ecmascript = { version = "0.150.0", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] }
swc_cached = "0.1.1"
tracing = { version = "0.1.32", features = ["release_max_level_off"] }

tracing = { version = "0.1.32", features = ["release_max_level_info"] }

[dev-dependencies]
swc_ecma_transforms_testing = "0.82.0"
Expand Down
2 changes: 2 additions & 0 deletions packages/next-swc/crates/emotion/Cargo.toml
Expand Up @@ -21,6 +21,8 @@ sourcemap = "6.0.1"
swc_atoms = "0.2.11"
swc_common = { version = "0.17.25", features = ["concurrent", "sourcemap"] }
swc_ecmascript = { version = "0.150.0", features = ["codegen", "utils", "visit"] }
swc_trace_macro = "0.1.1"
tracing = { version = "0.1.32", features = ["release_max_level_info"] }

[dev-dependencies]
swc_ecma_transforms_testing = "0.82.0"
Expand Down
2 changes: 2 additions & 0 deletions packages/next-swc/crates/emotion/src/lib.rs
Expand Up @@ -25,6 +25,7 @@ use swc_ecmascript::{
codegen::util::SourceMapperExt,
visit::{Fold, FoldWith},
};
use swc_trace_macro::swc_trace;

mod hash;

Expand Down Expand Up @@ -164,6 +165,7 @@ pub struct EmotionTransformer<C: Comments> {
in_jsx_element: bool,
}

#[swc_trace]
impl<C: Comments> EmotionTransformer<C> {
pub fn new(options: EmotionOptions, path: &Path, cm: Arc<SourceMap>, comments: C) -> Self {
EmotionTransformer {
Expand Down
5 changes: 5 additions & 0 deletions packages/next-swc/crates/napi/Cargo.toml
Expand Up @@ -24,6 +24,11 @@ swc_common = { version = "0.17.25", features = ["concurrent", "sourcemap"] }
swc_ecma_loader = { version = "0.29.1", features = ["node", "lru"] }
swc_ecmascript = { version = "0.150.0", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] }
swc_node_base = "0.5.2"
tracing = { version = "0.1.32", features = ["release_max_level_info"] }
tracing-futures = "0.2.5"
tracing-subscriber = "0.3.9"

tracing-chrome = "0.5.0"

[build-dependencies]
napi-build = "1"
5 changes: 5 additions & 0 deletions packages/next-swc/crates/napi/src/lib.rs
Expand Up @@ -73,6 +73,11 @@ fn init(mut exports: JsObject) -> napi::Result<()> {
exports.create_named_method("parse", parse::parse)?;

exports.create_named_method("getTargetTriple", util::get_target_triple)?;
exports.create_named_method(
"initCustomTraceSubscriber",
util::init_custom_trace_subscriber,
)?;
exports.create_named_method("teardownTraceSubscriber", util::teardown_trace_subscriber)?;

Ok(())
}
Expand Down
63 changes: 59 additions & 4 deletions packages/next-swc/crates/napi/src/util.rs
Expand Up @@ -26,13 +26,14 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/

use anyhow::{Context, Error};
use napi::{CallContext, Env, JsBuffer, JsString, Status};
use anyhow::{anyhow, Context, Error};
use napi::{CallContext, Env, JsBuffer, JsExternal, JsString, JsUndefined, JsUnknown, Status};
use serde::de::DeserializeOwned;
use std::any::type_name;
use std::{any::type_name, cell::RefCell, convert::TryFrom, path::PathBuf};
use tracing_chrome::{ChromeLayerBuilder, FlushGuard};
use tracing_subscriber::{filter, prelude::*, util::SubscriberInitExt, Layer};

static TARGET_TRIPLE: &str = include_str!(concat!(env!("OUT_DIR"), "/triple.txt"));

#[contextless_function]
pub fn get_target_triple(env: Env) -> napi::ContextlessResult<JsString> {
env.create_string(TARGET_TRIPLE).map(Some)
Expand Down Expand Up @@ -89,3 +90,57 @@ where
serde_json::from_str(s)
.with_context(|| format!("failed to deserialize as {}\nJSON: {}", type_name::<T>(), s))
}

/// Initialize tracing subscriber to emit traces. This configures subscribers
/// for Trace Event Format (https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview).
#[js_function(1)]
pub fn init_custom_trace_subscriber(cx: CallContext) -> napi::Result<JsExternal> {
let optional_trace_out_file_path = cx.get::<JsUnknown>(0)?;
let trace_out_file_path = match optional_trace_out_file_path.get_type()? {
napi::ValueType::String => Some(PathBuf::from(
JsString::try_from(optional_trace_out_file_path)?
.into_utf8()?
.as_str()?
.to_owned(),
)),
_ => None,
};

let mut layer = ChromeLayerBuilder::new().include_args(true);
if let Some(trace_out_file) = trace_out_file_path {
let dir = trace_out_file
.parent()
.ok_or_else(|| anyhow!("Not able to find path to the trace output"))
.convert_err()?;
std::fs::create_dir_all(dir)?;

layer = layer.file(trace_out_file);
}

let (chrome_layer, guard) = layer.build();
tracing_subscriber::registry()
.with(chrome_layer.with_filter(filter::filter_fn(|metadata| {
!metadata.target().contains("cranelift") && !metadata.name().contains("log ")
})))
.try_init()
.expect("Failed to register tracing subscriber");

let guard_cell = RefCell::new(Some(guard));
cx.env.create_external(guard_cell, None)
}

/// Teardown currently running tracing subscriber to flush out remaining traces.
/// This should be called when parent node.js process exits, otherwise generated
/// trace may drop traces in the buffer.
#[js_function(1)]
pub fn teardown_trace_subscriber(cx: CallContext) -> napi::Result<JsUndefined> {
let guard_external = cx.get::<JsExternal>(0)?;
let guard_cell = &*cx
.env
.get_value_external::<RefCell<Option<FlushGuard>>>(&guard_external)?;

if let Some(guard) = guard_cell.take() {
drop(guard);
}
cx.env.get_undefined()
}
3 changes: 2 additions & 1 deletion packages/next/build/index.ts
Expand Up @@ -113,7 +113,7 @@ import { TelemetryPlugin } from './webpack/plugins/telemetry-plugin'
import { MiddlewareManifest } from './webpack/plugins/middleware-plugin'
import { recursiveCopy } from '../lib/recursive-copy'
import { recursiveReadDir } from '../lib/recursive-readdir'
import { lockfilePatchPromise } from './swc'
import { lockfilePatchPromise, teardownTraceSubscriber } from './swc'

export type SsgRoute = {
initialRevalidateSeconds: number | false
Expand Down Expand Up @@ -2212,6 +2212,7 @@ export default async function build(

// Ensure all traces are flushed before finishing the command
await flushAllTraces()
teardownTraceSubscriber()
}
}

Expand Down
5 changes: 4 additions & 1 deletion packages/next/build/output/store.ts
@@ -1,7 +1,7 @@
import createStore from 'next/dist/compiled/unistore'
import stripAnsi from 'next/dist/compiled/strip-ansi'
import { flushAllTraces } from '../../trace'

import { teardownTraceSubscriber } from '../swc'
import * as Log from './log'

export type OutputState =
Expand Down Expand Up @@ -91,6 +91,7 @@ store.subscribe((state) => {

// Ensure traces are flushed after each compile in development mode
flushAllTraces()
teardownTraceSubscriber()
return
}

Expand All @@ -117,6 +118,7 @@ store.subscribe((state) => {
Log.warn(state.warnings.join('\n\n'))
// Ensure traces are flushed after each compile in development mode
flushAllTraces()
teardownTraceSubscriber()
return
}

Expand All @@ -132,4 +134,5 @@ store.subscribe((state) => {
)
// Ensure traces are flushed after each compile in development mode
flushAllTraces()
teardownTraceSubscriber()
})
2 changes: 2 additions & 0 deletions packages/next/build/swc/index.d.ts
Expand Up @@ -6,3 +6,5 @@ export function minifySync(src: string, options: any): string
export function bundle(options: any): Promise<any>
export function parse(src: string, options: any): any
export const lockfilePatchPromise: { cur?: Promise<void> }
export function initCustomTraceSubscriber(traceFileName?: string): void
export function teardownTraceSubscriber(): void
43 changes: 43 additions & 0 deletions packages/next/build/swc/index.js
Expand Up @@ -17,6 +17,7 @@ let nativeBindings
let wasmBindings
let downloadWasmPromise
let pendingBindings
let swcTraceFlushGuard
export const lockfilePatchPromise = {}

async function loadBindings() {
Expand Down Expand Up @@ -261,6 +262,8 @@ function loadNative() {
},

getTargetTriple: bindings.getTargetTriple,
initCustomTraceSubscriber: bindings.initCustomTraceSubscriber,
teardownTraceSubscriber: bindings.teardownTraceSubscriber,
}
return nativeBindings
}
Expand Down Expand Up @@ -320,3 +323,43 @@ export function getBinaryMetadata() {
target: bindings?.getTargetTriple?.(),
}
}

/**
* Initialize trace subscriber to emit traces.
*
*/
export const initCustomTraceSubscriber = (() => {
return (filename) => {
if (!swcTraceFlushGuard) {
// Wasm binary doesn't support trace emission
let bindings = loadNative()
swcTraceFlushGuard = bindings.initCustomTraceSubscriber(filename)
}
}
})()

/**
* Teardown swc's trace subscriber if there's an initialized flush guard exists.
*
* This is workaround to amend behavior with process.exit
* (https://github.com/vercel/next.js/blob/4db8c49cc31e4fc182391fae6903fb5ef4e8c66e/packages/next/bin/next.ts#L134=)
* seems preventing napi's cleanup hook execution (https://github.com/swc-project/swc/blob/main/crates/node/src/util.rs#L48-L51=),
*
* instead parent process manually drops guard when process gets signal to exit.
*/
export const teardownTraceSubscriber = (() => {
let flushed = false
return () => {
if (!flushed) {
flushed = true
try {
let bindings = loadNative()
if (swcTraceFlushGuard) {
bindings.teardownTraceSubscriber(swcTraceFlushGuard)
}
} catch (e) {
// Suppress exceptions, this fn allows to fail to load native bindings
}
}
}
})()
10 changes: 10 additions & 0 deletions packages/next/build/webpack-config.ts
Expand Up @@ -436,6 +436,16 @@ export default async function getBaseWebpackConfig(
}

const getBabelOrSwcLoader = () => {
if (useSWCLoader && config?.experimental?.swcTraceProfiling) {
// This will init subscribers once only in a single process lifecycle,
// even though it can be called multiple times.
// Subscriber need to be initialized _before_ any actual swc's call (transform, etcs)
// to collect correct trace spans when they are called.
require('./swc')?.initCustomTraceSubscriber?.(
path.join(distDir, `swc-trace-profile-${Date.now()}.json`)
)
}

return useSWCLoader
? {
loader: 'next-swc-loader',
Expand Down
1 change: 1 addition & 0 deletions packages/next/server/config-shared.ts
Expand Up @@ -132,6 +132,7 @@ export interface ExperimentalConfig {
skipDefaultConversion?: boolean
}
>
swcTraceProfiling?: boolean
}

/**
Expand Down

0 comments on commit 837e0a6

Please sign in to comment.