diff --git a/examples/AspNetCoreExample/Controllers/ValuesController.cs b/examples/AspNetCoreExample/Controllers/ValuesController.cs index 3cbe408..9733798 100644 --- a/examples/AspNetCoreExample/Controllers/ValuesController.cs +++ b/examples/AspNetCoreExample/Controllers/ValuesController.cs @@ -28,7 +28,14 @@ public async Task>> Get() var val = this.r.Next(); CompileMe(() => val); - + + try + { + var divide = 0; + var result = 1 / divide; + } + catch { } + return new string[] {"value1" + this.r.Next(), "value2"+ this.r.Next()}; } diff --git a/examples/AspNetCoreExample/Program.cs b/examples/AspNetCoreExample/Program.cs index 95efd35..a140702 100644 --- a/examples/AspNetCoreExample/Program.cs +++ b/examples/AspNetCoreExample/Program.cs @@ -26,6 +26,7 @@ public static void Main(string[] args) .WithGcStats() .WithJitStats() .WithThreadPoolStats() + .WithExceptionStats() .WithErrorHandler(ex => Console.WriteLine("ERROR: " + ex.ToString())) //.WithDebuggingMetrics(true); .StartCollecting(); diff --git a/src/prometheus-net.DotNetRuntime.Tests/StatsCollectors/IntegrationTests/ExceptionStatsCollectorTests.cs b/src/prometheus-net.DotNetRuntime.Tests/StatsCollectors/IntegrationTests/ExceptionStatsCollectorTests.cs new file mode 100644 index 0000000..dc81a08 --- /dev/null +++ b/src/prometheus-net.DotNetRuntime.Tests/StatsCollectors/IntegrationTests/ExceptionStatsCollectorTests.cs @@ -0,0 +1,52 @@ +using NUnit.Framework; +using Prometheus.DotNetRuntime.StatsCollectors; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Prometheus.DotNetRuntime.Tests.StatsCollectors.IntegrationTests +{ + [TestFixture] + internal class ExceptionStatsCollectorTests : StatsCollectorIntegrationTestBase + { + protected override ExceptionStatsCollector CreateStatsCollector() + { + return new ExceptionStatsCollector(); + } + + [Test] + public void Will_measure_when_occurring_an_exception() + { + // arrange + int divider = 0; + string exceptionMessage = string.Empty; + + // act + try + { + var result = 1 / divider; + } + catch (Exception ex) + { + exceptionMessage = ex.GetType().FullName; + } + + // assert + Assert.That(() => StatsCollector.ExceptionReasons.Labels(exceptionMessage).Value, Is.EqualTo(1).After(100, 1000)); + } + + [Test] + public void Will_measure_when_not_occurring_an_exception() + { + // arrange + int divider = 1; + string exceptionMessage = string.Empty; + + // act + var result = 1 / divider; + + // assert + Assert.That(() => StatsCollector.ExceptionReasons.Labels(exceptionMessage).Value, Is.EqualTo(0).After(100, 1000)); + } + } +} \ No newline at end of file diff --git a/src/prometheus-net.DotNetRuntime/DotNetRuntimeStatsBuilder.cs b/src/prometheus-net.DotNetRuntime/DotNetRuntimeStatsBuilder.cs index 06f05a8..4ef8ed5 100644 --- a/src/prometheus-net.DotNetRuntime/DotNetRuntimeStatsBuilder.cs +++ b/src/prometheus-net.DotNetRuntime/DotNetRuntimeStatsBuilder.cs @@ -28,7 +28,8 @@ public static Builder Default() .WithJitStats() .WithThreadPoolSchedulingStats() .WithThreadPoolStats() - .WithGcStats(); + .WithGcStats() + .WithExceptionStats(); } /// @@ -149,6 +150,15 @@ public Builder WithGcStats(double[] histogramBuckets = null) return this; } + /// + /// Includes quantitative and qualitative metrics of exceptions thrown + /// + public Builder WithExceptionStats() + { + StatsCollectors.AddOrReplace(new ExceptionStatsCollector()); + return this; + } + public Builder WithCustomCollector(IEventSourceStatsCollector statsCollector) { StatsCollectors.AddOrReplace(statsCollector); diff --git a/src/prometheus-net.DotNetRuntime/DotNetRuntimeStatsCollector.cs b/src/prometheus-net.DotNetRuntime/DotNetRuntimeStatsCollector.cs index d7a65a2..28f7667 100644 --- a/src/prometheus-net.DotNetRuntime/DotNetRuntimeStatsCollector.cs +++ b/src/prometheus-net.DotNetRuntime/DotNetRuntimeStatsCollector.cs @@ -3,6 +3,7 @@ using System.Collections.Immutable; using System.Linq; using System.Reflection; +using System.Runtime; using System.Runtime.InteropServices; using System.Runtime.Versioning; #if PROMV2 @@ -114,7 +115,8 @@ private void SetupConstantMetrics(MetricFactory metrics) "target_framework", "runtime_version", "os_version", - "process_architecture" + "process_architecture", + "gc_mode" ); buildInfo.Labels( @@ -122,7 +124,8 @@ private void SetupConstantMetrics(MetricFactory metrics) Assembly.GetEntryAssembly().GetCustomAttribute().FrameworkName, RuntimeInformation.FrameworkDescription, RuntimeInformation.OSDescription, - RuntimeInformation.ProcessArchitecture.ToString() + RuntimeInformation.ProcessArchitecture.ToString(), + GCSettings.IsServerGC ? "Server" : "Workstation" ) .Set(1); } diff --git a/src/prometheus-net.DotNetRuntime/StatsCollectors/ExceptionStatsCollector.cs b/src/prometheus-net.DotNetRuntime/StatsCollectors/ExceptionStatsCollector.cs new file mode 100644 index 0000000..c15a71d --- /dev/null +++ b/src/prometheus-net.DotNetRuntime/StatsCollectors/ExceptionStatsCollector.cs @@ -0,0 +1,46 @@ +using Prometheus.DotNetRuntime.EventSources; +using System; +using System.Diagnostics.Tracing; +#if PROMV2 +using Prometheus.Advanced; +#endif + + +namespace Prometheus.DotNetRuntime.StatsCollectors +{ + public class ExceptionStatsCollector : IEventSourceStatsCollector + { + private const int EventIdExceptionThrown = 80; + private const string LabelReason = "exception"; + + internal Counter ExceptionReasons { get; private set; } + + public Guid EventSourceGuid => DotNetRuntimeEventSource.Id; + + public EventKeywords Keywords => (EventKeywords)DotNetRuntimeEventSource.Keywords.Exception; + public EventLevel Level => EventLevel.Informational; + + public void RegisterMetrics(MetricFactory metrics) + { + ExceptionReasons = metrics.CreateCounter( + "dotnet_exception_reasons_total", + "Reasons that led to an exception", + LabelReason + ); + } + + public void UpdateMetrics() + { + + } + + public void ProcessEvent(EventWrittenEventArgs e) + { + if (e.EventId == EventIdExceptionThrown) + { + ExceptionReasons.Labels((string)e.Payload[0]).Inc(); + } + } + } + +}