Skip to content

Commit

Permalink
feat: Instrument dev-server to expose in diagnostics in client
Browse files Browse the repository at this point in the history
  • Loading branch information
dr1rrb committed Jun 17, 2024
1 parent 96d8c63 commit 8d2600b
Show file tree
Hide file tree
Showing 15 changed files with 721 additions and 82 deletions.
35 changes: 35 additions & 0 deletions src/Uno.UI.RemoteControl.Messaging/DevServerDiagnostics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#nullable enable
using System;
using System.Linq;
using System.Threading;
using Uno.UI.RemoteControl.HotReload.Messages;

namespace Uno.UI.RemoteControl;

public static class DevServerDiagnostics
{
private static readonly AsyncLocal<ISink> _current = new();

/// <summary>
/// Gets the current (async-local) sink for dev-server diagnostics.
/// </summary>
public static ISink Current
{
get => _current.Value ?? NullSink.Instance;
set => _current.Value = value;
}

public interface ISink
{
void ReportInvalidFrame<TContent>(Frame frame);
}

private class NullSink : ISink
{
public static NullSink Instance { get; } = new();

public void ReportInvalidFrame<TContent>(Frame frame)
{
}
}
}
2 changes: 1 addition & 1 deletion src/Uno.UI.RemoteControl.Messaging/IRemoteControlServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public interface IRemoteControlServer
{
string GetServerConfiguration(string key);

Task SendFrame(IMessage fileReload);
Task SendFrame(IMessage message);

Task SendMessageToIDEAsync(IdeMessage message);
}
Expand Down
104 changes: 66 additions & 38 deletions src/Uno.UI.RemoteControl.Messaging/Messages/Frame.cs
Original file line number Diff line number Diff line change
@@ -1,60 +1,88 @@
using System;
#nullable enable

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text;
using Newtonsoft.Json;

namespace Uno.UI.RemoteControl.HotReload.Messages
namespace Uno.UI.RemoteControl.HotReload.Messages;

