source dump of claude code
at main 286 lines 9.1 kB view raw
1import { feature } from 'bun:bundle' 2import type { WriteFileOptions } from 'fs' 3import { 4 closeSync, 5 writeFileSync as fsWriteFileSync, 6 fsyncSync, 7 openSync, 8} from 'fs' 9// biome-ignore lint: This file IS the cloneDeep wrapper - it must import the original 10import lodashCloneDeep from 'lodash-es/cloneDeep.js' 11import { addSlowOperation } from '../bootstrap/state.js' 12import { logForDebugging } from './debug.js' 13 14// Extended WriteFileOptions to include 'flush' which is available in Node.js 20.1.0+ 15// but not yet in @types/node 16type WriteFileOptionsWithFlush = 17 | WriteFileOptions 18 | (WriteFileOptions & { flush?: boolean }) 19 20// --- Slow operation logging infrastructure --- 21 22/** 23 * Threshold in milliseconds for logging slow JSON/clone operations. 24 * Operations taking longer than this will be logged for debugging. 25 * - Override: set CLAUDE_CODE_SLOW_OPERATION_THRESHOLD_MS to a number 26 * - Dev builds: 20ms (lower threshold for development) 27 * - Ants: 300ms (enabled for all internal users) 28 */ 29const SLOW_OPERATION_THRESHOLD_MS = (() => { 30 const envValue = process.env.CLAUDE_CODE_SLOW_OPERATION_THRESHOLD_MS 31 if (envValue !== undefined) { 32 const parsed = Number(envValue) 33 if (!Number.isNaN(parsed) && parsed >= 0) { 34 return parsed 35 } 36 } 37 if (process.env.NODE_ENV === 'development') { 38 return 20 39 } 40 if (process.env.USER_TYPE === 'ant') { 41 return 300 42 } 43 return Infinity 44})() 45 46// Re-export for callers that still need the threshold value directly 47export { SLOW_OPERATION_THRESHOLD_MS } 48 49// Module-level re-entrancy guard. logForDebugging writes to a debug file via 50// appendFileSync, which goes through slowLogging again. Without this guard, 51// a slow appendFileSync → dispose → logForDebugging → appendFileSync → dispose → ... 52let isLogging = false 53 54/** 55 * Extract the first stack frame outside this file, so the DevBar warning 56 * points at the actual caller instead of a useless `Object{N keys}`. 57 * Only called when an operation was actually slow — never on the fast path. 58 */ 59export function callerFrame(stack: string | undefined): string { 60 if (!stack) return '' 61 for (const line of stack.split('\n')) { 62 if (line.includes('slowOperations')) continue 63 const m = line.match(/([^/\\]+?):(\d+):\d+\)?$/) 64 if (m) return ` @ ${m[1]}:${m[2]}` 65 } 66 return '' 67} 68 69/** 70 * Builds a human-readable description from tagged template arguments. 71 * Only called when an operation was actually slow — never on the fast path. 72 * 73 * args[0] = TemplateStringsArray, args[1..n] = interpolated values 74 */ 75function buildDescription(args: IArguments): string { 76 const strings = args[0] as TemplateStringsArray 77 let result = '' 78 for (let i = 0; i < strings.length; i++) { 79 result += strings[i] 80 if (i + 1 < args.length) { 81 const v = args[i + 1] 82 if (Array.isArray(v)) { 83 result += `Array[${(v as unknown[]).length}]` 84 } else if (v !== null && typeof v === 'object') { 85 result += `Object{${Object.keys(v as Record<string, unknown>).length} keys}` 86 } else if (typeof v === 'string') { 87 result += v.length > 80 ? `${v.slice(0, 80)}` : v 88 } else { 89 result += String(v) 90 } 91 } 92 } 93 return result 94} 95 96class AntSlowLogger { 97 startTime: number 98 args: IArguments 99 err: Error 100 101 constructor(args: IArguments) { 102 this.startTime = performance.now() 103 this.args = args 104 // V8/JSC capture the stack at construction but defer the expensive string 105 // formatting until .stack is read — so this stays off the fast path. 106 this.err = new Error() 107 } 108 109 [Symbol.dispose](): void { 110 const duration = performance.now() - this.startTime 111 if (duration > SLOW_OPERATION_THRESHOLD_MS && !isLogging) { 112 isLogging = true 113 try { 114 const description = 115 buildDescription(this.args) + callerFrame(this.err.stack) 116 logForDebugging( 117 `[SLOW OPERATION DETECTED] ${description} (${duration.toFixed(1)}ms)`, 118 ) 119 addSlowOperation(description, duration) 120 } finally { 121 isLogging = false 122 } 123 } 124 } 125} 126 127const NOOP_LOGGER: Disposable = { [Symbol.dispose]() {} } 128 129// Must be regular functions (not arrows) to access `arguments` 130function slowLoggingAnt( 131 _strings: TemplateStringsArray, 132 ..._values: unknown[] 133): AntSlowLogger { 134 // eslint-disable-next-line prefer-rest-params 135 return new AntSlowLogger(arguments) 136} 137 138function slowLoggingExternal(): Disposable { 139 return NOOP_LOGGER 140} 141 142/** 143 * Tagged template for slow operation logging. 144 * 145 * In ANT builds: creates an AntSlowLogger that times the operation and logs 146 * if it exceeds the threshold. Description is built lazily only when slow. 147 * 148 * In external builds: returns a singleton no-op disposable. Zero allocations, 149 * zero timing. AntSlowLogger and buildDescription are dead-code-eliminated. 150 * 151 * @example 152 * using _ = slowLogging`structuredClone(${value})` 153 * const result = structuredClone(value) 154 */ 155export const slowLogging: { 156 (strings: TemplateStringsArray, ...values: unknown[]): Disposable 157} = feature('SLOW_OPERATION_LOGGING') ? slowLoggingAnt : slowLoggingExternal 158 159// --- Wrapped operations --- 160 161/** 162 * Wrapped JSON.stringify with slow operation logging. 163 * Use this instead of JSON.stringify directly to detect performance issues. 164 * 165 * @example 166 * import { jsonStringify } from './slowOperations.js' 167 * const json = jsonStringify(data) 168 * const prettyJson = jsonStringify(data, null, 2) 169 */ 170export function jsonStringify( 171 value: unknown, 172 replacer?: (this: unknown, key: string, value: unknown) => unknown, 173 space?: string | number, 174): string 175export function jsonStringify( 176 value: unknown, 177 replacer?: (number | string)[] | null, 178 space?: string | number, 179): string 180export function jsonStringify( 181 value: unknown, 182 replacer?: 183 | ((this: unknown, key: string, value: unknown) => unknown) 184 | (number | string)[] 185 | null, 186 space?: string | number, 187): string { 188 using _ = slowLogging`JSON.stringify(${value})` 189 return JSON.stringify( 190 value, 191 replacer as Parameters<typeof JSON.stringify>[1], 192 space, 193 ) 194} 195 196/** 197 * Wrapped JSON.parse with slow operation logging. 198 * Use this instead of JSON.parse directly to detect performance issues. 199 * 200 * @example 201 * import { jsonParse } from './slowOperations.js' 202 * const data = jsonParse(jsonString) 203 */ 204export const jsonParse: typeof JSON.parse = (text, reviver) => { 205 using _ = slowLogging`JSON.parse(${text})` 206 // V8 de-opts JSON.parse when a second argument is passed, even if undefined. 207 // Branch explicitly so the common (no-reviver) path stays on the fast path. 208 return typeof reviver === 'undefined' 209 ? JSON.parse(text) 210 : JSON.parse(text, reviver) 211} 212 213/** 214 * Wrapped structuredClone with slow operation logging. 215 * Use this instead of structuredClone directly to detect performance issues. 216 * 217 * @example 218 * import { clone } from './slowOperations.js' 219 * const copy = clone(originalObject) 220 */ 221export function clone<T>(value: T, options?: StructuredSerializeOptions): T { 222 using _ = slowLogging`structuredClone(${value})` 223 return structuredClone(value, options) 224} 225 226/** 227 * Wrapped cloneDeep with slow operation logging. 228 * Use this instead of lodash cloneDeep directly to detect performance issues. 229 * 230 * @example 231 * import { cloneDeep } from './slowOperations.js' 232 * const copy = cloneDeep(originalObject) 233 */ 234export function cloneDeep<T>(value: T): T { 235 using _ = slowLogging`cloneDeep(${value})` 236 return lodashCloneDeep(value) 237} 238 239/** 240 * Wrapper around fs.writeFileSync with slow operation logging. 241 * Supports flush option to ensure data is written to disk before returning. 242 * @param filePath The path to the file to write to 243 * @param data The data to write (string or Buffer) 244 * @param options Optional write options (encoding, mode, flag, flush) 245 * @deprecated Use `fs.promises.writeFile` instead for non-blocking writes. 246 * Sync file writes block the event loop and cause performance issues. 247 */ 248export function writeFileSync_DEPRECATED( 249 filePath: string, 250 data: string | NodeJS.ArrayBufferView, 251 options?: WriteFileOptionsWithFlush, 252): void { 253 using _ = slowLogging`fs.writeFileSync(${filePath}, ${data})` 254 255 // Check if flush is requested (for object-style options) 256 const needsFlush = 257 options !== null && 258 typeof options === 'object' && 259 'flush' in options && 260 options.flush === true 261 262 if (needsFlush) { 263 // Manual flush: open file, write, fsync, close 264 const encoding = 265 typeof options === 'object' && 'encoding' in options 266 ? options.encoding 267 : undefined 268 const mode = 269 typeof options === 'object' && 'mode' in options 270 ? options.mode 271 : undefined 272 let fd: number | undefined 273 try { 274 fd = openSync(filePath, 'w', mode) 275 fsWriteFileSync(fd, data, { encoding: encoding ?? undefined }) 276 fsyncSync(fd) 277 } finally { 278 if (fd !== undefined) { 279 closeSync(fd) 280 } 281 } 282 } else { 283 // No flush needed, use standard writeFileSync 284 fsWriteFileSync(filePath, data, options as WriteFileOptions) 285 } 286}