diff --git a/packages/server/dataloader/RootDataLoader.ts b/packages/server/dataloader/RootDataLoader.ts index 6f7954571a8..6da55d8edff 100644 --- a/packages/server/dataloader/RootDataLoader.ts +++ b/packages/server/dataloader/RootDataLoader.ts @@ -1,5 +1,4 @@ import DataLoader from 'dataloader' -import {Logger} from '../utils/Logger' import * as atlassianLoaders from './atlassianLoaders' import * as azureDevOpsLoaders from './azureDevOpsLoaders' import * as customLoaderMakers from './customLoaderMakers' @@ -94,10 +93,3 @@ export default class RootDataLoader< // RootDataLoader export const dataLoaderCache = new DataLoaderCache(RootDataLoader) - -// Can remove this after we verify there are no memory leaks in prod -// count staying constant or going down = good -setInterval(() => { - const workerCount = Object.keys(dataLoaderCache.workers).length - Logger.log({workerCount}) -}, 60_000).unref() diff --git a/packages/server/graphql/private/mutations/disconnectSocket.ts b/packages/server/graphql/private/mutations/disconnectSocket.ts index 9c62122875d..008f44048ca 100644 --- a/packages/server/graphql/private/mutations/disconnectSocket.ts +++ b/packages/server/graphql/private/mutations/disconnectSocket.ts @@ -19,6 +19,8 @@ const disconnectSocket: MutationResolvers['disconnectSocket'] = async ( redis.lrange(`presence:${userId}`, 0, -1) ]) if (!user) { + // user could've been deleted & then key not wiped + await redis.del(`presence:${userId}`) throw new Error(`User does not exist: ${userId}`) } const tms = user.tms ?? [] diff --git a/packages/server/utils/useDatadogTracing.ts b/packages/server/utils/useDatadogTracing.ts index bb7b112401a..20004a64163 100644 --- a/packages/server/utils/useDatadogTracing.ts +++ b/packages/server/utils/useDatadogTracing.ts @@ -1,6 +1,7 @@ import {handleStreamOrSingleExecutionResult, type ExecutionArgs} from '@envelop/core' +import {useOnResolve} from '@envelop/on-resolve' import tracer, {type opentelemetry, type Span} from 'dd-trace' -import {getOperationAST, type GraphQLResolveInfo} from 'graphql' +import {defaultFieldResolver, getNamedType, getOperationAST, type GraphQLResolveInfo} from 'graphql' import type {ExecutionResult} from 'graphql-ws' import type {Plugin} from 'graphql-yoga' import {Path} from 'graphql/jsutils/Path' @@ -48,43 +49,43 @@ export const useDatadogTracing = (config: Config): Plugin { - // // Ignore anything without a custom resolver since it's basically an identity function - // if (resolver === defaultFieldResolver) return - // const path = getPath(info, config) - // const computedPathString = path.join('.') - // const ddContext = context[ddSymbol] - // const {rootSpan, fields} = ddContext - // // if collapsed, we just measure the first item in a list - // if (config.collapse && fields[computedPathString]) return + onPluginInit({addPlugin}) { + addPlugin( + useOnResolve(({info, context, args, replaceResolver, resolver}) => { + // Ignore anything without a custom resolver since it's basically an identity function + if (resolver === defaultFieldResolver) return + const path = getPath(info, config) + const computedPathString = path.join('.') + const ddContext = context[ddSymbol] + const {rootSpan, fields} = ddContext + // if collapsed, we just measure the first item in a list + if (config.collapse && fields[computedPathString]) return - // const parentSpan = getParentSpan(path, fields) ?? rootSpan - // const {fieldName, returnType, parentType} = info - // const returnTypeName = getNamedType(info.returnType).name - // const parentTypeName = getNamedType(parentType).name - // const fieldSpan = tracer.startSpan('graphql.resolve', { - // childOf: parentSpan, - // tags: { - // 'resource.name': `${info.fieldName}:${returnType}`, - // 'span.type': 'graphql', - // 'graphql.resolver.fieldName': fieldName, - // 'graphql.resolver.typeName': parentTypeName, - // 'graphql.resolver.returnType': returnTypeName, - // 'graphql.resolver.fieldPath': computedPathString, - // ...makeVariables(config.excludeArgs, args, fieldName) - // } - // }) - // fields[computedPathString] = {span: fieldSpan} - // replaceResolver((...args) => tracer.scope().activate(fieldSpan, () => resolver(...args))) - // return ({result}) => { - // markSpanError(fieldSpan, result) - // fieldSpan.finish() - // } - // }) - // ) - // }, + const parentSpan = getParentSpan(path, fields) ?? rootSpan + const {fieldName, returnType, parentType} = info + const returnTypeName = getNamedType(info.returnType).name + const parentTypeName = getNamedType(parentType).name + const fieldSpan = tracer.startSpan('graphql.resolve', { + childOf: parentSpan, + tags: { + 'resource.name': `${info.fieldName}:${returnType}`, + 'span.type': 'graphql', + 'graphql.resolver.fieldName': fieldName, + 'graphql.resolver.typeName': parentTypeName, + 'graphql.resolver.returnType': returnTypeName, + 'graphql.resolver.fieldPath': computedPathString, + ...makeVariables(config.excludeArgs, args, fieldName) + } + }) + fields[computedPathString] = {span: fieldSpan} + replaceResolver((...args) => tracer.scope().activate(fieldSpan, () => resolver(...args))) + return ({result}) => { + markSpanError(fieldSpan, result) + fieldSpan.finish() + } + }) + ) + }, onExecute({args, extendContext, executeFn, setExecuteFn}) { const operationAst = getOperationAST(args.document, args.operationName)! const operationType = operationAst.operation