[release] goLanguageServer: track language server's restart history

And show it in automated issue reports. This will allow us to see if
crashes were potentially caused by a bad restart. Sometimes we notice
crashes that don't have any associated gopls stack traces, so we were
wondering if there is something about the way that we do restarts that
causes these issues.

Automated issues will list when and why the language server was
restarted, as well as when the issue was suggested.

Change-Id: I298cb4a4931bfedeb75bada446ddbc480eec1501
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/348973
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
Trust: Hyang-Ah Hana Kim <hyangah@gmail.com>
Trust: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Hyang-Ah Hana Kim <hyangah@gmail.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
(cherry picked from commit c71634cde6524a35566e9c0ec49a4b76b4560b33)
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/350139
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/src/goInstallTools.ts b/src/goInstallTools.ts
index e0b2377..cf23f64 100644
--- a/src/goInstallTools.ts
+++ b/src/goInstallTools.ts
@@ -34,7 +34,6 @@
 	getBinPath,
 	getBinPathWithExplanation,
 	getCheckForToolsUpdatesConfig,
-	getCurrentGoPath,
 	getGoVersion,
 	getTempFilePath,
 	getWorkspaceFolderPath,
@@ -44,7 +43,7 @@
 import { correctBinname, envPath, getCurrentGoRoot, setCurrentGoRoot } from './utils/pathUtils';
 import util = require('util');
 import vscode = require('vscode');
-import { isInPreviewMode } from './goLanguageServer';
+import { isInPreviewMode, RestartReason } from './goLanguageServer';
 
 // declinedUpdates tracks the tools that the user has declined to update.
 const declinedUpdates: Tool[] = [];
@@ -174,7 +173,7 @@
 		if (result.reason === '') {
 			// Restart the language server if a new binary has been installed.
 			if (result.tool.name === 'gopls') {
-				restartLanguageServer();
+				restartLanguageServer('installation');
 			}
 		} else {
 			failures.push(result);
diff --git a/src/goLanguageServer.ts b/src/goLanguageServer.ts
index 2698dc1..680dfe3 100644
--- a/src/goLanguageServer.ts
+++ b/src/goLanguageServer.ts
@@ -105,8 +105,37 @@
 let crashCount = 0;
 
 // Some metrics for automated issue reports:
-let manualRestartCount = 0;
-let totalStartCount = 0;
+let restartHistory: Restart[] = [];
+
+export function updateRestartHistory(reason: RestartReason, enabled: boolean) {
+	// Keep the history limited to 10 elements.
+	while (restartHistory.length > 10) {
+		restartHistory = restartHistory.slice(1);
+	}
+	restartHistory.push(new Restart(reason, new Date(), enabled));
+}
+
+function formatRestartHistory(): string {
+	const result: string[] = [];
+	for (const restart of restartHistory) {
+		result.push(`${restart.timestamp.toUTCString()}: ${restart.reason} (enabled: ${restart.enabled})`);
+	}
+	return result.join('\n');
+}
+
+export type RestartReason = 'activation' | 'manual' | 'config change' | 'installation';
+
+class Restart {
+	reason: RestartReason;
+	timestamp: Date;
+	enabled: boolean;
+
+	constructor(reason: RestartReason, timestamp: Date, enabled: boolean) {
+		this.reason = reason;
+		this.timestamp = timestamp;
+		this.enabled = enabled;
+	}
+}
 
 // defaultLanguageProviders is the list of providers currently registered.
 let defaultLanguageProviders: vscode.Disposable[] = [];
@@ -121,7 +150,7 @@
 
 // startLanguageServerWithFallback starts the language server, if enabled,
 // or falls back to the default language providers.
-export async function startLanguageServerWithFallback(ctx: vscode.ExtensionContext, activation: boolean) {
+export async function startLanguageServerWithFallback(ctx: vscode.ExtensionContext, reason: RestartReason) {
 	for (const folder of vscode.workspace.workspaceFolders || []) {
 		switch (folder.uri.scheme) {
 			case 'vsls':
@@ -148,9 +177,11 @@
 	const goConfig = getGoConfig();
 	const cfg = buildLanguageServerConfig(goConfig);
 
+	updateRestartHistory(reason, cfg.enabled);
+
 	// We have some extra prompts for gopls users and for people who have opted
 	// out of gopls.
-	if (activation) {
+	if (reason === 'activation') {
 		scheduleGoplsSuggestions();
 	}
 
@@ -406,9 +437,7 @@
 				"Looks like you're about to manually restart the language server.",
 				errorKind.manualRestart
 			);
-
-			manualRestartCount++;
-			restartLanguageServer();
+			restartLanguageServer('manual');
 		});
 		ctx.subscriptions.push(restartCommand);
 	}
@@ -418,7 +447,6 @@
 	disposeDefaultProviders();
 
 	languageServerDisposable = languageClient.start();
-	totalStartCount++;
 	ctx.subscriptions.push(languageServerDisposable);
 	await languageClient.onReady();
 	return true;
@@ -922,7 +950,7 @@
 		e.affectsConfiguration('go.formatTool')
 		// TODO: Should we check http.proxy too? That affects toolExecutionEnvironment too.
 	) {
-		restartLanguageServer();
+		restartLanguageServer('config change');
 	}
 
 	if (e.affectsConfiguration('go.useLanguageServer') && getGoConfig()['useLanguageServer'] === false) {
@@ -1384,6 +1412,7 @@
 
 Failed to auto-collect gopls trace: ${failureReason}.
 `;
+				const now = new Date();
 
 				const body = `
 gopls version: ${usersGoplsVersion}
@@ -1393,8 +1422,9 @@
 go version: ${goVersion?.format(true)}
 environment: ${extInfo.appName} ${process.platform}
 initialization error: ${initializationError}
-manual restart count: ${manualRestartCount}
-total start count: ${totalStartCount}
+issue timestamp: ${now.toUTCString()}
+restart history:
+${formatRestartHistory()}
 
 ATTENTION: PLEASE PROVIDE THE DETAILS REQUESTED BELOW.
 
diff --git a/src/goMain.ts b/src/goMain.ts
index e465a9a..eddba07 100644
--- a/src/goMain.ts
+++ b/src/goMain.ts
@@ -47,6 +47,7 @@
 import {
 	isInPreviewMode,
 	languageServerIsRunning,
+	RestartReason,
 	showServerOutputChannel,
 	startLanguageServerWithFallback,
 	watchLanguageServerConfiguration
@@ -124,7 +125,7 @@
 // restartLanguageServer wraps all of the logic needed to restart the
 // language server. It can be used to enable, disable, or otherwise change
 // the configuration of the server.
-export let restartLanguageServer = () => {
+export let restartLanguageServer = (reason: RestartReason) => {
 	return;
 };
 
@@ -887,12 +888,12 @@
 	// Set the function that is used to restart the language server.
 	// This is necessary, even if the language server is not currently
 	// in use.
-	restartLanguageServer = async () => {
-		startLanguageServerWithFallback(ctx, false);
+	restartLanguageServer = async (reason: RestartReason) => {
+		startLanguageServerWithFallback(ctx, reason);
 	};
 
 	// Start the language server, or fallback to the default language providers.
-	return startLanguageServerWithFallback(ctx, true);
+	return startLanguageServerWithFallback(ctx, 'activation');
 }
 
 function getCurrentGoPathCommand() {