Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(next-swc): introduce experimental tracing support for swc #35803

Merged
merged 7 commits into from May 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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()
timneutkens marked this conversation as resolved.
Show resolved Hide resolved
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