DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • How to Enhance the Performance of .NET Core Applications for Large Responses
  • Developing Minimal APIs Quickly With Open Source ASP.NET Core
  • Revolutionizing Content Management
  • Revolutionizing API Development: A Journey Through Clean Architecture With Adapter Pattern in ASP.NET Core

Trending

  • Understanding IEEE 802.11(Wi-Fi) Encryption and Authentication: Write Your Own Custom Packet Sniffer
  • Mastering Fluent Bit: Installing and Configuring Fluent Bit on Kubernetes (Part 3)
  • Build Your First AI Model in Python: A Beginner's Guide (1 of 3)
  • Unlocking AI Coding Assistants: Generate Unit Tests
  1. DZone
  2. Data Engineering
  3. Databases
  4. Feeding Server Timing API from ASP.NET Core

Feeding Server Timing API from ASP.NET Core

The latest addition to ASP.NET Core's performance metrics APIs is the Server Timing API. We take a look at how to make use of these metrics in your ASP.NET Core web app!

By 
Tomasz Pęczek user avatar
Tomasz Pęczek
·
Jun. 09, 17 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
8.0K Views

Join the DZone community and get the full member experience.

Join For Free

There is a number of Web APIs which allow you to measure the performance of web applications:

  • User Timing API (access to high precision timestamps).
  • Resource Timing API (timing information related to resources on a document).
  • Navigation Timing API (timing information related to navigation and elements).

The youngest member of the family is Server Timing API which allows for communicating the server performance metrics to the client. The API is not widely supported yet, but Chrome Devtools is able to interpret the information sent from the server and expose it as part of the request timing information. Let's see how this feature can be utilized from ASP.NET Core.

Basics of the Server Timing API

The Server Timing definition of metrics can be represented by the following structure.

public struct ServerTimingMetric
{
    private string _serverTimingMetric;

    public string Name { get; }

    public decimal? Value { get; }

    public string Description { get; }

    public ServerTimingMetric(string name, decimal? value, string description) {
        if (String.IsNullOrEmpty(name))
            throw new ArgumentNullException(nameof(name));

        Name = name;
        Value = value;
        Description = description;

        _serverTimingMetric = null;
    }

    public override string ToString() {
        if (_serverTimingMetric == null)
        {
            _serverTimingMetric = Name;

            if (Value.HasValue)
                _serverTimingMetric = _serverTimingMetric + "=" + Value.Value.ToString(CultureInfo.InvariantCulture);

            if (!String.IsNullOrEmpty(Description))
                _serverTimingMetric = _serverTimingMetric + ";\"" + Description + "\"";
        }

        return _serverTimingMetric;
    }
}


The only required property is name, which means that metric can be used for an indication that something has happened without any related duration information.

The metrics are delivered to the client through the Server-Timing response header. The header may occur multiple times in the response, which means that multiple metrics can be delivered through multiple headers or as a single, comma-separated list (or a combination of both). A class representing the header value could look like what I've shown below:

public class ServerTimingHeaderValue
{
    public ICollection<ServerTimingMetric> Metrics { get; }

    public ServerTimingHeaderValue() {
        Metrics = new List<ServerTimingMetric>();
    }

    public override string ToString() {
        return String.Join(",", Metrics);
    }
}


Knowing how to construct the header, we can try to feed the Chrome Devtools with some information. First, we can write an extension method which will simplify adding a header to the response.

public static class HttpResponseHeadersExtensions
{
    public static void SetServerTiming(this HttpResponse response, params ServerTimingMetric[] metrics) {
        ServerTimingHeaderValue serverTiming = new ServerTimingHeaderValue();

        foreach (ServerTimingMetric metric in metrics)
        {
            serverTiming.Metrics.Add(metric);
        }

        response.Headers.Append("Server-Timing", serverTiming.ToString());
    }
}


Now, we can create an empty web application and use the extension method for setting some metrics.

public class Startup
{
    ...

