Skip to content

Commit

Permalink
Added basic CLR metrics, some minor API refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
danielcrenna committed Apr 22, 2011
1 parent 4497b92 commit 38cc306
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 11 deletions.
102 changes: 101 additions & 1 deletion README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,104 @@ How To Use

PM> Install-Package Metrics

Work in progress, nothing to see here.
**Second** ...

Metrics comes with five types of metrics:

* **Gauges** are instantaneous readings of values (e.g., a queue depth).
* **Counters** are 64-bit integers which can be incremented or decremented.
* **Meters** are increment-only counters which keep track of the rate of events.
They provide mean rates, plus exponentially-weighted moving averages which
use the same formula that the UNIX 1-, 5-, and 15-minute load averages use.
* **Histograms** capture distribution measurements about a metric: the count,
maximum, minimum, mean, standard deviation, median, 75th percentile, 95th
percentile, 98th percentile, 99th percentile, and 99.9th percentile of the
recorded values. (They do so using a method called reservoir sampling which
allows them to efficiently keep a small, statistically representative sample
of all the measurements.)
* **Timers** record the duration as well as the rate of events. In addition to
the rate information that meters provide, timers also provide the same metrics
as histograms about the recorded durations. (The samples that timers keep in
order to calculate percentiles and such are biased towards more recent data,
since you probably care more about how your application is doing *now* as
opposed to how it's done historically.)

Metrics also has support for health checks:

HealthChecks.Register("database", () =>
{
if (Database.IsConnected)
{
return HealthCheck.Healthy;
}
else
{
return HealthCheck.Unhealthy("Not connected to database");
}
});

**Third**, start collecting your metrics.

If you're simply running a benchmark, you can print registered metrics to
standard error every 10s like this:

// Print to Console.Error every 10 seconds
Metrics.EnableConsoleReporting(10, TimeUnit.Seconds)

If you're writing a ASP.NET MVC-based web service, you can reference `Metrics.AspNetMvc` in
your web application project and register default routes:

using metrics;

public class MvcApplication : HttpApplication
{
// ...
protected void Application_Start()
{
AspNetMvc.Metrics.RegisterRoutes();
// ...
}

// ...
}

The default routes will respond to the following URIs:

* `/metrics`: A JSON object of all registered metrics and a host of CLR metrics.
* `/ping`: A simple `text/plain` "pong" for load-balancers.
* `/healthcheck`: Runs through all registered `HealthCheck` instances and reports the results. Returns a `200 OK` if all succeeded, or a `500 Internal Server Error` if any failed.
* `/threads`: A `text/plain` dump of all threads and their stack traces.

The URIs of these resources can be configured by setting properties prior to registering routes.
You may also choose to protect these URIs with HTTP Basic authentication:

using metrics;

public class MvcApplication : HttpApplication
{
// ...
protected void Application_Start()
{
AspNetMvc.Metrics.HealthCheckPath = "my-healthcheck-uri";
AspNetMvc.Metrics.PingPath = "my-ping-uri";
AspNetMvc.Metrics.MetricsPath = "my-metrics-uri";
AspNetMvc.Metrics.ThreadsPath = "my-threads-uri";

AspNetMvc.Metrics.RegisterRoutes("username", "password");
// ...
}

// ...
}
License
-------
The original Metrics project is Copyright (c) 2010-2011 Coda Hale, Yammer.com

This idiomatic port of Metrics to C# and .NET is Copyright (c) 2011 Daniel Crenna, Wildbit.com

Both works are published under The MIT License, see LICENSE
26 changes: 26 additions & 0 deletions metrics.Tests/Core/CLRProfilerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Diagnostics;
using metrics.Core;
using NUnit.Framework;

namespace metrics.Tests.Core
{
[TestFixture]
public class CLRProfilerTests
{
[Test]
public void Can_get_heap_usage()
{
var heap = CLRProfiler.HeapUsage;
Assert.IsNotNull(heap);
Trace.WriteLine(heap);
}

[Test]
public void Can_get_uptime()
{
var heap = CLRProfiler.Uptime;
Assert.IsNotNull(heap);
Trace.WriteLine(heap);
}
}
}
1 change: 1 addition & 0 deletions metrics.Tests/metrics.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Core\CLRProfilerTests.cs" />
<Compile Include="Core\GaugeTests.cs" />
<Compile Include="MetricsTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
Expand Down
63 changes: 61 additions & 2 deletions metrics/Core/ClrProfiler.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,71 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

namespace metrics.Core
{
public static class CLRProfiler
{
private const string CategoryMemory = ".NET CLR Memory";
private static readonly Process _process;
private static readonly IDictionary<Process, IDictionary<string, PerformanceCounter>> _counters;

static CLRProfiler()
{
_process = Process.GetCurrentProcess();
_counters = new Dictionary<Process, IDictionary<string, PerformanceCounter>>
{
{_process, new Dictionary<string, PerformanceCounter>()}
};
}

public static string DumpThreads()
{
throw new NotImplementedException();
return "Not implemented; how about a fork?";
}

/// <summary>
/// Returns the number of seconds the CLR process has been running
/// </summary>
public static long Uptime
{
get { return Convert.ToInt64(_process.TotalProcessorTime.TotalSeconds); }
}

/// <summary>
/// Returns the percentage of the CLR's heap which is being used
/// </summary>
public static double HeapUsage
{
get
{
var counter = GetOrInstallCounter("HeapUsage", CategoryMemory);
var used = WaitForNextRawValue(counter);

var available = _process.PrivateMemorySize64;
var usage = (double)used / available;
return usage;
}
}

private static long WaitForNextRawValue(PerformanceCounter counter)
{
long used;
while((used = counter.NextSample().RawValue) == 0)
{
Thread.Sleep(10);
}
return used;
}

private static PerformanceCounter GetOrInstallCounter(string property, string category)
{
if (!_counters[_process].ContainsKey(property))
{
_counters[_process].Add(property, new PerformanceCounter(category, "# bytes in all heaps", _process.ProcessName));
}
return _counters[_process][property];
}
}
}
}
2 changes: 1 addition & 1 deletion metrics/Core/GaugeMetric.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace metrics.Core
/// A gauge metric is an instantaneous reading of a partiular value. To
/// instrument a queue's depth, for example:
/// <example>
/// <code>
/// <code>
/// var queue = new Queue<int>();
/// var gauge = new GaugeMetric<int>(() => queue.Count);
/// </code>
Expand Down
4 changes: 4 additions & 0 deletions metrics/Core/HealthCheck.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ namespace metrics
/// </summary>
public class HealthCheck
{
public static Result Healthy { get { return Result.Healthy; } }
public static Result Unhealthy(string message) { return Result.Unhealthy(message); }
public static Result Unhealthy(Exception error) { return Result.Unhealthy(error); }

private readonly Func<Result> _check;

public String Name { get; private set; }
Expand Down
12 changes: 6 additions & 6 deletions metrics/Serialization/MetricsConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@

namespace metrics.Serialization
{
internal class MetricItem
{
public string Name { get; set; }
public IMetric Metric { get; set; }
}

/// <summary>
/// Properly serializes a metrics hash
/// </summary>
internal class MetricsConverter : JsonConverter
{
internal class MetricItem
{
public string Name { get; set; }
public IMetric Metric { get; set; }
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (!(value is IDictionary<MetricName, IMetric>))
Expand Down
2 changes: 1 addition & 1 deletion metrics/metrics.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Core\ClrProfiler.cs" />
<Compile Include="Core\CLRProfiler.cs" />
<Compile Include="Core\CounterMetric.cs" />
<Compile Include="Core\GaugeMetric.cs" />
<Compile Include="Core\HealthCheck.cs" />
Expand Down

0 comments on commit 38cc306

Please sign in to comment.