Skip to content

Commit

Permalink
Introduce --profile (#7284)
Browse files Browse the repository at this point in the history
* Introduce --profile

Chrome DevTools compatible profiler

* Changeset

* doc(codegen): add doc for the profile flag

Co-authored-by: Charly POLY <cpoly55@gmail.com>
  • Loading branch information
kamilkisiela and charlypoly committed Feb 2, 2022
1 parent bef4376 commit 754a337
Show file tree
Hide file tree
Showing 14 changed files with 425 additions and 214 deletions.
8 changes: 8 additions & 0 deletions .changeset/silent-bags-sell.md
@@ -0,0 +1,8 @@
---
'@graphql-cli/codegen': minor
'@graphql-codegen/cli': minor
'@graphql-codegen/core': minor
'@graphql-codegen/plugin-helpers': minor
---

Performance Profiler --profile
243 changes: 131 additions & 112 deletions packages/graphql-codegen-cli/src/codegen.ts
Expand Up @@ -52,20 +52,6 @@ function createCache<T>(loader: (key: string) => Promise<T>) {
}

export async function executeCodegen(input: CodegenContext | Types.Config): Promise<Types.FileOutput[]> {
function wrapTask(task: () => void | Promise<void>, source: string) {
return async () => {
try {
await Promise.resolve().then(() => task());
} catch (error) {
if (source && !(error instanceof GraphQLError)) {
error.source = source;
}

throw error;
}
};
}

const context = ensureContext(input);
const config = context.getConfig();
const pluginContext = context.getPluginContext();
Expand Down Expand Up @@ -117,6 +103,21 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom
documents: documents,
};
});
function wrapTask(task: () => void | Promise<void>, source: string, taskName: string) {
return () => {
return context.profiler.run(async () => {
try {
await Promise.resolve().then(() => task());
} catch (error) {
if (source && !(error instanceof GraphQLError)) {
error.source = source;
}

throw error;
}
}, taskName);
};
}

