Skip to content

Commit c594f75

Browse files
committed
feat: add support for profiling through SANITY_DEBUG_PROFILING
1 parent e3e3ad1 commit c594f75

File tree

6 files changed

+95
-42
lines changed

6 files changed

+95
-42
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,45 @@
1+
import {lstatSync, writeFileSync} from 'node:fs'
2+
import {close as closeInspector, open as openInspector, Session} from 'node:inspector/promises'
3+
import {join} from 'node:path'
4+
15
import debugIt from 'debug'
26

37
export const debug = debugIt('sanity:core')
8+
9+
function isDir(path: string): boolean {
10+
try {
11+
return lstatSync(path).isDirectory()
12+
} catch {
13+
return false
14+
}
15+
}
16+
17+
/**
18+
* Runs a function such that it will be profiled when the environment variable
19+
* SANITY_DEBUG_PROFILING is set to a directory. A file (starting with `key`) will
20+
* be placed in said directory. The generated file can be inspected by using the
21+
* Speedscpe NPM package: `speedscope ${filename}` opens a UI in the browser.
22+
*/
23+
export async function withTracingProfiling<T>(key: string, fn: () => Promise<T>): Promise<T> {
24+
const dir = process.env.SANITY_DEBUG_PROFILING
25+
if (!dir) return await fn()
26+
27+
if (!isDir(dir))
28+
throw new Error(`SANITY_DEBUG_PROFILING (${JSON.stringify(dir)}) must be set to a directory`)
29+
30+
const filenamePrefix = join(dir, key)
31+
32+
openInspector()
33+
const session = new Session()
34+
session.connect()
35+
await session.post('Profiler.enable')
36+
await session.post('Profiler.start')
37+
try {
38+
return await fn()
39+
} finally {
40+
closeInspector()
41+
const fullname = `${filenamePrefix}-${Date.now()}-${Math.floor(Math.random() * 10000)}.cpuprofile`
42+
const {profile} = await session.post('Profiler.stop')
43+
writeFileSync(fullname, JSON.stringify(profile))
44+
}
45+
}

packages/sanity/src/_internal/cli/threads/extractManifest.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {isMainThread, parentPort, workerData as _workerData} from 'node:worker_t
33
import {extractCreateWorkspaceManifest} from '../../manifest/extractWorkspaceManifest'
44
import {getStudioWorkspaces} from '../util/getStudioWorkspaces'
55
import {mockBrowserEnvironment} from '../util/mockBrowserEnvironment'
6+
import {withTracingProfiling} from '../debug'
67

78
/** @internal */
89
export interface ExtractManifestWorkerData {
@@ -30,4 +31,4 @@ async function main() {
3031
}
3132
}
3233

33-
main()
34+
withTracingProfiling('extractManifest', main)

packages/sanity/src/_internal/cli/threads/extractSchema.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {type Workspace} from 'sanity'
55

66
import {getStudioWorkspaces} from '../util/getStudioWorkspaces'
77
import {mockBrowserEnvironment} from '../util/mockBrowserEnvironment'
8+
import {withTracingProfiling} from '../debug'
89

910
/** @internal */
1011
export interface ExtractSchemaWorkerData {
@@ -48,7 +49,7 @@ async function main() {
4849
}
4950
}
5051

51-
main()
52+
withTracingProfiling('extractSchema', main)
5253