[DebuggerDisplay("{Name}-{Scope}")]
public class Frame
{
[DebuggerDisplay("{Name}-{Scope}")]
public class Frame
public Frame(short version, string scope, string name, string content)
{
public Frame(short version, string scope, string name, string content)
{
Version = version;
Scope = scope;
Name = name;
Content = content;
}
Version = version;
Scope = scope;
Name = name;
Content = content;
}

public int Version { get; }
public int Version { get; }

public string Scope { get; }
public string Scope { get; }

public string Name { get; }
public string Name { get; }

public string Content { get; }
public string Content { get; }

public static Frame Read(Stream stream)
public static Frame Read(Stream stream)
{
using (var reader = new BinaryReader(stream, Encoding.UTF8))
{
using (var reader = new BinaryReader(stream, Encoding.UTF8))
{
var version = reader.ReadInt16();
var scope = reader.ReadString();
var name = reader.ReadString();
var content = reader.ReadString();
var version = reader.ReadInt16();
var scope = reader.ReadString();
var name = reader.ReadString();
var content = reader.ReadString();

return new Frame(version, scope, name, content);
}
return new Frame(version, scope, name, content);
}
}

public static Frame Create<T>(short version, string scope, string name, T content)
=> new Frame(
version,
scope,
name,
JsonConvert.SerializeObject(content)
);
public static Frame Create<T>(short version, string scope, string name, T content)
=> new Frame(
version,
scope,
name,
JsonConvert.SerializeObject(content)
);

public void WriteTo(Stream stream)
{
var writer = new BinaryWriter(stream, Encoding.UTF8);
public T GetContent<T>()
=> TryGetContent<T>(out var content) ? content : throw new InvalidOperationException("Invalid frame");

writer.Write((short)Version);
writer.Write(Scope);
writer.Write(Name);
writer.Write(Content);
public bool TryGetContent<T>([NotNullWhen(true)] out T? content)
{
try
{
content = JsonConvert.DeserializeObject<T>(Content);
if (content is not null)
{
return true;
}
else
{
DevServerDiagnostics.Current.ReportInvalidFrame<T>(this);
return false;
}
}
catch (Exception)
{
DevServerDiagnostics.Current.ReportInvalidFrame<T>(this);
content = default;
return false;
}
}

public void WriteTo(Stream stream)
{
var writer = new BinaryWriter(stream, Encoding.UTF8);

writer.Write((short)Version);
writer.Write(Scope);
writer.Write(Name);
writer.Write(Content);
}
}
28 changes: 28 additions & 0 deletions src/Uno.UI.RemoteControl.Messaging/WellKnownScopes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#nullable enable
using System;
using System.Linq;

namespace Uno.UI.RemoteControl;

public static class WellKnownScopes
{
/// <summary>
/// Reserved for internal usage of communication channel between dev-server and IDE (i.e. KeepAliveIdeMessage).
/// </summary>
public const string IdeChannel = "IdeChannel";

/// <summary>
/// Reserved for internal usage of communication channel between dev-server and client (i.e. KeepAliveMessage).
/// </summary>
public const string DevServerChannel = "RemoteControlServer";

/// <summary>
/// For hot-reload messages, client, server and IDE.
/// </summary>
public const string HotReload = "HotReload";

/// <summary>
/// For messages used for testing purpose (e.g. UpdateFile)
/// </summary>
public const string Testing = "UnoRuntimeTests";
}
9 changes: 9 additions & 0 deletions src/Uno.UI.RemoteControl.Messaging/_Compat/IsExternalInit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#nullable enable
using System;
using System.Linq;

namespace System.Runtime.CompilerServices;

internal static class IsExternalInit
{
}
25 changes: 25 additions & 0 deletions src/Uno.UI.RemoteControl.Messaging/_Compat/NotNullWhenAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#nullable enable
using System;
using System.Linq;

namespace System.Diagnostics.CodeAnalysis;

/// <summary>
/// Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.
/// </summary>
[global::System.AttributeUsage(global::System.AttributeTargets.Parameter, Inherited = false)]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal sealed class NotNullWhenAttribute : global::System.Attribute
{
/// <summary>
/// Initializes the attribute with the specified return value condition.
/// </summary>
/// <param name="returnValue">The return value condition. If the method returns this value, the associated parameter will not be null.</param>
public NotNullWhenAttribute(bool returnValue)
{
ReturnValue = returnValue;
}

/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="..\Uno.UI.RemoteControl\Helpers\VersionHelper.cs" Link="Helpers/%(Filename)" />
<Compile Include="..\Uno.UI.RemoteControl\Messages\**\*.cs" Link="Messages/%(Filename)" />
</ItemGroup>

Expand Down
32 changes: 32 additions & 0 deletions src/Uno.UI.RemoteControl/Helpers/VersionHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Linq;
using System.Reflection;

namespace Uno.UI.RemoteControl.Helpers;

internal class VersionHelper
{
public static string GetVersion(Type type)
=> GetVersion(type.Assembly);

public static string GetVersion(Assembly assembly)
{
if (assembly
.GetCustomAttributesData()
.FirstOrDefault(data => data.AttributeType.Name.Contains("AssemblyInformationalVersion", StringComparison.OrdinalIgnoreCase))
?.ConstructorArguments
.FirstOrDefault()
.Value
?.ToString() is { Length: > 0 } informationalVersion)
{
return informationalVersion;
}

if (assembly.GetName().Version is { } assemblyVersion)
{
return assemblyVersion.ToString();
}

return "--unknown--";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

namespace Uno.UI.RemoteControl.HotReload;

public partial class ClientHotReloadProcessor : IRemoteControlProcessor
public partial class ClientHotReloadProcessor : IClientProcessor
{
private string? _projectPath;
private string[]? _xamlPaths;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Uno.UI.RemoteControl;

internal interface IRemoteControlProcessor
internal interface IClientProcessor
{
string Scope { get; }

Expand Down
33 changes: 23 additions & 10 deletions src/Uno.UI.RemoteControl/Messages/KeepAliveMessage.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
namespace Uno.UI.RemoteControl.Messages
using System;
using Uno.UI.RemoteControl.Helpers;

namespace Uno.UI.RemoteControl.Messages;

public record KeepAliveMessage : IMessage
{
public class KeepAliveMessage : IMessage
{
public const string Name = nameof(KeepAliveMessage);
private static readonly string _localVersion = VersionHelper.GetVersion(typeof(KeepAliveMessage));

public const string Name = nameof(KeepAliveMessage);

public string Scope => WellKnownScopes.DevServerChannel;

string IMessage.Name => Name;

public KeepAliveMessage()
{
}
/// <summary>
/// The version of the dev-server version of the sender.
/// </summary>
public string? AssemblyVersion { get; init; } = _localVersion;

public string Scope => "RemoteControlServer";
/// <summary>
/// Sequence ID of the ping.
/// </summary>
public ulong SequenceId { get; init; }

string IMessage.Name => Name;
}
public KeepAliveMessage Next()
=> this with { SequenceId = SequenceId + 1 };
}
29 changes: 15 additions & 14 deletions src/Uno.UI.RemoteControl/Messages/ProcessorsDiscovery.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
namespace Uno.UI.RemoteControl.Messages
using static Uno.UI.RemoteControl.Messages.ProcessorsDiscoveryResponse;

namespace Uno.UI.RemoteControl.Messages;

public class ProcessorsDiscovery : IMessage
{
public class ProcessorsDiscovery : IMessage
{
public const string Name = nameof(ProcessorsDiscovery);
public const string Name = nameof(ProcessorsDiscovery);

public ProcessorsDiscovery(string basePath, string appInstanceId = "")
{
BasePath = basePath;
AppInstanceId = appInstanceId;
}
public ProcessorsDiscovery(string basePath, string appInstanceId = "")
{
BasePath = basePath;
AppInstanceId = appInstanceId;
}

public string Scope => "RemoteControlServer";
public string Scope => WellKnownScopes.DevServerChannel;

string IMessage.Name => Name;
string IMessage.Name => Name;

public string BasePath { get; }
public string BasePath { get; }

public string AppInstanceId { get; }
}
public string AppInstanceId { get; }
}
16 changes: 16 additions & 0 deletions src/Uno.UI.RemoteControl/Messages/ProcessorsDiscoveryResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Collections.Immutable;
using System.Linq;

namespace Uno.UI.RemoteControl.Messages;

public record ProcessorsDiscoveryResponse(IImmutableList<string> Assemblies, IImmutableList<DiscoveredProcessor> Processors) : IMessage
{
public const string Name = nameof(ProcessorsDiscoveryResponse);

public string Scope => WellKnownScopes.DevServerChannel;

string IMessage.Name => Name;
}

public record DiscoveredProcessor(string AssemblyPath, string Type, string Version, bool IsLoaded, string? LoadError = null);
Loading

0 comments on commit 8d2600b

Please sign in to comment.