1/* Tracing events for the compiler. */ 2 3/*@internal*/ 4namespace ts { // eslint-disable-line local/one-namespace-per-file 5 // should be used as tracing?.___ 6 export let tracing: typeof tracingEnabled | undefined; 7 // enable the above using startTracing() 8 9 // `tracingEnabled` should never be used directly, only through the above 10 namespace tracingEnabled { // eslint-disable-line local/one-namespace-per-file 11 type Mode = "project" | "build" | "server"; 12 13 let fs: typeof import("fs"); 14 15 let traceCount = 0; 16 let traceFd = 0; 17 18 let mode: Mode; 19 20 const typeCatalog: Type[] = []; // NB: id is index + 1 21 22 let legendPath: string | undefined; 23 const legend: TraceRecord[] = []; 24 25 // The actual constraint is that JSON.stringify be able to serialize it without throwing. 26 interface Args { 27 [key: string]: string | number | boolean | null | undefined | Args | readonly (string | number | boolean | null | undefined | Args)[]; 28 } 29 30 /** Starts tracing for the given project. */ 31 export function startTracing(tracingMode: Mode, traceDir: string, configFilePath?: string) { 32 Debug.assert(!tracing, "Tracing already started"); 33 34 if (fs === undefined) { 35 try { 36 fs = require("fs"); 37 } 38 catch (e) { 39 throw new Error(`tracing requires having fs\n(original error: ${e.message || e})`); 40 } 41 } 42 43 mode = tracingMode; 44 typeCatalog.length = 0; 45 46 if (legendPath === undefined) { 47 legendPath = combinePaths(traceDir, "legend.json"); 48 } 49 50 // Note that writing will fail later on if it exists and is not a directory 51 if (!fs.existsSync(traceDir)) { 52 fs.mkdirSync(traceDir, { recursive: true }); 53 } 54 55 const countPart = 56 mode === "build" ? `.${process.pid}-${++traceCount}` 57 : mode === "server" ? `.${process.pid}` 58 : ``; 59 const tracePath = combinePaths(traceDir, `trace${countPart}.json`); 60 const typesPath = combinePaths(traceDir, `types${countPart}.json`); 61 62 legend.push({ 63 configFilePath, 64 tracePath, 65 typesPath, 66 }); 67 68 traceFd = fs.openSync(tracePath, "w"); 69 tracing = tracingEnabled; // only when traceFd is properly set 70 71 // Start with a prefix that contains some metadata that the devtools profiler expects (also avoids a warning on import) 72 const meta = { cat: "__metadata", ph: "M", ts: 1000 * timestamp(), pid: 1, tid: 1 }; 73 fs.writeSync(traceFd, 74 "[\n" 75 + [{ name: "process_name", args: { name: "tsc" }, ...meta }, 76 { name: "thread_name", args: { name: "Main" }, ...meta }, 77 { name: "TracingStartedInBrowser", ...meta, cat: "disabled-by-default-devtools.timeline" }] 78 .map(v => JSON.stringify(v)).join(",\n")); 79 } 80 81 /** Stops tracing for the in-progress project and dumps the type catalog. */ 82 export function stopTracing() { 83 Debug.assert(tracing, "Tracing is not in progress"); 84 Debug.assert(!!typeCatalog.length === (mode !== "server")); // Have a type catalog iff not in server mode 85 86 fs.writeSync(traceFd, `\n]\n`); 87 fs.closeSync(traceFd); 88 tracing = undefined; 89 90 if (typeCatalog.length) { 91 dumpTypes(typeCatalog); 92 } 93 else { 94 // We pre-computed this path for convenience, but clear it 95 // now that the file won't be created. 96 legend[legend.length - 1].typesPath = undefined; 97 } 98 } 99 100 export function recordType(type: Type): void { 101 if (mode !== "server") { 102 typeCatalog.push(type); 103 } 104 } 105 106 export const enum Phase { 107 Parse = "parse", 108 Program = "program", 109 Bind = "bind", 110 Check = "check", // Before we get into checking types (e.g. checkSourceFile) 111 CheckTypes = "checkTypes", 112 Emit = "emit", 113 Session = "session", 114 } 115 116 export function instant(phase: Phase, name: string, args?: Args) { 117 writeEvent("I", phase, name, args, `"s":"g"`); 118 } 119 120 const eventStack: { phase: Phase, name: string, args?: Args, time: number, separateBeginAndEnd: boolean }[] = []; 121 122 /** 123 * @param separateBeginAndEnd - used for special cases where we need the trace point even if the event 124 * never terminates (typically for reducing a scenario too big to trace to one that can be completed). 125 * In the future we might implement an exit handler to dump unfinished events which would deprecate 126 * these operations. 127 */ 128 export function push(phase: Phase, name: string, args?: Args, separateBeginAndEnd = false) { 129 if (separateBeginAndEnd) { 130 writeEvent("B", phase, name, args); 131 } 132 eventStack.push({ phase, name, args, time: 1000 * timestamp(), separateBeginAndEnd }); 133 } 134 export function pop(results?: Args) { 135 Debug.assert(eventStack.length > 0); 136 writeStackEvent(eventStack.length - 1, 1000 * timestamp(), results); 137 eventStack.length--; 138 } 139 export function popAll() { 140 const endTime = 1000 * timestamp(); 141 for (let i = eventStack.length - 1; i >= 0; i--) { 142 writeStackEvent(i, endTime); 143 } 144 eventStack.length = 0; 145 } 146 // sample every 10ms 147 const sampleInterval = 1000 * 10; 148 function writeStackEvent(index: number, endTime: number, results?: Args) { 149 const { phase, name, args, time, separateBeginAndEnd } = eventStack[index]; 150 if (separateBeginAndEnd) { 151 Debug.assert(!results, "`results` are not supported for events with `separateBeginAndEnd`"); 152 writeEvent("E", phase, name, args, /*extras*/ undefined, endTime); 153 } 154 // test if [time,endTime) straddles a sampling point 155 else if (sampleInterval - (time % sampleInterval) <= endTime - time) { 156 writeEvent("X", phase, name, { ...args, results }, `"dur":${endTime - time}`, time); 157 } 158 } 159 160 function writeEvent(eventType: string, phase: Phase, name: string, args: Args | undefined, extras?: string, 161 time: number = 1000 * timestamp()) { 162 163 // In server mode, there's no easy way to dump type information, so we drop events that would require it. 164 if (mode === "server" && phase === Phase.CheckTypes) return; 165 166 performance.mark("beginTracing"); 167 fs.writeSync(traceFd, `,\n{"pid":1,"tid":1,"ph":"${eventType}","cat":"${phase}","ts":${time},"name":"${name}"`); 168 if (extras) fs.writeSync(traceFd, `,${extras}`); 169 if (args) fs.writeSync(traceFd, `,"args":${JSON.stringify(args)}`); 170 fs.writeSync(traceFd, `}`); 171 performance.mark("endTracing"); 172 performance.measure("Tracing", "beginTracing", "endTracing"); 173 } 174 175 function getLocation(node: Node | undefined) { 176 const file = getSourceFileOfNode(node); 177 return !file 178 ? undefined 179 : { 180 path: file.path, 181 start: indexFromOne(getLineAndCharacterOfPosition(file, node!.pos)), 182 end: indexFromOne(getLineAndCharacterOfPosition(file, node!.end)), 183 }; 184 185 function indexFromOne(lc: LineAndCharacter): LineAndCharacter { 186 return { 187 line: lc.line + 1, 188 character: lc.character + 1, 189 }; 190 } 191 } 192 193 function dumpTypes(types: readonly Type[]) { 194 performance.mark("beginDumpTypes"); 195 196 const typesPath = legend[legend.length - 1].typesPath!; 197 const typesFd = fs.openSync(typesPath, "w"); 198 199 const recursionIdentityMap = new Map<object, number>(); 200 201 // Cleverness: no line break here so that the type ID will match the line number 202 fs.writeSync(typesFd, "["); 203 204 const numTypes = types.length; 205 for (let i = 0; i < numTypes; i++) { 206 const type = types[i]; 207 const objectFlags = (type as any).objectFlags; 208 const symbol = type.aliasSymbol ?? type.symbol; 209 210 // It's slow to compute the display text, so skip it unless it's really valuable (or cheap) 211 let display: string | undefined; 212 if ((objectFlags & ObjectFlags.Anonymous) | (type.flags & TypeFlags.Literal)) { 213 try { 214 display = type.checker?.typeToString(type); 215 } 216 catch { 217 display = undefined; 218 } 219 } 220 221 let indexedAccessProperties: object = {}; 222 if (type.flags & TypeFlags.IndexedAccess) { 223 const indexedAccessType = type as IndexedAccessType; 224 indexedAccessProperties = { 225 indexedAccessObjectType: indexedAccessType.objectType?.id, 226 indexedAccessIndexType: indexedAccessType.indexType?.id, 227 }; 228 } 229 230 let referenceProperties: object = {}; 231 if (objectFlags & ObjectFlags.Reference) { 232 const referenceType = type as TypeReference; 233 referenceProperties = { 234 instantiatedType: referenceType.target?.id, 235 typeArguments: referenceType.resolvedTypeArguments?.map(t => t.id), 236 referenceLocation: getLocation(referenceType.node), 237 }; 238 } 239 240 let conditionalProperties: object = {}; 241 if (type.flags & TypeFlags.Conditional) { 242 const conditionalType = type as ConditionalType; 243 conditionalProperties = { 244 conditionalCheckType: conditionalType.checkType?.id, 245 conditionalExtendsType: conditionalType.extendsType?.id, 246 conditionalTrueType: conditionalType.resolvedTrueType?.id ?? -1, 247 conditionalFalseType: conditionalType.resolvedFalseType?.id ?? -1, 248 }; 249 } 250 251 let substitutionProperties: object = {}; 252 if (type.flags & TypeFlags.Substitution) { 253 const substitutionType = type as SubstitutionType; 254 substitutionProperties = { 255 substitutionBaseType: substitutionType.baseType?.id, 256 constraintType: substitutionType.constraint?.id, 257 }; 258 } 259 260 let reverseMappedProperties: object = {}; 261 if (objectFlags & ObjectFlags.ReverseMapped) { 262 const reverseMappedType = type as ReverseMappedType; 263 reverseMappedProperties = { 264 reverseMappedSourceType: reverseMappedType.source?.id, 265 reverseMappedMappedType: reverseMappedType.mappedType?.id, 266 reverseMappedConstraintType: reverseMappedType.constraintType?.id, 267 }; 268 } 269 270 let evolvingArrayProperties: object = {}; 271 if (objectFlags & ObjectFlags.EvolvingArray) { 272 const evolvingArrayType = type as EvolvingArrayType; 273 evolvingArrayProperties = { 274 evolvingArrayElementType: evolvingArrayType.elementType.id, 275 evolvingArrayFinalType: evolvingArrayType.finalArrayType?.id, 276 }; 277 } 278 279 // We can't print out an arbitrary object, so just assign each one a unique number. 280 // Don't call it an "id" so people don't treat it as a type id. 281 let recursionToken: number | undefined; 282 const recursionIdentity = type.checker.getRecursionIdentity(type); 283 if (recursionIdentity) { 284 recursionToken = recursionIdentityMap.get(recursionIdentity); 285 if (!recursionToken) { 286 recursionToken = recursionIdentityMap.size; 287 recursionIdentityMap.set(recursionIdentity, recursionToken); 288 } 289 } 290 291 const descriptor = { 292 id: type.id, 293 intrinsicName: (type as any).intrinsicName, 294 symbolName: symbol?.escapedName && unescapeLeadingUnderscores(symbol.escapedName), 295 recursionId: recursionToken, 296 isTuple: objectFlags & ObjectFlags.Tuple ? true : undefined, 297 unionTypes: (type.flags & TypeFlags.Union) ? (type as UnionType).types?.map(t => t.id) : undefined, 298 intersectionTypes: (type.flags & TypeFlags.Intersection) ? (type as IntersectionType).types.map(t => t.id) : undefined, 299 aliasTypeArguments: type.aliasTypeArguments?.map(t => t.id), 300 keyofType: (type.flags & TypeFlags.Index) ? (type as IndexType).type?.id : undefined, 301 ...indexedAccessProperties, 302 ...referenceProperties, 303 ...conditionalProperties, 304 ...substitutionProperties, 305 ...reverseMappedProperties, 306 ...evolvingArrayProperties, 307 destructuringPattern: getLocation(type.pattern), 308 firstDeclaration: getLocation(symbol?.declarations?.[0]), 309 flags: Debug.formatTypeFlags(type.flags).split("|"), 310 display, 311 }; 312 313 fs.writeSync(typesFd, JSON.stringify(descriptor)); 314 if (i < numTypes - 1) { 315 fs.writeSync(typesFd, ",\n"); 316 } 317 } 318 319 fs.writeSync(typesFd, "]\n"); 320 321 fs.closeSync(typesFd); 322 323 performance.mark("endDumpTypes"); 324 performance.measure("Dump types", "beginDumpTypes", "endDumpTypes"); 325 } 326 327 export function dumpLegend() { 328 if (!legendPath) { 329 return; 330 } 331 332 fs.writeFileSync(legendPath, JSON.stringify(legend)); 333 } 334 335 interface TraceRecord { 336 configFilePath?: string; 337 tracePath: string; 338 typesPath?: string; 339 } 340 } 341 342 // define after tracingEnabled is initialized 343 export const startTracing = tracingEnabled.startTracing; 344 export const dumpTracingLegend = tracingEnabled.dumpLegend; 345 346 export interface TracingNode { 347 tracingPath?: Path; 348 } 349} 350