mirror of https://github.com/webpack/webpack.git
				
				
				
			
		
			
				
	
	
		
			562 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			562 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
/*
 | 
						|
	MIT License http://www.opensource.org/licenses/mit-license.php
 | 
						|
*/
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
const { Tracer } = require("chrome-trace-event");
 | 
						|
const {
 | 
						|
	JAVASCRIPT_MODULES,
 | 
						|
	CSS_MODULES,
 | 
						|
	WEBASSEMBLY_MODULES,
 | 
						|
	JSON_MODULE_TYPE
 | 
						|
} = require("../ModuleTypeConstants");
 | 
						|
const createSchemaValidation = require("../util/create-schema-validation");
 | 
						|
const { dirname, mkdirpSync } = require("../util/fs");
 | 
						|
 | 
						|
/** @typedef {import("inspector").Session} Session */
 | 
						|
/** @typedef {import("tapable").FullTap} FullTap */
 | 
						|
/** @typedef {import("../../declarations/plugins/debug/ProfilingPlugin").ProfilingPluginOptions} ProfilingPluginOptions */
 | 
						|
/** @typedef {import("../Compilation")} Compilation */
 | 
						|
/** @typedef {import("../Compiler")} Compiler */
 | 
						|
/** @typedef {import("../ContextModuleFactory")} ContextModuleFactory */
 | 
						|
/** @typedef {import("../ModuleFactory")} ModuleFactory */
 | 
						|
/** @typedef {import("../NormalModuleFactory")} NormalModuleFactory */
 | 
						|
/** @typedef {import("../Parser")} Parser */
 | 
						|
/** @typedef {import("../ResolverFactory")} ResolverFactory */
 | 
						|
/** @typedef {import("../util/fs").IntermediateFileSystem} IntermediateFileSystem */
 | 
						|
 | 
						|
const validate = createSchemaValidation(
 | 
						|
	require("../../schemas/plugins/debug/ProfilingPlugin.check.js"),
 | 
						|
	() => require("../../schemas/plugins/debug/ProfilingPlugin.json"),
 | 
						|
	{
 | 
						|
		name: "Profiling Plugin",
 | 
						|
		baseDataPath: "options"
 | 
						|
	}
 | 
						|
);
 | 
						|
 | 
						|
/** @typedef {{ Session: typeof import("inspector").Session }} Inspector */
 | 
						|
 | 
						|
/** @type {Inspector | undefined} */
 | 
						|
let inspector;
 | 
						|
 | 
						|
try {
 | 
						|
	// eslint-disable-next-line n/no-unsupported-features/node-builtins
 | 
						|
	inspector = require("inspector");
 | 
						|
} catch (_err) {
 | 
						|
	// eslint-disable-next-line no-console
 | 
						|
	console.log("Unable to CPU profile in < node 8.0");
 | 
						|
}
 | 
						|
 | 
						|
