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

[Parameter Capturing] Support sending messages to managed in-proc features from dotnet-monitor #4775

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b376574
Add optional payloads to profiler messages
schmittjoseph Jun 28, 2023
c3666a0
Fix send formatting
schmittjoseph Jun 28, 2023
f42cd4b
Remove extra JsonSerializerOptions
schmittjoseph Jun 28, 2023
52f6be2
Cleanup format strings
schmittjoseph Jun 28, 2023
110816e
Cleanup formatting
schmittjoseph Jun 28, 2023
93d1573
Cleanup namespace
schmittjoseph Jun 28, 2023
96971fc
Fix tests
schmittjoseph Jun 28, 2023
c872c0f
Fix variable name
schmittjoseph Jun 28, 2023
2511076
Revert pinning on error
schmittjoseph Jun 28, 2023
d3e5318
Simplify profiler message source ctor
schmittjoseph Jun 28, 2023
7bf3b79
Remove extra null decl
schmittjoseph Jun 28, 2023
f8109fe
Standardize hresult
schmittjoseph Jun 28, 2023
909bfdc
Fix x86 build
schmittjoseph Jun 28, 2023
f1c7d23
Standardize hresult
schmittjoseph Jun 28, 2023
83c4111
Address PR feedback
schmittjoseph Jun 29, 2023
b0bf966
Remove leftover assert
schmittjoseph Jun 29, 2023
050066f
Split payload reads
schmittjoseph Jun 29, 2023
78eccec
Rename test
schmittjoseph Jun 29, 2023
414f7e0
Add comment
schmittjoseph Jun 29, 2023
6f4691b
Remove extra todo
schmittjoseph Jun 29, 2023
feb2d13
Address PR feedback
schmittjoseph Jun 30, 2023
edeef37
Init g_pManagedMessageCallback
schmittjoseph Jun 30, 2023
fd05688
Use ctor for atomic init instead of copy
schmittjoseph Jun 30, 2023
932759b
Remove singleton instance
schmittjoseph Jun 30, 2023
4a29ce8
Cleanup
schmittjoseph Jun 30, 2023
8109f52
Address PR feedback
schmittjoseph Jun 30, 2023
f12c450
Address PR feedback
schmittjoseph Jul 7, 2023
d29f22b
Add comments
schmittjoseph Jul 12, 2023
dcf8afa
Update src/MonitorProfiler/MainProfiler/MainProfiler.cpp
schmittjoseph Jul 12, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,18 @@
</ItemGroup>

<ItemGroup>
<Compile Include="..\Tools\dotnet-monitor\ToolIdentifiers.cs" />
<Compile Include="..\Tools\dotnet-monitor\InProcessFeatures\InProcessFeaturesIdentifiers.cs" Link="InProcessFeaturesIdentifiers.cs" />
<Compile Include="..\Tools\dotnet-monitor\Profiler\ProfilerIdentifiers.cs" />
<Compile Include="..\Tools\dotnet-monitor\DisposableHelper.cs" Link="DisposableHelper.cs" />
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="Microsoft.Diagnostics.Monitoring.HostingStartup.UnitTests" />
<InternalsVisibleTo Include="Microsoft.Diagnostics.Monitoring.UnitTestApp" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Microsoft.Diagnostics.Monitoring.StartupHook\Microsoft.Diagnostics.Monitoring.StartupHook.csproj" />
</ItemGroup>

<ItemGroup>
<Compile Update="ParameterCapturing\ParameterCapturingStrings.Designer.cs">
<DesignTime>True</DesignTime>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Diagnostics.Tools.Monitor;
using Microsoft.Diagnostics.Monitoring.StartupHook;
using Microsoft.Diagnostics.Monitoring.StartupHook.MonitorMessageDispatcher;
using Microsoft.Diagnostics.Tools.Monitor.Profiler;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;

