提交 465b6512 编写于 作者: H Heejae Chang

support VS telemetry and NFW in service hub

上级 2c38d9ab
......@@ -31,7 +31,8 @@ public static async Task<RemoteHostClient> CreateAsync(Workspace workspace, bool
// make sure connection is done right
var current = $"VS ({Process.GetCurrentProcess().Id})";
var host = await instance._rpc.InvokeAsync<string>(WellKnownRemoteHostServices.RemoteHostService_Connect, current).ConfigureAwait(false);
var telemetrySession = default(string);
var host = await instance._rpc.InvokeAsync<string>(WellKnownRemoteHostServices.RemoteHostService_Connect, current, telemetrySession).ConfigureAwait(false);
// TODO: change this to non fatal watson and make VS to use inproc implementation
Contract.ThrowIfFalse(host == current.ToString());
......
......@@ -12,6 +12,7 @@
using Microsoft.CodeAnalysis.Remote;
using Microsoft.ServiceHub.Client;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.Telemetry;
using Roslyn.Utilities;
using StreamJsonRpc;
......@@ -41,7 +42,7 @@ internal sealed partial class ServiceHubRemoteHostClient : RemoteHostClient
var instance = new ServiceHubRemoteHostClient(workspace, primary, hostGroup, remoteHostStream);
// make sure connection is done right
var host = await instance._rpc.InvokeAsync<string>(WellKnownRemoteHostServices.RemoteHostService_Connect, current).ConfigureAwait(false);
var host = await instance._rpc.InvokeAsync<string>(WellKnownRemoteHostServices.RemoteHostService_Connect, current, TelemetryService.DefaultSession.SerializeSettings()).ConfigureAwait(false);
// TODO: change this to non fatal watson and make VS to use inproc implementation
Contract.ThrowIfFalse(host == current.ToString());
......
......@@ -33,7 +33,7 @@ public void TestRemoteHostConnect()
var remoteHostService = CreateService();
var input = "Test";
var output = remoteHostService.Connect(input);
var output = remoteHostService.Connect(input, serializedSession: null);
Assert.Equal(input, output);
}
......
......@@ -362,5 +362,7 @@ internal enum FunctionId
SolutionSynchronizationService_GetRemotableData,
AssetService_SynchronizeProjectAssetsAsync,
FileTextLoader_FileLengthThresholdExceeded,
RemoteHost_Connect,
RemoteHost_Disconnect,
}
}
......@@ -70,6 +70,9 @@
<Compile Include="Shared\RoslynJsonConverter.SolutionIdConverters.cs" />
<Compile Include="Shared\ServerDirectStream.cs" />
<Compile Include="Shared\ClientDirectStream.cs" />
<Compile Include="Telemetry\VSTelemetryCache.cs" />
<Compile Include="Telemetry\VSTelemetryLogger.cs" />
<Compile Include="Telemetry\WatsonReporter.cs" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.LiveUnitTesting.BuildManager" />
......
......@@ -6,10 +6,13 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Remote.Storage;
using Microsoft.CodeAnalysis.Remote.Telemetry;
using Microsoft.CodeAnalysis.Storage;
using Microsoft.VisualStudio.Telemetry;
using RoslynLogger = Microsoft.CodeAnalysis.Internal.Log.Logger;
namespace Microsoft.CodeAnalysis.Remote
......@@ -25,6 +28,7 @@ internal class RemoteHostService : ServiceHubServiceBase
private const string LoggingFunctionIdTextFileName = "ServiceHubFunctionIds.txt";
private string _host;
private int _primaryInstance;
static RemoteHostService()
{
......@@ -45,15 +49,22 @@ static RemoteHostService()
Rpc.StartListening();
}
public string Connect(string host)
public string Connect(string host, string serializedSession)
{
_primaryInstance = InstanceId;
var existing = Interlocked.CompareExchange(ref _host, host, null);
SetGlobalContext(serializedSession);
if (existing != null && existing != host)
{
LogError($"{host} is given for {existing}");
}
// log telemetry that service hub started
RoslynLogger.Log(FunctionId.RemoteHost_Connect, KeyValueLogMessage.Create(SetSessionInfo));
return _host;
}
......@@ -119,6 +130,41 @@ public async Task SynchronizePrimaryWorkspaceAsync(Checksum checksum)
return _ => false;
}
private void SetSessionInfo(Dictionary<string, object> m)
{
m["Host"] = _host;
m["InstanceId"] = _primaryInstance;
}
private static void SetGlobalContext(string serializedSession)
{
// set global telemetry session
var session = GetTelemetrySession(serializedSession);
if (session == null)
{
return;
}
// set roslyn loggers
VSTelemetryLogger.SetTelemetrySession(session);
RoslynLogger.SetLogger(AggregateLogger.Create(new VSTelemetryLogger(session), RoslynLogger.GetLogger()));
// set both handler as NFW
FatalError.Handler = WatsonReporter.Report;
FatalError.NonFatalHandler = WatsonReporter.Report;
}
private static TelemetrySession GetTelemetrySession(string serializedSession)
{
var session = serializedSession != null ? new TelemetrySession(serializedSession) : null;
// actually starting the session
session?.Start();
return session;
}
#region PersistentStorageService messages
public void PersistentStorageService_RegisterPrimarySolutionId(SolutionId solutionId)
......
......@@ -17,9 +17,10 @@ internal abstract class ServiceHubServiceBase : IDisposable
{
private static int s_instanceId;
private readonly int _instanceId;
private readonly CancellationTokenSource _cancellationTokenSource;
protected readonly int InstanceId;
protected readonly JsonRpc Rpc;
protected readonly TraceSource Logger;
protected readonly AssetStorage AssetStorage;
......@@ -32,7 +33,7 @@ internal abstract class ServiceHubServiceBase : IDisposable
[Obsolete("For backward compatibility. this will be removed once all callers moved to new ctor")]
protected ServiceHubServiceBase(Stream stream, IServiceProvider serviceProvider)
{
_instanceId = Interlocked.Add(ref s_instanceId, 1);
InstanceId = Interlocked.Add(ref s_instanceId, 1);
// in unit test, service provider will return asset storage, otherwise, use the default one
AssetStorage = (AssetStorage)serviceProvider.GetService(typeof(AssetStorage)) ?? AssetStorage.Default;
......@@ -51,7 +52,7 @@ protected ServiceHubServiceBase(Stream stream, IServiceProvider serviceProvider)
protected ServiceHubServiceBase(IServiceProvider serviceProvider, Stream stream)
{
_instanceId = Interlocked.Add(ref s_instanceId, 1);
InstanceId = Interlocked.Add(ref s_instanceId, 1);
// in unit test, service provider will return asset storage, otherwise, use the default one
AssetStorage = (AssetStorage)serviceProvider.GetService(typeof(AssetStorage)) ?? AssetStorage.Default;
......@@ -70,7 +71,7 @@ protected ServiceHubServiceBase(IServiceProvider serviceProvider, Stream stream)
Rpc.Disconnected += OnRpcDisconnected;
}
protected string DebugInstanceString => $"{GetType()} ({_instanceId})";
protected string DebugInstanceString => $"{GetType()} ({InstanceId})";
protected RoslynServices RoslynServices
{
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using Microsoft.CodeAnalysis.Internal.Log;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Remote.Telemetry
{
internal static class VSTelemetryCache
{
public const string EventPrefix = "vs/ide/vbcs/";
public const string PropertyPrefix = "vs.ide.vbcs.";
// these don't have concurrency limit on purpose to reduce chance of lock contention.
// if that becomes a problem - by showing up in our perf investigation, then we will consider adding concurrency limit.
private static readonly ConcurrentDictionary<Key, string> s_eventMap = new ConcurrentDictionary<Key, string>();
private static readonly ConcurrentDictionary<Key, string> s_propertyMap = new ConcurrentDictionary<Key, string>();
public static string GetEventName(this FunctionId functionId, string eventKey = null)
{
return s_eventMap.GetOrAdd(new Key(functionId, eventKey), CreateEventName);
}
public static string GetPropertyName(this FunctionId functionId, string propertyKey)
{
return s_propertyMap.GetOrAdd(new Key(functionId, propertyKey), CreatePropertyName);
}
private static string CreateEventName(Key key)
{
return (EventPrefix + Enum.GetName(typeof(FunctionId), key.FunctionId).Replace('_', '/') + (key.ItemKey == null ? string.Empty : ("/" + key.ItemKey))).ToLowerInvariant();
}
private static string CreatePropertyName(Key key)
{
return (PropertyPrefix + Enum.GetName(typeof(FunctionId), key.FunctionId).Replace('_', '.') + "." + key.ItemKey).ToLowerInvariant();
}
private struct Key : IEquatable<Key>
{
public readonly FunctionId FunctionId;
public readonly string ItemKey;
public Key(FunctionId functionId, string itemKey)
{
this.FunctionId = functionId;
this.ItemKey = itemKey;
}
public override int GetHashCode()
{
return Hash.Combine((int)FunctionId, ItemKey == null ? 0 : ItemKey.GetHashCode());
}
public override bool Equals(object obj)
{
return obj is Key && Equals((Key)obj);
}
public bool Equals(Key key)
{
return this.FunctionId == key.FunctionId && this.ItemKey == key.ItemKey;
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.VisualStudio.Telemetry;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Remote.Telemetry
{
internal class VSTelemetryLogger : ILogger
{
/// <summary>
/// Telemetry session. can be null if it is not available in current context
/// such as in unit test
/// </summary>
private static TelemetrySession s_sessionOpt;
private const string Start = "Start";
private const string End = "End";
private const string BlockId = "BlockId";
private const string Duration = "Duration";
private const string CancellationRequested = "CancellationRequested";
private readonly TelemetrySession _session;
public VSTelemetryLogger(TelemetrySession session)
{
Contract.ThrowIfNull(session);
_session = session;
}
public static TelemetrySession SessionOpt => s_sessionOpt;
public static void SetTelemetrySession(TelemetrySession session)
{
// it can be only set once
Contract.ThrowIfFalse(s_sessionOpt == null);
s_sessionOpt = session;
}
public bool IsEnabled(FunctionId functionId)
{
return true;
}
public void Log(FunctionId functionId, LogMessage logMessage)
{
var kvLogMessage = logMessage as KeyValueLogMessage;
if (kvLogMessage == null)
{
return;
}
try
{
// guard us from exception thrown by telemetry
if (!kvLogMessage.ContainsProperty)
{
_session.PostEvent(functionId.GetEventName());
return;
}
var telemetryEvent = CreateTelemetryEvent(functionId, logMessage: kvLogMessage);
_session.PostEvent(telemetryEvent);
}
catch
{
}
}
public void LogBlockStart(FunctionId functionId, LogMessage logMessage, int blockId, CancellationToken cancellationToken)
{
var kvLogMessage = logMessage as KeyValueLogMessage;
if (kvLogMessage == null)
{
return;
}
try
{
// guard us from exception thrown by telemetry
var telemetryEvent = CreateTelemetryEvent(functionId, Start, kvLogMessage);
SetBlockId(telemetryEvent, functionId, blockId);
_session.PostEvent(telemetryEvent);
}
catch
{
}
}
public void LogBlockEnd(FunctionId functionId, LogMessage logMessage, int blockId, int delta, CancellationToken cancellationToken)
{
var kvLogMessage = logMessage as KeyValueLogMessage;
if (kvLogMessage == null)
{
return;
}
try
{
// guard us from exception thrown by telemetry
var telemetryEvent = CreateTelemetryEvent(functionId, End);
SetBlockId(telemetryEvent, functionId, blockId);
var durationName = functionId.GetPropertyName(Duration);
telemetryEvent.Properties.Add(durationName, delta);
var cancellationName = functionId.GetPropertyName(CancellationRequested);
telemetryEvent.Properties.Add(cancellationName, cancellationToken.IsCancellationRequested);
_session.PostEvent(telemetryEvent);
}
catch
{
}
}
private TelemetryEvent CreateTelemetryEvent(FunctionId functionId, string eventKey = null, KeyValueLogMessage logMessage = null)
{
var eventName = functionId.GetEventName(eventKey);
var telemetryEvent = new TelemetryEvent(eventName);
if (logMessage == null || !logMessage.ContainsProperty)
{
return telemetryEvent;
}
foreach (var kv in logMessage.Properties)
{
var propertyName = functionId.GetPropertyName(kv.Key);
// call SetProperty. VS telemetry will take care of finding correct
// API based on given object type for us.
//
// numeric data will show up in ES with measurement prefix.
telemetryEvent.Properties.Add(propertyName, kv.Value);
}
return telemetryEvent;
}
private void SetBlockId(TelemetryEvent telemetryEvent, FunctionId functionId, int blockId)
{
var blockIdName = functionId.GetPropertyName(BlockId);
telemetryEvent.Properties.Add(blockIdName, blockId);
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.VisualStudio.Telemetry;
namespace Microsoft.CodeAnalysis.Remote.Telemetry
{
internal class WatsonReporter
{
/// <summary>
/// Report Non-Fatal Watson
/// </summary>
/// <param name="exception">Exception that triggered this non-fatal error</param>
public static void Report(Exception exception)
{
Report("Roslyn NonFatal Watson", exception);
}
/// <summary>
/// Report Non-Fatal Watson
/// </summary>
/// <param name="description">any description you want to save with this watson report</param>
/// <param name="exception">Exception that triggered this non-fatal error</param>
public static void Report(string description, Exception exception)
{
VSTelemetryLogger.SessionOpt?.PostFault(
eventName: FunctionId.NonFatalWatson.GetEventName(),
description: description,
exceptionObject: exception);
}
}
}
{
"dependencies": {
"StreamJsonRpc": "1.0.2-rc",
"Newtonsoft.Json": "8.0.3"
"Newtonsoft.Json": "8.0.3",
"Microsoft.VisualStudio.Telemetry": "15.0.26201-alpha"
},
"frameworks": {
"net46": { }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册