Skip to content
This repository was archived by the owner on Oct 16, 2020. It is now read-only.

Commit 5cac90f

Browse files
authored
Add helpers for tracing (#283)
1 parent f090ea7 commit 5cac90f

File tree

6 files changed

+382
-232
lines changed

6 files changed

+382
-232
lines changed

src/fs.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import iterate from 'iterare';
55
import { Span } from 'opentracing';
66
import Semaphore from 'semaphore-async-await';
77
import { InMemoryFileSystem } from './memfs';
8+
import { tracePromise } from './tracing';
89
import { normalizeUri, uri2path } from './util';
910

1011
export interface FileSystem {
@@ -144,22 +145,17 @@ export class FileSystemUpdater {
144145
* @param childOf A parent span for tracing
145146
*/
146147
fetchStructure(childOf = new Span()): Promise<void> {
147-
const promise = (async () => {
148-
const span = childOf.tracer().startSpan('Fetch workspace structure', { childOf });
148+
const promise = tracePromise('Fetch workspace structure', childOf, async span => {
149149
try {
150150
const uris = await this.remoteFs.getWorkspaceFiles(undefined, span);
151151
for (const uri of uris) {
152152
this.inMemoryFs.add(uri);
153153
}
154154
} catch (err) {
155155
this.structureFetch = undefined;
156-
span.setTag('error', true);
157-
span.log({ 'event': 'error', 'error.object': err, 'message': err.message, 'stack': err.stack });
158156
throw err;
159-
} finally {
160-
span.finish();
161157
}
162-
})();
158+
});
163159
this.structureFetch = promise;
164160
return promise;
165161
}

src/lang-handler.ts

Lines changed: 35 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
TextDocumentContentParams,
1919
WorkspaceFilesParams
2020
} from './request-type';
21+
import { traceObservable } from './tracing';
2122

2223
export interface LanguageClient {
2324
/**
@@ -90,47 +91,41 @@ export class RemoteLanguageClient {
9091
* @return Emits the value of the result field or the error
9192
*/
9293
private request(method: string, params: any[] | { [attr: string]: any }, childOf = new Span()): Observable<any> {
93-
const tracer = childOf.tracer();
94-
const span = tracer.startSpan(`Request ${method}`, { childOf });
95-
span.setTag('params', inspect(params));
96-
return new Observable<any>(subscriber => {
97-
// Generate a request ID
98-
const id = this.idCounter++;
99-
const message: RequestMessage & HasMeta = { jsonrpc: '2.0', method, id, params, meta: {} };
100-
tracer.inject(span, FORMAT_TEXT_MAP, message.meta);
101-
// Send request
102-
this.output.write(message);
103-
let receivedResponse = false;
104-
// Subscribe to message events
105-
const messageSub = Observable.fromEvent<Message>(this.input, 'message')
106-
// Find response message with the correct ID
107-
.filter(msg => isReponseMessage(msg) && msg.id === id)
108-
.take(1)
109-
// Emit result or error
110-
.map((msg: ResponseMessage): any => {
111-
receivedResponse = true;
112-
if (msg.error) {
113-
throw Object.assign(new Error(msg.error.message), msg.error);
94+
return traceObservable(`Request ${method}`, childOf, span => {
95+
span.setTag('params', inspect(params));
96+
return new Observable<any>(subscriber => {
97+
// Generate a request ID
98+
const id = this.idCounter++;
99+
const message: RequestMessage & HasMeta = { jsonrpc: '2.0', method, id, params, meta: {} };
100+
childOf.tracer().inject(span, FORMAT_TEXT_MAP, message.meta);
101+
// Send request
102+
this.output.write(message);
103+
let receivedResponse = false;
104+
// Subscribe to message events
105+
const messageSub = Observable.fromEvent<Message>(this.input, 'message')
106+
// Find response message with the correct ID
107+
.filter(msg => isReponseMessage(msg) && msg.id === id)
108+
.take(1)
109+
// Emit result or error
110+
.map((msg: ResponseMessage): any => {
111+
receivedResponse = true;
112+
if (msg.error) {
113+
throw Object.assign(new Error(msg.error.message), msg.error);
114+
}
115+
return msg.result;
116+
})
117+
// Forward events to subscriber
118+
.subscribe(subscriber);
119+
// Handler for unsubscribe()
120+
return () => {
121+
// Unsubscribe message event subscription (removes listener)
122+
messageSub.unsubscribe();
123+
if (!receivedResponse) {
124+
// Send LSP $/cancelRequest to client
125+
this.notify('$/cancelRequest', { id });
114126
}
115-
return msg.result;
116-
})
117-
// Forward events to subscriber
118-
.subscribe(subscriber);
119-
// Handler for unsubscribe()
120-
return () => {
121-
// Unsubscribe message event subscription (removes listener)
122-
messageSub.unsubscribe();
123-
if (!receivedResponse) {
124-
// Send LSP $/cancelRequest to client
125-
this.notify('$/cancelRequest', { id });
126-
}
127-
};
128-
}).catch(err => {
129-
span.setTag('error', true);
130-
span.log({ 'event': 'error', 'error.object': err, 'message': err.message, 'stack': err.stack });
131-
throw err;
132-
}).finally(() => {
133-
span.finish();
127+
};
128+
});
134129
});
135130
}
136131

src/project-manager.ts

Lines changed: 75 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Disposable } from './disposable';
88
import { FileSystemUpdater } from './fs';
99
import { Logger, NoopLogger } from './logging';
1010
import { InMemoryFileSystem } from './memfs';
11+
import { traceObservable, tracePromise, traceSync } from './tracing';
1112
import {
1213
isConfigFile,
1314
isDeclarationFile,
@@ -201,35 +202,32 @@ export class ProjectManager implements Disposable {
201202
* Then creates new ProjectConfigurations, resets existing and invalidates file references.
202203
*/
203204
ensureModuleStructure(childOf = new Span()): Promise<void> {
204-
const span = childOf.tracer().startSpan('Ensure module structure', { childOf });
205-
try {
206-
if (!this.ensuredModuleStructure) {
207-
this.ensuredModuleStructure = (async () => {
208-
await this.updater.ensureStructure();
209-
// Ensure content of all all global .d.ts, [tj]sconfig.json, package.json files
210-
await Promise.all(
211-
iterate(this.localFs.uris())
212-
.filter(uri => isGlobalTSFile(uri) || isConfigFile(uri) || isPackageJsonFile(uri))
213-
.map(uri => this.updater.ensure(uri))
214-
);
215-
// Reset all compilation state
216-
// TODO ze incremental compilation instead
217-
for (const config of this.configurations()) {
218-
config.reset();
219-
}
220-
// Require re-processing of file references
221-
this.invalidateReferencedFiles();
222-
})();
205+
return tracePromise('Ensure module structure', childOf, async span => {
206+
try {
207+
if (!this.ensuredModuleStructure) {
208+
this.ensuredModuleStructure = (async () => {
209+
await this.updater.ensureStructure();
210+
// Ensure content of all all global .d.ts, [tj]sconfig.json, package.json files
211+
await Promise.all(
212+
iterate(this.localFs.uris())
213+
.filter(uri => isGlobalTSFile(uri) || isConfigFile(uri) || isPackageJsonFile(uri))
214+
.map(uri => this.updater.ensure(uri))
215+
);
216+
// Reset all compilation state
217+
// TODO ze incremental compilation instead
218+
for (const config of this.configurations()) {
219+
config.reset();
220+
}
221+
// Require re-processing of file references
222+
this.invalidateReferencedFiles();
223+
})();
224+
}
225+
await this.ensuredModuleStructure;
226+
} catch (err) {
227+
this.ensuredModuleStructure = undefined;
228+
throw err;
223229
}
224-
return this.ensuredModuleStructure;
225-
} catch (err) {
226-
this.ensuredModuleStructure = undefined;
227-
span.setTag('error', true);
228-
span.log({ 'event': 'error', 'error.object': err, 'message': err.message, 'stack': err.stack });
229-
throw err;
230-
} finally {
231-
span.finish();
232-
}
230+
});
233231
}
234232

235233
/**
@@ -246,56 +244,50 @@ export class ProjectManager implements Disposable {
246244
* This includes all js/ts files, tsconfig files and package.json files.
247245
* Invalidates project configurations after execution
248246
*/
249-
async ensureOwnFiles(childOf = new Span()): Promise<void> {
250-
const span = childOf.tracer().startSpan('Ensure own files', { childOf });
251-
try {
252-
if (!this.ensuredOwnFiles) {
253-
this.ensuredOwnFiles = (async () => {
254-
await this.updater.ensureStructure(span);
255-
await Promise.all(
256-
iterate(this.localFs.uris())
257-
.filter(uri => !uri.includes('/node_modules/') && isJSTSFile(uri) || isConfigFile(uri) || isPackageJsonFile(uri))
258-
.map(uri => this.updater.ensure(uri))
259-
);
260-
})();
247+
ensureOwnFiles(childOf = new Span()): Promise<void> {
248+
return tracePromise('Ensure own files', childOf, async span => {
249+
try {
250+
if (!this.ensuredOwnFiles) {
251+
this.ensuredOwnFiles = (async () => {
252+
await this.updater.ensureStructure(span);
253+
await Promise.all(
254+
iterate(this.localFs.uris())
255+
.filter(uri => !uri.includes('/node_modules/') && isJSTSFile(uri) || isConfigFile(uri) || isPackageJsonFile(uri))
256+
.map(uri => this.updater.ensure(uri))
257+
);
258+
})();
259+
}
260+
await this.ensuredOwnFiles;
261+
} catch (err) {
262+
this.ensuredOwnFiles = undefined;
263+
throw err;
261264
}
262-
await this.ensuredOwnFiles;
263-
} catch (err) {
264-
this.ensuredOwnFiles = undefined;
265-
span.setTag('error', true);
266-
span.log({ 'event': 'error', 'error.object': err, 'message': err.message, 'stack': err.stack });
267-
throw err;
268-
} finally {
269-
span.finish();
270-
}
265+
});
271266
}
272267

273268
/**
274269
* Ensures all files were fetched from the remote file system.
275270
* Invalidates project configurations after execution
276271
*/
277-
async ensureAllFiles(childOf = new Span()): Promise<void> {
278-
const span = childOf.tracer().startSpan('Ensure all files', { childOf });
279-
try {
280-
if (!this.ensuredAllFiles) {
281-
this.ensuredAllFiles = (async () => {
272+
ensureAllFiles(childOf = new Span()): Promise<void> {
273+
return tracePromise('Ensure all files', childOf, async span => {
274+
try {
275+
if (!this.ensuredAllFiles) {
276+
this.ensuredAllFiles = (async () => {
282277
await this.updater.ensureStructure(span);
283278
await Promise.all(
284279
iterate(this.localFs.uris())
285280
.filter(uri => isJSTSFile(uri) || isConfigFile(uri) || isPackageJsonFile(uri))
286281
.map(uri => this.updater.ensure(uri))
287282
);
288-
})();
283+
})();
284+
}
285+
await this.ensuredAllFiles;
286+
} catch (err) {
287+
this.ensuredAllFiles = undefined;
288+
throw err;
289289
}
290-
await this.ensuredAllFiles;
291-
} catch (err) {
292-
this.ensuredAllFiles = undefined;
293-
span.setTag('error', true);
294-
span.log({ 'event': 'error', 'error.object': err, 'message': err.message, 'stack': err.stack });
295-
throw err;
296-
} finally {
297-
span.finish();
298-
}
290+
});
299291
}
300292

301293
/**
@@ -316,33 +308,24 @@ export class ProjectManager implements Disposable {
316308
* @return Observable of file URIs ensured
317309
*/
318310
ensureReferencedFiles(uri: string, maxDepth = 30, ignore = new Set<string>(), childOf = new Span()): Observable<string> {
319-
const span = childOf.tracer().startSpan('Ensure referenced files', { childOf });
320-
span.addTags({ uri, maxDepth });
321-
ignore.add(uri);
322-
return Observable.from(this.ensureModuleStructure(span))
323-
// If max depth was reached, don't go any further
324-
.mergeMap(() => maxDepth === 0 ? [] : this.resolveReferencedFiles(uri))
325-
// Prevent cycles
326-
.filter(referencedUri => !ignore.has(referencedUri))
327-
// Call method recursively with one less dep level
328-
.mergeMap(referencedUri =>
329-
this.ensureReferencedFiles(referencedUri, maxDepth - 1, ignore)
330-
// Continue even if an import wasn't found
331-
.catch(err => {
332-
this.logger.error(`Error resolving file references for ${uri}:`, err);
333-
return [];
334-
})
335-
)
336-
// Log errors to span
337-
.catch(err => {
338-
span.setTag('error', true);
339-
span.log({ 'event': 'error', 'error.object': err, 'message': err.message, 'stack': err.stack });
340-
throw err;
341-
})
342-
// Finish span
343-
.finally(() => {
344-
span.finish();
345-
});
311+
return traceObservable('Ensure referenced files', childOf, span => {
312+
span.addTags({ uri, maxDepth });
313+
ignore.add(uri);
314+
return Observable.from(this.ensureModuleStructure(span))
315+
// If max depth was reached, don't go any further
316+
.mergeMap(() => maxDepth === 0 ? [] : this.resolveReferencedFiles(uri))
317+
// Prevent cycles
318+
.filter(referencedUri => !ignore.has(referencedUri))
319+
// Call method recursively with one less dep level
320+
.mergeMap(referencedUri =>
321+
this.ensureReferencedFiles(referencedUri, maxDepth - 1, ignore)
322+
// Continue even if an import wasn't found
323+
.catch(err => {
324+
this.logger.error(`Error resolving file references for ${uri}:`, err);
325+
return [];
326+
})
327+
);
328+
});
346329
}
347330

348331
/**
@@ -822,17 +805,7 @@ export class ProjectConfiguration {
822805
* @return program object (cached result of parsing and typechecking done by TS service)
823806
*/
824807
getProgram(childOf = new Span()): ts.Program | undefined {
825-
const span = childOf.tracer().startSpan('Get program', { childOf });
826-
try {
827-
return this.getService().getProgram();
828-
} catch (err) {
829-
span.setTag('error', true);
830-
span.log({ 'event': 'error', 'error.object': err, 'message': err.message, 'stack': err.stack });
831-
this.logger.error(`Cannot create program object for ${this.rootFilePath}`, err);
832-
throw err;
833-
} finally {
834-
span.finish();
835-
}
808+
return traceSync('Get program', childOf, span => this.getService().getProgram());
836809
}
837810

838811
/**

0 commit comments

Comments
 (0)