Skip to content

Commit 35f01de

Browse files
authored
Merge pull request #226 from digma-ai/feat4demo/span-dur-chg-notif
Feat4demo/span dur chg notif
2 parents 2ae851c + d781a90 commit 35f01de

File tree

11 files changed

+372
-154
lines changed

11 files changed

+372
-154
lines changed

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@
1111
"python.linting.enabled": true,
1212
"digma.environment": "DEV",
1313
"torque.activeProfile": "",
14-
"editor.detectIndentation": false
14+
"editor.detectIndentation": false,
15+
"editor.tabSize": 4,
1516
}

README.md

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,20 @@ This is a [Visual Studio Code](https://code.visualstudio.com) extension for Di
99
### 🤨 What does this extension do?
1010
It provides code objects insights and runtime analytics inside the IDE. The IDE is inteded to be extensible (currentluy refactoring toward that), so that anyone would be able to define new types of insights based on the collected data.
1111

12-
- [Code Objects Discovery](#code-object-discovery)
13-
- [Pull Request Insights](#pr-insights) (WIP)
14-
- [Code Insights](#code-insights)
15-
- [Runtime Errors](#runtime-errors)
16-
- [Runtime Errors Drilldown](#runtime-errors-drilldown)
17-
- [Code Objects Annotation](#code-obj-annotation)
18-
- [Usage Analytics](#usage-analytics)
19-
- [Selecting Environments](#environment)
12+
- [Digma Visual Studio Code Plugin](#digma-visual-studio-code-plugin)
13+
- [🤨 What does this extension do?](#-what-does-this-extension-do)
14+
- [🔬 Code Object Discovery](#-code-object-discovery)
15+
- [🧑‍💻 Pull Request Insights (WIP)](#-pull-request-insights-wip)
16+
- [🧑‍🔬 Code Insights](#-code-insights)
17+
- [🪳 Runtime Errors](#-runtime-errors)
18+
- [? What is a code object flow ?](#-what-is-a-code-object-flow-)
19+
- [👓 Runtime Errors Drilldown](#-runtime-errors-drilldown)
20+
- [🔦 Code Objects Annotation](#-code-objects-annotation)
21+
- [🎯 Usage Analytics](#-usage-analytics)
22+
- [፨ Selecting Environments](#-selecting-environments)
23+
- [⚙️ Extension Settings](#️-extension-settings)
24+
- [How to Build](#how-to-build)
25+
- [License](#license)
2026

2127

2228
#### 🔬 [Code Object Discovery](#code-object-discovery)
@@ -115,11 +121,12 @@ Environments can be easily assigned to observability data collected via an env v
115121
This extension contributes the following settings:
116122
| Key | Description |
117123
| :-- | :---------- |
118-
| `digma.enableCodeLens` | Enable/Disable methods codelens regarding errors.|
124+
| `digma.enableCodeLens` | Enable/disable methods codelens regarding errors.|
119125
| `digma.url` | Digma api endpoint url.|
120126
| `digma.environment` | Filter the telemtry data by environment. <br/> Can be set from the [Context](#context-panel) panel, by selecting from the **Environment** dropdown. |
121127
| `digma.hideFramesOutsideWorkspace` | Show/Hide frame of files that do not belog to the opened workspace(s)<br/>Can be in [Error Flow Details](#error-flow-details-panel) panel, by checking/unchecking the **Workspace only** checkbox). |
122128
| `digma.sourceControl` | Workspace's source control - used to open files in specific revision.<br/>Only `git` is supported for now. |
129+
| `digma.enableNotifications` | Enable/disable insight event notifications.|
123130

124131
## How to Build
125132

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@
135135
"digma.customHeader": {
136136
"type": "string",
137137
"description": "Specify a custom header to requests sent to Digma server. Useful in case Digma is deployed behind an ingress component, and need be routed (Example: \"Host:digma.loremipsum.com\")."
138+
},
139+
"digma.enableNotifications": {
140+
"type": "boolean",
141+
"default": false
138142
}
139143
}
140144
}

src/extension.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,15 @@ import { ErrorsLineDecorator } from './views/codeAnalytics/decorators/errorsLine
1818
import { HotspotMarkerDecorator } from './views/codeAnalytics/decorators/hotspotMarkerDecorator';
1919
import { EnvSelectStatusBar } from './views/codeAnalytics/StatusBar/envSelectStatusBar';
2020
import { InsightsStatusBar } from './views/codeAnalytics/StatusBar/insightsStatusBar';
21+
import { EnvironmentManager } from './services/EnvironmentManager';
22+
import { EventManager } from './services/EventManager';
23+
import { Scheduler } from './services/Scheduler';
2124

2225
export async function activate(context: vscode.ExtensionContext)
2326
{
27+
const scheduler = new Scheduler();
28+
context.subscriptions.push(scheduler);
29+
2430
const supportedLanguages = [
2531
new PythonLanguageExtractor(),
2632
new CSharpLanguageExtractor(),
@@ -40,12 +46,8 @@ export async function activate(context: vscode.ExtensionContext)
4046
const editorHelper = new EditorHelper(sourceControl, documentInfoProvider);
4147
const codeLensProvider = new AnalyticsCodeLens(documentInfoProvider, workspaceState);
4248

43-
if(!workspaceState.environment){
44-
const firstEnv = (await analyticsProvider.getEnvironments()).firstOrDefault();
45-
if(firstEnv) {
46-
workspaceState.setEnvironment(firstEnv);
47-
}
48-
}
49+
const environmentManager = new EnvironmentManager(analyticsProvider, workspaceState);
50+
await environmentManager.initializeCurrentEnvironment();
4951

5052
const envStatusbar = new EnvSelectStatusBar(workspaceState);
5153
const insightBar = new InsightsStatusBar(workspaceState,documentInfoProvider, editorHelper, context);
@@ -60,15 +62,21 @@ export async function activate(context: vscode.ExtensionContext)
6062
context.subscriptions.push(sourceControl);
6163
context.subscriptions.push(documentInfoProvider);
6264
context.subscriptions.push(new CodeAnalyticsView(analyticsProvider, documentInfoProvider,
63-
context.extensionUri, editorHelper,workspaceState,codeLensProvider,envStatusbar));
65+
context.extensionUri, editorHelper, workspaceState, codeLensProvider, envStatusbar, environmentManager));
6466
context.subscriptions.push(new ErrorsLineDecorator(documentInfoProvider));
6567
context.subscriptions.push(new HotspotMarkerDecorator(documentInfoProvider));
6668
context.subscriptions.push(new VsCodeDebugInstrumentation(analyticsProvider));
6769

68-
70+
context.subscriptions.push(new EventManager(
71+
scheduler,
72+
analyticsProvider,
73+
environmentManager,
74+
documentInfoProvider,
75+
editorHelper,
76+
));
6977
}
7078

7179
// this method is called when your extension is deactivated
7280
export function deactivate() {
7381

74-
}
82+
}

src/services/EnvironmentManager.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { WorkspaceState } from './../state';
2+
import { AnalyticsProvider } from './analyticsProvider';
3+
4+
export type Environment = string;
5+
6+
export class EnvironmentManager {
7+
private _environments: Environment[] = [];
8+
9+
constructor(
10+
private analyticsProvider: AnalyticsProvider,
11+
private workspaceState: WorkspaceState,
12+
) {
13+
}
14+
15+
public async getEnvironments(): Promise<Environment[]> {
16+
if (this._environments.length === 0) {
17+
this._environments = await this.analyticsProvider.getEnvironments();
18+
}
19+
20+
return this._environments;
21+
}
22+
23+
public async initializeCurrentEnvironment() {
24+
if(!this.workspaceState.environment){
25+
const environments = await this.getEnvironments();
26+
const firstEnvironment = (environments).firstOrDefault();
27+
if(firstEnvironment) {
28+
this.workspaceState.setEnvironment(firstEnvironment);
29+
}
30+
}
31+
}
32+
}

src/services/EventManager.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import * as vscode from 'vscode';
2+
import { EditorHelper } from './EditorHelper';
3+
import { DocumentInfoProvider } from './documentInfoProvider';
4+
import { SpanSearchInfo } from './../views/codeAnalytics/InsightListView/Common/SpanSearch';
5+
import { AnalyticsProvider, CodeObjectDurationChangeEvent } from './analyticsProvider';
6+
import { Scheduler } from './Scheduler';
7+
import { EnvironmentManager } from './EnvironmentManager';
8+
import { SpanSearch } from '../views/codeAnalytics/InsightListView/Common/SpanSearch';
9+
import { Settings } from '../settings';
10+
11+
export class EventManager implements vscode.Disposable {
12+
private lastFetch = new Date();
13+
14+
constructor(
15+
scheduler: Scheduler,
16+
private analyticsProvider: AnalyticsProvider,
17+
private environmentManager: EnvironmentManager,
18+
private documentInfoProvider: DocumentInfoProvider,
19+
private editorHelper: EditorHelper,
20+
) {
21+
if(Settings.enableNotifications.value) {
22+
scheduler.schedule(15, this.fetchEvents.bind(this));
23+
}
24+
}
25+
26+
private async fetchEvents() {
27+
const environments = await this.environmentManager.getEnvironments();
28+
29+
const now = new Date();
30+
const { events } = await this.analyticsProvider.getEvents(environments, this.lastFetch);
31+
this.lastFetch = now;
32+
33+
events.forEach(async (event) => {
34+
const eventData = <CodeObjectDurationChangeEvent>event;
35+
const message = `A possible change was detected in "${eventData?.span?.displayName}". Would you like to check it out?`;
36+
const item = 'Go';
37+
const response = await vscode.window.showInformationMessage(message, item);
38+
if(response === item) {
39+
const span: SpanSearchInfo = {
40+
instrumentationLibrary: eventData.span.instrumentationLibrary,
41+
name: eventData.span.name,
42+
};
43+
const spanSearch = new SpanSearch(this.documentInfoProvider);
44+
const spanLocations = await spanSearch.searchForSpans([span]);
45+
const spanLocation = spanLocations[0];
46+
if(spanLocation !== undefined) {
47+
const uri = spanLocation.documentUri;
48+
const line = spanLocation.range.end.line + 1;
49+
50+
const document = await this.editorHelper.openTextDocumentFromUri(uri);
51+
await this.editorHelper.openFileAndLine(document, line);
52+
}
53+
}
54+
});
55+
}
56+
57+
dispose() {
58+
}
59+
}

src/services/Scheduler.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
type Interval = number;
2+
type Time = number;
3+
4+
export class Scheduler {
5+
private _timer?: NodeJS.Timeout;
6+
private readonly _jobs: Job[] = [];
7+
8+
constructor() {
9+
this.nextTick();
10+
}
11+
12+
private async tick() {
13+
this._jobs
14+
.filter((job) => !job.running && job.schedule.isReady(Date.now(), job.previous))
15+
.forEach(async (job) => {
16+
try {
17+
job.running = true;
18+
await job.action();
19+
}
20+
finally {
21+
// record the time the job ended rather than started to avoid starvation
22+
job.previous = Date.now();
23+
job.running = false;
24+
}
25+
});
26+
27+
this.nextTick();
28+
}
29+
30+
private nextTick() {
31+
this._timer = setTimeout(this.tick.bind(this), 1000);
32+
}
33+
34+
public schedule(interval: Interval, action: Function) {
35+
this._jobs.push({
36+
schedule: new IntervalBasedSchedule(interval),
37+
action: action,
38+
});
39+
}
40+
41+
public dispose() {
42+
if (this._timer) {
43+
clearTimeout(this._timer);
44+
}
45+
}
46+
}
47+
48+
interface Schedule {
49+
isReady(now: Time, previous?: Time): boolean;
50+
}
51+
52+
class IntervalBasedSchedule implements Schedule {
53+
constructor(private interval: Interval) {
54+
}
55+
56+
public isReady(now: Time, previous?: Time) {
57+
return !previous || (now - previous) > this.interval * 1000;
58+
}
59+
}
60+
61+
interface Job {
62+
previous?: Time
63+
running?: boolean
64+
schedule: Schedule
65+
action: Function
66+
}

0 commit comments

Comments
 (0)