X

Reading Windows and Linux memory metrics with .NET Core

Until .NET Core gets its own cross-platform system metrics support I needed something simple to get system memory metrics of servers where ASP.NET Core application is running. I wasn’t able to find a nice solution but I still worked out something to get system memory metrics with .NET Core on Windows and Linux.

My scenario

I solved both cases by using simple command line. Whoever comes after me has simple playground and no complex surprises. Hard to screw anything up until we have something solid in .NET Core. I’m also using same trick on some Azure VM-s.

For short this is how my solution works:

  1. Detect OS type
  2. Run command line utility delivered with OS
  3. Read and parse utility output
  4. Return memory metrics object shown below

Same thing can be probably written on Nodejs with few lines of code but I have my own plans with C#.

What about performance? Doesn’t hurt bad, I can say. On Windows it takes ~160ms to get memory metrics and on Linux it was ~130ms average. As I don’t ask memory metrics every second I can live with these numbers.

Is it Linux or Windows?

Windows and Linux use different methods to get memory metrics and therefore application needs to know on what operating system it is running. There’s RuntimeInformation in System.Runtime.InteropServices namespace.

private bool IsUnix()
{
    var isUnix = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ||
                 RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
   
    return isUnix;
}

I don’t have any Mac and therefore I’m not very sure if the same code works there. But it is based on Unix and chances are good there are some utils that work on Unix and Mac.

Use case. If you web application on Linux VM running on Azure and classic Guest metrics doesn’t work stable (in my case sometimes I get information about memory and sometimes not) then you can use my code to get valid memory metrics from Linux box.

System memory metrics on Windows

On Windows I went with wmic. If you like something else then feel free to change the code. It’s simple command that returns free and total memory of system.

wmic OS get FreePhysicalMemory,TotalVisibleMemorySize /Value

We get back few numbers and whitespace to trim. These number are in kilobytes.

It’s easy to parse these numbers out after trimming all the whitespace around them.

System memory metrics on Linux

On Linux I found nice command line utility called free. It gives also output that is easy to parse. Good thing is – we can ask metrics in megabytes.

free -m

Here is the output of command.

Again simple string to parse.

Notice bash? With Windows Subsystem for Linux we get Linux running on Windows box and we can run bash from Windows command line to execute commands on Linux. Crazy, isn’t it? Find out more from my blog post Running ASP.NET Core applications on Windows Subsystem for Linux.

Writing memory metrics client

Putting all together here is the system memory metrics client I wrote.

public class MemoryMetrics
{
    public double Total;
    public double Used;
    public double Free;
}

public class MemoryMetricsClient
{
    public MemoryMetrics GetMetrics()
    {
        if(IsUnix())
        {
            return GetUnixMetrics();
        }

        return GetWindowsMetrics();
    }

    private bool IsUnix()
    {
        var isUnix = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ||
                     RuntimeInformation.IsOSPlatform(OSPlatform.Linux);

        return isUnix;
    }

    private MemoryMetrics GetWindowsMetrics()
    {
        var output = "";

        var info = new ProcessStartInfo();
        info.FileName = "wmic";
        info.Arguments = "OS get FreePhysicalMemory,TotalVisibleMemorySize /Value";
        info.RedirectStandardOutput = true;
       
        using(var process = Process.Start(info))
        {               
            output = process.StandardOutput.ReadToEnd();
        }

        var lines = output.Trim().Split("\n");
        var freeMemoryParts = lines[0].Split("=", StringSplitOptions.RemoveEmptyEntries);
        var totalMemoryParts = lines[1].Split("=", StringSplitOptions.RemoveEmptyEntries);
   
        var metrics = new MemoryMetrics();
        metrics.Total = Math.Round(double.Parse(totalMemoryParts[1]) / 1024, 0);
        metrics.Free = Math.Round(double.Parse(freeMemoryParts[1]) / 1024, 0);
        metrics.Used = metrics.Total - metrics.Free;
       
        return metrics;           
    }

    private MemoryMetrics GetUnixMetrics()
    {
        var output = "";

        var info = new ProcessStartInfo("free -m");
        info.FileName = "/bin/bash";
        info.Arguments = "-c \"free -m\"";
        info.RedirectStandardOutput = true;
       
        using(var process = Process.Start(info))
        {               
            output = process.StandardOutput.ReadToEnd();
            Console.WriteLine(output);
        }

        var lines = output.Split("\n");
        var memory = lines[1].Split(" ", StringSplitOptions.RemoveEmptyEntries);
   
        var metrics = new MemoryMetrics();
        metrics.Total = double.Parse(memory[1]);
        metrics.Used = double.Parse(memory[2]);
        metrics.Free = double.Parse(memory[3]);

        return metrics;           
    }
}

Easiest way to try out the code is to have Windows 10 with Windows Subsystem for Linux (WSL) and some Linux from Microsoft store.

For console application here is Program class to use.

class Program
{
    static void Main(string[] args)
    {
        var client = new MemoryMetricsClient();
        var metrics = client.GetMetrics();

        Console.WriteLine("Total: " + metrics.Total);
        Console.WriteLine("Used : " + metrics.Used);
        Console.WriteLine("Free : " + metrics.Free);
    }       
}

Those who want to replace memory metrics code can go with console program as it’s smallest thing possible on .NET Core.

Practical example. Want to see straight from practice example? Head over to my blog post System memory health check for ASP.NET Core.

Wrapping up

Although .NET Core doesn’t offer any cross-platform system metrics solution out-of-box we can still come out with our custom solution by using output of command line utilities. I admit it’s not nice solution. Let’s be honest – it’s far from nice but it works with minimal side effects. Spending 200ms on getting few numbers once per minute or half a minute doesn’t put more load to system until command line utilites we are using are small. For me this solution works well until things get better on .NET Core.

Liked this post? Empower your friends by sharing it!
Categories: .NET Linux Windows

View Comments (8)

  • Not sure I agree with this. You are creating a bash process and the free process. You can just read /proc/meminfo (open as a file), no process forking.. .

  • Thanks for feedback, Matt!

    I'm still n00b on Linux and I really appreciate when guys smarter than me give me good advice.

    I will try out how /proc/meminfo works out in this scenario.

  • I concur with Matt Freeman's post that "/proc/meminfo" it's a good way to get the memory data without creating new processes. That said, I believe that is specific to Linux variants and does not work on OSX. I'll be researching that.

    I'm creating a .Net Standard 2/2.1 diagnostic library that will hopefully return current memory info with support for each of the OS's. It will additionally include some ASP.Net Core 3 Middleware to wire up routes to expose memory info as API's.

  • It would be nice to have something small and lightweight to get basic resource and performance metrics. I would like to have something on Azure small VM-s. Microsoft's own VM guest agent is unstable and often puts too much load on VM-s.

    Perhaps /proc/meminfo is not available on Mac but it's damn fast on Linux and therefore perfect fit for small VM-s.

  • Thanks for the very useful article. I have some questions regarding the Linux memory metrics. Can the memory usage of a NET 6 application which targets a linux host also be calculated by testing the application first in WSL? Or would the values calculated in WSL be completely different from the values on the "real" linux host?

Related Post