Skip to content

Commit 754a337

Browse files
Introduce --profile (#7284)
* Introduce --profile Chrome DevTools compatible profiler * Changeset * doc(codegen): add doc for the profile flag Co-authored-by: Charly POLY <cpoly55@gmail.com>
1 parent bef4376 commit 754a337

File tree

14 files changed

+425
-214
lines changed

14 files changed

+425
-214
lines changed

‎.changeset/silent-bags-sell.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@graphql-cli/codegen': minor
3+
'@graphql-codegen/cli': minor
4+
'@graphql-codegen/core': minor
5+
'@graphql-codegen/plugin-helpers': minor
6+
---
7+
8+
Performance Profiler --profile

‎packages/graphql-codegen-cli/src/codegen.ts

Lines changed: 131 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,6 @@ function createCache<T>(loader: (key: string) => Promise<T>) {
5252
}
5353

5454
export async function executeCodegen(input: CodegenContext | Types.Config): Promise<Types.FileOutput[]> {
55-
function wrapTask(task: () => void | Promise<void>, source: string) {
56-
return async () => {
57-
try {
58-
await Promise.resolve().then(() => task());
59-
} catch (error) {
60-
if (source && !(error instanceof GraphQLError)) {
61-
error.source = source;
62-
}
63-
64-
throw error;
65-
}
66-
};
67-
}
68-
6955
const context = ensureContext(input);
7056
const config = context.getConfig();
7157
const pluginContext = context.getPluginContext();
@@ -117,6 +103,21 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom
117103
documents: documents,
118104
};
119105
});
106+
function wrapTask(task: () => void | Promise<void>, source: string, taskName: string) {
107+
return () => {
108+
return context.profiler.run(async () => {
109+
try {
110+
await Promise.resolve().then(() => task());
111+
} catch (error) {
112+
if (source && !(error instanceof GraphQLError)) {
113+
error.source = source;
114+
}
115+
116+
throw error;
117+
}
118+
}, taskName);
119+
};
120+
}
120121

