diff --git a/source/Container.Manager/Azure/ContainerCountMetricReporter.cs b/source/Container.Manager/Azure/ContainerCountMetricReporter.cs index 4ccf16d2b..e549268e6 100644 --- a/source/Container.Manager/Azure/ContainerCountMetricReporter.cs +++ b/source/Container.Manager/Azure/ContainerCountMetricReporter.cs @@ -8,39 +8,39 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -namespace SharpLab.Container.Manager.Azure { - public class ContainerCountMetricReporter : BackgroundService { - private static readonly string ContainerProcessName = Path.GetFileNameWithoutExtension(Container.Program.ExeFileName); - private static readonly MetricIdentifier ContainerCountMetric = new("Custom Metrics", "Container Count"); +namespace SharpLab.Container.Manager.Azure; - private readonly TelemetryClient _telemetryClient; - private readonly ILogger _logger; +public class ContainerCountMetricReporter : BackgroundService { + private static readonly string ContainerProcessName = Path.GetFileNameWithoutExtension(Container.Program.ExeFileName); + private static readonly MetricIdentifier ContainerCountMetric = new("Custom Metrics", "Container Count"); - public ContainerCountMetricReporter( - TelemetryClient telemetryClient, - ILogger logger - ) { - _telemetryClient = telemetryClient; - _logger = logger; - } + private readonly TelemetryClient _telemetryClient; + private readonly ILogger _logger; - protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - while (!stoppingToken.IsCancellationRequested) { - try { - var count = 0; - foreach (var process in Process.GetProcessesByName(ContainerProcessName)) { - count += 1; - process.Dispose(); - } + public ContainerCountMetricReporter( + TelemetryClient telemetryClient, + ILogger logger + ) { + _telemetryClient = telemetryClient; + _logger = logger; + } - _telemetryClient.GetMetric(ContainerCountMetric).TrackValue(count); - } - catch (Exception ex) { - _logger.LogError(ex, "Failed to report container count"); - await Task.Delay(TimeSpan.FromMinutes(4), stoppingToken); + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { + while (!stoppingToken.IsCancellationRequested) { + try { + var count = 0; + foreach (var process in Process.GetProcessesByName(ContainerProcessName)) { + count += 1; + process.Dispose(); } - await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); + + _telemetryClient.GetMetric(ContainerCountMetric).TrackValue(count); + } + catch (Exception ex) { + _logger.LogError(ex, "Failed to report container count"); + await Task.Delay(TimeSpan.FromMinutes(4), stoppingToken); } + await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); } } } diff --git a/source/Container.Manager/Startup.cs b/source/Container.Manager/Startup.cs index b5c704fab..66a0b8906 100644 --- a/source/Container.Manager/Startup.cs +++ b/source/Container.Manager/Startup.cs @@ -9,67 +9,67 @@ using SharpLab.Container.Manager.Endpoints; using SharpLab.Container.Manager.Internal; -namespace SharpLab.Container.Manager { - [SupportedOSPlatform("windows")] - public class Startup { - // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 +namespace SharpLab.Container.Manager; - public void ConfigureServices(IServiceCollection services) - { - // TODO: proper DI, e.g. Autofac - services.AddSingleton(new ProcessRunnerConfiguration( - workingDirectoryPath: AppContext.BaseDirectory, - exeFileName: Container.Program.ExeFileName, - essentialAccessCapabilitySid: "S-1-15-3-1024-4233803318-1181731508-1220533431-3050556506-2713139869-1168708946-594703785-1824610955", - maximumMemorySize: 30 * 1024 * 1024, - maximumCpuPercentage: 1 - )); - services.AddSingleton(); - services.AddSingleton(); +[SupportedOSPlatform("windows")] +public class Startup { + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - services.AddSingleton(); + public void ConfigureServices(IServiceCollection services) + { + // TODO: proper DI, e.g. Autofac + services.AddSingleton(new ProcessRunnerConfiguration( + workingDirectoryPath: AppContext.BaseDirectory, + exeFileName: Container.Program.ExeFileName, + essentialAccessCapabilitySid: "S-1-15-3-1024-4233803318-1181731508-1220533431-3050556506-2713139869-1168708946-594703785-1824610955", + maximumMemorySize: 30 * 1024 * 1024, + maximumCpuPercentage: 1 + )); + services.AddSingleton(); + services.AddSingleton(); - var authorizationToken = Environment.GetEnvironmentVariable("SHARPLAB_CONTAINER_HOST_AUTHORIZATION_TOKEN") - ?? throw new Exception("Required environment variable SHARPLAB_CONTAINER_HOST_AUTHORIZATION_TOKEN was not provided."); - services.AddSingleton(new ExecutionEndpointSettings(authorizationToken)); - services.AddSingleton(); + services.AddSingleton(); - services.AddSingleton(); + var authorizationToken = Environment.GetEnvironmentVariable("SHARPLAB_CONTAINER_HOST_AUTHORIZATION_TOKEN") + ?? throw new Exception("Required environment variable SHARPLAB_CONTAINER_HOST_AUTHORIZATION_TOKEN was not provided."); + services.AddSingleton(new ExecutionEndpointSettings(authorizationToken)); + services.AddSingleton(); - services.AddHostedService(); - services.AddSingleton(); - services.AddHostedService(c => c.GetRequiredService()); + services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddHostedService(); + services.AddSingleton(); + services.AddHostedService(c => c.GetRequiredService()); - ConfigureAzureDependentServices(services); - } + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); - private void ConfigureAzureDependentServices(IServiceCollection services) { - var connectionString = Environment.GetEnvironmentVariable("SHARPLAB_TELEMETRY_CONNECTION_STRING"); - if (connectionString == null) { - Console.WriteLine("[WARN] AppInsights connection string was not found."); - return; - } + ConfigureAzureDependentServices(services); + } - var configuration = new TelemetryConfiguration { ConnectionString = connectionString }; - services.AddSingleton(new TelemetryClient(configuration)); - services.AddHostedService(); + private void ConfigureAzureDependentServices(IServiceCollection services) { + var connectionString = Environment.GetEnvironmentVariable("SHARPLAB_TELEMETRY_CONNECTION_STRING"); + if (connectionString == null) { + Console.WriteLine("[WARN] AppInsights connection string was not found."); + return; } - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app) { - app.UseRouting(); + var configuration = new TelemetryConfiguration { ConnectionString = connectionString }; + services.AddSingleton(new TelemetryClient(configuration)); + services.AddHostedService(); + } - app.UseEndpoints(endpoints => { - endpoints.MapGet("/status", app.ApplicationServices.GetRequiredService().ExecuteAsync); - endpoints.MapPost("/", app.ApplicationServices.GetRequiredService().ExecuteAsync); - }); - } + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app) { + app.UseRouting(); + + app.UseEndpoints(endpoints => { + endpoints.MapGet("/status", app.ApplicationServices.GetRequiredService().ExecuteAsync); + endpoints.MapPost("/", app.ApplicationServices.GetRequiredService().ExecuteAsync); + }); } } diff --git a/source/NetFramework/Server/Common/CommonModule.cs b/source/NetFramework/Server/Common/CommonModule.cs index 393de9a61..22b22e166 100644 --- a/source/NetFramework/Server/Common/CommonModule.cs +++ b/source/NetFramework/Server/Common/CommonModule.cs @@ -7,56 +7,56 @@ using SharpLab.Server.Common.Internal; using SharpLab.Server.Common.Languages; -namespace SharpLab.Server.Common { - [UsedImplicitly] - public class CommonModule : Module { - protected override void Load(ContainerBuilder builder) { - RegisterExternals(builder); - - builder.RegisterType() - .As() - .SingleInstance(); - - builder.RegisterType() - .As() - .As() - .SingleInstance(); - - builder.RegisterType() - .As() - .SingleInstance(); - - builder.RegisterType() - .As() - .SingleInstance(); - - builder.RegisterType() - .As() - .SingleInstance(); - - builder.RegisterType() - .As() - .SingleInstance(); - - var webAppName = EnvironmentHelper.GetRequiredEnvironmentVariable("SHARPLAB_WEBAPP_NAME"); - builder.RegisterType() - .As() - .SingleInstance() - .WithParameter("webAppName", webAppName); - } - - private void RegisterExternals(ContainerBuilder builder) { - builder.RegisterInstance(new RecyclableMemoryStreamManager()) - .AsSelf(); - - builder.RegisterInstance>(() => new HttpClient()) - .As>() - .SingleInstance() - .PreserveExistingDefaults(); // allows tests and other overrides - - builder.RegisterType() - .As() - .SingleInstance(); - } +namespace SharpLab.Server.Common; + +[UsedImplicitly] +public class CommonModule : Module { + protected override void Load(ContainerBuilder builder) { + RegisterExternals(builder); + + builder.RegisterType() + .As() + .SingleInstance(); + + builder.RegisterType() + .As() + .As() + .SingleInstance(); + + builder.RegisterType() + .As() + .SingleInstance(); + + builder.RegisterType() + .As() + .SingleInstance(); + + builder.RegisterType() + .As() + .SingleInstance(); + + builder.RegisterType() + .As() + .SingleInstance(); + + var webAppName = EnvironmentHelper.GetRequiredEnvironmentVariable("SHARPLAB_WEBAPP_NAME"); + builder.RegisterType() + .As() + .SingleInstance() + .WithParameter("webAppName", webAppName); + } + + private void RegisterExternals(ContainerBuilder builder) { + builder.RegisterInstance(new RecyclableMemoryStreamManager()) + .AsSelf(); + + builder.RegisterInstance>(() => new HttpClient()) + .As>() + .SingleInstance() + .PreserveExistingDefaults(); // allows tests and other overrides + + builder.RegisterType() + .As() + .SingleInstance(); } } diff --git a/source/NetFramework/Server/Common/FeatureTracker.cs b/source/NetFramework/Server/Common/FeatureTracker.cs index 9c615bc18..f59ec2868 100644 --- a/source/NetFramework/Server/Common/FeatureTracker.cs +++ b/source/NetFramework/Server/Common/FeatureTracker.cs @@ -1,51 +1,35 @@ using SharpLab.Server.Monitoring; -using System.Collections.Generic; -using System.Linq; namespace SharpLab.Server.Common; public class FeatureTracker : IFeatureTracker { - private readonly IMetricMonitor _branchMonitor; - private readonly IReadOnlyDictionary _languageMetricMonitors; - private readonly IReadOnlyDictionary _targetMetricMonitors; - private readonly IMetricMonitor _optimizeDebugMonitor; - private readonly IMetricMonitor _optimizeReleaseMonitor; + private readonly string _webAppName; + private readonly IOneDimensionMetricMonitor _branchMetricMonitor; + private readonly IOneDimensionMetricMonitor _languageMetricMonitor; + private readonly IOneDimensionMetricMonitor _targetMetricMonitor; + private readonly IOneDimensionMetricMonitor _optimizeMetricMonitor; public FeatureTracker(IMonitor monitor, string webAppName) { - _branchMonitor = monitor.MetricSlow("feature", $"Branch: {webAppName}"); - _languageMetricMonitors = LanguageNames.All.ToDictionary( - name => name, - name => monitor.MetricSlow("feature", $"Language: {name}") - ); - _targetMetricMonitors = TargetNames.All.ToDictionary( - name => name, - name => monitor.MetricSlow("feature", $"Target: {name}") - ); - - _optimizeDebugMonitor = monitor.MetricSlow("feature", "Optimize: Debug"); - _optimizeReleaseMonitor = monitor.MetricSlow("feature", "Optimize: Release"); + _webAppName = webAppName; + _branchMetricMonitor = monitor.MetricSlow("feature", "Branch", "Branch"); + _languageMetricMonitor = monitor.MetricSlow("feature", "Language", "Language"); + _targetMetricMonitor = monitor.MetricSlow("feature", "Target", "Target"); + _optimizeMetricMonitor = monitor.MetricSlow("feature", "Optimize", "Optimize"); } public void TrackBranch() { - _branchMonitor.Track(1); + _branchMetricMonitor.Track(_webAppName, 1); } public void TrackLanguage(string languageName) { - if (_languageMetricMonitors.TryGetValue(languageName, out var metricMonitor)) - metricMonitor.Track(1); + _languageMetricMonitor.Track(languageName, 1); } public void TrackTarget(string targetName) { - if (_targetMetricMonitors.TryGetValue(targetName, out var metricMonitor)) - metricMonitor.Track(1); + _targetMetricMonitor.Track(targetName, 1); } - public void TrackOptimize(string? optimize) { - var monitor = optimize switch { - Optimize.Debug => _optimizeDebugMonitor, - Optimize.Release => _optimizeReleaseMonitor, - _ => null - }; - monitor?.Track(1); + public void TrackOptimize(string optimize) { + _optimizeMetricMonitor.Track(optimize, 1); } } diff --git a/source/NetFramework/Server/Common/IFeatureTracker.cs b/source/NetFramework/Server/Common/IFeatureTracker.cs index c1e4053fb..373ce9b7c 100644 --- a/source/NetFramework/Server/Common/IFeatureTracker.cs +++ b/source/NetFramework/Server/Common/IFeatureTracker.cs @@ -4,5 +4,5 @@ public interface IFeatureTracker { void TrackBranch(); void TrackLanguage(string languageName); void TrackTarget(string targetName); - void TrackOptimize(string? optimize); + void TrackOptimize(string optimize); } \ No newline at end of file diff --git a/source/NetFramework/Server/Common/LanguageNames.cs b/source/NetFramework/Server/Common/LanguageNames.cs index 6b07860f5..54ed93db4 100644 --- a/source/NetFramework/Server/Common/LanguageNames.cs +++ b/source/NetFramework/Server/Common/LanguageNames.cs @@ -1,4 +1,3 @@ -using System.Collections.Immutable; using CodeAnalysis = Microsoft.CodeAnalysis; namespace SharpLab.Server.Common; @@ -8,8 +7,4 @@ public class LanguageNames { public const string VisualBasic = CodeAnalysis.LanguageNames.VisualBasic; public const string FSharp = CodeAnalysis.LanguageNames.FSharp; public const string IL = "IL"; - - public static readonly ImmutableArray All = ImmutableArray.Create( - CSharp, VisualBasic, FSharp, IL - ); } diff --git a/source/NetFramework/Server/Common/TargetNames.cs b/source/NetFramework/Server/Common/TargetNames.cs index 3bc32fd02..40dfd46c8 100644 --- a/source/NetFramework/Server/Common/TargetNames.cs +++ b/source/NetFramework/Server/Common/TargetNames.cs @@ -1,5 +1,3 @@ -using System.Collections.Immutable; - namespace SharpLab.Server.Common; public static class TargetNames { @@ -10,8 +8,4 @@ public static class TargetNames { public const string Run = "Run"; public const string Verify = "Verify"; public const string Explain = "Explain"; - - public static readonly ImmutableArray All = ImmutableArray.Create( - CSharp, IL, Ast, JitAsm, Run, Verify, Explain - ); } \ No newline at end of file diff --git a/source/NetFramework/Server/Integration/Azure/ApplicationInsightsMetricMonitor.cs b/source/NetFramework/Server/Integration/Azure/ApplicationInsightsMetricMonitor.cs index b0100a6e9..2ca9d175a 100644 --- a/source/NetFramework/Server/Integration/Azure/ApplicationInsightsMetricMonitor.cs +++ b/source/NetFramework/Server/Integration/Azure/ApplicationInsightsMetricMonitor.cs @@ -3,7 +3,7 @@ namespace SharpLab.Server.Integration.Azure; -public class ApplicationInsightsMetricMonitor : IMetricMonitor { +public class ApplicationInsightsMetricMonitor : IZeroDimensionMetricMonitor, IOneDimensionMetricMonitor { private readonly Metric _metric; public ApplicationInsightsMetricMonitor(Metric metric) { @@ -15,4 +15,8 @@ public ApplicationInsightsMetricMonitor(Metric metric) { public void Track(double value) { _metric.TrackValue(value); } + + public void Track(string dimension, double value) { + _metric.TrackValue(value, dimension); + } } \ No newline at end of file diff --git a/source/NetFramework/Server/Integration/Azure/ApplicationInsightsMonitor.cs b/source/NetFramework/Server/Integration/Azure/ApplicationInsightsMonitor.cs index 79f6df85f..7bc1fb8a3 100644 --- a/source/NetFramework/Server/Integration/Azure/ApplicationInsightsMonitor.cs +++ b/source/NetFramework/Server/Integration/Azure/ApplicationInsightsMonitor.cs @@ -23,8 +23,14 @@ public ApplicationInsightsMonitor(TelemetryClient client, string webAppName, Fun _createMetricMonitor = Argument.NotNull(nameof(createMetricMonitor), createMetricMonitor); } - public IMetricMonitor MetricSlow(string @namespace, string name) { - var metric = _client.GetMetric(new MetricIdentifier(@namespace, name)); + public IZeroDimensionMetricMonitor MetricSlow(string @namespace, string name) + => MetricSlowInternal(new (@namespace, name)); + + public IOneDimensionMetricMonitor MetricSlow(string @namespace, string name, string dimension) + => MetricSlowInternal(new (@namespace, name, dimension)); + + private ApplicationInsightsMetricMonitor MetricSlowInternal(MetricIdentifier identifier) { + var metric = _client.GetMetric(identifier); return _createMetricMonitor(metric); } diff --git a/source/NetFramework/Server/Monitoring/DefaultTraceMetricMonitor.cs b/source/NetFramework/Server/Monitoring/DefaultTraceMetricMonitor.cs index 93232c6ce..fb3631d49 100644 --- a/source/NetFramework/Server/Monitoring/DefaultTraceMetricMonitor.cs +++ b/source/NetFramework/Server/Monitoring/DefaultTraceMetricMonitor.cs @@ -2,7 +2,7 @@ namespace SharpLab.Server.Monitoring; -public class DefaultTraceMetricMonitor : IMetricMonitor { +public class DefaultTraceMetricMonitor : IZeroDimensionMetricMonitor, IOneDimensionMetricMonitor { private readonly string _namespace; private readonly string _name; @@ -17,4 +17,8 @@ public DefaultTraceMetricMonitor(string @namespace, string name) { public void Track(double value) { Trace.TraceInformation("Metric {0} {1}: {2}.", _namespace, _name, value); } + + public void Track(string dimension, double value) { + Trace.TraceInformation("Metric {0} {1}: {2} {3}.", _namespace, _name, dimension, value); + } } diff --git a/source/NetFramework/Server/Monitoring/DefaultTraceMonitor.cs b/source/NetFramework/Server/Monitoring/DefaultTraceMonitor.cs index 3ac36dab8..8276ec652 100644 --- a/source/NetFramework/Server/Monitoring/DefaultTraceMonitor.cs +++ b/source/NetFramework/Server/Monitoring/DefaultTraceMonitor.cs @@ -15,7 +15,11 @@ public DefaultTraceMonitor( _createMetricMonitor = createMetricMonitor; } - public IMetricMonitor MetricSlow(string @namespace, string name) { + public IZeroDimensionMetricMonitor MetricSlow(string @namespace, string name) { + return _createMetricMonitor((@namespace, name)); + } + + public IOneDimensionMetricMonitor MetricSlow(string @namespace, string name, string dimension) { return _createMetricMonitor((@namespace, name)); } diff --git a/source/NetFramework/Server/Monitoring/IMetricMonitor.cs b/source/NetFramework/Server/Monitoring/IMetricMonitor.cs deleted file mode 100644 index 750559645..000000000 --- a/source/NetFramework/Server/Monitoring/IMetricMonitor.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace SharpLab.Server.Monitoring; - -public interface IMetricMonitor { - void Track(double value); -} diff --git a/source/NetFramework/Server/Monitoring/IMonitor.cs b/source/NetFramework/Server/Monitoring/IMonitor.cs index 27ba5d559..77262ec5d 100644 --- a/source/NetFramework/Server/Monitoring/IMonitor.cs +++ b/source/NetFramework/Server/Monitoring/IMonitor.cs @@ -4,7 +4,8 @@ namespace SharpLab.Server.Monitoring; public interface IMonitor { - IMetricMonitor MetricSlow(string @namespace, string name); + IZeroDimensionMetricMonitor MetricSlow(string @namespace, string name); + IOneDimensionMetricMonitor MetricSlow(string @namespace, string name, string dimension); void Event(string eventName, IWorkSession? session, IDictionary? extras = null); void Exception(Exception exception, IWorkSession? session, IDictionary? extras = null); } diff --git a/source/NetFramework/Server/Monitoring/IOneDimensionMetricMonitor.cs b/source/NetFramework/Server/Monitoring/IOneDimensionMetricMonitor.cs new file mode 100644 index 000000000..1d72a3333 --- /dev/null +++ b/source/NetFramework/Server/Monitoring/IOneDimensionMetricMonitor.cs @@ -0,0 +1,5 @@ +namespace SharpLab.Server.Monitoring; + +public interface IOneDimensionMetricMonitor { + void Track(string dimension, double value); +} diff --git a/source/NetFramework/Server/Monitoring/IZeroDimensionMetricMonitor.cs b/source/NetFramework/Server/Monitoring/IZeroDimensionMetricMonitor.cs new file mode 100644 index 000000000..2aac8df2f --- /dev/null +++ b/source/NetFramework/Server/Monitoring/IZeroDimensionMetricMonitor.cs @@ -0,0 +1,5 @@ +namespace SharpLab.Server.Monitoring; + +public interface IZeroDimensionMetricMonitor { + void Track(double value); +} diff --git a/source/NetFramework/Server/StartupHelper.cs b/source/NetFramework/Server/StartupHelper.cs index 02b1e5be5..0867478ef 100644 --- a/source/NetFramework/Server/StartupHelper.cs +++ b/source/NetFramework/Server/StartupHelper.cs @@ -8,43 +8,43 @@ using MirrorSharp.Owin; using SharpLab.Server.Common; -namespace SharpLab.Server { - public static class StartupHelper { - // Chrome would limit to 10 mins I believe - public static readonly TimeSpan CorsPreflightMaxAge = TimeSpan.FromHours(1); - - public static void ConfigureContainer(ContainerBuilder builder) { - var assembly = Assembly.GetExecutingAssembly(); +namespace SharpLab.Server; - builder - .RegisterAssemblyModulesInDirectoryOf(assembly) - .WhereFileMatches("SharpLab.*"); - } +public static class StartupHelper { + // Chrome would limit to 10 mins I believe + public static readonly TimeSpan CorsPreflightMaxAge = TimeSpan.FromHours(1); + + public static void ConfigureContainer(ContainerBuilder builder) { + var assembly = Assembly.GetExecutingAssembly(); + + builder + .RegisterAssemblyModulesInDirectoryOf(assembly) + .WhereFileMatches("SharpLab.*"); + } - public static MirrorSharpOptions CreateMirrorSharpOptions(ILifetimeScope container) { - var options = new MirrorSharpOptions { - IncludeExceptionDetails = true, - StatusTestCommands = { - ('O', "x-optimize=debug,x-target=C#,x-no-cache=true,language=C#"), - ('R', "0:0:0::using System; public class C { public void M() { } }"), - ('U', "") - } - }; - var languages = container.Resolve(); - foreach (var language in languages) { - language.SlowSetup(options); + public static MirrorSharpOptions CreateMirrorSharpOptions(ILifetimeScope container) { + var options = new MirrorSharpOptions { + IncludeExceptionDetails = true, + StatusTestCommands = { + ('O', "x-optimize=debug,x-target=C#,x-no-cache=true,language=C#"), + ('R', "0:0:0::using System; public class C { public void M() { } }"), + ('U', "") } - return options; + }; + var languages = container.Resolve(); + foreach (var language in languages) { + language.SlowSetup(options); } + return options; + } - public static MirrorSharpServices CreateMirrorSharpServices(ILifetimeScope container) { - return new MirrorSharpServices { - SetOptionsFromClient = container.Resolve(), - SlowUpdate = container.Resolve(), - RoslynSourceTextGuard = container.Resolve(), - RoslynCompilationGuard = container.Resolve(), - ExceptionLogger = container.Resolve() - }; - } + public static MirrorSharpServices CreateMirrorSharpServices(ILifetimeScope container) { + return new MirrorSharpServices { + SetOptionsFromClient = container.Resolve(), + SlowUpdate = container.Resolve(), + RoslynSourceTextGuard = container.Resolve(), + RoslynCompilationGuard = container.Resolve(), + ExceptionLogger = container.Resolve() + }; } } diff --git a/source/NetFramework/Tests/Common/Unit/LanguageNamesTests.cs b/source/NetFramework/Tests/Common/Unit/LanguageNamesTests.cs deleted file mode 100644 index abca419fe..000000000 --- a/source/NetFramework/Tests/Common/Unit/LanguageNamesTests.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Xunit; -using SharpLab.Server.Common; -using System.Linq; -using System.Reflection; - -namespace SharpLab.Tests.Common.Unit; - -public class LanguageNamesTests { - [Fact] - public void All_IncludesAllConstants() { - // Arrange - var constants = typeof(LanguageNames) - .GetFields(BindingFlags.Static | BindingFlags.Public) - .Where(f => f.IsLiteral) - .Select(f => (string)f.GetRawConstantValue()!); - - // Act - var all = LanguageNames.All; - - // Assert - Assert.Equal(constants, all); - } -} diff --git a/source/NetFramework/Tests/Common/Unit/TargetNamesTests.cs b/source/NetFramework/Tests/Common/Unit/TargetNamesTests.cs deleted file mode 100644 index cf4aad67b..000000000 --- a/source/NetFramework/Tests/Common/Unit/TargetNamesTests.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Xunit; -using SharpLab.Server.Common; -using System.Linq; -using System.Reflection; - -namespace SharpLab.Tests.Common.Unit; - -public class TargetNamesTests { - [Fact] - public void All_IncludesAllConstants() { - // Arrange - var constants = typeof(TargetNames) - .GetFields(BindingFlags.Static | BindingFlags.Public) - .Where(f => f.IsLiteral) - .Select(f => (string)f.GetRawConstantValue()!); - - // Act - var all = TargetNames.All; - - // Assert - Assert.Equal(constants, all); - } -} diff --git a/source/NetFramework/Tests/ExecutionTests.cs b/source/NetFramework/Tests/ExecutionTests.cs index dc58b415e..773678627 100644 --- a/source/NetFramework/Tests/ExecutionTests.cs +++ b/source/NetFramework/Tests/ExecutionTests.cs @@ -21,124 +21,125 @@ using SharpLab.Server.Common; using SharpLab.Tests.Internal; -namespace SharpLab.Tests { - public class ExecutionTests { - private readonly ITestOutputHelper _output; - - public ExecutionTests(ITestOutputHelper output) { - _output = output; - - #if DEBUG - var testName = ((ITest) - _output - .GetType() - .GetField("test", BindingFlags.Instance | BindingFlags.NonPublic)! - .GetValue(_output)! - ).DisplayName.Replace(GetType().FullName + ".", ""); - var safeTestName = Regex.Replace(testName, "[^a-zA-Z._-]+", "_"); - if (safeTestName.Length > 100) - safeTestName = safeTestName.Substring(0, 100) + "-" + safeTestName.GetHashCode(); - - var testPath = Path.Combine( - AppContext.BaseDirectory, "assembly-log", - GetType().Name, safeTestName, - "{0}.dll" - ); - //AssemblyLog.Enable(testPath); - #endif - } +namespace SharpLab.Tests; + +public class ExecutionTests { + private readonly ITestOutputHelper _output; + + public ExecutionTests(ITestOutputHelper output) { + _output = output; + + #if DEBUG + var testName = ((ITest) + _output + .GetType() + .GetField("test", BindingFlags.Instance | BindingFlags.NonPublic)! + .GetValue(_output)! + ).DisplayName.Replace(GetType().FullName + ".", ""); + var safeTestName = Regex.Replace(testName, "[^a-zA-Z._-]+", "_"); + if (safeTestName.Length > 100) + safeTestName = safeTestName.Substring(0, 100) + "-" + safeTestName.GetHashCode(); + + var testPath = Path.Combine( + AppContext.BaseDirectory, "assembly-log", + GetType().Name, safeTestName, + "{0}.dll" + ); + //AssemblyLog.Enable(testPath); + #endif + } - [Theory] - [InlineData("Exception.DivideByZero.cs", 4, "DivideByZeroException")] - [InlineData("Exception.DivideByZero.Catch.cs", 5, "DivideByZeroException")] - [InlineData("Exception.DivideByZero.Catch.When.True.cs", 5, "DivideByZeroException")] - [InlineData("Exception.DivideByZero.Catch.When.False.cs", 5, "DivideByZeroException")] - [InlineData("Exception.DivideByZero.Finally.cs", 5, "DivideByZeroException")] - [InlineData("Exception.DivideByZero.Catch.Finally.cs", 5, "DivideByZeroException")] - [InlineData("Exception.DivideByZero.Catch.Finally.WriteLine.cs", 5, "DivideByZeroException", Optimize.Debug)] - [InlineData("Exception.DivideByZero.Catch.Finally.WriteLine.cs", 5, "DivideByZeroException", Optimize.Release)] - [InlineData("Exception.Throw.New.Finally.cs", 8, "Exception", Optimize.Debug)] - public async Task SlowUpdate_ReportsExceptionInFlow(string resourceName, int expectedLineNumber, string expectedExceptionTypeName, string optimize = Optimize.Debug) { - var driver = await NewTestDriverAsync(LoadCodeFromResource(resourceName), optimize: optimize); - - var result = await driver.SendSlowUpdateAsync(); - var steps = result.ExtensionResult!.Flow - .Select(s => new { s.Line, s.Exception }) - .ToArray(); - - AssertIsSuccess(result, allowRuntimeException: true); - Assert.Contains(new { Line = expectedLineNumber, Exception = (string?)expectedExceptionTypeName }, steps); - } + [Theory] + [InlineData("Exception.DivideByZero.cs", 4, "DivideByZeroException")] + [InlineData("Exception.DivideByZero.Catch.cs", 5, "DivideByZeroException")] + [InlineData("Exception.DivideByZero.Catch.When.True.cs", 5, "DivideByZeroException")] + [InlineData("Exception.DivideByZero.Catch.When.False.cs", 5, "DivideByZeroException")] + [InlineData("Exception.DivideByZero.Finally.cs", 5, "DivideByZeroException")] + [InlineData("Exception.DivideByZero.Catch.Finally.cs", 5, "DivideByZeroException")] + [InlineData("Exception.DivideByZero.Catch.Finally.WriteLine.cs", 5, "DivideByZeroException", Optimize.Debug)] + [InlineData("Exception.DivideByZero.Catch.Finally.WriteLine.cs", 5, "DivideByZeroException", Optimize.Release)] + [InlineData("Exception.Throw.New.Finally.cs", 8, "Exception", Optimize.Debug)] + public async Task SlowUpdate_ReportsExceptionInFlow(string resourceName, int expectedLineNumber, string expectedExceptionTypeName, string optimize = Optimize.Debug) { + var driver = await NewTestDriverAsync(LoadCodeFromResource(resourceName), optimize: optimize); + + var result = await driver.SendSlowUpdateAsync(); + var steps = result.ExtensionResult!.Flow + .Select(s => new { s.Line, s.Exception }) + .ToArray(); + + AssertIsSuccess(result, allowRuntimeException: true); + Assert.Contains(new { Line = expectedLineNumber, Exception = (string?)expectedExceptionTypeName }, steps); + } - [Theory] - [InlineData("Notes.Variable.AssignCall.cs")] - [InlineData("Notes.Variable.ManyVariables.cs")] - [InlineData("Notes.Return.Simple.cs")] - [InlineData("Notes.Return.Ref.cs")] - [InlineData("Notes.Return.Ref.Readonly.cs")] - [InlineData("Notes.Loop.For.10Iterations.cs")] - [InlineData("Notes.Variable.MultipleDeclarationsOnTheSameLine.cs")] - [InlineData("Notes.Variable.LongName.cs")] - [InlineData("Notes.Variable.LongValue.cs")] - [InlineData("Notes.Regression.ToStringNull.cs")] // https://github.com/ashmind/SharpLab/issues/380 - public async Task SlowUpdate_ReportsValueNotes(string resourceName) { - var code = LoadCodeFromResource(resourceName); - var expected = code.Split("\r\n").Select((line, index) => new { - Line = index + 1, - Notes = Regex.Match(line, @"//\s+\[(.+)\]\s*$").Groups[1].Value - }).Where(x => !string.IsNullOrEmpty(x.Notes)).ToArray(); - - var driver = await NewTestDriverAsync(code); - - var result = await driver.SendSlowUpdateAsync(); - var steps = result.ExtensionResult?.Flow - .Select(s => new { s.Line, s.Notes }) - .Where(s => !string.IsNullOrEmpty(s.Notes)) - .GroupBy(s => s.Line) - .Select(g => new { Line = g.Key, Notes = string.Join("; ", g.Select(s => s.Notes)) }) - .ToArray(); - - AssertIsSuccess(result); - Assert.Equal(expected, steps); - } + [Theory] + [InlineData("Notes.Variable.AssignCall.cs")] + [InlineData("Notes.Variable.ManyVariables.cs")] + [InlineData("Notes.Return.Simple.cs")] + [InlineData("Notes.Return.Ref.cs")] + [InlineData("Notes.Return.Ref.Readonly.cs")] + [InlineData("Notes.Loop.For.10Iterations.cs")] + [InlineData("Notes.Variable.MultipleDeclarationsOnTheSameLine.cs")] + [InlineData("Notes.Variable.LongName.cs")] + [InlineData("Notes.Variable.LongValue.cs")] + [InlineData("Notes.Regression.ToStringNull.cs")] // https://github.com/ashmind/SharpLab/issues/380 + public async Task SlowUpdate_ReportsValueNotes(string resourceName) { + var code = LoadCodeFromResource(resourceName); + var expected = code.Split("\r\n").Select((line, index) => new { + Line = index + 1, + Notes = Regex.Match(line, @"//\s+\[(.+)\]\s*$").Groups[1].Value + }).Where(x => !string.IsNullOrEmpty(x.Notes)).ToArray(); + + var driver = await NewTestDriverAsync(code); + + var result = await driver.SendSlowUpdateAsync(); + var steps = result.ExtensionResult?.Flow + .Select(s => new { s.Line, s.Notes }) + .Where(s => !string.IsNullOrEmpty(s.Notes)) + .GroupBy(s => s.Line) + .Select(g => new { Line = g.Key, Notes = string.Join("; ", g.Select(s => s.Notes)) }) + .ToArray(); + + AssertIsSuccess(result); + Assert.Equal(expected, steps); + } - [Theory] - [InlineData("void M(int a) {}", "M(1)", 1, "a: 1")] - [InlineData("void M(int a) {\r\n}", "M(1)", 1, "a: 1")] - [InlineData("void M(int a)\r\n{}", "M(1)", 1, "a: 1", true)] - [InlineData("void M(int a\r\n) {}", "M(1)", 1, "a: 1", true)] - [InlineData("void M(\r\nint a\r\n) {}", "M(1)", 2, "a: 1", true)] - [InlineData("void M(int a) {\r\n\r\nConsole.WriteLine();}", "M(1)", 1, "a: 1")] - [InlineData("void M(in int a) {}", "M(1)", 1, "a: 1")] - [InlineData("void M(ref int a) {}", "int x = 1; M(ref x)", 1, "a: 1")] - [InlineData("void M(int a, int b) {}", "M(1, 2)", 1, "a: 1, b: 2")] - [InlineData("void M(int a, out int b) { b = 1; }", "M(1, out var _)", 1, "a: 1")] - [InlineData("void M(int a, int b = 0) {}", "M(1)", 1, "a: 1, b: 0")] - public async Task SlowUpdate_ReportsValueNotes_ForCSharpStaticMethodArguments(string methodCode, string methodCallCode, int expectedMethodLineNumber, string expectedNotes, bool expectedSkipped = false) { - var driver = await NewTestDriverAsync(@" + [Theory] + [InlineData("void M(int a) {}", "M(1)", 1, "a: 1")] + [InlineData("void M(int a) {\r\n}", "M(1)", 1, "a: 1")] + [InlineData("void M(int a)\r\n{}", "M(1)", 1, "a: 1", true)] + [InlineData("void M(int a\r\n) {}", "M(1)", 1, "a: 1", true)] + [InlineData("void M(\r\nint a\r\n) {}", "M(1)", 2, "a: 1", true)] + [InlineData("void M(int a) {\r\n\r\nConsole.WriteLine();}", "M(1)", 1, "a: 1")] + [InlineData("void M(in int a) {}", "M(1)", 1, "a: 1")] + [InlineData("void M(ref int a) {}", "int x = 1; M(ref x)", 1, "a: 1")] + [InlineData("void M(int a, int b) {}", "M(1, 2)", 1, "a: 1, b: 2")] + [InlineData("void M(int a, out int b) { b = 1; }", "M(1, out var _)", 1, "a: 1")] + [InlineData("void M(int a, int b = 0) {}", "M(1)", 1, "a: 1, b: 0")] + public async Task SlowUpdate_ReportsValueNotes_ForCSharpStaticMethodArguments(string methodCode, string methodCallCode, int expectedMethodLineNumber, string expectedNotes, bool expectedSkipped = false) { + var driver = await NewTestDriverAsync(@" using System; public static class Program { public static void Main() { " + methodCallCode + @"; } static " + methodCode + @" } "); - var methodStartLine = 4; // see above - - var result = await driver.SendSlowUpdateAsync(); - var steps = result.ExtensionResult?.Flow - .Select(s => new { s.Line, s.Notes, s.Skipped }) - .ToArray(); - - AssertIsSuccess(result); - Assert.Contains( - new { Line = methodStartLine + expectedMethodLineNumber, Notes = (string?)expectedNotes, Skipped = expectedSkipped }, - steps - ); - } + var methodStartLine = 4; // see above + + var result = await driver.SendSlowUpdateAsync(); + var steps = result.ExtensionResult?.Flow + .Select(s => new { s.Line, s.Notes, s.Skipped }) + .ToArray(); + + AssertIsSuccess(result); + Assert.Contains( + new { Line = methodStartLine + expectedMethodLineNumber, Notes = (string?)expectedNotes, Skipped = expectedSkipped }, + steps + ); + } - [Fact] - public async Task SlowUpdate_ReportsValueNotes_ForCSharpInstanceMethodArguments() { - var driver = await NewTestDriverAsync(@" + [Fact] + public async Task SlowUpdate_ReportsValueNotes_ForCSharpInstanceMethodArguments() { + var driver = await NewTestDriverAsync(@" using System; public class Program { public static void Main() { new Program().M(1); } @@ -146,18 +147,18 @@ public void M(int a) {} } "); - var result = await driver.SendSlowUpdateAsync(); - var steps = result.ExtensionResult?.Flow - .Select(s => new { s.Line, s.Notes }) - .ToArray(); + var result = await driver.SendSlowUpdateAsync(); + var steps = result.ExtensionResult?.Flow + .Select(s => new { s.Line, s.Notes }) + .ToArray(); - AssertIsSuccess(result); - Assert.Contains(new { Line = 5, Notes = (string?)"a: 1" }, steps); - } + AssertIsSuccess(result); + Assert.Contains(new { Line = 5, Notes = (string?)"a: 1" }, steps); + } - [Fact] - public async Task SlowUpdate_ReportsValueNotes_ForCSharpConstructorArguments() { - var driver = await NewTestDriverAsync(@" + [Fact] + public async Task SlowUpdate_ReportsValueNotes_ForCSharpConstructorArguments() { + var driver = await NewTestDriverAsync(@" using System; public class Program { Program(int a) {} @@ -165,138 +166,138 @@ public class Program { } "); - var result = await driver.SendSlowUpdateAsync(); - var steps = result.ExtensionResult?.Flow - .Select(s => new { s.Line, s.Notes }) - .ToArray(); + var result = await driver.SendSlowUpdateAsync(); + var steps = result.ExtensionResult?.Flow + .Select(s => new { s.Line, s.Notes }) + .ToArray(); - AssertIsSuccess(result); - Assert.Contains(new { Line = 4, Notes = (string?)"a: 1" }, steps); - } + AssertIsSuccess(result); + Assert.Contains(new { Line = 4, Notes = (string?)"a: 1" }, steps); + } - [Fact] - public async Task SlowUpdate_IncludesReturnValueInOutput() { - var driver = await NewTestDriverAsync(@" + [Fact] + public async Task SlowUpdate_IncludesReturnValueInOutput() { + var driver = await NewTestDriverAsync(@" public static class Program { public static int Main() { return 3; } } "); - var result = await driver.SendSlowUpdateAsync(); + var result = await driver.SendSlowUpdateAsync(); - AssertIsSuccess(result); - Assert.Equal("Return: 3", result.ExtensionResult?.GetOutputAsString()); - } + AssertIsSuccess(result); + Assert.Equal("Return: 3", result.ExtensionResult?.GetOutputAsString()); + } - [Fact] - public async Task SlowUpdate_IncludesExceptionInOutput() { - var driver = await NewTestDriverAsync(@" + [Fact] + public async Task SlowUpdate_IncludesExceptionInOutput() { + var driver = await NewTestDriverAsync(@" public static class Program { public static int Main() { throw new System.Exception(""Test""); } } "); - var result = await driver.SendSlowUpdateAsync(); + var result = await driver.SendSlowUpdateAsync(); - AssertIsSuccess(result, allowRuntimeException: true); - Assert.Matches("^Exception: System.Exception: Test", result.ExtensionResult?.GetOutputAsString()); - } + AssertIsSuccess(result, allowRuntimeException: true); + Assert.Matches("^Exception: System.Exception: Test", result.ExtensionResult?.GetOutputAsString()); + } - [Theory] - [InlineData("3.Inspect();", "Inspect: 3")] - [InlineData("(1, 2, 3).Inspect();", "Inspect: (1, 2, 3)")] - [InlineData("new[] { 1, 2, 3 }.Inspect();", "Inspect: { 1, 2, 3 }")] - [InlineData("3.Dump();", "Dump: 3")] - public async Task SlowUpdate_IncludesSimpleInspectAndDumpInOutput(string code, string expectedOutput) { - var driver = await NewTestDriverAsync(@" + [Theory] + [InlineData("3.Inspect();", "Inspect: 3")] + [InlineData("(1, 2, 3).Inspect();", "Inspect: (1, 2, 3)")] + [InlineData("new[] { 1, 2, 3 }.Inspect();", "Inspect: { 1, 2, 3 }")] + [InlineData("3.Dump();", "Dump: 3")] + public async Task SlowUpdate_IncludesSimpleInspectAndDumpInOutput(string code, string expectedOutput) { + var driver = await NewTestDriverAsync(@" public static class Program { public static void Main() { " + code + @" } } "); - var result = await driver.SendSlowUpdateAsync(); + var result = await driver.SendSlowUpdateAsync(); - AssertIsSuccess(result); - Assert.Equal(expectedOutput, result.ExtensionResult?.GetOutputAsString()); - } + AssertIsSuccess(result); + Assert.Equal(expectedOutput, result.ExtensionResult?.GetOutputAsString()); + } - [Theory] - [InlineData("Output.Inspect.Heap.Simple.cs2output")] - [InlineData("Output.Inspect.Heap.Struct.cs2output")] - [InlineData("Output.Inspect.Heap.Struct.Nested.cs2output")] - [InlineData("Output.Inspect.Heap.Int32.cs2output")] - [InlineData("Output.Inspect.Heap.Null.cs2output", true)] - public async Task SlowUpdate_IncludesInspectHeapInOutput(string resourceName, bool allowExceptions = false) { - var code = TestCode.FromResource("Execution." + resourceName); - var driver = await NewTestDriverAsync(code.Original); + [Theory] + [InlineData("Output.Inspect.Heap.Simple.cs2output")] + [InlineData("Output.Inspect.Heap.Struct.cs2output")] + [InlineData("Output.Inspect.Heap.Struct.Nested.cs2output")] + [InlineData("Output.Inspect.Heap.Int32.cs2output")] + [InlineData("Output.Inspect.Heap.Null.cs2output", true)] + public async Task SlowUpdate_IncludesInspectHeapInOutput(string resourceName, bool allowExceptions = false) { + var code = TestCode.FromResource("Execution." + resourceName); + var driver = await NewTestDriverAsync(code.Original); - var result = await SendSlowUpdateWithRetryOnMovedObjectsAsync(driver); + var result = await SendSlowUpdateWithRetryOnMovedObjectsAsync(driver); - AssertIsSuccess(result, allowRuntimeException: allowExceptions); - await code.AssertIsExpectedAsync(result.ExtensionResult?.GetOutputAsString(), _output); - } + AssertIsSuccess(result, allowRuntimeException: allowExceptions); + await code.AssertIsExpectedAsync(result.ExtensionResult?.GetOutputAsString(), _output); + } - [Theory] - [InlineData("Output.Inspect.MemoryGraph.Int32.cs2output")] - [InlineData("Output.Inspect.MemoryGraph.String.cs2output")] - [InlineData("Output.Inspect.MemoryGraph.Arrays.cs2output")] - [InlineData("Output.Inspect.MemoryGraph.Variables.cs2output")] - [InlineData("Output.Inspect.MemoryGraph.DateTime.cs2output")] // https://github.com/ashmind/SharpLab/issues/379 - [InlineData("Output.Inspect.MemoryGraph.Null.cs2output")] - public async Task SlowUpdate_IncludesInspectMemoryGraphInOutput(string resourceName) { - var code = TestCode.FromResource("Execution." + resourceName); - var driver = await NewTestDriverAsync(code.Original); - - var result = await SendSlowUpdateWithRetryOnMovedObjectsAsync(driver); - - AssertIsSuccess(result); - await code.AssertIsExpectedAsync(result.ExtensionResult?.GetOutputAsString(), _output); - } + [Theory] + [InlineData("Output.Inspect.MemoryGraph.Int32.cs2output")] + [InlineData("Output.Inspect.MemoryGraph.String.cs2output")] + [InlineData("Output.Inspect.MemoryGraph.Arrays.cs2output")] + [InlineData("Output.Inspect.MemoryGraph.Variables.cs2output")] + [InlineData("Output.Inspect.MemoryGraph.DateTime.cs2output")] // https://github.com/ashmind/SharpLab/issues/379 + [InlineData("Output.Inspect.MemoryGraph.Null.cs2output")] + public async Task SlowUpdate_IncludesInspectMemoryGraphInOutput(string resourceName) { + var code = TestCode.FromResource("Execution." + resourceName); + var driver = await NewTestDriverAsync(code.Original); + + var result = await SendSlowUpdateWithRetryOnMovedObjectsAsync(driver); + + AssertIsSuccess(result); + await code.AssertIsExpectedAsync(result.ExtensionResult?.GetOutputAsString(), _output); + } - [Theory] - [InlineData("Console.Write(\"abc\");", "abc")] - [InlineData("Console.WriteLine(\"abc\");", "abc{newline}")] - [InlineData("Console.Write('a');", "a")] - [InlineData("Console.Write(3);", "3")] - [InlineData("Console.Write(3.1);", "3.1")] - [InlineData("Console.Write(new object());", "System.Object")] - public async Task SlowUpdate_IncludesConsoleInOutput(string code, string expectedOutput) { - var driver = await NewTestDriverAsync(@" + [Theory] + [InlineData("Console.Write(\"abc\");", "abc")] + [InlineData("Console.WriteLine(\"abc\");", "abc{newline}")] + [InlineData("Console.Write('a');", "a")] + [InlineData("Console.Write(3);", "3")] + [InlineData("Console.Write(3.1);", "3.1")] + [InlineData("Console.Write(new object());", "System.Object")] + public async Task SlowUpdate_IncludesConsoleInOutput(string code, string expectedOutput) { + var driver = await NewTestDriverAsync(@" using System; public static class Program { public static void Main() { " + code + @" } } "); - var result = await driver.SendSlowUpdateAsync(); + var result = await driver.SendSlowUpdateAsync(); - AssertIsSuccess(result); - Assert.Equal( - expectedOutput.Replace("{newline}", Environment.NewLine), - result.ExtensionResult?.GetOutputAsString() - ); - } + AssertIsSuccess(result); + Assert.Equal( + expectedOutput.Replace("{newline}", Environment.NewLine), + result.ExtensionResult?.GetOutputAsString() + ); + } - [Fact] - public async Task SlowUpdate_DoesNotIncludePreviousConsoleOutput_IfRunTwice() { - var driver = await NewTestDriverAsync(@" + [Fact] + public async Task SlowUpdate_DoesNotIncludePreviousConsoleOutput_IfRunTwice() { + var driver = await NewTestDriverAsync(@" using System; public static class Program { public static void Main() { Console.Write('I'); } } "); - await driver.SendSlowUpdateAsync(); - var result = await driver.SendSlowUpdateAsync(); + await driver.SendSlowUpdateAsync(); + var result = await driver.SendSlowUpdateAsync(); - AssertIsSuccess(result); - Assert.Equal("I", result.ExtensionResult?.GetOutputAsString()); - } + AssertIsSuccess(result); + Assert.Equal("I", result.ExtensionResult?.GetOutputAsString()); + } - [Theory] - [InlineData("Console.Write(3.1);", "cs-CZ", "3.1")] - public async Task SlowUpdate_IncludesConsoleInOutput_UsingInvariantCulture(string code, string currentCultureName, string expectedOutput) { - var driver = await NewTestDriverAsync(@" + [Theory] + [InlineData("Console.Write(3.1);", "cs-CZ", "3.1")] + public async Task SlowUpdate_IncludesConsoleInOutput_UsingInvariantCulture(string code, string currentCultureName, string expectedOutput) { + var driver = await NewTestDriverAsync(@" using System; using System.Globalization; public static class Program { @@ -307,27 +308,27 @@ public static void Main() { } "); - var result = await driver.SendSlowUpdateAsync(); + var result = await driver.SendSlowUpdateAsync(); - AssertIsSuccess(result); - Assert.Equal( - expectedOutput.Replace("{newline}", Environment.NewLine), - result.ExtensionResult?.GetOutputAsString() - ); - } + AssertIsSuccess(result); + Assert.Equal( + expectedOutput.Replace("{newline}", Environment.NewLine), + result.ExtensionResult?.GetOutputAsString() + ); + } - [Theory] - [InlineData("Api.Expressions.Simple.cs")] - public async Task SlowUpdate_AllowsExpectedApis(string resourceName) { - var driver = await NewTestDriverAsync(LoadCodeFromResource(resourceName)); - var result = await driver.SendSlowUpdateAsync(); + [Theory] + [InlineData("Api.Expressions.Simple.cs")] + public async Task SlowUpdate_AllowsExpectedApis(string resourceName) { + var driver = await NewTestDriverAsync(LoadCodeFromResource(resourceName)); + var result = await driver.SendSlowUpdateAsync(); - AssertIsSuccess(result); - } + AssertIsSuccess(result); + } - [Fact] - public async Task SlowUpdate_ExecutesVisualBasic() { - var driver = await NewTestDriverAsync(@" + [Fact] + public async Task SlowUpdate_ExecutesVisualBasic() { + var driver = await NewTestDriverAsync(@" Imports System Public Module Program Public Sub Main() @@ -336,28 +337,28 @@ End Sub End Module ", LanguageNames.VisualBasic); - var result = await driver.SendSlowUpdateAsync(); + var result = await driver.SendSlowUpdateAsync(); - AssertIsSuccess(result); - Assert.Equal("Test", result.ExtensionResult?.GetOutputAsString()); - } + AssertIsSuccess(result); + Assert.Equal("Test", result.ExtensionResult?.GetOutputAsString()); + } - [Fact] - public async Task SlowUpdate_ExecutesFSharp() { - var driver = await NewTestDriverAsync(@" + [Fact] + public async Task SlowUpdate_ExecutesFSharp() { + var driver = await NewTestDriverAsync(@" open System printf ""Test"" ", "F#"); - var result = await driver.SendSlowUpdateAsync(); + var result = await driver.SendSlowUpdateAsync(); - AssertIsSuccess(result); - Assert.Equal("Test", result.ExtensionResult?.GetOutputAsString()); - } + AssertIsSuccess(result); + Assert.Equal("Test", result.ExtensionResult?.GetOutputAsString()); + } - [Fact] - public async Task SlowUpdate_ExecutesFSharp_WithExplicitEntryPoint() { - var driver = await NewTestDriverAsync(@" + [Fact] + public async Task SlowUpdate_ExecutesFSharp_WithExplicitEntryPoint() { + var driver = await NewTestDriverAsync(@" open System [] @@ -366,30 +367,30 @@ open System 0 ", "F#"); - var result = await driver.SendSlowUpdateAsync(); + var result = await driver.SendSlowUpdateAsync(); - AssertIsSuccess(result); - Assert.Equal("Test\nReturn: 0", result.ExtensionResult?.GetOutputAsString()); - } + AssertIsSuccess(result); + Assert.Equal("Test\nReturn: 0", result.ExtensionResult?.GetOutputAsString()); + } - [Theory] - [InlineData("Regression.CertainLoop.cs")] - [InlineData("Regression.FSharpNestedLambda.fs", LanguageNames.FSharp)] - [InlineData("Regression.NestedAnonymousObject.cs")] - [InlineData("Regression.ReturnRef.cs")] - public async Task SlowUpdate_DoesNotFail(string resourceName, string languageName = LanguageNames.CSharp) { - var driver = await NewTestDriverAsync(LoadCodeFromResource(resourceName), languageName); - var result = await driver.SendSlowUpdateAsync(); - AssertIsSuccess(result); - } + [Theory] + [InlineData("Regression.CertainLoop.cs")] + [InlineData("Regression.FSharpNestedLambda.fs", LanguageNames.FSharp)] + [InlineData("Regression.NestedAnonymousObject.cs")] + [InlineData("Regression.ReturnRef.cs")] + public async Task SlowUpdate_DoesNotFail(string resourceName, string languageName = LanguageNames.CSharp) { + var driver = await NewTestDriverAsync(LoadCodeFromResource(resourceName), languageName); + var result = await driver.SendSlowUpdateAsync(); + AssertIsSuccess(result); + } - [Theory] // https://github.com/ashmind/SharpLab/issues/388 - [InlineData("void M(Span s) {}", "M(new Span())")] - [InlineData("void M(ref Span s) {}", "var s = new Span(); M(ref s)")] - [InlineData("void M(ReadOnlySpan s) {}", "M(new ReadOnlySpan())")] - [InlineData("void M(ref ReadOnlySpan s) {}", "var s = new ReadOnlySpan(); M(ref s)")] - public async Task SlowUpdate_DoesNotFail_OnSpanArguments(string methodCode, string methodCallCode) { - var driver = await NewTestDriverAsync(@" + [Theory] // https://github.com/ashmind/SharpLab/issues/388 + [InlineData("void M(Span s) {}", "M(new Span())")] + [InlineData("void M(ref Span s) {}", "var s = new Span(); M(ref s)")] + [InlineData("void M(ReadOnlySpan s) {}", "M(new ReadOnlySpan())")] + [InlineData("void M(ref ReadOnlySpan s) {}", "var s = new ReadOnlySpan(); M(ref s)")] + public async Task SlowUpdate_DoesNotFail_OnSpanArguments(string methodCode, string methodCallCode) { + var driver = await NewTestDriverAsync(@" using System; public static class Program { public static void Main() { @@ -398,17 +399,17 @@ public static void Main() { static " + methodCode + @" } "); - var result = await driver.SendSlowUpdateAsync(); - AssertIsSuccess(result); - } + var result = await driver.SendSlowUpdateAsync(); + AssertIsSuccess(result); + } - [Theory] - [InlineData("digits.Sort((a, b) => a.CompareTo(b));")] // https://github.com/ashmind/SharpLab/issues/411 - [InlineData("digits.Sort(delegate(int a, int b) { return a.CompareTo(b); });")] - [InlineData("int Compare(int a, int b) => a.CompareTo(b); digits.Sort(Compare);")] - [InlineData("digits.Find(a => a > 0);")] - public async Task SlowUpdate_DoesNotFail_OnNestedMethodCall_ForCSharp(string callWithAnonymousMethodCode) { - var driver = await NewTestDriverAsync(@" + [Theory] + [InlineData("digits.Sort((a, b) => a.CompareTo(b));")] // https://github.com/ashmind/SharpLab/issues/411 + [InlineData("digits.Sort(delegate(int a, int b) { return a.CompareTo(b); });")] + [InlineData("int Compare(int a, int b) => a.CompareTo(b); digits.Sort(Compare);")] + [InlineData("digits.Find(a => a > 0);")] + public async Task SlowUpdate_DoesNotFail_OnNestedMethodCall_ForCSharp(string callWithAnonymousMethodCode) { + var driver = await NewTestDriverAsync(@" using System.Collections.Generic; public static class Program { public static void Main(string[] args) { @@ -417,13 +418,13 @@ public static void Main(string[] args) { } } "); - var result = await driver.SendSlowUpdateAsync(); - AssertIsSuccess(result); - } + var result = await driver.SendSlowUpdateAsync(); + AssertIsSuccess(result); + } - [Fact] // https://github.com/ashmind/SharpLab/issues/411 - public async Task SlowUpdate_DoesNotFail_OnLambdaParameterList_ForVisualBasic() { - var driver = await NewTestDriverAsync(@" + [Fact] // https://github.com/ashmind/SharpLab/issues/411 + public async Task SlowUpdate_DoesNotFail_OnLambdaParameterList_ForVisualBasic() { + var driver = await NewTestDriverAsync(@" Imports System.Collections.Generic Public Module Program Public Sub Main(ByVal args() As String) @@ -432,105 +433,104 @@ Dim list as New List(of Integer) End Sub End Module ", LanguageNames.VisualBasic); - var result = await driver.SendSlowUpdateAsync(); - AssertIsSuccess(result); - } + var result = await driver.SendSlowUpdateAsync(); + AssertIsSuccess(result); + } - [Theory] - [InlineData("Regression.Disposable.cs", Skip = "Fails GitHub Actions, see https://github.com/ashmind/SharpLab/issues/591")] - public async Task SlowUpdate_DoesNotFail_OnAnyGuard(string resourceName) { - var driver = await NewTestDriverAsync(LoadCodeFromResource(resourceName), LanguageNames.CSharp); - var result = await driver.SendSlowUpdateAsync(); + [Theory] + [InlineData("Regression.Disposable.cs", Skip = "Fails GitHub Actions, see https://github.com/ashmind/SharpLab/issues/591")] + public async Task SlowUpdate_DoesNotFail_OnAnyGuard(string resourceName) { + var driver = await NewTestDriverAsync(LoadCodeFromResource(resourceName), LanguageNames.CSharp); + var result = await driver.SendSlowUpdateAsync(); - AssertIsSuccess(result, allowRuntimeException: true); - Assert.DoesNotMatch("GuardException", result.ExtensionResult?.GetOutputAsString()); - } + AssertIsSuccess(result, allowRuntimeException: true); + Assert.DoesNotMatch("GuardException", result.ExtensionResult?.GetOutputAsString()); + } - // Currently Inspect.Heap/MemoryGraph does not promise to always work as expected if GCs happen - // during its operation. So for now we retry in the tests. - private async Task> SendSlowUpdateWithRetryOnMovedObjectsAsync(MirrorSharpTestDriver driver) { - var result = await driver.SendSlowUpdateAsync(); - var tryCount = 1; - while ((result.ExtensionResult?.GetOutputAsString().Contains("Failed to find object type for address") ?? false) && tryCount < 10) { - _output.WriteLine($"Failed to find object type for address, retrying ({tryCount}) ..."); - result = await driver.SendSlowUpdateAsync(); - tryCount += 1; - } - return result!; + // Currently Inspect.Heap/MemoryGraph does not promise to always work as expected if GCs happen + // during its operation. So for now we retry in the tests. + private async Task> SendSlowUpdateWithRetryOnMovedObjectsAsync(MirrorSharpTestDriver driver) { + var result = await driver.SendSlowUpdateAsync(); + var tryCount = 1; + while ((result.ExtensionResult?.GetOutputAsString().Contains("Failed to find object type for address") ?? false) && tryCount < 10) { + _output.WriteLine($"Failed to find object type for address, retrying ({tryCount}) ..."); + result = await driver.SendSlowUpdateAsync(); + tryCount += 1; } + return result!; + } - private static void AssertIsSuccess(SlowUpdateResult result, bool allowRuntimeException = false) { - var errors = result.JoinErrors(); - Assert.True(string.IsNullOrEmpty(errors), errors); - var output = result.ExtensionResult?.GetOutputAsString(); - Assert.DoesNotMatch("InvalidProgramException", output); + private static void AssertIsSuccess(SlowUpdateResult result, bool allowRuntimeException = false) { + var errors = result.JoinErrors(); + Assert.True(string.IsNullOrEmpty(errors), errors); + var output = result.ExtensionResult?.GetOutputAsString(); + Assert.DoesNotMatch("InvalidProgramException", output); - if (allowRuntimeException) - return; - Assert.DoesNotMatch("Exception:", output); - } + if (allowRuntimeException) + return; + Assert.DoesNotMatch("Exception:", output); + } - private static string LoadCodeFromResource(string resourceName) { - return EmbeddedResource.ReadAllText(typeof(ExecutionTests), "TestCode.Execution." + resourceName); - } + private static string LoadCodeFromResource(string resourceName) { + return EmbeddedResource.ReadAllText(typeof(ExecutionTests), "TestCode.Execution." + resourceName); + } - private static async Task NewTestDriverAsync( - string code, - string languageName = LanguageNames.CSharp, - string optimize = Optimize.Debug - ) { - var driver = TestEnvironment.NewDriver().SetText(code); - await driver.SendSetOptionsAsync(languageName, TargetNames.Run, optimize); - return driver; - } + private static async Task NewTestDriverAsync( + string code, + string languageName = LanguageNames.CSharp, + string optimize = Optimize.Debug + ) { + var driver = TestEnvironment.NewDriver().SetText(code); + await driver.SendSetOptionsAsync(languageName, TargetNames.Run, optimize); + return driver; + } - private class ExecutionResultData { - [JsonIgnore] - public IList Flow { get; } = new List(); - [JsonProperty("flow")] - private IList FlowRaw { get; } = new List(); - [JsonProperty] - private IList Output { get; } = new List(); - - public string GetOutputAsString() { - return string.Join("\n", Output.Select(token => { - if (token is JObject @object) - return ConvertOutputObjectToString(@object); - return token.Value(); - })); - } + private class ExecutionResultData { + [JsonIgnore] + public IList Flow { get; } = new List(); + [JsonProperty("flow")] + private IList FlowRaw { get; } = new List(); + [JsonProperty] + private IList Output { get; } = new List(); + + public string GetOutputAsString() { + return string.Join("\n", Output.Select(token => { + if (token is JObject @object) + return ConvertOutputObjectToString(@object); + return token.Value(); + })); + } - private string ConvertOutputObjectToString(JObject @object) { - if (@object.Value("type") == "inspection:simple") - return @object.Value("title") + ": " + @object.Value("value"); - return @object.ToString(); - } + private string ConvertOutputObjectToString(JObject @object) { + if (@object.Value("type") == "inspection:simple") + return @object.Value("title") + ": " + @object.Value("value"); + return @object.ToString(); + } - [OnDeserialized] - private void OnDeserialized(StreamingContext context) { - foreach (var token in FlowRaw) { - Flow.Add(ParseStepData(token)); - } + [OnDeserialized] + private void OnDeserialized(StreamingContext context) { + foreach (var token in FlowRaw) { + Flow.Add(ParseStepData(token)); } + } - private FlowStepData ParseStepData(JToken token) { - if (token is JValue value) - return new FlowStepData { Line = value.Value() }; + private FlowStepData ParseStepData(JToken token) { + if (token is JValue value) + return new FlowStepData { Line = value.Value() }; - return new FlowStepData { - Line = token.Value("line"), - Exception = token.Value("exception"), - Notes = token.Value("notes"), - Skipped = token.Value("skipped") ?? false, - }; - } + return new FlowStepData { + Line = token.Value("line"), + Exception = token.Value("exception"), + Notes = token.Value("notes"), + Skipped = token.Value("skipped") ?? false, + }; } + } - private class FlowStepData { - public int Line { get; set; } - public string? Exception { get; set; } - public string? Notes { get; set; } - public bool Skipped { get; set; } - } + private class FlowStepData { + public int Line { get; set; } + public string? Exception { get; set; } + public string? Notes { get; set; } + public bool Skipped { get; set; } } } \ No newline at end of file diff --git a/source/NetFramework/Tests/Internal/TestEnvironment.cs b/source/NetFramework/Tests/Internal/TestEnvironment.cs index 875b03efe..8a0687dcd 100644 --- a/source/NetFramework/Tests/Internal/TestEnvironment.cs +++ b/source/NetFramework/Tests/Internal/TestEnvironment.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.CompilerServices; using Autofac; using MirrorSharp; using MirrorSharp.Advanced; diff --git a/source/Server/Caching/CachingTracker.cs b/source/Server/Caching/CachingTracker.cs index b1edd67df..eae39bc55 100644 --- a/source/Server/Caching/CachingTracker.cs +++ b/source/Server/Caching/CachingTracker.cs @@ -3,10 +3,10 @@ namespace SharpLab.Server.Caching; public class CachingTracker : ICachingTracker { - private readonly IMetricMonitor _cacheableRequestCountMonitor; - private readonly IMetricMonitor _noCacheRequestCountMonitor; - private readonly IMetricMonitor _blobUploadRequestCountMonitor; - private readonly IMetricMonitor _blobUploadErrorCountMonitor; + private readonly IZeroDimensionMetricMonitor _cacheableRequestCountMonitor; + private readonly IZeroDimensionMetricMonitor _noCacheRequestCountMonitor; + private readonly IZeroDimensionMetricMonitor _blobUploadRequestCountMonitor; + private readonly IZeroDimensionMetricMonitor _blobUploadErrorCountMonitor; public CachingTracker(IMonitor monitor) { _cacheableRequestCountMonitor = monitor.MetricSlow("caching", "Caching: Cacheable Requests"); diff --git a/source/Server/Common/FeatureTracker.cs b/source/Server/Common/FeatureTracker.cs index 9c615bc18..f59ec2868 100644 --- a/source/Server/Common/FeatureTracker.cs +++ b/source/Server/Common/FeatureTracker.cs @@ -1,51 +1,35 @@ using SharpLab.Server.Monitoring; -using System.Collections.Generic; -using System.Linq; namespace SharpLab.Server.Common; public class FeatureTracker : IFeatureTracker { - private readonly IMetricMonitor _branchMonitor; - private readonly IReadOnlyDictionary _languageMetricMonitors; - private readonly IReadOnlyDictionary _targetMetricMonitors; - private readonly IMetricMonitor _optimizeDebugMonitor; - private readonly IMetricMonitor _optimizeReleaseMonitor; + private readonly string _webAppName; + private readonly IOneDimensionMetricMonitor _branchMetricMonitor; + private readonly IOneDimensionMetricMonitor _languageMetricMonitor; + private readonly IOneDimensionMetricMonitor _targetMetricMonitor; + private readonly IOneDimensionMetricMonitor _optimizeMetricMonitor; public FeatureTracker(IMonitor monitor, string webAppName) { - _branchMonitor = monitor.MetricSlow("feature", $"Branch: {webAppName}"); - _languageMetricMonitors = LanguageNames.All.ToDictionary( - name => name, - name => monitor.MetricSlow("feature", $"Language: {name}") - ); - _targetMetricMonitors = TargetNames.All.ToDictionary( - name => name, - name => monitor.MetricSlow("feature", $"Target: {name}") - ); - - _optimizeDebugMonitor = monitor.MetricSlow("feature", "Optimize: Debug"); - _optimizeReleaseMonitor = monitor.MetricSlow("feature", "Optimize: Release"); + _webAppName = webAppName; + _branchMetricMonitor = monitor.MetricSlow("feature", "Branch", "Branch"); + _languageMetricMonitor = monitor.MetricSlow("feature", "Language", "Language"); + _targetMetricMonitor = monitor.MetricSlow("feature", "Target", "Target"); + _optimizeMetricMonitor = monitor.MetricSlow("feature", "Optimize", "Optimize"); } public void TrackBranch() { - _branchMonitor.Track(1); + _branchMetricMonitor.Track(_webAppName, 1); } public void TrackLanguage(string languageName) { - if (_languageMetricMonitors.TryGetValue(languageName, out var metricMonitor)) - metricMonitor.Track(1); + _languageMetricMonitor.Track(languageName, 1); } public void TrackTarget(string targetName) { - if (_targetMetricMonitors.TryGetValue(targetName, out var metricMonitor)) - metricMonitor.Track(1); + _targetMetricMonitor.Track(targetName, 1); } - public void TrackOptimize(string? optimize) { - var monitor = optimize switch { - Optimize.Debug => _optimizeDebugMonitor, - Optimize.Release => _optimizeReleaseMonitor, - _ => null - }; - monitor?.Track(1); + public void TrackOptimize(string optimize) { + _optimizeMetricMonitor.Track(optimize, 1); } } diff --git a/source/Server/Common/IFeatureTracker.cs b/source/Server/Common/IFeatureTracker.cs index c1e4053fb..373ce9b7c 100644 --- a/source/Server/Common/IFeatureTracker.cs +++ b/source/Server/Common/IFeatureTracker.cs @@ -4,5 +4,5 @@ public interface IFeatureTracker { void TrackBranch(); void TrackLanguage(string languageName); void TrackTarget(string targetName); - void TrackOptimize(string? optimize); + void TrackOptimize(string optimize); } \ No newline at end of file diff --git a/source/Server/Common/LanguageNames.cs b/source/Server/Common/LanguageNames.cs index 22213e918..54ed93db4 100644 --- a/source/Server/Common/LanguageNames.cs +++ b/source/Server/Common/LanguageNames.cs @@ -1,4 +1,3 @@ -using System.Collections.Immutable; using CodeAnalysis = Microsoft.CodeAnalysis; namespace SharpLab.Server.Common; @@ -8,8 +7,4 @@ public class LanguageNames { public const string VisualBasic = CodeAnalysis.LanguageNames.VisualBasic; public const string FSharp = CodeAnalysis.LanguageNames.FSharp; public const string IL = "IL"; - - public static readonly ImmutableArray All = [ - CSharp, VisualBasic, FSharp, IL - ]; } diff --git a/source/Server/Common/TargetNames.cs b/source/Server/Common/TargetNames.cs index a569191aa..aeff69bf1 100644 --- a/source/Server/Common/TargetNames.cs +++ b/source/Server/Common/TargetNames.cs @@ -1,5 +1,3 @@ -using System.Collections.Immutable; - namespace SharpLab.Server.Common; public static class TargetNames { @@ -11,8 +9,4 @@ public static class TargetNames { public const string RunIL = "Run IL"; public const string Verify = "Verify"; public const string Explain = "Explain"; - - public static readonly ImmutableArray All = [ - CSharp, IL, Ast, JitAsm, Run, RunIL, Verify, Explain - ]; } \ No newline at end of file diff --git a/source/Server/Integration/Azure/ApplicationInsightsMetricMonitor.cs b/source/Server/Integration/Azure/ApplicationInsightsMetricMonitor.cs index b0100a6e9..2ca9d175a 100644 --- a/source/Server/Integration/Azure/ApplicationInsightsMetricMonitor.cs +++ b/source/Server/Integration/Azure/ApplicationInsightsMetricMonitor.cs @@ -3,7 +3,7 @@ namespace SharpLab.Server.Integration.Azure; -public class ApplicationInsightsMetricMonitor : IMetricMonitor { +public class ApplicationInsightsMetricMonitor : IZeroDimensionMetricMonitor, IOneDimensionMetricMonitor { private readonly Metric _metric; public ApplicationInsightsMetricMonitor(Metric metric) { @@ -15,4 +15,8 @@ public ApplicationInsightsMetricMonitor(Metric metric) { public void Track(double value) { _metric.TrackValue(value); } + + public void Track(string dimension, double value) { + _metric.TrackValue(value, dimension); + } } \ No newline at end of file diff --git a/source/Server/Integration/Azure/ApplicationInsightsMonitor.cs b/source/Server/Integration/Azure/ApplicationInsightsMonitor.cs index 3354912ee..ad3415687 100644 --- a/source/Server/Integration/Azure/ApplicationInsightsMonitor.cs +++ b/source/Server/Integration/Azure/ApplicationInsightsMonitor.cs @@ -23,8 +23,14 @@ public ApplicationInsightsMonitor(TelemetryClient client, string webAppName, Fun _createMetricMonitor = Argument.NotNull(nameof(createMetricMonitor), createMetricMonitor); } - public IMetricMonitor MetricSlow(string @namespace, string name) { - var metric = _client.GetMetric(new MetricIdentifier(@namespace, name)); + public IZeroDimensionMetricMonitor MetricSlow(string @namespace, string name) + => MetricSlowInternal(new (@namespace, name)); + + public IOneDimensionMetricMonitor MetricSlow(string @namespace, string name, string dimension) + => MetricSlowInternal(new (@namespace, name, dimension)); + + private ApplicationInsightsMetricMonitor MetricSlowInternal(MetricIdentifier identifier) { + var metric = _client.GetMetric(identifier); return _createMetricMonitor(metric); } diff --git a/source/Server/MirrorSharp/SlowUpdate.cs b/source/Server/MirrorSharp/SlowUpdate.cs index 2d047adc8..486646c17 100644 --- a/source/Server/MirrorSharp/SlowUpdate.cs +++ b/source/Server/MirrorSharp/SlowUpdate.cs @@ -33,8 +33,8 @@ public class SlowUpdate : ISlowUpdateExtension { private readonly RecyclableMemoryStreamManager _memoryStreamManager; private readonly IFeatureTracker _featureTracker; private readonly IMonitor _monitor; - private readonly IMetricMonitor _containerRunCountMonitor; - private readonly IMetricMonitor _containerFailureCountMonitor; + private readonly IZeroDimensionMetricMonitor _containerRunCountMonitor; + private readonly IZeroDimensionMetricMonitor _containerFailureCountMonitor; public SlowUpdate( ICSharpTopLevelProgramSupport topLevelProgramSupport, @@ -93,7 +93,7 @@ IMonitor monitor if (targetName is not (TargetNames.Run or TargetNames.Verify) && !_decompilers.ContainsKey(targetName)) throw new NotSupportedException($"Target '{targetName}' is not (yet?) supported by this branch."); - _featureTracker.TrackOptimize(session.GetOptimize()); + _featureTracker.TrackOptimize(session.GetOptimize()!); MemoryStream? assemblyStream = null; MemoryStream? symbolStream = null; diff --git a/source/Server/Monitoring/DefaultLoggerMetricMonitor.cs b/source/Server/Monitoring/DefaultLoggerMetricMonitor.cs index 21e2e6c59..9fea7da20 100644 --- a/source/Server/Monitoring/DefaultLoggerMetricMonitor.cs +++ b/source/Server/Monitoring/DefaultLoggerMetricMonitor.cs @@ -2,12 +2,15 @@ namespace SharpLab.Server.Monitoring; -public class DefaultLoggerMetricMonitor : IMetricMonitor { +public class DefaultLoggerMetricMonitor : IZeroDimensionMetricMonitor, IOneDimensionMetricMonitor { private readonly ILogger _logger; private readonly string _namespace; private readonly string _name; - public DefaultLoggerMetricMonitor(ILogger logger, string @namespace, string name) { + public DefaultLoggerMetricMonitor( + ILogger logger, + string @namespace, string name + ) { Argument.NotNull(nameof(logger), logger); Argument.NotNullOrEmpty(nameof(@namespace), @namespace); Argument.NotNullOrEmpty(nameof(name), name); @@ -20,4 +23,8 @@ public DefaultLoggerMetricMonitor(ILogger logger, string @ public void Track(double value) { _logger.LogInformation("Metric {Namespace} {Name}: {Value}.", _namespace, _name, value); } + + public void Track(string dimension, double value) { + _logger.LogInformation("Metric {Namespace} {Name}: {Dimension} {Value}.", _namespace, _name, dimension, value); + } } diff --git a/source/Server/Monitoring/DefaultLoggerMonitor.cs b/source/Server/Monitoring/DefaultLoggerMonitor.cs index 6e2c45747..b07299ca2 100644 --- a/source/Server/Monitoring/DefaultLoggerMonitor.cs +++ b/source/Server/Monitoring/DefaultLoggerMonitor.cs @@ -17,7 +17,11 @@ ILogger logger _logger = logger; } - public IMetricMonitor MetricSlow(string @namespace, string name) { + public IZeroDimensionMetricMonitor MetricSlow(string @namespace, string name) { + return _createMetricMonitor((@namespace, name)); + } + + public IOneDimensionMetricMonitor MetricSlow(string @namespace, string name, string dimension) { return _createMetricMonitor((@namespace, name)); } diff --git a/source/Server/Monitoring/IMetricMonitor.cs b/source/Server/Monitoring/IMetricMonitor.cs deleted file mode 100644 index 750559645..000000000 --- a/source/Server/Monitoring/IMetricMonitor.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace SharpLab.Server.Monitoring; - -public interface IMetricMonitor { - void Track(double value); -} diff --git a/source/Server/Monitoring/IMonitor.cs b/source/Server/Monitoring/IMonitor.cs index 10fedc74a..4445769f9 100644 --- a/source/Server/Monitoring/IMonitor.cs +++ b/source/Server/Monitoring/IMonitor.cs @@ -4,6 +4,7 @@ namespace SharpLab.Server.Monitoring; public interface IMonitor { - IMetricMonitor MetricSlow(string @namespace, string name); + IZeroDimensionMetricMonitor MetricSlow(string @namespace, string name); + IOneDimensionMetricMonitor MetricSlow(string @namespace, string name, string dimension); void Exception(Exception exception, IWorkSession? session, IDictionary? extras = null); } diff --git a/source/Server/Monitoring/IOneDimensionMetricMonitor.cs b/source/Server/Monitoring/IOneDimensionMetricMonitor.cs new file mode 100644 index 000000000..1d72a3333 --- /dev/null +++ b/source/Server/Monitoring/IOneDimensionMetricMonitor.cs @@ -0,0 +1,5 @@ +namespace SharpLab.Server.Monitoring; + +public interface IOneDimensionMetricMonitor { + void Track(string dimension, double value); +} diff --git a/source/Server/Monitoring/IZeroDimensionMetricMonitor.cs b/source/Server/Monitoring/IZeroDimensionMetricMonitor.cs new file mode 100644 index 000000000..2aac8df2f --- /dev/null +++ b/source/Server/Monitoring/IZeroDimensionMetricMonitor.cs @@ -0,0 +1,5 @@ +namespace SharpLab.Server.Monitoring; + +public interface IZeroDimensionMetricMonitor { + void Track(double value); +} diff --git a/source/Tests/Common/Unit/LanguageNamesTests.cs b/source/Tests/Common/Unit/LanguageNamesTests.cs deleted file mode 100644 index abca419fe..000000000 --- a/source/Tests/Common/Unit/LanguageNamesTests.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Xunit; -using SharpLab.Server.Common; -using System.Linq; -using System.Reflection; - -namespace SharpLab.Tests.Common.Unit; - -public class LanguageNamesTests { - [Fact] - public void All_IncludesAllConstants() { - // Arrange - var constants = typeof(LanguageNames) - .GetFields(BindingFlags.Static | BindingFlags.Public) - .Where(f => f.IsLiteral) - .Select(f => (string)f.GetRawConstantValue()!); - - // Act - var all = LanguageNames.All; - - // Assert - Assert.Equal(constants, all); - } -} diff --git a/source/Tests/Common/Unit/TargetNamesTests.cs b/source/Tests/Common/Unit/TargetNamesTests.cs deleted file mode 100644 index cf4aad67b..000000000 --- a/source/Tests/Common/Unit/TargetNamesTests.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Xunit; -using SharpLab.Server.Common; -using System.Linq; -using System.Reflection; - -namespace SharpLab.Tests.Common.Unit; - -public class TargetNamesTests { - [Fact] - public void All_IncludesAllConstants() { - // Arrange - var constants = typeof(TargetNames) - .GetFields(BindingFlags.Static | BindingFlags.Public) - .Where(f => f.IsLiteral) - .Select(f => (string)f.GetRawConstantValue()!); - - // Act - var all = TargetNames.All; - - // Assert - Assert.Equal(constants, all); - } -} diff --git a/source/WebApp.Server/Assets/AssetsModule.cs b/source/WebApp.Server/Assets/AssetsModule.cs index 7f3b29c35..013b83e48 100644 --- a/source/WebApp.Server/Assets/AssetsModule.cs +++ b/source/WebApp.Server/Assets/AssetsModule.cs @@ -1,25 +1,25 @@ using System; using Autofac; -namespace SharpLab.WebApp.Server.Assets { - public class AssetsModule : Module { - protected override void Load(ContainerBuilder builder) { - string GetRequiredEnvironmentVariable(string key) => Environment.GetEnvironmentVariable(key) - ?? throw new Exception($"{key} was not found in the environment."); +namespace SharpLab.WebApp.Server.Assets; - var baseUrl = GetRequiredEnvironmentVariable("SHARPLAB_ASSETS_BASE_URL"); - var latestUrl = GetRequiredEnvironmentVariable("SHARPLAB_ASSETS_LATEST_URL_V2"); - builder.RegisterType() - .WithParameter(new NamedParameter("baseUrl", new Uri(baseUrl))) - .WithParameter(new NamedParameter("latestUrlAbsolute", new Uri(latestUrl))) - .As() - .SingleInstance(); +public class AssetsModule : Module { + protected override void Load(ContainerBuilder builder) { + string GetRequiredEnvironmentVariable(string key) => Environment.GetEnvironmentVariable(key) + ?? throw new Exception($"{key} was not found in the environment."); - var reloadToken = GetRequiredEnvironmentVariable("SHARPLAB_ASSETS_RELOAD_TOKEN"); - builder.RegisterType() - .WithParameter(new NamedParameter("reloadToken", reloadToken)) - .AsSelf() - .SingleInstance(); - } + var baseUrl = GetRequiredEnvironmentVariable("SHARPLAB_ASSETS_BASE_URL"); + var latestUrl = GetRequiredEnvironmentVariable("SHARPLAB_ASSETS_LATEST_URL_V2"); + builder.RegisterType() + .WithParameter(new NamedParameter("baseUrl", new Uri(baseUrl))) + .WithParameter(new NamedParameter("latestUrlAbsolute", new Uri(latestUrl))) + .As() + .SingleInstance(); + + var reloadToken = GetRequiredEnvironmentVariable("SHARPLAB_ASSETS_RELOAD_TOKEN"); + builder.RegisterType() + .WithParameter(new NamedParameter("reloadToken", reloadToken)) + .AsSelf() + .SingleInstance(); } }