Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Heartbeat #4

Merged
merged 17 commits into from
Jul 15, 2022
13 changes: 4 additions & 9 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const EXTENSION_ID = 'gitpod.gitpod-desktop';
const FIRST_INSTALL_KEY = 'gitpod-desktop.firstInstall';

let telemetry: TelemetryReporter;
let remoteConnector: RemoteConnector;

export async function activate(context: vscode.ExtensionContext) {
const packageJSON = vscode.extensions.getExtension(EXTENSION_ID)!.packageJSON;
Expand Down Expand Up @@ -71,9 +72,8 @@ export async function activate(context: vscode.ExtensionContext) {
}));

const authProvider = new GitpodAuthenticationProvider(context, logger, telemetry);
const remoteConnector = new RemoteConnector(context, logger, telemetry);
remoteConnector = new RemoteConnector(context, logger, telemetry);
context.subscriptions.push(authProvider);
context.subscriptions.push(remoteConnector);
context.subscriptions.push(vscode.window.registerUriHandler({
handleUri(uri: vscode.Uri) {
// logger.trace('Handling Uri...', uri.toString());
Expand All @@ -85,18 +85,13 @@ export async function activate(context: vscode.ExtensionContext) {
}
}));

if (await remoteConnector.checkRemoteConnectionSuccessful()) {
context.subscriptions.push(vscode.commands.registerCommand('gitpod.api.autoTunnel', remoteConnector.autoTunnelCommand, remoteConnector));
}

if (!context.globalState.get<boolean>(FIRST_INSTALL_KEY, false)) {
await context.globalState.update(FIRST_INSTALL_KEY, true);
telemetry.sendTelemetryEvent('gitpod_desktop_installation', { kind: 'install' });
}
}

export async function deactivate() {
if (telemetry) {
await telemetry.dispose();
}
await remoteConnector?.dispose();
await telemetry?.dispose();
}
130 changes: 130 additions & 0 deletions src/heartbeat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Gitpod. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';
jeanp413 marked this conversation as resolved.
Show resolved Hide resolved
import { Disposable } from './common/dispose';
import Log from './common/logger';
import { withServerApi } from './internalApi';
import TelemetryReporter from './telemetryReporter';