121122
async function normalize() {
122123
/* Load Require extensions */
@@ -232,119 +233,137 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom
232233
[
233234
{
234235
title: 'Load GraphQL schemas',
235-
task: wrapTask(async () => {
236-
debugLog(`[CLI] Loading Schemas`);
237-
238-
const schemaPointerMap: any = {};
239-
const allSchemaUnnormalizedPointers = [...rootSchemas, ...outputSpecificSchemas];
240-
for (const unnormalizedPtr of allSchemaUnnormalizedPointers) {
241-
if (typeof unnormalizedPtr === 'string') {
242-
schemaPointerMap[unnormalizedPtr] = {};
243-
} else if (typeof unnormalizedPtr === 'object') {
244-
Object.assign(schemaPointerMap, unnormalizedPtr);
236+
task: wrapTask(
237+
async () => {
238+
debugLog(`[CLI] Loading Schemas`);
239+
240+
const schemaPointerMap: any = {};
241+
const allSchemaUnnormalizedPointers = [...rootSchemas, ...outputSpecificSchemas];
242+
for (const unnormalizedPtr of allSchemaUnnormalizedPointers) {
243+
if (typeof unnormalizedPtr === 'string') {
244+
schemaPointerMap[unnormalizedPtr] = {};
245+
} else if (typeof unnormalizedPtr === 'object') {
246+
Object.assign(schemaPointerMap, unnormalizedPtr);
247+
}
245248
}
246-
}
247249

248-
const hash = JSON.stringify(schemaPointerMap);
249-
const result = await schemaLoadingCache.load(hash);
250+
const hash = JSON.stringify(schemaPointerMap);
251+
const result = await schemaLoadingCache.load(hash);
250252

251-
outputSchemaAst = await result.outputSchemaAst;
252-
outputSchema = result.outputSchema;
253-
}, filename),
253+
outputSchemaAst = await result.outputSchemaAst;
254+
outputSchema = result.outputSchema;
255+
},
256+
filename,
257+
`Load GraphQL schemas: ${filename}`
258+
),
254259
},
255260
{
256261
title: 'Load GraphQL documents',
257-
task: wrapTask(async () => {
258-
debugLog(`[CLI] Loading Documents`);
262+
task: wrapTask(
263+
async () => {
264+
debugLog(`[CLI] Loading Documents`);
259265

260-
// get different cache for shared docs and output specific docs
261-
const results = await Promise.all(
262-
[rootDocuments, outputSpecificDocuments].map(docs => {
263-
const hash = JSON.stringify(docs);
264-
return documentsLoadingCache.load(hash);
265-
})
266-
);
266+
// get different cache for shared docs and output specific docs
267+
const results = await Promise.all(
268+
[rootDocuments, outputSpecificDocuments].map(docs => {
269+
const hash = JSON.stringify(docs);
270+
return documentsLoadingCache.load(hash);
271+
})
272+
);
267273

268-
const documents: Types.DocumentFile[] = [];
274+
const documents: Types.DocumentFile[] = [];
269275

270-
results.forEach(source => documents.push(...source.documents));
276+
results.forEach(source => documents.push(...source.documents));
271277

272-
if (documents.length > 0) {
273-
outputDocuments.push(...documents);
274-
}
275-
}, filename),
278+
if (documents.length > 0) {
279+
outputDocuments.push(...documents);
280+
}
281+
},
282+
filename,
283+
`Load GraphQL documents: ${filename}`
284+
),
276285
},
277286
{
278287
title: 'Generate',
279-
task: wrapTask(async () => {
280-
debugLog(`[CLI] Generating output`);
281-
282-
const normalizedPluginsArray = normalizeConfig(outputConfig.plugins);
283-
const pluginLoader = config.pluginLoader || makeDefaultLoader(context.cwd);
284-
const pluginPackages = await Promise.all(
285-
normalizedPluginsArray.map(plugin => getPluginByName(Object.keys(plugin)[0], pluginLoader))
286-
);
287-
const pluginMap: { [name: string]: CodegenPlugin } = {};
288-
const preset: Types.OutputPreset = hasPreset
289-
? typeof outputConfig.preset === 'string'
290-
? await getPresetByName(outputConfig.preset, makeDefaultLoader(context.cwd))
291-
: outputConfig.preset
292-
: null;
293-
294-
pluginPackages.forEach((pluginPackage, i) => {
295-
const plugin = normalizedPluginsArray[i];
296-
const name = Object.keys(plugin)[0];
297-
298-
pluginMap[name] = pluginPackage;
299-
});
300-
301-
const mergedConfig = {
302-
...rootConfig,
303-
...(typeof outputFileTemplateConfig === 'string'
304-
? { value: outputFileTemplateConfig }
305-
: outputFileTemplateConfig),
306-
};
307-
308-
let outputs: Types.GenerateOptions[] = [];
309-
310-
if (hasPreset) {
311-
outputs = await preset.buildGeneratesSection({
312-
baseOutputDir: filename,
313-
presetConfig: outputConfig.presetConfig || {},
314-
plugins: normalizedPluginsArray,
315-
schema: outputSchema,
316-
schemaAst: outputSchemaAst,
317-
documents: outputDocuments,
318-
config: mergedConfig,
319-
pluginMap,
320-
pluginContext,
288+
task: wrapTask(
289+
async () => {
290+
debugLog(`[CLI] Generating output`);
291+
292+
const normalizedPluginsArray = normalizeConfig(outputConfig.plugins);
293+
const pluginLoader = config.pluginLoader || makeDefaultLoader(context.cwd);
294+
const pluginPackages = await Promise.all(
295+
normalizedPluginsArray.map(plugin => getPluginByName(Object.keys(plugin)[0], pluginLoader))
296+
);
297+
const pluginMap: { [name: string]: CodegenPlugin } = {};
298+
const preset: Types.OutputPreset = hasPreset
299+
? typeof outputConfig.preset === 'string'
300+
? await getPresetByName(outputConfig.preset, makeDefaultLoader(context.cwd))
301+
: outputConfig.preset
302+
: null;
303+
304+
pluginPackages.forEach((pluginPackage, i) => {
305+
const plugin = normalizedPluginsArray[i];
306+
const name = Object.keys(plugin)[0];
307+
308+
pluginMap[name] = pluginPackage;
321309
});
322-
} else {
323-
outputs = [
324-
{
325-
filename,
326-
plugins: normalizedPluginsArray,
327-
schema: outputSchema,
328-
schemaAst: outputSchemaAst,
329-
documents: outputDocuments,
330-
config: mergedConfig,
331-
pluginMap,
332-
pluginContext,
333-
},
334-
];
335-
}
336-
337-
const process = async (outputArgs: Types.GenerateOptions) => {
338-
const output = await codegen(outputArgs);
339-
result.push({
340-
filename: outputArgs.filename,
341-
content: output,
342-
hooks: outputConfig.hooks || {},
343-
});
344-
};
345310

346-
await Promise.all(outputs.map(process));
347-
}, filename),
311+
const mergedConfig = {
312+
...rootConfig,
313+
...(typeof outputFileTemplateConfig === 'string'
314+
? { value: outputFileTemplateConfig }
315+
: outputFileTemplateConfig),
316+
};
317+
318+
let outputs: Types.GenerateOptions[] = [];
319+
320+
if (hasPreset) {
321+
outputs = await context.profiler.run(
322+
async () =>
323+
preset.buildGeneratesSection({
324+
baseOutputDir: filename,
325+
presetConfig: outputConfig.presetConfig || {},
326+
plugins: normalizedPluginsArray,
327+
schema: outputSchema,
328+
schemaAst: outputSchemaAst,
329+
documents: outputDocuments,
330+
config: mergedConfig,
331+
pluginMap,
332+
pluginContext,
333+
profiler: context.profiler,
334+
}),
335+
`Build Generates Section: ${filename}`
336+
);
337+
} else {
338+
outputs = [
339+
{
340+
filename,
341+
plugins: normalizedPluginsArray,
342+
schema: outputSchema,
343+
schemaAst: outputSchemaAst,
344+
documents: outputDocuments,
345+
config: mergedConfig,
346+
pluginMap,
347+
pluginContext,
348+
profiler: context.profiler,
349+
},
350+
];
351+
}
352+
353+
const process = async (outputArgs: Types.GenerateOptions) => {
354+
const output = await codegen(outputArgs);
355+
result.push({
356+
filename: outputArgs.filename,
357+
content: output,
358+
hooks: outputConfig.hooks || {},
359+
});
360+
};
361+
362+
await context.profiler.run(() => Promise.all(outputs.map(process)), `Codegen: ${filename}`);
363+
},
364+
filename,
365+
`Generate: ${filename}`
366+
),
348367
},
349368
],
350369
{

‎packages/graphql-codegen-cli/src/config.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { cosmiconfig, defaultLoaders } from 'cosmiconfig';
22
import { resolve } from 'path';
3-
import { DetailedError, Types } from '@graphql-codegen/plugin-helpers';
3+
import { DetailedError, Types, Profiler, createProfiler, createNoopProfiler } from '@graphql-codegen/plugin-helpers';
44
import { env } from 'string-env-interpolation';
55
import yargs from 'yargs';
66
import { GraphQLConfig } from 'graphql-config';
@@ -21,6 +21,7 @@ export type YamlCliFlags = {
2121
project: string;
2222
silent: boolean;
2323
errorsOnly: boolean;
24+
profile: boolean;
2425
};
2526

2627
export function generateSearchPlaces(moduleName: string) {
@@ -214,6 +215,10 @@ export function buildOptions() {
214215
describe: 'Only print errors',
215216
type: 'boolean' as const,
216217
},
218+
profile: {
219+
describe: 'Use profiler to measure performance',
220+
type: 'boolean' as const,
221+
},
217222
p: {
218223
alias: 'project',
219224
describe: 'Name of a project in GraphQL Config',
@@ -272,6 +277,10 @@ export function updateContextWithCliFlags(context: CodegenContext, cliFlags: Yam
272277
context.useProject(cliFlags.project);
273278
}
274279

280+
if (cliFlags.profile === true) {
281+
context.useProfiler();
282+
}
283+
275284
context.updateConfig(config);
276285
}
277286

@@ -281,8 +290,11 @@ export class CodegenContext {
281290
private config: Types.Config;
282291
private _project?: string;
283292
private _pluginContext: { [key: string]: any } = {};
293+
284294
cwd: string;
285295
filepath: string;
296+
profiler: Profiler;
297+
profilerOutput?: string;
286298

287299
constructor({
288300
config,
@@ -297,6 +309,7 @@ export class CodegenContext {
297309
this._graphqlConfig = graphqlConfig;
298310
this.filepath = this._graphqlConfig ? this._graphqlConfig.filepath : filepath;
299311
this.cwd = this._graphqlConfig ? this._graphqlConfig.dirpath : process.cwd();
312+
this.profiler = createNoopProfiler();
300313
}
301314

302315
useProject(name?: string) {
@@ -332,6 +345,16 @@ export class CodegenContext {
332345
};
333346
}
334347

348+
useProfiler() {
349+
this.profiler = createProfiler();
350+
351+
const now = new Date(); // 2011-10-05T14:48:00.000Z
352+
const datetime = now.toISOString().split('.')[0]; // 2011-10-05T14:48:00
353+
const datetimeNormalized = datetime.replace(/-|:/g, ''); // 20111005T144800
354+
355+
this.profilerOutput = `codegen-${datetimeNormalized}.json`;
356+
}
357+
335358
getPluginContext(): { [key: string]: any } {
336359
return this._pluginContext;
337360
}

0 commit comments

Comments
 (0)