Expand All @@ -29,40 +29,14 @@ private static extern void RequestFunctionProbeInstallation(
private readonly object _requestLocker = new();
private long _disposedState;

private readonly string? _profilerModulePath;

public FunctionProbesManager(IFunctionProbes probes)
{
_profilerModulePath = Environment.GetEnvironmentVariable(ProfilerIdentifiers.EnvironmentVariables.ModulePath);
if (!File.Exists(_profilerModulePath))
{
throw new FileNotFoundException(_profilerModulePath);
}

NativeLibrary.SetDllImportResolver(typeof(ParameterCapturingService).Assembly, ResolveDllImport);
ProfilerResolver.InitializeResolver<FunctionProbesManager>();

RequestFunctionProbeRegistration(FunctionProbesStub.GetProbeFunctionId());
FunctionProbesStub.Instance = probes;
}

private IntPtr ResolveDllImport(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
// DllImport for Windows automatically loads in-memory modules (such as the profiler). This is not the case for Linux/MacOS.
// If we fail resolving the DllImport, we have to load the profiler ourselves.
if (_profilerModulePath == null ||
libraryName != ProfilerIdentifiers.LibraryRootFileName)
{
return IntPtr.Zero;
}

if (NativeLibrary.TryLoad(_profilerModulePath, out IntPtr handle))
{
return handle;
}

return IntPtr.Zero;
}

public void StopCapturing()
{
lock (_requestLocker)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Diagnostics.Monitoring.HostingStartup.ParameterCapturing.FunctionProbes;
using Microsoft.Diagnostics.Tools.Monitor;
using Microsoft.Diagnostics.Monitoring.StartupHook;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@
<Compile Include="..\Tools\dotnet-monitor\Exceptions\ExceptionEvents.cs" Link="Exceptions\Eventing\ExceptionEvents.cs" />
<Compile Include="..\Tools\dotnet-monitor\StartupHook\StartupHookIdentifiers.cs" Link="StartupHookIdentifiers.cs" />
<Compile Include="..\Tools\dotnet-monitor\ToolIdentifiers.cs" Link="ToolIdentifiers.cs" />
<Compile Include="..\Tools\dotnet-monitor\Profiler\ProfilerIdentifiers.cs" />
<Compile Include="..\Microsoft.Diagnostics.Monitoring.WebApi\ProfilerMessage.cs" Link="MonitorMessageDispatcher\ProfilerMessage.cs" />
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="Microsoft.Diagnostics.Monitoring.StartupHook.UnitTests" />
<InternalsVisibleTo Include="Microsoft.Diagnostics.Monitoring.HostingStartup" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

namespace Microsoft.Diagnostics.Monitoring.StartupHook.MonitorMessageDispatcher
{
internal sealed class MonitorMessageArgs : EventArgs
{
public MonitorMessageArgs(IpcCommand command, IntPtr nativeBuffer, long bufferSize)
{
Command = command;
NativeBuffer = nativeBuffer;
BufferSize = bufferSize;
}

public IpcCommand Command { get; private set; }
public IntPtr NativeBuffer { get; private set; }
public long BufferSize { get; private set; }
}

internal interface IMonitorMessageSource : IDisposable
{
public event EventHandler<MonitorMessageArgs> MonitorMessage;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.


using System.IO;
using System;
using System.Text.Json;
using System.Collections.Generic;

namespace Microsoft.Diagnostics.Monitoring.StartupHook.MonitorMessageDispatcher
{
internal sealed class MonitorMessageDispatcher : IDisposable
{
internal struct MessageDispatchEntry
{
public Type DeserializeType { get; set; }
public Action<object> Callback { get; set; }
}

private readonly object _dispatchTableLocker = new();
private readonly Dictionary<IpcCommand, MessageDispatchEntry> _dispatchTable = new();

private readonly IMonitorMessageSource _messageSource;

private long _disposedState;

public MonitorMessageDispatcher(IMonitorMessageSource messageSource)
{
_messageSource = messageSource;
_messageSource.MonitorMessage += OnMessage;
}

public void RegisterCallback<T>(IpcCommand command, Action<T> callback)
{
MessageDispatchEntry dispatchEntry = new()
{
DeserializeType = typeof(T),
Callback = (obj) =>
{
callback((T)obj);
}
};

lock (_dispatchTableLocker)
{
if (!_dispatchTable.TryAdd(command, dispatchEntry))
{
throw new InvalidOperationException("Callback already registered for the requested command.");
}
}
}

public void UnregisterCallback(IpcCommand command)
{
lock (_dispatchTableLocker)
{
_dispatchTable.Remove(command, out _);
}
}

private void OnMessage(object? sender, MonitorMessageArgs args)
{
lock (_dispatchTableLocker)
{
if (!_dispatchTable.TryGetValue(args.Command, out MessageDispatchEntry dispatchEntry))
{
throw new NotSupportedException("Unsupported message type.");
}

object? payload = null;
unsafe
{
using UnmanagedMemoryStream memoryStream = new((byte*)args.NativeBuffer.ToPointer(), args.BufferSize);
// Exceptions thrown during deserialization will be handled by the message source
payload = JsonSerializer.Deserialize(memoryStream, dispatchEntry.DeserializeType);
}

if (payload == null)
{
throw new ArgumentException("Could not deserialize.");
}

dispatchEntry.Callback(payload);
}
}

public void Dispose()
{
if (!DisposableHelper.CanDispose(ref _disposedState))
return;

_messageSource.MonitorMessage -= OnMessage;
_messageSource.Dispose();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.


using System.Runtime.InteropServices;
using System;
using Microsoft.Diagnostics.Tools.Monitor.Profiler;

namespace Microsoft.Diagnostics.Monitoring.StartupHook.MonitorMessageDispatcher
{
internal sealed class ProfilerMessageSource : IMonitorMessageSource
{
public event EventHandler<MonitorMessageArgs>? MonitorMessage;

public delegate int ProfilerMessageCallback(IpcCommand command, IntPtr nativeBuffer, long bufferSize);

[DllImport(ProfilerIdentifiers.LibraryRootFileName, CallingConvention = CallingConvention.StdCall, PreserveSig = false)]
private static extern void RegisterMonitorMessageCallback(ProfilerMessageCallback callback);

[DllImport(ProfilerIdentifiers.LibraryRootFileName, CallingConvention = CallingConvention.StdCall, PreserveSig = false)]
private static extern void UnregisterMonitorMessageCallback();

private long _disposedState;

public ProfilerMessageSource()
{
ProfilerResolver.InitializeResolver<ProfilerMessageSource>();
RegisterMonitorMessageCallback(OnProfilerMessage);
}

private void RaiseMonitorMessage(MonitorMessageArgs e)
{
MonitorMessage?.Invoke(this, e);
}

private int OnProfilerMessage(IpcCommand command, IntPtr nativeBuffer, long bufferSize)
{
try
{
if (bufferSize == 0)
{
throw new ArgumentException(nameof(bufferSize));
}

if (nativeBuffer == IntPtr.Zero)
{
throw new ArgumentException(nameof(nativeBuffer));
}

RaiseMonitorMessage(new MonitorMessageArgs(command, nativeBuffer, bufferSize));
}
catch (Exception ex)
{
return Marshal.GetHRForException(ex);
schmittjoseph marked this conversation as resolved.
Show resolved Hide resolved
}

return 0;
}

public void Dispose()
{
if (!DisposableHelper.CanDispose(ref _disposedState))
return;

try
{
UnregisterMonitorMessageCallback();
}
catch
{

}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.


using System.Runtime.InteropServices;
using System.IO;
using System.Reflection;
using System;
using Microsoft.Diagnostics.Tools.Monitor.Profiler;
using System.Collections.Generic;

namespace Microsoft.Diagnostics.Monitoring.StartupHook
{
internal static class ProfilerResolver
{
private static readonly Lazy<string> s_profilerModulePath = new Lazy<string>(() => Environment.GetEnvironmentVariable(ProfilerIdentifiers.EnvironmentVariables.ModulePath) ?? string.Empty);
private static readonly Lazy<bool> s_profilerModuleExists = new Lazy<bool>(() => File.Exists(s_profilerModulePath.Value));

private static readonly HashSet<Assembly> s_registeredAssemblies = new HashSet<Assembly>();
private static readonly object s_registeredAssembliesLocker = new();

public static void InitializeResolver(Type type)
{
if (!s_profilerModuleExists.Value)
{
throw new FileNotFoundException(s_profilerModulePath.Value);
}

Assembly assembly = type.Assembly;
lock (s_registeredAssembliesLocker)
{
if (!s_registeredAssemblies.Add(assembly))
{
return;
}

NativeLibrary.SetDllImportResolver(assembly, ResolveProfilerDllImport);
}
}

public static void InitializeResolver<T>()
{
InitializeResolver(typeof(T));
}

private static IntPtr ResolveProfilerDllImport(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
// DllImport for Windows automatically loads in-memory modules (such as the profiler). This is not the case for Linux/MacOS.
// If we fail resolving the DllImport, we have to load the profiler ourselves.
if (s_profilerModulePath.Value == null ||
libraryName != ProfilerIdentifiers.LibraryRootFileName)
{
return IntPtr.Zero;
}

if (NativeLibrary.TryLoad(s_profilerModulePath.Value, out IntPtr handle))
{
return handle;
}

return IntPtr.Zero;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Diagnostics.Monitoring.StartupHook.MonitorMessageDispatcher;

namespace Microsoft.Diagnostics.Tools.Monitor.StartupHook
{
internal static class SharedInternals
{
public static MonitorMessageDispatcher? MessageDispatcher { get; set; }
}
}
Loading