Skip to content

Commit 61d08ab

Browse files
authored
Go span disco (#111)
* disco * add go span extractor * cleanup * delete ds_store file * CR changes, code rearrangement
1 parent b1aaf26 commit 61d08ab

File tree

8 files changed

+261
-9
lines changed

8 files changed

+261
-9
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
node_modules
22
out/**
33
*.vsix
4-
.vscode-test/**
4+
.vscode-test/**
5+
.DS_Store

src/services/codeInspector.ts

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export class CodeInspector {
3232
return methodInfo;
3333
}
3434

35-
public async getTokensFromSymbolProvider(
35+
public async getDefinitionWithTokens(
3636
usageDocument: vscode.TextDocument,
3737
usagePosition: vscode.Position,
3838
symbolProvider: SymbolProvider,
@@ -45,6 +45,48 @@ export class CodeInspector {
4545
return { ...definition, tokens };
4646
}
4747

48+
public async getTypeFromSymbolProvider(usageDocument: vscode.TextDocument,
49+
usagePosition: vscode.Position,
50+
symbolProvider: SymbolProvider): Promise<string | undefined>{
51+
const definition = await this.getType(usageDocument, usagePosition);
52+
if(!definition){
53+
return;
54+
}
55+
56+
const tokens = await symbolProvider.getTokens(definition.document);
57+
58+
59+
const traceDefToken = tokens.find(x => x.range.intersection(definition.location.range));
60+
if(!traceDefToken) {
61+
return;
62+
}
63+
64+
if(traceDefToken.type === TokenType.type){
65+
return traceDefToken.text;
66+
}
67+
return;
68+
}
69+
private async getType(
70+
usageDocument: vscode.TextDocument,
71+
usagePosition: vscode.Position,
72+
): Promise<Definition | undefined> {
73+
let results: any[] = await vscode.commands.executeCommand("vscode.executeTypeDefinitionProvider",usageDocument.uri, usagePosition);
74+
if(!results?.length || !results[0].uri || !results[0].range){
75+
return;
76+
}
77+
78+
const location = <vscode.Location>results[0];
79+
const document = await vscode.workspace.openTextDocument(location.uri);
80+
if(!document){
81+
return;
82+
}
83+
84+
return {
85+
document,
86+
location,
87+
};
88+
}
89+
4890
private async getDefinition(
4991
usageDocument: vscode.TextDocument,
5092
usagePosition: vscode.Position,
@@ -93,7 +135,7 @@ export class CodeInspector {
93135
return true;
94136
}
95137

96-
const parentInfo = await this.getTokensFromSymbolProvider(definition.document, parentToken.range.start, symbolProvider);
138+
const parentInfo = await this.getDefinitionWithTokens(definition.document, parentToken.range.start, symbolProvider);
97139
if(!parentInfo) {
98140
return false;
99141
}

src/services/languages/csharp/spanExtractor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export class CSharpSpanExtractor implements ISpanExtractor {
3434
}
3535

3636
const activityTokenPosition = tokens[i].range.start;
37-
const activityDefinition = await this._codeInspector.getTokensFromSymbolProvider(document, activityTokenPosition, symbolProvider);
37+
const activityDefinition = await this._codeInspector.getDefinitionWithTokens(document, activityTokenPosition, symbolProvider);
3838
if(!activityDefinition) {
3939
continue;
4040
}

src/services/languages/go/languageExtractor.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as vscode from 'vscode';
22
import { CodeInspector } from '../../codeInspector';
33
import { IEndpointExtractor, ILanguageExtractor, IMethodExtractor, ISpanExtractor } from '../extractors';
44
import { GoMethodExtractor } from './methodExtractor';
5+
import { GoSpanExtractor } from './spanExtractor';
56

67

78

@@ -28,6 +29,8 @@ export class GoLanguageExtractor implements ILanguageExtractor
2829
}
2930

3031
public getSpanExtractors(codeInspector: CodeInspector): ISpanExtractor[] {
31-
return [];
32+
return [
33+
new GoSpanExtractor(codeInspector)
34+
];
3235
}
3336
}
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import * as vscode from 'vscode';
2+
import { TextDocument } from "vscode";
3+
import { CodeInspector } from '../../codeInspector';
4+
import { ISpanExtractor, SpanInfo, SymbolInfo } from '../extractors';
5+
import { SymbolProvider } from '../symbolProvider';
6+
import { Token, TokenType } from '../tokens';
7+
import { Logger } from '../../logger';
8+
9+
10+
export class GoSpanExtractor implements ISpanExtractor {
11+
constructor(private _codeInspector: CodeInspector) {}
12+
13+
14+
15+
tryGetVariable(tokens: Token[], range: vscode.Range): [Token, number]| undefined{
16+
const referencedDefinitionIdx = tokens.findIndex(x => x.range.intersection(range) && x.type === TokenType.variable);
17+
if(referencedDefinitionIdx < 0) {
18+
return;
19+
}
20+
return [tokens[referencedDefinitionIdx], referencedDefinitionIdx];
21+
}
22+
23+
tryGetSpanName(tokens: Token [], index: number): string | undefined{
24+
const searchIndexLimit = index+10;
25+
while(index < searchIndexLimit){
26+
if(index >= tokens.length){
27+
break;
28+
}
29+
if(tokens[index].type === TokenType.string){
30+
return this.cleanText(tokens[index].text);
31+
}
32+
index++;
33+
}
34+
35+
36+
}
37+
38+
/*
39+
supported tracer name (instrumentation library)extraction
40+
var tracerName = "tracer_name"
41+
func Method(ctx context.Context){
42+
tracer := otel.GetTracerProvider().Tracer(tracerName)
43+
_, span := tracer.Start(ctx, "span_name")
44+
}
45+
46+
func Method(ctx context.Context){
47+
tracer := otel.GetTracerProvider().Tracer("tracer_name")
48+
_, span := tracer.Start(ctx, "span_name")
49+
}
50+
*/
51+
async tryGetInstrumentationName(document: TextDocument,codeInspector: CodeInspector, symbolProvider: SymbolProvider, tokens: Token [], range: vscode.Range) : Promise<string | undefined>{
52+
let tracerVariable = this.tryGetVariable(tokens, range);
53+
if(tracerVariable === undefined){
54+
return undefined;
55+
}
56+
let referencedDefinitionIdx = tracerVariable[1];
57+
const searchIterationLimit = 10;
58+
for (var i:number = 0; i < searchIterationLimit; i++) {
59+
let currIndex = referencedDefinitionIdx+1+i;
60+
if(currIndex >= tokens.length){
61+
break;
62+
}
63+
if(tokens[currIndex].type === TokenType.function && tokens[currIndex].text ==="Tracer"){
64+
const token = tokens[currIndex+1];
65+
if(token.type === TokenType.string){
66+
return this.cleanText(token.text);
67+
} else if(token.type === TokenType.variable){ //support tracer name from variable of type string
68+
const definition = await codeInspector.getDefinitionWithTokens(document,token.range.start,symbolProvider);
69+
if(definition !== undefined)
70+
{
71+
let tracerVariable = this.tryGetVariable(definition.tokens, definition.location.range);
72+
if(tracerVariable!== undefined){
73+
let stringValueIndex = tracerVariable[1]+1;
74+
if (stringValueIndex< definition.tokens.length && definition.tokens[stringValueIndex].type === TokenType.string){
75+
return this.cleanText(definition.tokens[stringValueIndex].text);
76+
}
77+
}
78+
}
79+
}
80+
return undefined;
81+
}
82+
}
83+
}
84+
85+
/*
86+
func NewUserController(service Service) *UserController {
87+
return &UserController{
88+
service: service,
89+
tracer: otel.Tracer("tracer_name"),
90+
}
91+
}
92+
func (controller *UserController) Get(w http.ResponseWriter, req *http.Request) {
93+
_, span := controller.tracer.Start(req.Context(), "span_name")
94+
defer span.End()
95+
}
96+
97+
func (u *userService) Get(ctx context.Context, id string) (User, error) {
98+
tracer := otel.GetTracerProvider().Tracer("trace_name")
99+
_, span := tracer.Start(ctx, "span_name")
100+
defer span.End()
101+
}
102+
103+
//!!!!!not supported (cannot resolve span name)
104+
func (u *userService) Get(ctx context.Context, id string) (User, error) {
105+
tracer := otel.GetTracerProvider().Tracer("UserService")
106+
_, span := tracer.Start(ctx, funcName(0))
107+
defer span.End()
108+
}
109+
*/
110+
async extractSpans(
111+
document: TextDocument,
112+
symbolInfos: SymbolInfo[],
113+
tokens: Token[],
114+
symbolProvider: SymbolProvider,
115+
): Promise<SpanInfo[]> {
116+
const results: SpanInfo[] = [];
117+
118+
for(var symbol of symbolInfos){
119+
Logger.info("Span discovering for function: "+symbol.displayName);
120+
try {
121+
const funcStartTokenIndex = tokens.findIndex(x => x.range.intersection(symbol.range));
122+
let funcEndTokenIndex: number = tokens.length-1;
123+
for (let index = funcStartTokenIndex+1; index < tokens.length; index++) {
124+
if(!tokens[index].range.intersection(symbol.range)){
125+
funcEndTokenIndex = index;
126+
break;
127+
}
128+
}
129+
130+
for (let index = funcStartTokenIndex; index < funcEndTokenIndex; index++) {
131+
let token = tokens[index];
132+
if(token.type !== TokenType.function || token.text !== "Start"){
133+
continue;
134+
}
135+
const traceVarToken = tokens[index-1];
136+
if(traceVarToken.type !== TokenType.variable){
137+
continue;
138+
}
139+
const traceDefType = await this._codeInspector.getTypeFromSymbolProvider(document, traceVarToken.range.start, symbolProvider);
140+
if(traceDefType !== "Tracer") {
141+
continue;
142+
}
143+
let references : vscode.Location[] = await vscode.commands.executeCommand("vscode.executeReferenceProvider", document.uri,traceVarToken.range.start);
144+
let instrumentationLibraries = new Set();
145+
for(let refLocation of references)
146+
{
147+
let _tokens: Token [];
148+
let _range: vscode.Range;
149+
let _document: TextDocument;
150+
if(refLocation.uri.fsPath !== document.uri.fsPath) { //defined in a different document
151+
_document = await vscode.workspace.openTextDocument(refLocation.uri);
152+
_tokens = await symbolProvider.getTokens(_document);
153+
}
154+
else{
155+
_tokens = tokens;
156+
_document = document;
157+
}
158+
_range = refLocation.range;
159+
const instrumentationName = await this.tryGetInstrumentationName(_document, this._codeInspector, symbolProvider, _tokens, _range);
160+
if(instrumentationName !== undefined){
161+
instrumentationLibraries.add(instrumentationName);
162+
}
163+
}
164+
if(instrumentationLibraries.size === 0){
165+
continue;
166+
}
167+
168+
if(instrumentationLibraries.size > 1){
169+
Logger.warn("ambiguous tracer name(instrumentation library) found: "+instrumentationLibraries.toString());
170+
continue;
171+
}
172+
const instrumentationLibrary = instrumentationLibraries.values().next().value;
173+
174+
const spanName = this.tryGetSpanName(tokens, index+1);
175+
176+
if(spanName === undefined){
177+
Logger.info("Span discovery failed, span name could not be resolved for tracer '"+instrumentationLibrary+"'");
178+
continue;
179+
}
180+
181+
results.push(new SpanInfo(
182+
instrumentationLibrary + '$_$' + spanName,
183+
spanName,
184+
token.range,
185+
document.uri));
186+
187+
Logger.info("* Span found: "+instrumentationLibrary+"/"+spanName);
188+
189+
190+
191+
192+
}
193+
}
194+
catch (error){
195+
Logger.error('Span discovery failed with error', error);
196+
}
197+
}
198+
return results;
199+
}
200+
201+
202+
private cleanText(text: string): string {
203+
return text.replace(/\"/g, '');
204+
}
205+
206+
}

src/services/languages/python/spanExtractor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export class PythonSpanExtractor implements ISpanExtractor {
4949
}
5050

5151
const tracerTokenPosition = tracerToken.range.start;
52-
const tracerDefinition = await this._codeInspector.getTokensFromSymbolProvider(document, tracerTokenPosition, symbolProvider);
52+
const tracerDefinition = await this._codeInspector.getDefinitionWithTokens(document, tracerTokenPosition, symbolProvider);
5353
if(!tracerDefinition) {
5454
continue;
5555
}

src/services/languages/symbolProvider.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,6 @@ export class SymbolProvider
151151

152152
return [];
153153
}
154-
155154
public async getTokens(document: vscode.TextDocument, range?: vscode.Range): Promise<Token[]> {
156155
let tokes: Token[] = [];
157156
try {
@@ -188,7 +187,7 @@ export class SymbolProvider
188187
if(!semanticTokens) {
189188
return tokes;
190189
}
191-
190+
192191
let line = 0;
193192
let char = 0;
194193
for(let i = 0; i < semanticTokens.data.length; i += 5) {

src/services/languages/tokens.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ export enum TokenType {
3636
string = 'string',
3737
plainKeyword = 'plainKeyword',
3838
namespace = 'namespace',
39-
keyword = 'keyword'
39+
keyword = 'keyword',
40+
type = 'type'
4041
}
4142

4243
export function matchTokenSequence(tokens: Token[], matchers: TokenMatcher[]): boolean {

0 commit comments

Comments
 (0)