Skip to content

Commit

Permalink
1) Add feature: possible to measure the performance of methods (actio…
Browse files Browse the repository at this point in the history
…ns) in an *AspNetCore MVC* application using the special `WatchingPerformanceAttribute` attribute, as well as configure the methods performance watching for the controllers in `Startup.cs`;

2) Add code refactoring.
  • Loading branch information
unchase committed Mar 8, 2020
1 parent 9aa3a96 commit c3ba79c
Show file tree
Hide file tree
Showing 32 changed files with 1,266 additions and 146 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@

These are the changes to each version that has been released on the official [NuGet Gallery (Common)](https://www.nuget.org/packages/Unchase.FluentPerformanceMeter) and [NuGet Gallery (MVC)](https://www.nuget.org/packages/Unchase.FluentPerformanceMeter.AspNetCore.Mvc).

## v2.0.0 `(2020-03-08)`

- [x] Add feature: possible to measure the performance of methods (actions) in an *AspNetCore MVC* application using the special `WatchingPerformanceAttribute` attribute, as well as configure the methods performance watching for the controllers in `Startup.cs`
- [x] Add code refactoring

## v1.2.3 `(2020-03-03)`

- [x] Replace all `Task` by `ValueTask`
Expand Down
97 changes: 97 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ The data obtained as a result of the method’s performance measurement can be u
* [Examples of usage](#SimpleSamples)
* [Method's performance measurement](#SimpleSamples)
* [Method's performance measurement with `DiagnosticSource`](#DiagnosticSourceSample)
* [Method's performance measurement with `WatchingPerformanceAttribute` attribute](#WatchingPerformanceSample)
* [Measuring the performance of an external library method](#SampleExternal)
* [Adding Custom Data and spliting into Steps](#SampleCustomData)
* [Excluding the measurement](#SampleIgnore)
Expand Down Expand Up @@ -278,6 +279,102 @@ After calling the method `SimpleWatchingMethodStartWithArgs` and calling `GetPer
}
```

### <a name="WatchingPerformanceSample"></a> Method's performance measurement with `WatchingPerformanceAttribute` attribute

Starting with [*v2.0.0*](https://github.com/unchase/Unchase.FluentPerformanceMeter/releases/tag/v2.0.0), it became possible to measure the performance of methods (actions) in an *AspNetCore MVC* application using the special `WatchingPerformanceAttribute` attribute, as well as configure the methods performance watching for the controllers in `Startup.cs`. To do this, add the *NuGet* package [`Unchase.FluentPerformanceMeter.AspNetCore.Mvc`](https://www.nuget.org/Unchase.FluentPerformanceMeter.AspNetCore.Mvc) to the project and add the following code to `Startap.cs`:

```csharp
public void ConfigureServices(IServiceCollection services)
{
// ...
// allows to measure methods performance for class "MeasurableController" with configuring options
services.AddPerformanceMeter<MeasurableController>(options =>
{
// ALL of this is optional. You can simply call .AddPerformanceMeter<MeasurableController>() for all defaults
// Defaults: In-Memory for 5 minutes, everything watched, every user can see
// excludes a method from performance watching
options.ExcludeMethod(nameof(PerformanceMeterController.SimpleWatchingMethodStartWatchingPerformanceAttribute));

// to control which requests are watched, use the Func<HttpRequest, bool> option:
options.ShouldWatching = request => request.HttpContext.User.IsInRole("Dev");

// allows to add custom data from custom attributes ("MethodCustomDataAttribute", "MethodCallerAttribute") to performance watching
options.AddCustomDataFromCustomAttributes = false;

// allows to use "IgnoreMethodPerformanceAttribute" for excluding from performance watching
options.UseIgnoreMethodPerformanceAttribute = false;

// allows to watch actions performance annotated with special attribute ("WatchingPerformanceAttribute")
options.WatchForAnnotatedWithAttributeOnly = false;

// excludes a path from being watched
options.IgnorePath("/some_path");

// allows to add route path to custom data (with "pm_path" key)
options.AddRoutePathToCustomData = false;

// set cache time for the watched performance results for the controller class
options.SetMethodCallsCacheTime(5);

// adds common custom data (anonymous class) to class performance information
options.AddCustomData("Custom anonymous class", new { Name = "Custom Name", Value = 1 });

// set default exception handler for the controller class
options.SetDefaultExceptionHandler((ex) => Debug.WriteLine(ex.Message));
});
// ... and for "MeasurableSecondController" (without configuring options)
services.AddPerformanceMeter<MeasurableSecondController>();
// ... the same for another controllers
services.AddMvc();

// ...
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// ...
app.UseRouting();

app.UseEndpoints(c =>
{
c.MapControllers();

// use performance watching for concrete controller (for example, "MeasurableController")
app.UsePerformanceMeterFor<MeasurableController>();
// ... the same for another controllers
});
}
```

Then mark with the attribute `WatchingPerformanceAttribute` either individual methods:

```csharp
[HttpGet("SimpleWatchingMethodStartWatchingPerformanceAttribute")]
[WatchingPerformance]
public ActionResult SimpleWatchingMethodStartWatchingPerformanceAttribute()
{
return Ok();
}
```

or the whole class:

```csharp
[ApiController]
[Route("api/v1/[controller]")]
[Produces("application/json")]
[SwaggerTag("Unchase.PerformanceMeter Test WebAPI Controller")]
[WatchingPerformance]
public class PerformanceMeterController : ControllerBase
{
// measurable methods (actions)
}
```

### <a name="SampleExternal"></a> Measuring the performance of an external library method

To measure the performance of a *public* method of a *public* class of a third-party library, you should explicitly specify the class itself and the name of its method:
Expand Down
98 changes: 98 additions & 0 deletions README_RU.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
* [Примеры использования](#SimpleSamples)
* [Измерение производительности метода](#SimpleSamples)
* [Измерение производительности метода с помощью `DiagnosticSource`](#DiagnosticSourceSample)
* [Измерение производительности метода с помощью атрибута `WatchingPerformanceAttribute`](#WatchingPerformanceSample)
* [Измерение производительности метода используемой библиотеки](#SampleExternal)
* [Добавление дополнительных данных (Custom Data) и разбиение на шаги (Steps)](#SampleCustomData)
* [Исключение из замера (Ignore)](#SampleIgnore)
Expand Down Expand Up @@ -283,6 +284,103 @@ public ActionResult SimpleWatchingMethodStartWithArgs(DTOArgument arg)
}
```

### <a name="WatchingPerformanceSample"></a> Измерение производительности метода с помощью атрибута `WatchingPerformanceAttribute`

Начиная с версии [*v2.0.0*](https://github.com/unchase/Unchase.FluentPerformanceMeter/releases/tag/v2.0.0) появилась возможность мерять производительность методов в *AspNetCore MVC* приложении с помощью специального атрибута `WatchingPerformanceAttribute`, а также конфигурировать сбор производительности методов контроллера в `Startup.cs`.
Для этого необходимо добавить в проект *NuGet* пакет [`Unchase.FluentPerformanceMeter.AspNetCore.Mvc`](https://www.nuget.org/Unchase.FluentPerformanceMeter.AspNetCore.Mvc), и добавить в `Startap.cs` следующий код:

```csharp
public void ConfigureServices(IServiceCollection services)
{
// ...
// allows to measure methods performance for class "MeasurableController" with configuring options
services.AddPerformanceMeter<MeasurableController>(options =>
{
// ALL of this is optional. You can simply call .AddPerformanceMeter<MeasurableController>() for all defaults
// Defaults: In-Memory for 5 minutes, everything watched, every user can see
// excludes a method from performance watching
options.ExcludeMethod(nameof(PerformanceMeterController.SimpleWatchingMethodStartWatchingPerformanceAttribute));

// to control which requests are watched, use the Func<HttpRequest, bool> option:
options.ShouldWatching = request => request.HttpContext.User.IsInRole("Dev");

// allows to add custom data from custom attributes ("MethodCustomDataAttribute", "MethodCallerAttribute") to performance watching
options.AddCustomDataFromCustomAttributes = false;

// allows to use "IgnoreMethodPerformanceAttribute" for excluding from performance watching
options.UseIgnoreMethodPerformanceAttribute = false;

// allows to watch actions performance annotated with special attribute ("WatchingPerformanceAttribute")
options.WatchForAnnotatedWithAttributeOnly = false;

// excludes a path from being watched
options.IgnorePath("/some_path");

// allows to add route path to custom data (with "pm_path" key)
options.AddRoutePathToCustomData = false;

// set cache time for the watched performance results for the controller class
options.SetMethodCallsCacheTime(5);

// adds common custom data (anonymous class) to class performance information
options.AddCustomData("Custom anonymous class", new { Name = "Custom Name", Value = 1 });

// set default exception handler for the controller class
options.SetDefaultExceptionHandler((ex) => Debug.WriteLine(ex.Message));
});
// ... and for "MeasurableSecondController" (without configuring options)
services.AddPerformanceMeter<MeasurableSecondController>();
// ... the same for another controllers
services.AddMvc();

// ...
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// ...
app.UseRouting();

app.UseEndpoints(c =>
{
c.MapControllers();

// use performance watching for concrete controller (for example, "MeasurableController")
app.UsePerformanceMeterFor<MeasurableController>();
// ... the same for another controllers
});
}
```

После чего пометить атрибутом `WatchingPerformanceAttribute` либо отдельные методы:

```csharp
[HttpGet("SimpleWatchingMethodStartWatchingPerformanceAttribute")]
[WatchingPerformance]
public ActionResult SimpleWatchingMethodStartWatchingPerformanceAttribute()
{
return Ok();
}
```

либо весь класс:

```csharp
[ApiController]
[Route("api/v1/[controller]")]
[Produces("application/json")]
[SwaggerTag("Unchase.PerformanceMeter Test WebAPI Controller")]
[WatchingPerformance]
public class PerformanceMeterController : ControllerBase
{
// measurable methods (actions)
}
```

### <a name="SampleExternal"></a> Измерение производительности метода используемой библиотеки

Чтобы замерить производительность *public* метода *public* класса сторонней используемой библиотеки, необходимо явно задать сам класс и имя его метода:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ public AddMethodArgumentsToCustomDataAttribute(string argumentsKey = "arguments"
/// <param name="context"><see cref="ActionExecutingContext"/>.</param>
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.Items.TryGetValue("PerformanceMeter", out object performanceMeter))
if (context.HttpContext.Items.TryGetValue("PerformanceMeter", out var performanceMeter))
{
var tryAddCustomDataMethod = performanceMeter.GetType().GetMethod("TryAddCustomData");
tryAddCustomDataMethod.Invoke(performanceMeter, new object[] { this._argumentsKey, context.ActionArguments });
tryAddCustomDataMethod?.Invoke(performanceMeter, new object[] { this._argumentsKey, context.ActionArguments });
}

base.OnActionExecuting(context);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace Unchase.FluentPerformanceMeter.AspNetCore.Mvc.Attributes
{
/// <summary>
/// Attribute to indicate that performance watching will be performed with diagnostic source.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class WatchingPerformanceAttribute : Attribute { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using System;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Routing;
using Unchase.FluentPerformanceMeter.Attributes;
using Unchase.FluentPerformanceMeter.Builders;
using Unchase.FluentPerformanceMeter.Extensions;

namespace Unchase.FluentPerformanceMeter.AspNetCore.Mvc.Common
{
internal static class PerformanceMeterCommonMethods
{
#region Properties

internal static string QueryStringCustomDataKey { get; set; } = "pm_queryString";

internal static string UserIdentityNameCustomDataKey { get; } = "pm_userIdentityName";

internal static string PathCustomDataKey { get; } = "pm_path";

#endregion

#region Methods

internal static bool ShouldWatching<TClass>(HttpRequest request, PerformanceMeterMvcOptions<TClass> options)
where TClass : ControllerBase
{
foreach (var ignored in options.IgnoredPaths)
{
if (ignored != null && request.Path.Value.Contains(ignored, StringComparison.OrdinalIgnoreCase))
{
return false;
}
}

return options.ShouldWatching?.Invoke(request) ?? true;
}

internal static bool CheckExcludedMethods<TClass>(ControllerActionDescriptor controllerActionDescriptor,
PerformanceMeterMvcOptions<TClass> options) where TClass : ControllerBase
{
return options?.ExcludedMethods?.Contains(controllerActionDescriptor.ActionName) ?? false;
}

internal static bool CheckAnnotatedAttribute<TClass>(ControllerActionDescriptor controllerActionDescriptor,
PerformanceMeterMvcOptions<TClass> options, Type attributeType) where TClass : ControllerBase
{
return (options == null || options.WatchForAnnotatedWithAttributeOnly)
&& !controllerActionDescriptor.MethodInfo
.GetCustomAttributes(attributeType, false).Any()
&& !controllerActionDescriptor.ControllerTypeInfo
.GetCustomAttributes(attributeType, false).Any();
}

internal static bool CheckIgnoreMethodPerformanceAttribute<TClass>(
ControllerActionDescriptor controllerActionDescriptor, PerformanceMeterMvcOptions<TClass> options)
where TClass : ControllerBase
{
return (options == null || options.UseIgnoreMethodPerformanceAttribute)
&& controllerActionDescriptor.MethodInfo
.GetCustomAttributes(typeof(IgnoreMethodPerformanceAttribute), false).Any();
}

internal static SettingsBuilder<TClass> AddCustomDataFromCustomAttributes<TClass>(this SettingsBuilder<TClass> settingsBuilder, HttpContext context, ControllerActionDescriptor controllerActionDescriptor, PerformanceMeterMvcOptions<TClass> options) where TClass : ControllerBase
{
if (options == null || options.AddCustomDataFromCustomAttributes)
{
foreach (MethodCustomDataAttribute methodCustomData in controllerActionDescriptor.MethodInfo.GetCustomAttributes(typeof(MethodCustomDataAttribute), false))
{
settingsBuilder = settingsBuilder.CustomData(methodCustomData.Key, methodCustomData.Value);
}
if (context.Request.QueryString.HasValue)
settingsBuilder = settingsBuilder.CustomData(QueryStringCustomDataKey, context.Request.QueryString.Value);
if (context.User.Identity.IsAuthenticated)
settingsBuilder = settingsBuilder.CustomData(UserIdentityNameCustomDataKey, context.User.Identity.Name);

// add caller from attributes
settingsBuilder = settingsBuilder.CallerFrom(context.Connection?.RemoteIpAddress?.ToString() ?? context.Connection?.LocalIpAddress?.ToString());
foreach (MethodCallerAttribute methodCaller in controllerActionDescriptor.MethodInfo.GetCustomAttributes(typeof(MethodCallerAttribute), false))
{
settingsBuilder = settingsBuilder.CallerFrom(methodCaller.Caller);
}
}

// add route path to custom data
if (options == null || options.AddRoutePathToCustomData)
{
var url = StringBuilderCache.Get()
.Append(context.Request.Scheme)
.Append("://")
.Append(context.Request.Host.Value)
.Append(context.Request.PathBase.Value)
.Append(context.Request.Path.Value)
.Append(context.Request.QueryString.Value)
.ToStringRecycle();

var routeData = context.GetRouteData();
if (routeData != null)
{
settingsBuilder = settingsBuilder.CustomData(PathCustomDataKey, routeData.Values["controller"] + "/" + routeData.Values["action"]);
}
else
{
if (url.Length > 50)
url = url.Remove(50);
settingsBuilder = settingsBuilder.CustomData(PathCustomDataKey, url);
}
}

return settingsBuilder;
}

#endregion
}
}
Loading

0 comments on commit c3ba79c

Please sign in to comment.