    public void Configure(IApplicationBuilder app) {
        ...

        app.Run(async (context) =>
        {
            context.Response.SetServerTiming(
                new ServerTimingMetric("cache", 300, "Cache"),
                new ServerTimingMetric("sql", 900, "Sql Server"),
                new ServerTimingMetric("fs", 600, "FileSystem"),
                new ServerTimingMetric("cpu", 1230, "Total CPU")
            );

            await context.Response.WriteAsync("-- Demo.AspNetCore.ServerTiming --");
        });
    }
}


After hitting F5 and navigating to the demo application in Chrome, the metrics should be visible in the Chrome Devtools.

Chrome Network Tab - Server Timing

Making It More Usable

The above demo shows that Server Timing API works, but from a developer perspective, we would want an easy way to get metrics from different places in the application. In the case of ASP.NET Core, it usually means middleware and service.

The service can be quite simple, it just needs to expose the collection of metrics.

public interface IServerTiming
{
    ICollection<ServerTimingMetric> Metrics { get; }
}

internal class ServerTiming : IServerTiming
{
    public ICollection<ServerTimingMetric> Metrics { get; }

    public ServerTiming() {
        Metrics = new List<ServerTimingMetric>();
    }
}


The important part is that metrics need to be collected per request. This can be achieved by properly scoping the service at registration.

public static class ServerTimingServiceCollectionExtensions
{
    public static IServiceCollection AddServerTiming(this IServiceCollection services) {
        services.AddScoped<IServerTiming, ServerTiming>();

        return services;
    }
}


The missing part is the middleware, which will set the Server-Timing header with the metrics gathered by the service. The tricky part is that the header value should be set as late as possible (so there is a chance for other components in the pipeline to provide metrics). Setting the header value before invoking the next step in the pipeline would usually be too early, while trying to do so after that might result in error, as headers could have already been sent to client. The solution to this challenge is the HttpResponse.OnStarting method, which allows adding a delegate which will be invoked just before sending the response headers.

public class ServerTimingMiddleware
{
    private readonly RequestDelegate _next;

    private static Task _completedTask = Task.FromResult<object>(null);

    public ServerTimingMiddleware(RequestDelegate next) {
        _next = next ?? throw new ArgumentNullException(nameof(next));
    }

    public Task Invoke(HttpContext context) {
        HandleServerTiming(context);

        return _next(context);
    }

    private void HandleServerTiming(HttpContext context) {
        context.Response.OnStarting(() => {
            IServerTiming serverTiming = context.RequestServices.GetRequiredService<IServerTiming>();

            if (serverTiming.Metrics.Count > 0)
            {
                context.Response.SetServerTiming(serverTiming.Metrics.ToArray());
            }

            return _completedTask;
        });
    }
}


Below is the same demo I performed toward the beginning of this article but based on middleware and service. The result is exactly the same, but now the service is accessible through DI which allows for the easy gathering of metrics.

public class Startup
{
    public void ConfigureServices(IServiceCollection services) {
        services.AddServerTiming();
    }

    public void Configure(IApplicationBuilder app) {
        ...

        app.UseServerTiming()
            .Run(async (context) =>
            {
                IServerTiming serverTiming = context.RequestServices
                    .GetRequiredService<IServerTiming>();

                serverTiming.Metrics.Add(new ServerTimingMetric("cache", 300, "Cache"));
                serverTiming.Metrics.Add(new ServerTimingMetric("sql", 900, "Sql Server"));
                serverTiming.Metrics.Add(new ServerTimingMetric("fs", 600, "FileSystem"));
                serverTiming.Metrics.Add(new ServerTimingMetric("cpu", 1230, "Total CPU"));

                await context.Response.WriteAsync("-- Demo.AspNetCore.ServerTiming --");
            });
    }
}


It is important to remember that it is the server who is in full control of which metrics are communicated to the client and when - which may mean that the middleware (or metrics gathering) should be used conditionally.

I've made all the classes mentioned in this article (and some more) available on GitHub and NuGet.

API ASP.NET ASP.NET Core Metric (unit)

Published at DZone with permission of Tomasz Pęczek. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • How to Enhance the Performance of .NET Core Applications for Large Responses
  • Developing Minimal APIs Quickly With Open Source ASP.NET Core
  • Revolutionizing Content Management
  • Revolutionizing API Development: A Journey Through Clean Architecture With Adapter Pattern in ASP.NET Core

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!