Skip to content

Commit

Permalink
feat(gatsby): Add tracing for graphql resolvers (#23589)
Browse files Browse the repository at this point in the history
Add tracing for graphql resolvers
  • Loading branch information
freiksenet committed May 12, 2020
1 parent 9ca2537 commit e124aae
Show file tree
Hide file tree
Showing 26 changed files with 1,015 additions and 524 deletions.
1 change: 1 addition & 0 deletions docs/docs/gatsby-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ At the root of a Gatsby site, compile your application and make it ready for dep
| `--no-uglify` | Build site without uglifying JS bundles (for debugging) |
| `--profile` | Build site with react profiling. See [Profiling Site Performance with React Profiler](/docs/profiling-site-performance-with-react-profiler/) |
| `--open-tracing-config-file` | Tracer configuration file (OpenTracing compatible). See [Performance Tracing](/docs/performance-tracing/) |
| `--graphql-tracing` | Trace (see above) every graphql resolver, may have performance implications. |
| `--no-color`, `--no-colors` | Disables colored terminal output |
In addition to these build options, there are some optional [build environment variables](/docs/environment-variables/#build-variables) for more advanced configurations that can adjust how a build runs. For example, setting `CI=true` as an environment variable will tailor output for [dumb terminals](https://en.wikipedia.org/wiki/Computer_terminal#Dumb_terminals).
Expand Down
2 changes: 2 additions & 0 deletions docs/docs/performance-tracing.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Gatsby allows a build to be traced, enabling you to find which plugins or parts

Gatsby code is instrumented with OpenTracing, which is a general tracing API that is implementation agnostic. Therefore, you'll need to include and configure an OpenTracing compatible library in your application, as well as a backend to collect the trace data.

In addition, Gatsby has additional tracing for GraphQL resolvers. This traces every resolver and might have performance impact, so it's disabled by default. You can enable it with `--graphql-tracing` argument for the build command.

The steps required to add tracing are below. Or, you can skip ahead if you want specific instructions for [Jaeger](/docs/performance-tracing/#local-jaeger-with-docker) or [Zipkin](/docs/performance-tracing/#local-zipkin-with-docker).

### 1. Library dependency
Expand Down
10 changes: 10 additions & 0 deletions packages/gatsby-cli/src/create-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ function buildLocalCommands(cli, isLocalSite) {
default: ``,
describe: `Custom HTTPS CA certificate file (also required: --https, --cert-file, --key-file). See https://www.gatsbyjs.org/docs/local-https/`,
})
.option(`graphql-tracing`, {
type: `boolean`,
describe: `Trace every graphql resolver, may have performance implications`,
default: false,
})
.option(`open-tracing-config-file`, {
type: `string`,
describe: `Tracer configuration file (OpenTracing compatible). See https://gatsby.dev/tracing`,
Expand Down Expand Up @@ -187,6 +192,11 @@ function buildLocalCommands(cli, isLocalSite) {
default: false,
describe: `Build site with react profiling (this can add some additional overhead). See https://reactjs.org/docs/profiler`,
})
.option(`graphql-tracing`, {
type: `boolean`,
describe: `Trace every graphql resolver, may have performance implications`,
default: false,
})
.option(`open-tracing-config-file`, {
type: `string`,
describe: `Tracer configuration file (OpenTracing compatible). See https://gatsby.dev/tracing`,
Expand Down
15 changes: 8 additions & 7 deletions packages/gatsby-cli/src/reporter/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ import { ErrorMeta, CreateLogAction } from "./types"
const errorFormatter = getErrorFormatter()
const tracer = globalTracer()

interface IActivityArgs {
export interface IActivityArgs {
id?: string
parentSpan?: Span
tags?: { [key: string]: any }
}

let isVerbose = false
Expand Down Expand Up @@ -189,8 +190,8 @@ class Reporter {
text: string,
activityArgs: IActivityArgs = {}
): ITimerReporter => {
let { parentSpan, id } = activityArgs
const spanArgs = parentSpan ? { childOf: parentSpan } : {}
let { parentSpan, id, tags } = activityArgs
const spanArgs = parentSpan ? { childOf: parentSpan, tags } : { tags }
if (!id) {
id = text
}
Expand All @@ -214,8 +215,8 @@ class Reporter {
text: string,
activityArgs: IActivityArgs = {}
): IPhantomReporter => {
let { parentSpan, id } = activityArgs
const spanArgs = parentSpan ? { childOf: parentSpan } : {}
let { parentSpan, id, tags } = activityArgs
const spanArgs = parentSpan ? { childOf: parentSpan, tags } : { tags }
if (!id) {
id = text
}
Expand All @@ -234,8 +235,8 @@ class Reporter {
start = 0,
activityArgs: IActivityArgs = {}
): IProgressReporter => {
let { parentSpan, id } = activityArgs
const spanArgs = parentSpan ? { childOf: parentSpan } : {}
let { parentSpan, id, tags } = activityArgs
const spanArgs = parentSpan ? { childOf: parentSpan, tags } : { tags }
if (!id) {
id = text
}
Expand Down
74 changes: 40 additions & 34 deletions packages/gatsby/src/bootstrap/create-graphql-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ type Runner = (

export const createGraphQLRunner = (
store: Store<IGatsbyState>,
reporter: Reporter
reporter: Reporter,
{ parentSpan, graphqlTracing } = { parentSpan: null, graphqlTracing: false }
): Runner => {
// TODO: Move tracking of changed state inside GraphQLRunner itself. https://github.com/gatsbyjs/gatsby/issues/20941
let runner = new GraphQLRunner(store)
let runner = new GraphQLRunner(store, { graphqlTracing })

const eventTypes: string[] = [
`DELETE_CACHE`,
Expand All @@ -39,43 +40,48 @@ export const createGraphQLRunner = (
})

return (query, context): ReturnType<Runner> =>
runner.query(query, context).then(result => {
if (result.errors) {
const structuredErrors = result.errors
.map(e => {
// Find the file where graphql was called.
const file = stackTrace
.parse(e)
.find(file => /createPages/.test(file.getFunctionName()))
runner
.query(query, context, {
queryName: `gatsby-node query`,
parentSpan,
})
.then(result => {
if (result.errors) {
const structuredErrors = result.errors
.map(e => {
// Find the file where graphql was called.
const file = stackTrace
.parse(e)
.find(file => /createPages/.test(file.getFunctionName()))

if (file) {
const structuredError = errorParser({
message: e.message,
location: {
start: {
line: file.getLineNumber(),
column: file.getColumnNumber(),
if (file) {
const structuredError = errorParser({
message: e.message,
location: {
start: {
line: file.getLineNumber(),
column: file.getColumnNumber(),
},
},
},
filePath: file.getFileName(),
})
structuredError.context = {
...structuredError.context,
fromGraphQLFunction: true,
filePath: file.getFileName(),
})
structuredError.context = {
...structuredError.context,
fromGraphQLFunction: true,
}
return structuredError
}
return structuredError
}

return null
})
.filter(Boolean)
return null
})
.filter(Boolean)

if (structuredErrors.length) {
// panic on build exits the process
reporter.panicOnBuild(structuredErrors)
if (structuredErrors.length) {
// panic on build exits the process
reporter.panicOnBuild(structuredErrors)
}
}
}

return result
})
return result
})
}
6 changes: 5 additions & 1 deletion packages/gatsby/src/bootstrap/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type BootstrapArgs = {
directory: string,
prefixPaths?: boolean,
parentSpan: Object,
graphqlTracing: boolean,
}

module.exports = async (args: BootstrapArgs) => {
Expand Down Expand Up @@ -456,7 +457,10 @@ module.exports = async (args: BootstrapArgs) => {
payload: _.flattenDeep([extensions, apiResults]),
})

const graphqlRunner = createGraphQLRunner(store, reporter)
const graphqlRunner = createGraphQLRunner(store, reporter, {
graphqlTracing: args.graphqlTracing,
parentSpan: args.parentSpan ? args.parentSpan : bootstrapSpan,
})

// Collect pages.
activity = reporter.activityTimer(`createPages`, {
Expand Down
6 changes: 5 additions & 1 deletion packages/gatsby/src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ interface IBuildArgs extends IProgram {
prefixPaths: boolean
noUglify: boolean
profile: boolean
graphqlTracing: boolean
openTracingConfigFile: string
}

Expand Down Expand Up @@ -70,7 +71,10 @@ module.exports = async function build(program: IBuildArgs): Promise<void> {
parentSpan: buildSpan,
})

const graphqlRunner = new GraphQLRunner(store, { collectStats: true })
const graphqlRunner = new GraphQLRunner(store, {
collectStats: true,
graphqlTracing: program.graphqlTracing,
})

const {
processPageQueries,
Expand Down
30 changes: 24 additions & 6 deletions packages/gatsby/src/commands/develop.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fs from "fs"
import openurl from "better-opn"
import chokidar from "chokidar"
import { SchemaComposer } from "graphql-compose"

import webpackHotMiddleware from "webpack-hot-middleware"
import webpackDevMiddleware from "webpack-dev-middleware"
Expand All @@ -11,7 +12,7 @@ import webpack from "webpack"
import graphqlHTTP from "express-graphql"
import graphqlPlayground from "graphql-playground-middleware-express"
import graphiqlExplorer from "gatsby-graphiql-explorer"
import { formatError } from "graphql"
import { formatError, GraphQLSchema } from "graphql"

import webpackConfig from "../utils/webpack.config"
import bootstrap from "../bootstrap"
Expand Down Expand Up @@ -88,7 +89,7 @@ interface IServer {
webpackActivity: ActivityTracker
}

async function startServer(program: IProgram): Promise<IServer> {
async function startServer(program: IDevelopArgs): Promise<IServer> {
const indexHTMLActivity = report.phantomActivity(`building index.html`, {})
indexHTMLActivity.start()
const directory = program.directory
Expand Down Expand Up @@ -178,7 +179,16 @@ async function startServer(program: IProgram): Promise<IServer> {
graphqlEndpoint,
graphqlHTTP(
(): graphqlHTTP.OptionsData => {
const { schema, schemaCustomization } = store.getState()
const {
schema,
schemaCustomization,
}: {
schema: GraphQLSchema
schemaCustomization: {
composer: SchemaComposer<any>
context: any
}
} = store.getState()

return {
schema,
Expand Down Expand Up @@ -347,7 +357,11 @@ async function startServer(program: IProgram): Promise<IServer> {
return { compiler, listener, webpackActivity }
}

module.exports = async (program: IProgram): Promise<void> => {
interface IDevelopArgs extends IProgram {
graphqlTracing: boolean
}

module.exports = async (program: IDevelopArgs): Promise<void> => {
// We want to prompt the feedback request when users quit develop
// assuming they pass the heuristic check to know they are a user
// we want to request feedback from, and we're not annoying them.
Expand Down Expand Up @@ -428,7 +442,9 @@ module.exports = async (program: IProgram): Promise<void> => {
// Start the schema hot reloader.
bootstrapSchemaHotReloader()

await queryUtil.initialProcessQueries()
await queryUtil.initialProcessQueries({
graphqlTracing: program.graphqlTracing,
})

require(`../redux/actions`).boundActionCreators.setProgramStatus(
`BOOTSTRAP_QUERY_RUNNING_FINISHED`
Expand All @@ -438,7 +454,9 @@ module.exports = async (program: IProgram): Promise<void> => {
await waitUntilAllJobsComplete()
requiresWriter.startListener()
db.startAutosave()
queryUtil.startListeningToDevelopQueue()
queryUtil.startListeningToDevelopQueue({
graphqlTracing: program.graphqlTracing,
})
queryWatcher.startWatchDeletePage()

let { compiler, webpackActivity } = await startServer(program)
Expand Down

0 comments on commit e124aae

Please sign in to comment.