-
Notifications
You must be signed in to change notification settings - Fork 27k
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
Improvements to webpack tracing, including hot-reload #21652
Changes from all commits
600f161
ae3e542
7b0f8c0
96095b6
e1ff565
4064ec7
c664775
3b38a7d
93becc2
31c6e1e
11fc4b7
24850bb
b57819d
dc54680
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,13 @@ | ||
import { tracer } from '../../tracer' | ||
import { tracer, stackPush, stackPop } from '../../tracer' | ||
import { webpack, isWebpack5 } from 'next/dist/compiled/webpack/webpack' | ||
import { | ||
Span, | ||
trace, | ||
ProxyTracerProvider, | ||
NoopTracerProvider, | ||
} from '@opentelemetry/api' | ||
|
||
const pluginName = 'ProfilingPlugin' | ||
|
||
export const spans = new WeakMap() | ||
|
||
function getNormalModuleLoaderHook(compilation: any) { | ||
|
@@ -14,26 +19,87 @@ function getNormalModuleLoaderHook(compilation: any) { | |
return compilation.hooks.normalModuleLoader | ||
} | ||
|
||
function tracingIsEnabled() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I found something that works! No need for a top-level span to be present for the plugin to activate. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be moved to tracer.ts so that it can be reused in other places if needed? |
||
const tracerProvider: any = trace.getTracerProvider() | ||
if (tracerProvider instanceof ProxyTracerProvider) { | ||
const proxyDelegate: any = tracerProvider.getDelegate() | ||
return !(proxyDelegate instanceof NoopTracerProvider) | ||
} | ||
return false | ||
} | ||
|
||
export class ProfilingPlugin { | ||
compiler: any | ||
|
||
apply(compiler: any) { | ||
// Only enabled when instrumentation is loaded | ||
const currentSpan = tracer.getCurrentSpan() | ||
if (!currentSpan || !currentSpan.isRecording()) { | ||
// Only enable plugin when instrumentation is loaded | ||
if (!tracingIsEnabled()) { | ||
return | ||
} | ||
this.traceTopLevelHooks(compiler) | ||
this.traceCompilationHooks(compiler) | ||
this.compiler = compiler | ||
} | ||
|
||
compiler.hooks.compile.tap(pluginName, () => { | ||
const span = tracer.startSpan('webpack-compile', { | ||
attributes: { name: compiler.name }, | ||
}) | ||
spans.set(compiler, span) | ||
traceHookPair( | ||
spanName: string, | ||
startHook: any, | ||
stopHook: any, | ||
attrs?: any, | ||
onSetSpan?: (span: Span | undefined) => void | ||
) { | ||
let span: Span | undefined | ||
startHook.tap(pluginName, () => { | ||
span = stackPush(this.compiler, spanName, attrs) | ||
onSetSpan?.(span) | ||
}) | ||
stopHook.tap(pluginName, () => { | ||
stackPop(this.compiler, span) | ||
}) | ||
} | ||
|
||
traceLoopedHook(spanName: string, startHook: any, stopHook: any) { | ||
let span: Span | undefined | ||
startHook.tap(pluginName, () => { | ||
if (!span) { | ||
span = stackPush(this.compiler, spanName) | ||
} | ||
}) | ||
compiler.hooks.done.tap(pluginName, () => { | ||
spans.get(compiler).end() | ||
stopHook.tap(pluginName, () => { | ||
stackPop(this.compiler, span) | ||
}) | ||
} | ||
|
||
traceTopLevelHooks(compiler: any) { | ||
this.traceHookPair( | ||
'webpack-compile', | ||
compiler.hooks.compile, | ||
compiler.hooks.done, | ||
() => { | ||
return { attributes: { name: compiler.name } } | ||
}, | ||
(span) => spans.set(compiler, span) | ||
) | ||
this.traceHookPair( | ||
'webpack-prepare-env', | ||
compiler.hooks.environment, | ||
compiler.hooks.afterEnvironment | ||
) | ||
this.traceHookPair( | ||
'webpack-invalidated', | ||
compiler.hooks.invalid, | ||
compiler.hooks.done | ||
) | ||
} | ||
|
||
traceCompilationHooks(compiler: any) { | ||
compiler.hooks.compilation.tap(pluginName, (compilation: any) => { | ||
compilation.hooks.buildModule.tap(pluginName, (module: any) => { | ||
tracer.withSpan(spans.get(compiler), () => { | ||
const compilerSpan = spans.get(compiler) | ||
if (!compilerSpan) { | ||
return | ||
} | ||
tracer.withSpan(compilerSpan, () => { | ||
const span = tracer.startSpan('build-module') | ||
span.setAttribute('name', module.userRequest) | ||
spans.set(module, span) | ||
|
@@ -51,6 +117,45 @@ export class ProfilingPlugin { | |
compilation.hooks.succeedModule.tap(pluginName, (module: any) => { | ||
spans.get(module).end() | ||
}) | ||
|
||
if (isWebpack5) { | ||
this.traceHookPair( | ||
'webpack-compilation', | ||
compilation.hooks.beforeCompile, | ||
compilation.hooks.afterCompile | ||
) | ||
} | ||
|
||
this.traceHookPair( | ||
'webpack-compilation-chunk-graph', | ||
compilation.hooks.beforeChunks, | ||
compilation.hooks.afterChunks | ||
) | ||
this.traceHookPair( | ||
'webpack-compilation-optimize', | ||
compilation.hooks.optimize, | ||
compilation.hooks.reviveModules | ||
) | ||
this.traceLoopedHook( | ||
'webpack-compilation-optimize-modules', | ||
compilation.hooks.optimizeModules, | ||
compilation.hooks.afterOptimizeModules | ||
) | ||
this.traceLoopedHook( | ||
'webpack-compilation-optimize-chunks', | ||
compilation.hooks.optimizeChunks, | ||
compilation.hooks.afterOptimizeChunks | ||
) | ||
this.traceHookPair( | ||
'webpack-compilation-optimize-tree', | ||
compilation.hooks.optimizeTree, | ||
compilation.hooks.afterOptimizeTree | ||
) | ||
this.traceHookPair( | ||
'webpack-compilation-hash', | ||
compilation.hooks.beforeHash, | ||
compilation.hooks.afterHash | ||
) | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using this approach allowed for easier association between parent and child. The error handling in
stackPop
down below also helped to identify when the start/stop tracing events were placed incorrectly.