5354
function getWorkspace({
5455
workspaces,

packages/sanity/src/_internal/cli/threads/getGraphQLAPIs.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ import {type Workspace} from 'sanity'
88

99
import {type SchemaDefinitionish, type TypeResolvedGraphQLAPI} from '../actions/graphql/types'
1010
import {getStudioWorkspaces} from '../util/getStudioWorkspaces'
11+
import {withTracingProfiling} from '../debug'
1112

12-
if (isMainThread || !parentPort) {
13+
const port = parentPort
14+
15+
if (isMainThread || !port) {
1316
throw new Error('This module must be run as a worker thread')
1417
}
1518

16-
getGraphQLAPIsForked(parentPort)
19+
withTracingProfiling('getGraphQLAPIs', async () => getGraphQLAPIsForked(port))
1720

1821
async function getGraphQLAPIsForked(parent: MessagePort): Promise<void> {
1922
const {cliConfig, cliConfigPath, workDir} = workerData

packages/sanity/src/_internal/cli/threads/validateDocuments.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
type WorkerChannelEvent,
2424
type WorkerChannelStream,
2525
} from '../util/workerChannels'
26+
import {withTracingProfiling} from '../debug'
2627

2728
const MAX_VALIDATION_CONCURRENCY = 100
2829
const DOCUMENT_VALIDATION_TIMEOUT = 30000
@@ -129,7 +130,7 @@ async function* readerToGenerator(reader: ReadableStreamDefaultReader<Uint8Array
129130
}
130131
}
131132

132-
validateDocuments()
133+
withTracingProfiling('validateDocuments', validateDocuments)
133134

134135
async function loadWorkspace() {
135136
const workspaces = await getStudioWorkspaces({basePath: workDir, configPath})

packages/sanity/src/_internal/cli/threads/validateSchema.ts

Lines changed: 42 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {resolveSchemaTypes} from 'sanity'
66

77
import {getStudioConfig} from '../util/getStudioWorkspaces'
88
import {mockBrowserEnvironment} from '../util/mockBrowserEnvironment'
9+
import {withTracingProfiling} from '../debug'
910

1011
/** @internal */
1112
export interface ValidateSchemaWorkerData {
@@ -29,49 +30,53 @@ if (isMainThread || !parentPort) {
2930
throw new Error('This module must be run as a worker thread')
3031
}
3132

32-
const cleanup = mockBrowserEnvironment(workDir)
33+
async function main() {
34+
const cleanup = mockBrowserEnvironment(workDir)
3335

34-
try {
35-
const workspaces = getStudioConfig({basePath: workDir})
36+
try {
37+
const workspaces = getStudioConfig({basePath: workDir})
3638

37-
if (!workspaces.length) {
38-
throw new Error(`Configuration did not return any workspaces.`)
39-
}
40-
41-
let workspace
42-
if (workspaceName) {
43-
workspace = workspaces.find((w) => w.name === workspaceName)
44-
if (!workspace) {
45-
throw new Error(`Could not find any workspaces with name \`${workspaceName}\``)
39+
if (!workspaces.length) {
40+
throw new Error(`Configuration did not return any workspaces.`)
4641
}
47-
} else {
48-
if (workspaces.length !== 1) {
49-
throw new Error(
50-
"Multiple workspaces found. Please specify which workspace to use with '--workspace'.",
51-
)
42+
43+
let workspace
44+
if (workspaceName) {
45+
workspace = workspaces.find((w) => w.name === workspaceName)
46+
if (!workspace) {
47+
throw new Error(`Could not find any workspaces with name \`${workspaceName}\``)
48+
}
49+
} else {
50+
if (workspaces.length !== 1) {
51+
throw new Error(
52+
"Multiple workspaces found. Please specify which workspace to use with '--workspace'.",
53+
)
54+
}
55+
workspace = workspaces[0]
5256
}
53-
workspace = workspaces[0]
54-
}
5557

56-
const schemaTypes = resolveSchemaTypes({
57-
config: workspace,
58-
context: {dataset: workspace.dataset, projectId: workspace.projectId},
59-
})
58+
const schemaTypes = resolveSchemaTypes({
59+
config: workspace,
60+
context: {dataset: workspace.dataset, projectId: workspace.projectId},
61+
})
6062

61-
const validation = groupProblems(validateSchema(schemaTypes).getTypes())
63+
const validation = groupProblems(validateSchema(schemaTypes).getTypes())
6264

63-
const result: ValidateSchemaWorkerResult = {
64-
validation: validation
65-
.map((group) => ({
66-
...group,
67-
problems: group.problems.filter((problem) =>
68-
level === 'error' ? problem.severity === 'error' : true,
69-
),
70-
}))
71-
.filter((group) => group.problems.length),
72-
}
65+
const result: ValidateSchemaWorkerResult = {
66+
validation: validation
67+
.map((group) => ({
68+
...group,
69+
problems: group.problems.filter((problem) =>
70+
level === 'error' ? problem.severity === 'error' : true,
71+
),
72+
}))
73+
.filter((group) => group.problems.length),
74+
}
7375

74-
parentPort?.postMessage(result)
75-
} finally {
76-
cleanup()
76+
parentPort?.postMessage(result)
77+
} finally {
78+
cleanup()
79+
}
7780
}
81+
82+
withTracingProfiling('validateSchema', main)

0 commit comments

Comments
 (0)