export class HeartbeatManager extends Disposable {

static HEARTBEAT_INTERVAL = 10000;

private lastActivity = new Date().getTime();
private isWorkspaceRunning = true;
private heartBeatHandle: NodeJS.Timer | undefined;

constructor(
readonly gitpodHost: string,
readonly workspaceId: string,
readonly instanceId: string,
private readonly accessToken: string,
private readonly logger: Log,
private readonly telemetry: TelemetryReporter
) {
super();

this._register(vscode.window.onDidChangeActiveTextEditor(this.updateLastActivitiy, this));
this._register(vscode.window.onDidChangeVisibleTextEditors(this.updateLastActivitiy, this));
this._register(vscode.window.onDidChangeTextEditorSelection(this.updateLastActivitiy, this));
this._register(vscode.window.onDidChangeTextEditorVisibleRanges(this.updateLastActivitiy, this));
this._register(vscode.window.onDidChangeTextEditorOptions(this.updateLastActivitiy, this));
this._register(vscode.window.onDidChangeTextEditorViewColumn(this.updateLastActivitiy, this));
this._register(vscode.window.onDidChangeActiveTerminal(this.updateLastActivitiy, this));
this._register(vscode.window.onDidOpenTerminal(this.updateLastActivitiy, this));
this._register(vscode.window.onDidCloseTerminal(this.updateLastActivitiy, this));
this._register(vscode.window.onDidChangeTerminalState(this.updateLastActivitiy, this));
this._register(vscode.window.onDidChangeWindowState(this.updateLastActivitiy, this));
this._register(vscode.window.onDidChangeActiveColorTheme(this.updateLastActivitiy, this));
this._register(vscode.authentication.onDidChangeSessions(this.updateLastActivitiy, this));
this._register(vscode.debug.onDidChangeActiveDebugSession(this.updateLastActivitiy, this));
this._register(vscode.debug.onDidStartDebugSession(this.updateLastActivitiy, this));
this._register(vscode.debug.onDidReceiveDebugSessionCustomEvent(this.updateLastActivitiy, this));
this._register(vscode.debug.onDidTerminateDebugSession(this.updateLastActivitiy, this));
this._register(vscode.debug.onDidChangeBreakpoints(this.updateLastActivitiy, this));
this._register(vscode.extensions.onDidChange(this.updateLastActivitiy, this));
this._register(vscode.languages.onDidChangeDiagnostics(this.updateLastActivitiy, this));
this._register(vscode.tasks.onDidStartTask(this.updateLastActivitiy, this));
this._register(vscode.tasks.onDidStartTaskProcess(this.updateLastActivitiy, this));
this._register(vscode.tasks.onDidEndTask(this.updateLastActivitiy, this));
this._register(vscode.tasks.onDidEndTaskProcess(this.updateLastActivitiy, this));
this._register(vscode.workspace.onDidChangeWorkspaceFolders(this.updateLastActivitiy, this));
this._register(vscode.workspace.onDidOpenTextDocument(this.updateLastActivitiy, this));
this._register(vscode.workspace.onDidCloseTextDocument(this.updateLastActivitiy, this));
this._register(vscode.workspace.onDidChangeTextDocument(this.updateLastActivitiy, this));
this._register(vscode.workspace.onDidSaveTextDocument(this.updateLastActivitiy, this));
this._register(vscode.workspace.onDidChangeNotebookDocument(this.updateLastActivitiy, this));
this._register(vscode.workspace.onDidSaveNotebookDocument(this.updateLastActivitiy, this));
this._register(vscode.workspace.onDidOpenNotebookDocument(this.updateLastActivitiy, this));
this._register(vscode.workspace.onDidCloseNotebookDocument(this.updateLastActivitiy, this));
this._register(vscode.workspace.onWillCreateFiles(this.updateLastActivitiy, this));
this._register(vscode.workspace.onDidCreateFiles(this.updateLastActivitiy, this));
this._register(vscode.workspace.onWillDeleteFiles(this.updateLastActivitiy, this));
this._register(vscode.workspace.onDidDeleteFiles(this.updateLastActivitiy, this));
this._register(vscode.workspace.onWillRenameFiles(this.updateLastActivitiy, this));
this._register(vscode.workspace.onDidRenameFiles(this.updateLastActivitiy, this));
this._register(vscode.workspace.onDidChangeConfiguration(this.updateLastActivitiy, this));
this._register(vscode.languages.registerHoverProvider('*', {
provideHover: () => {
this.updateLastActivitiy();
return null;
}
}));

this.logger.trace(`Heartbeat manager for workspace ${workspaceId} (${instanceId}) - ${gitpodHost} started`);

// Start heatbeating interval
this.sendHeartBeat();
this.heartBeatHandle = setInterval(() => {
jeanp413 marked this conversation as resolved.
Show resolved Hide resolved
if (this.lastActivity + HeartbeatManager.HEARTBEAT_INTERVAL < new Date().getTime()) {
// no activity, no heartbeat
return;
}

this.sendHeartBeat();
}, HeartbeatManager.HEARTBEAT_INTERVAL);
}

private updateLastActivitiy() {
this.lastActivity = new Date().getTime();
}

private async sendHeartBeat(wasClosed?: true) {
const suffix = wasClosed ? 'closed heartbeat' : 'heartbeat';
try {
await withServerApi(this.accessToken, this.gitpodHost, async service => {
const workspaceInfo = await service.server.getWorkspace(this.workspaceId);
this.isWorkspaceRunning = workspaceInfo.latestInstance?.status?.phase === 'running';
jeanp413 marked this conversation as resolved.
Show resolved Hide resolved
if (this.isWorkspaceRunning) {
await service.server.sendHeartBeat({ instanceId: this.instanceId, wasClosed });
} else {
this.stopHeartbeat();
}
}, this.logger);
if (wasClosed) {
jeanp413 marked this conversation as resolved.
Show resolved Hide resolved
this.telemetry.sendTelemetryEvent('ide_close_signal', { workspaceId: this.workspaceId, instanceId: this.instanceId, clientKind: 'vscode' });
jeanp413 marked this conversation as resolved.
Show resolved Hide resolved
this.logger.trace('send ' + suffix);
}
} catch (err) {
this.logger.error(`failed to send ${suffix}:`, err);
}
}

private stopHeartbeat() {
if (this.heartBeatHandle) {
clearInterval(this.heartBeatHandle);
this.heartBeatHandle = undefined;
}
}

public override async dispose(): Promise<void> {
this.stopHeartbeat();
if (this.isWorkspaceRunning) {
await this.sendHeartBeat(true);
}
super.dispose();
}
}
2 changes: 1 addition & 1 deletion src/internalApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import ReconnectingWebSocket from 'reconnecting-websocket';
import * as vscode from 'vscode';
import Log from './common/logger';

type UsedGitpodFunction = ['getLoggedInUser', 'getGitpodTokenScopes', 'getWorkspace', 'getOwnerToken'];
type UsedGitpodFunction = ['getLoggedInUser', 'getGitpodTokenScopes', 'getWorkspace', 'getOwnerToken', 'sendHeartBeat'];
jeanp413 marked this conversation as resolved.
Show resolved Hide resolved
type Union<Tuple extends any[], Union = never> = Tuple[number] | Union;
export type GitpodConnection = Omit<GitpodServiceImpl<GitpodClient, GitpodServer>, 'server'> & {
server: Pick<GitpodServer, Union<UsedGitpodFunction>>;
Expand Down
Loading