async function normalize() {
/* Load Require extensions */
Expand Down Expand Up @@ -232,119 +233,137 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom
[
{
title: 'Load GraphQL schemas',
task: wrapTask(async () => {
debugLog(`[CLI] Loading Schemas`);

const schemaPointerMap: any = {};
const allSchemaUnnormalizedPointers = [...rootSchemas, ...outputSpecificSchemas];
for (const unnormalizedPtr of allSchemaUnnormalizedPointers) {
if (typeof unnormalizedPtr === 'string') {
schemaPointerMap[unnormalizedPtr] = {};
} else if (typeof unnormalizedPtr === 'object') {
Object.assign(schemaPointerMap, unnormalizedPtr);
task: wrapTask(
async () => {
debugLog(`[CLI] Loading Schemas`);

const schemaPointerMap: any = {};
const allSchemaUnnormalizedPointers = [...rootSchemas, ...outputSpecificSchemas];
for (const unnormalizedPtr of allSchemaUnnormalizedPointers) {
if (typeof unnormalizedPtr === 'string') {
schemaPointerMap[unnormalizedPtr] = {};
} else if (typeof unnormalizedPtr === 'object') {
Object.assign(schemaPointerMap, unnormalizedPtr);
}
}
}

const hash = JSON.stringify(schemaPointerMap);
const result = await schemaLoadingCache.load(hash);
const hash = JSON.stringify(schemaPointerMap);
const result = await schemaLoadingCache.load(hash);

outputSchemaAst = await result.outputSchemaAst;
outputSchema = result.outputSchema;
}, filename),
outputSchemaAst = await result.outputSchemaAst;
outputSchema = result.outputSchema;
},
filename,
`Load GraphQL schemas: ${filename}`
),
},
{
title: 'Load GraphQL documents',
task: wrapTask(async () => {
debugLog(`[CLI] Loading Documents`);
task: wrapTask(
async () => {
debugLog(`[CLI] Loading Documents`);

// get different cache for shared docs and output specific docs
const results = await Promise.all(
[rootDocuments, outputSpecificDocuments].map(docs => {
const hash = JSON.stringify(docs);
return documentsLoadingCache.load(hash);
})
);
// get different cache for shared docs and output specific docs
const results = await Promise.all(
[rootDocuments, outputSpecificDocuments].map(docs => {
const hash = JSON.stringify(docs);
return documentsLoadingCache.load(hash);
})
);

const documents: Types.DocumentFile[] = [];
const documents: Types.DocumentFile[] = [];

results.forEach(source => documents.push(...source.documents));
results.forEach(source => documents.push(...source.documents));

if (documents.length > 0) {
outputDocuments.push(...documents);
}
}, filename),
if (documents.length > 0) {
outputDocuments.push(...documents);
}
},
filename,
`Load GraphQL documents: ${filename}`
),
},
{
title: 'Generate',
task: wrapTask(async () => {
debugLog(`[CLI] Generating output`);

const normalizedPluginsArray = normalizeConfig(outputConfig.plugins);
const pluginLoader = config.pluginLoader || makeDefaultLoader(context.cwd);
const pluginPackages = await Promise.all(
normalizedPluginsArray.map(plugin => getPluginByName(Object.keys(plugin)[0], pluginLoader))
);
const pluginMap: { [name: string]: CodegenPlugin } = {};
const preset: Types.OutputPreset = hasPreset
? typeof outputConfig.preset === 'string'
? await getPresetByName(outputConfig.preset, makeDefaultLoader(context.cwd))
: outputConfig.preset
: null;

pluginPackages.forEach((pluginPackage, i) => {
const plugin = normalizedPluginsArray[i];
const name = Object.keys(plugin)[0];

pluginMap[name] = pluginPackage;
});

const mergedConfig = {
...rootConfig,
...(typeof outputFileTemplateConfig === 'string'
? { value: outputFileTemplateConfig }
: outputFileTemplateConfig),
};

let outputs: Types.GenerateOptions[] = [];

if (hasPreset) {
outputs = await preset.buildGeneratesSection({
baseOutputDir: filename,
presetConfig: outputConfig.presetConfig || {},
plugins: normalizedPluginsArray,
schema: outputSchema,
schemaAst: outputSchemaAst,
documents: outputDocuments,
config: mergedConfig,
pluginMap,
pluginContext,
task: wrapTask(
async () => {
debugLog(`[CLI] Generating output`);

const normalizedPluginsArray = normalizeConfig(outputConfig.plugins);
const pluginLoader = config.pluginLoader || makeDefaultLoader(context.cwd);
const pluginPackages = await Promise.all(
normalizedPluginsArray.map(plugin => getPluginByName(Object.keys(plugin)[0], pluginLoader))
);
const pluginMap: { [name: string]: CodegenPlugin } = {};
const preset: Types.OutputPreset = hasPreset
? typeof outputConfig.preset === 'string'
? await getPresetByName(outputConfig.preset, makeDefaultLoader(context.cwd))
: outputConfig.preset
: null;

pluginPackages.forEach((pluginPackage, i) => {
const plugin = normalizedPluginsArray[i];
const name = Object.keys(plugin)[0];

pluginMap[name] = pluginPackage;
});
} else {
outputs = [
{
filename,
plugins: normalizedPluginsArray,
schema: outputSchema,
schemaAst: outputSchemaAst,
documents: outputDocuments,
config: mergedConfig,
pluginMap,
pluginContext,
},
];
}

const process = async (outputArgs: Types.GenerateOptions) => {
const output = await codegen(outputArgs);
result.push({
filename: outputArgs.filename,
content: output,
hooks: outputConfig.hooks || {},
});
};

await Promise.all(outputs.map(process));
}, filename),
const mergedConfig = {
...rootConfig,
...(typeof outputFileTemplateConfig === 'string'
? { value: outputFileTemplateConfig }
: outputFileTemplateConfig),
};

let outputs: Types.GenerateOptions[] = [];

if (hasPreset) {
outputs = await context.profiler.run(
async () =>
preset.buildGeneratesSection({
baseOutputDir: filename,
presetConfig: outputConfig.presetConfig || {},
plugins: normalizedPluginsArray,
schema: outputSchema,
schemaAst: outputSchemaAst,
documents: outputDocuments,
config: mergedConfig,
pluginMap,
pluginContext,
profiler: context.profiler,
}),
`Build Generates Section: ${filename}`
);
} else {
outputs = [
{
filename,
plugins: normalizedPluginsArray,
schema: outputSchema,
schemaAst: outputSchemaAst,
documents: outputDocuments,
config: mergedConfig,
pluginMap,
pluginContext,
profiler: context.profiler,
},
];
}

const process = async (outputArgs: Types.GenerateOptions) => {
const output = await codegen(outputArgs);
result.push({
filename: outputArgs.filename,
content: output,
hooks: outputConfig.hooks || {},
});
};

await context.profiler.run(() => Promise.all(outputs.map(process)), `Codegen: ${filename}`);
},
filename,
`Generate: ${filename}`
),
},
],
{
Expand Down
25 changes: 24 additions & 1 deletion packages/graphql-codegen-cli/src/config.ts
@@ -1,6 +1,6 @@
import { cosmiconfig, defaultLoaders } from 'cosmiconfig';
import { resolve } from 'path';
import { DetailedError, Types } from '@graphql-codegen/plugin-helpers';
import { DetailedError, Types, Profiler, createProfiler, createNoopProfiler } from '@graphql-codegen/plugin-helpers';
import { env } from 'string-env-interpolation';
import yargs from 'yargs';
import { GraphQLConfig } from 'graphql-config';
Expand All @@ -21,6 +21,7 @@ export type YamlCliFlags = {
project: string;
silent: boolean;
errorsOnly: boolean;
profile: boolean;
};

export function generateSearchPlaces(moduleName: string) {
Expand Down Expand Up @@ -214,6 +215,10 @@ export function buildOptions() {
describe: 'Only print errors',
type: 'boolean' as const,
},
profile: {
describe: 'Use profiler to measure performance',
type: 'boolean' as const,
},
p: {
alias: 'project',
describe: 'Name of a project in GraphQL Config',
Expand Down Expand Up @@ -272,6 +277,10 @@ export function updateContextWithCliFlags(context: CodegenContext, cliFlags: Yam
context.useProject(cliFlags.project);
}

if (cliFlags.profile === true) {
context.useProfiler();
}

context.updateConfig(config);
}

Expand All @@ -281,8 +290,11 @@ export class CodegenContext {
private config: Types.Config;
private _project?: string;
private _pluginContext: { [key: string]: any } = {};

cwd: string;
filepath: string;
profiler: Profiler;
profilerOutput?: string;

constructor({
config,
Expand All @@ -297,6 +309,7 @@ export class CodegenContext {
this._graphqlConfig = graphqlConfig;
this.filepath = this._graphqlConfig ? this._graphqlConfig.filepath : filepath;
this.cwd = this._graphqlConfig ? this._graphqlConfig.dirpath : process.cwd();
this.profiler = createNoopProfiler();
}

useProject(name?: string) {
Expand Down Expand Up @@ -332,6 +345,16 @@ export class CodegenContext {
};
}

useProfiler() {
this.profiler = createProfiler();

const now = new Date(); // 2011-10-05T14:48:00.000Z
const datetime = now.toISOString().split('.')[0]; // 2011-10-05T14:48:00
const datetimeNormalized = datetime.replace(/-|:/g, ''); // 20111005T144800

this.profilerOutput = `codegen-${datetimeNormalized}.json`;
}

getPluginContext(): { [key: string]: any } {
return this._pluginContext;
}
Expand Down

1 comment on commit 754a337

@vercel
Copy link

@vercel vercel bot commented on 754a337 Feb 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.