class Profiler {
 | 
						|
	/**
 | 
						|
	 * @param {Inspector} inspector inspector
 | 
						|
	 */
 | 
						|
	constructor(inspector) {
 | 
						|
		/** @type {undefined | Session} */
 | 
						|
		this.session = undefined;
 | 
						|
		this.inspector = inspector;
 | 
						|
		this._startTime = 0;
 | 
						|
	}
 | 
						|
 | 
						|
	hasSession() {
 | 
						|
		return this.session !== undefined;
 | 
						|
	}
 | 
						|
 | 
						|
	startProfiling() {
 | 
						|
		if (this.inspector === undefined) {
 | 
						|
			return Promise.resolve();
 | 
						|
		}
 | 
						|
 | 
						|
		try {
 | 
						|
			this.session = new /** @type {Inspector} */ (inspector).Session();
 | 
						|
			/** @type {Session} */
 | 
						|
			(this.session).connect();
 | 
						|
		} catch (_) {
 | 
						|
			this.session = undefined;
 | 
						|
			return Promise.resolve();
 | 
						|
		}
 | 
						|
 | 
						|
		const hrtime = process.hrtime();
 | 
						|
		this._startTime = hrtime[0] * 1000000 + Math.round(hrtime[1] / 1000);
 | 
						|
 | 
						|
		return Promise.all([
 | 
						|
			this.sendCommand("Profiler.setSamplingInterval", {
 | 
						|
				interval: 100
 | 
						|
			}),
 | 
						|
			this.sendCommand("Profiler.enable"),
 | 
						|
			this.sendCommand("Profiler.start")
 | 
						|
		]);
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param {string} method method name
 | 
						|
	 * @param {EXPECTED_OBJECT=} params params
 | 
						|
	 * @returns {Promise<EXPECTED_ANY | void>} Promise for the result
 | 
						|
	 */
 | 
						|
	sendCommand(method, params) {
 | 
						|
		if (this.hasSession()) {
 | 
						|
			return new Promise((res, rej) => {
 | 
						|
				/** @type {Session} */
 | 
						|
				(this.session).post(method, params, (err, params) => {
 | 
						|
					if (err !== null) {
 | 
						|
						rej(err);
 | 
						|
					} else {
 | 
						|
						res(params);
 | 
						|
					}
 | 
						|
				});
 | 
						|
			});
 | 
						|
		}
 | 
						|
		return Promise.resolve();
 | 
						|
	}
 | 
						|
 | 
						|
	destroy() {
 | 
						|
		if (this.hasSession()) {
 | 
						|
			/** @type {Session} */
 | 
						|
			(this.session).disconnect();
 | 
						|
		}
 | 
						|
 | 
						|
		return Promise.resolve();
 | 
						|
	}
 | 
						|
 | 
						|
	stopProfiling() {
 | 
						|
		return this.sendCommand("Profiler.stop").then(({ profile }) => {
 | 
						|
			const hrtime = process.hrtime();
 | 
						|
			const endTime = hrtime[0] * 1000000 + Math.round(hrtime[1] / 1000);
 | 
						|
			// Avoid coverage problems due indirect changes
 | 
						|
			/* istanbul ignore next */
 | 
						|
			if (profile.startTime < this._startTime || profile.endTime > endTime) {
 | 
						|
				// In some cases timestamps mismatch and we need to adjust them
 | 
						|
				// Both process.hrtime and the inspector timestamps claim to be relative
 | 
						|
				// to a unknown point in time. But they do not guarantee that this is the
 | 
						|
				// same point in time.
 | 
						|
				const duration = profile.endTime - profile.startTime;
 | 
						|
				const ownDuration = endTime - this._startTime;
 | 
						|
				const untracked = Math.max(0, ownDuration - duration);
 | 
						|
				profile.startTime = this._startTime + untracked / 2;
 | 
						|
				profile.endTime = endTime - untracked / 2;
 | 
						|
			}
 | 
						|
			return { profile };
 | 
						|
		});
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * an object that wraps Tracer and Profiler with a counter
 | 
						|
 * @typedef {object} Trace
 | 
						|
 * @property {Tracer} trace instance of Tracer
 | 
						|
 * @property {number} counter Counter
 | 
						|
 * @property {Profiler} profiler instance of Profiler
 | 
						|
 * @property {(callback: (err?: null | Error) => void) => void} end the end function
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {IntermediateFileSystem} fs filesystem used for output
 | 
						|
 * @param {string} outputPath The location where to write the log.
 | 
						|
 * @returns {Trace} The trace object
 | 
						|
 */
 | 
						|
const createTrace = (fs, outputPath) => {
 | 
						|
	const trace = new Tracer();
 | 
						|
	const profiler = new Profiler(/** @type {Inspector} */ (inspector));
 | 
						|
	if (/\/|\\/.test(outputPath)) {
 | 
						|
		const dirPath = dirname(fs, outputPath);
 | 
						|
		mkdirpSync(fs, dirPath);
 | 
						|
	}
 | 
						|
	const fsStream = fs.createWriteStream(outputPath);
 | 
						|
 | 
						|
	let counter = 0;
 | 
						|
 | 
						|
	trace.pipe(fsStream);
 | 
						|
	// These are critical events that need to be inserted so that tools like
 | 
						|
	// chrome dev tools can load the profile.
 | 
						|
	trace.instantEvent({
 | 
						|
		name: "TracingStartedInPage",
 | 
						|
		id: ++counter,
 | 
						|
		cat: ["disabled-by-default-devtools.timeline"],
 | 
						|
		args: {
 | 
						|
			data: {
 | 
						|
				sessionId: "-1",
 | 
						|
				page: "0xfff",
 | 
						|
				frames: [
 | 
						|
					{
 | 
						|
						frame: "0xfff",
 | 
						|
						url: "webpack",
 | 
						|
						name: ""
 | 
						|
					}
 | 
						|
				]
 | 
						|
			}
 | 
						|
		}
 | 
						|
	});
 | 
						|
 | 
						|
	trace.instantEvent({
 | 
						|
		name: "TracingStartedInBrowser",
 | 
						|
		id: ++counter,
 | 
						|
		cat: ["disabled-by-default-devtools.timeline"],
 | 
						|
		args: {
 | 
						|
			data: {
 | 
						|
				sessionId: "-1"
 | 
						|
			}
 | 
						|
		}
 | 
						|
	});
 | 
						|
 | 
						|
	return {
 | 
						|
		trace,
 | 
						|
		counter,
 | 
						|
		profiler,
 | 
						|
		end: callback => {
 | 
						|
			trace.push("]");
 | 
						|
			// Wait until the write stream finishes.
 | 
						|
			fsStream.on("close", () => {
 | 
						|
				callback();
 | 
						|
			});
 | 
						|
			// Tear down the readable trace stream.
 | 
						|
			trace.push(null);
 | 
						|
		}
 | 
						|
	};
 | 
						|
};
 | 
						|
 | 
						|
const PLUGIN_NAME = "ProfilingPlugin";
 | 
						|
 | 
						|
class ProfilingPlugin {
 | 
						|
	/**
 | 
						|
	 * @param {ProfilingPluginOptions=} options options object
 | 
						|
	 */
 | 
						|
	constructor(options = {}) {
 | 
						|
		validate(options);
 | 
						|
		this.outputPath = options.outputPath || "events.json";
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Apply the plugin
 | 
						|
	 * @param {Compiler} compiler the compiler instance
 | 
						|
	 * @returns {void}
 | 
						|
	 */
 | 
						|
	apply(compiler) {
 | 
						|
		const tracer = createTrace(
 | 
						|
			/** @type {IntermediateFileSystem} */
 | 
						|
			(compiler.intermediateFileSystem),
 | 
						|
			this.outputPath
 | 
						|
		);
 | 
						|
		tracer.profiler.startProfiling();
 | 
						|
 | 
						|
		// Compiler Hooks
 | 
						|
		for (const hookName of Object.keys(compiler.hooks)) {
 | 
						|
			const hook =
 | 
						|
				compiler.hooks[/** @type {keyof Compiler["hooks"]} */ (hookName)];
 | 
						|
			if (hook) {
 | 
						|
				hook.intercept(makeInterceptorFor("Compiler", tracer)(hookName));
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		for (const hookName of Object.keys(compiler.resolverFactory.hooks)) {
 | 
						|
			const hook =
 | 
						|
				compiler.resolverFactory.hooks[
 | 
						|
					/** @type {keyof ResolverFactory["hooks"]} */
 | 
						|
					(hookName)
 | 
						|
				];
 | 
						|
			if (hook) {
 | 
						|
				hook.intercept(makeInterceptorFor("Resolver", tracer)(hookName));
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		compiler.hooks.compilation.tap(
 | 
						|
			PLUGIN_NAME,
 | 
						|
			(compilation, { normalModuleFactory, contextModuleFactory }) => {
 | 
						|
				interceptAllHooksFor(compilation, tracer, "Compilation");
 | 
						|
				interceptAllHooksFor(
 | 
						|
					normalModuleFactory,
 | 
						|
					tracer,
 | 
						|
					"Normal Module Factory"
 | 
						|
				);
 | 
						|
				interceptAllHooksFor(
 | 
						|
					contextModuleFactory,
 | 
						|
					tracer,
 | 
						|
					"Context Module Factory"
 | 
						|
				);
 | 
						|
				interceptAllParserHooks(normalModuleFactory, tracer);
 | 
						|
				interceptAllGeneratorHooks(normalModuleFactory, tracer);
 | 
						|
				interceptAllJavascriptModulesPluginHooks(compilation, tracer);
 | 
						|
				interceptAllCssModulesPluginHooks(compilation, tracer);
 | 
						|
			}
 | 
						|
		);
 | 
						|
 | 
						|
		// We need to write out the CPU profile when we are all done.
 | 
						|
		compiler.hooks.done.tapAsync(
 | 
						|
			{
 | 
						|
				name: PLUGIN_NAME,
 | 
						|
				stage: Infinity
 | 
						|
			},
 | 
						|
			(stats, callback) => {
 | 
						|
				if (compiler.watchMode) return callback();
 | 
						|
				tracer.profiler.stopProfiling().then(parsedResults => {
 | 
						|
					if (parsedResults === undefined) {
 | 
						|
						tracer.profiler.destroy();
 | 
						|
						tracer.end(callback);
 | 
						|
						return;
 | 
						|
					}
 | 
						|
 | 
						|
					const cpuStartTime = parsedResults.profile.startTime;
 | 
						|
					const cpuEndTime = parsedResults.profile.endTime;
 | 
						|
 | 
						|
					tracer.trace.completeEvent({
 | 
						|
						name: "TaskQueueManager::ProcessTaskFromWorkQueue",
 | 
						|
						id: ++tracer.counter,
 | 
						|
						cat: ["toplevel"],
 | 
						|
						ts: cpuStartTime,
 | 
						|
						args: {
 | 
						|
							// eslint-disable-next-line camelcase
 | 
						|
							src_file: "../../ipc/ipc_moji_bootstrap.cc",
 | 
						|
							// eslint-disable-next-line camelcase
 | 
						|
							src_func: "Accept"
 | 
						|
						}
 | 
						|
					});
 | 
						|
 | 
						|
					tracer.trace.completeEvent({
 | 
						|
						name: "EvaluateScript",
 | 
						|
						id: ++tracer.counter,
 | 
						|
						cat: ["devtools.timeline"],
 | 
						|
						ts: cpuStartTime,
 | 
						|
						dur: cpuEndTime - cpuStartTime,
 | 
						|
						args: {
 | 
						|
							data: {
 | 
						|
								url: "webpack",
 | 
						|
								lineNumber: 1,
 | 
						|
								columnNumber: 1,
 | 
						|
								frame: "0xFFF"
 | 
						|
							}
 | 
						|
						}
 | 
						|
					});
 | 
						|
 | 
						|
					tracer.trace.instantEvent({
 | 
						|
						name: "CpuProfile",
 | 
						|
						id: ++tracer.counter,
 | 
						|
						cat: ["disabled-by-default-devtools.timeline"],
 | 
						|
						ts: cpuEndTime,
 | 
						|
						args: {
 | 
						|
							data: {
 | 
						|
								cpuProfile: parsedResults.profile
 | 
						|
							}
 | 
						|
						}
 | 
						|
					});
 | 
						|
 | 
						|
					tracer.profiler.destroy();
 | 
						|
					tracer.end(callback);
 | 
						|
				});
 | 
						|
			}
 | 
						|
		);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {EXPECTED_ANY & { hooks: TODO }} instance instance
 | 
						|
 * @param {Trace} tracer tracer
 | 
						|
 * @param {string} logLabel log label
 | 
						|
 */
 | 
						|
const interceptAllHooksFor = (instance, tracer, logLabel) => {
 | 
						|
	if (Reflect.has(instance, "hooks")) {
 | 
						|
		for (const hookName of Object.keys(instance.hooks)) {
 | 
						|
			const hook = instance.hooks[hookName];
 | 
						|
			if (hook && !hook._fakeHook) {
 | 
						|
				hook.intercept(makeInterceptorFor(logLabel, tracer)(hookName));
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {NormalModuleFactory} moduleFactory normal module factory
 | 
						|
 * @param {Trace} tracer tracer
 | 
						|
 */
 | 
						|
const interceptAllParserHooks = (moduleFactory, tracer) => {
 | 
						|
	const moduleTypes = [
 | 
						|
		...JAVASCRIPT_MODULES,
 | 
						|
		JSON_MODULE_TYPE,
 | 
						|
		...WEBASSEMBLY_MODULES,
 | 
						|
		...CSS_MODULES
 | 
						|
	];
 | 
						|
 | 
						|
	for (const moduleType of moduleTypes) {
 | 
						|
		moduleFactory.hooks.parser
 | 
						|
			.for(moduleType)
 | 
						|
			.tap(PLUGIN_NAME, (parser, parserOpts) => {
 | 
						|
				interceptAllHooksFor(parser, tracer, "Parser");
 | 
						|
			});
 | 
						|
	}
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {NormalModuleFactory} moduleFactory normal module factory
 | 
						|
 * @param {Trace} tracer tracer
 | 
						|
 */
 | 
						|
const interceptAllGeneratorHooks = (moduleFactory, tracer) => {
 | 
						|
	const moduleTypes = [
 | 
						|
		...JAVASCRIPT_MODULES,
 | 
						|
		JSON_MODULE_TYPE,
 | 
						|
		...WEBASSEMBLY_MODULES,
 | 
						|
		...CSS_MODULES
 | 
						|
	];
 | 
						|
 | 
						|
	for (const moduleType of moduleTypes) {
 | 
						|
		moduleFactory.hooks.generator
 | 
						|
			.for(moduleType)
 | 
						|
			.tap(PLUGIN_NAME, (parser, parserOpts) => {
 | 
						|
				interceptAllHooksFor(parser, tracer, "Generator");
 | 
						|
			});
 | 
						|
	}
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {Compilation} compilation compilation
 | 
						|
 * @param {Trace} tracer tracer
 | 
						|
 */
 | 
						|
const interceptAllJavascriptModulesPluginHooks = (compilation, tracer) => {
 | 
						|
	interceptAllHooksFor(
 | 
						|
		{
 | 
						|
			hooks:
 | 
						|
				require("../javascript/JavascriptModulesPlugin").getCompilationHooks(
 | 
						|
					compilation
 | 
						|
				)
 | 
						|
		},
 | 
						|
		tracer,
 | 
						|
		"JavascriptModulesPlugin"
 | 
						|
	);
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {Compilation} compilation compilation
 | 
						|
 * @param {Trace} tracer tracer
 | 
						|
 */
 | 
						|
const interceptAllCssModulesPluginHooks = (compilation, tracer) => {
 | 
						|
	interceptAllHooksFor(
 | 
						|
		{
 | 
						|
			hooks: require("../css/CssModulesPlugin").getCompilationHooks(compilation)
 | 
						|
		},
 | 
						|
		tracer,
 | 
						|
		"CssModulesPlugin"
 | 
						|
	);
 | 
						|
};
 | 
						|
 | 
						|
/** @typedef {(...args: EXPECTED_ANY[]) => EXPECTED_ANY | Promise<(...args: EXPECTED_ANY[]) => EXPECTED_ANY>} PluginFunction */
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {string} instance instance
 | 
						|
 * @param {Trace} tracer tracer
 | 
						|
 * @returns {(hookName: string) => TODO} interceptor
 | 
						|
 */
 | 
						|
const makeInterceptorFor = (instance, tracer) => hookName => ({
 | 
						|
	/**
 | 
						|
	 * @param {FullTap} tapInfo tap info
 | 
						|
	 * @returns {FullTap} modified full tap
 | 
						|
	 */
 | 
						|
	register: tapInfo => {
 | 
						|
		const { name, type, fn: internalFn } = tapInfo;
 | 
						|
		const newFn =
 | 
						|
			// Don't tap our own hooks to ensure stream can close cleanly
 | 
						|
			name === PLUGIN_NAME
 | 
						|
				? internalFn
 | 
						|
				: makeNewProfiledTapFn(hookName, tracer, {
 | 
						|
						name,
 | 
						|
						type,
 | 
						|
						fn: /** @type {PluginFunction} */ (internalFn)
 | 
						|
					});
 | 
						|
		return { ...tapInfo, fn: newFn };
 | 
						|
	}
 | 
						|
});
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {string} hookName Name of the hook to profile.
 | 
						|
 * @param {Trace} tracer The trace object.
 | 
						|
 * @param {object} options Options for the profiled fn.
 | 
						|
 * @param {string} options.name Plugin name
 | 
						|
 * @param {"sync" | "async" | "promise"} options.type Plugin type (sync | async | promise)
 | 
						|
 * @param {PluginFunction} options.fn Plugin function
 | 
						|
 * @returns {PluginFunction} Chainable hooked function.
 | 
						|
 */
 | 
						|
const makeNewProfiledTapFn = (hookName, tracer, { name, type, fn }) => {
 | 
						|
	const defaultCategory = ["blink.user_timing"];
 | 
						|
 | 
						|
	switch (type) {
 | 
						|
		case "promise":
 | 
						|
			return (...args) => {
 | 
						|
				const id = ++tracer.counter;
 | 
						|
				tracer.trace.begin({
 | 
						|
					name,
 | 
						|
					id,
 | 
						|
					cat: defaultCategory
 | 
						|
				});
 | 
						|
				const promise =
 | 
						|
					/** @type {Promise<(...args: EXPECTED_ANY[]) => EXPECTED_ANY>} */
 | 
						|
					(fn(...args));
 | 
						|
				return promise.then(r => {
 | 
						|
					tracer.trace.end({
 | 
						|
						name,
 | 
						|
						id,
 | 
						|
						cat: defaultCategory
 | 
						|
					});
 | 
						|
					return r;
 | 
						|
				});
 | 
						|
			};
 | 
						|
		case "async":
 | 
						|
			return (...args) => {
 | 
						|
				const id = ++tracer.counter;
 | 
						|
				tracer.trace.begin({
 | 
						|
					name,
 | 
						|
					id,
 | 
						|
					cat: defaultCategory
 | 
						|
				});
 | 
						|
				const callback = args.pop();
 | 
						|
				fn(
 | 
						|
					...args,
 | 
						|
					/**
 | 
						|
					 * @param {...EXPECTED_ANY[]} r result
 | 
						|
					 */
 | 
						|
					(...r) => {
 | 
						|
						tracer.trace.end({
 | 
						|
							name,
 | 
						|
							id,
 | 
						|
							cat: defaultCategory
 | 
						|
						});
 | 
						|
						callback(...r);
 | 
						|
					}
 | 
						|
				);
 | 
						|
			};
 | 
						|
		case "sync":
 | 
						|
			return (...args) => {
 | 
						|
				const id = ++tracer.counter;
 | 
						|
				// Do not instrument ourself due to the CPU
 | 
						|
				// profile needing to be the last event in the trace.
 | 
						|
				if (name === PLUGIN_NAME) {
 | 
						|
					return fn(...args);
 | 
						|
				}
 | 
						|
 | 
						|
				tracer.trace.begin({
 | 
						|
					name,
 | 
						|
					id,
 | 
						|
					cat: defaultCategory
 | 
						|
				});
 | 
						|
				let r;
 | 
						|
				try {
 | 
						|
					r = fn(...args);
 | 
						|
				} catch (err) {
 | 
						|
					tracer.trace.end({
 | 
						|
						name,
 | 
						|
						id,
 | 
						|
						cat: defaultCategory
 | 
						|
					});
 | 
						|
					throw err;
 | 
						|
				}
 | 
						|
				tracer.trace.end({
 | 
						|
					name,
 | 
						|
					id,
 | 
						|
					cat: defaultCategory
 | 
						|
				});
 | 
						|
				return r;
 | 
						|
			};
 | 
						|
		default:
 | 
						|
			return fn;
 | 
						|
	}
 | 
						|
};
 | 
						|
 | 
						|
module.exports = ProfilingPlugin;
 | 
						|
module.exports.Profiler = Profiler;
 |