Health Service reports

Applies to: Windows Server 2019, Windows Server 2016

What are reports

The Health Service reduces the work required to get live performance and capacity information from your Storage Spaces Direct cluster. One new cmdlet provides a curated list of essential metrics, which are collected efficiently and aggregated dynamically across nodes, with built-in logic to detect cluster membership. All values are real-time and point-in-time only.

Usage in PowerShell

Use this cmdlet to get metrics for the entire Storage Spaces Direct cluster:

Get-StorageSubSystem Cluster* | Get-StorageHealthReport

The optional Count parameter indicates how many sets of values to return, at one second intervals.

Get-StorageSubSystem Cluster* | Get-StorageHealthReport -Count <Count>  

You can also get metrics for one specific volume or server:

Get-Volume -FileSystemLabel <Label> | Get-StorageHealthReport -Count <Count>  

Get-StorageNode -Name <Name> | Get-StorageHealthReport -Count <Count>

Usage in .NET and C#

Connect

In order to query the Health Service, you will need to establish a CimSession with the cluster. To do so, you will need some things that are only available in full .NET, meaning you cannot readily do this directly from a web or mobile app. These code samples will use C#, the most straightforward choice for this data access layer.

...
using System.Security;
using Microsoft.Management.Infrastructure;

public CimSession Connect(string Domain = "...", string Computer = "...", string Username = "...", string Password = "...")
{
    SecureString PasswordSecureString = new SecureString();
    foreach (char c in Password)
    {
        PasswordSecureString.AppendChar(c);
    }

    CimCredential Credentials = new CimCredential(
        PasswordAuthenticationMechanism.Default, Domain, Username, PasswordSecureString);
    WSManSessionOptions SessionOptions = new WSManSessionOptions();
    SessionOptions.AddDestinationCredentials(Credentials);
    Session = CimSession.Create(Computer, SessionOptions);
    return Session;
}

The provided Username should be a local Administrator of the target Computer.

It is recommended that you construct the Password SecureString directly from user input in real-time, so their password is never stored in memory in cleartext. This helps mitigate a variety of security concerns. But in practice, constructing it as above is common for prototyping purposes.

Discover objects

With the CimSession established, you can query Windows Management Instrumentation (WMI) on the cluster.

Before you can get faults or metrics, you will need to get instances of several relevant objects. First, the MSFT_StorageSubSystem which represents Storage Spaces Direct on the cluster. Using that, you can get every MSFT_StorageNode in the cluster, and every MSFT_Volume, the data volumes. Finally, you will need the MSFT_StorageHealth, the Health Service itself, too.

CimInstance Cluster;
List<CimInstance> Nodes;
List<CimInstance> Volumes;
CimInstance HealthService;

public void DiscoverObjects(CimSession Session)
{
    // Get MSFT_StorageSubSystem for Storage Spaces Direct
    Cluster = Session.QueryInstances(@"root\microsoft\windows\storage", "WQL", "SELECT * FROM MSFT_StorageSubSystem")
        .First(Instance => (Instance.CimInstanceProperties["FriendlyName"].Value.ToString()).Contains("Cluster"));

    // Get MSFT_StorageNode for each cluster node
    Nodes = Session.EnumerateAssociatedInstances(Cluster.CimSystemProperties.Namespace,
        Cluster, "MSFT_StorageSubSystemToStorageNode", null, "StorageSubSystem", "StorageNode").ToList();

    // Get MSFT_Volumes for each data volume
    Volumes = Session.EnumerateAssociatedInstances(Cluster.CimSystemProperties.Namespace,
        Cluster, "MSFT_StorageSubSystemToVolume", null, "StorageSubSystem", "Volume").ToList();

    // Get MSFT_StorageHealth itself
    HealthService = Session.EnumerateAssociatedInstances(Cluster.CimSystemProperties.Namespace,
        Cluster, "MSFT_StorageSubSystemToStorageHealth", null, "StorageSubSystem", "StorageHealth").First();
}

These are the same objects you get in PowerShell using cmdlets like Get-StorageSubSystem, Get-StorageNode, and Get-Volume.

You can access all the same properties, documented at Storage Management API Classes.

...
using System.Diagnostics;

foreach (CimInstance Node in Nodes)
{
    // For illustration, write each node's Name to the console. You could also write State (up/down), or anything else!
    Debug.WriteLine("Discovered Node " + Node.CimInstanceProperties["Name"].Value.ToString());
}

Invoke GetReport to begin streaming samples of an expert-curated list of essential metrics, which are collected efficiently and aggregated dynamically across nodes, with built-in logic to detect cluster membership. Samples will arrive every second thereafter. All values are real-time and point-in-time only.

Metrics can be streamed for three scopes: the cluster, any node, or any volume.

The complete list of metrics available at each scope in Windows Server 2016 is documented below.

IObserver.OnNext()

This sample code uses the Observer Design Pattern to implement an Observer whose OnNext() method will be invoked when each new sample of metrics arrives. Its OnCompleted() method will be called if/when streaming ends. For example, you might use it to reinitiate streaming, so it continues indefinitely.

class MetricsObserver<T> : IObserver<T>
{
    public void OnNext(T Result)
    {
        // Cast
        CimMethodStreamedResult StreamedResult = Result as CimMethodStreamedResult;

        if (StreamedResult != null)
        {
            // For illustration, you could store the metrics in this dictionary
            Dictionary<string, string> Metrics = new Dictionary<string, string>();

            // Unpack
            CimInstance Report = (CimInstance)StreamedResult.ItemValue;
            IEnumerable<CimInstance> Records = (IEnumerable<CimInstance>)Report.CimInstanceProperties["Records"].Value;
            foreach (CimInstance Record in Records)
            {
                /// Each Record has "Name", "Value", and "Units"
                Metrics.Add(
                    Record.CimInstanceProperties["Name"].Value.ToString(),
                    Record.CimInstanceProperties["Value"].Value.ToString()
                    );
            }

            // TODO: Whatever you want!
        }
    }
    public void OnError(Exception e)
    {
        // Handle Exceptions
    }
    public void OnCompleted()
    {
        // Reinvoke BeginStreamingMetrics(), defined in the next section
    }
}

Begin streaming

With the Observer defined, you can begin streaming.

Specify the target CimInstance to which you want the metrics scoped. It can be the cluster, any node, or any volume.

The count parameter is the number of samples before streaming ends.

CimInstance Target = Cluster; // From among the objects discovered in DiscoverObjects()

public void BeginStreamingMetrics(CimSession Session, CimInstance HealthService, CimInstance Target)
{
    // Set Parameters
    CimMethodParametersCollection MetricsParams = new CimMethodParametersCollection();
    MetricsParams.Add(CimMethodParameter.Create("TargetObject", Target, CimType.Instance, CimFlags.In));
    MetricsParams.Add(CimMethodParameter.Create("Count", 999, CimType.UInt32, CimFlags.In));
    // Enable WMI Streaming
    CimOperationOptions Options = new CimOperationOptions();
    Options.EnableMethodResultStreaming = true;
    // Invoke API
    CimAsyncMultipleResults<CimMethodResultBase> InvokeHandler;
    InvokeHandler = Session.InvokeMethodAsync(
        HealthService.CimSystemProperties.Namespace, HealthService, "GetReport", MetricsParams, Options
        );
    // Subscribe the Observer
    MetricsObserver<CimMethodResultBase> Observer = new MetricsObserver<CimMethodResultBase>(this);
    IDisposable Disposeable = InvokeHandler.Subscribe(Observer);
}

Needless to say, these metrics can be visualized, stored in a database, or used in whatever way you see fit.

Properties of reports

Every sample of metrics is one "report" which contains many "records" corresponding to individual metrics.

For the full schema, inspect the MSFT_StorageHealthReport and MSFT_HealthRecord classes in storagewmi.mof.

Each metric has just three properties, per this table.

Property Example
Name IOLatencyAverage
Value 0.00021
Units 3

Units = { 0, 1, 2, 3, 4 }, where 0 = "Bytes", 1 = "BytesPerSecond", 2 = "CountPerSecond", 3 = "Seconds", or 4 = "Percentage".

Coverage

Below are the metrics available for each scope in Windows Server 2016.

MSFT_StorageSubSystem

Name Units
CPUUsage 4
CapacityPhysicalPooledAvailable 0
CapacityPhysicalPooledTotal 0
CapacityPhysicalTotal 0
CapacityPhysicalUnpooled 0
CapacityVolumesAvailable 0
CapacityVolumesTotal 0
IOLatencyAverage 3
IOLatencyRead 3
IOLatencyWrite 3
IOPSRead 2
IOPSTotal 2
IOPSWrite 2
IOThroughputRead 1
IOThroughputTotal 1
IOThroughputWrite 1
MemoryAvailable 0
MemoryTotal 0

MSFT_StorageNode

Name Units
CPUUsage 4
IOLatencyAverage 3
IOLatencyRead 3
IOLatencyWrite 3
IOPSRead 2
IOPSTotal 2
IOPSWrite 2
IOThroughputRead 1
IOThroughputTotal 1
IOThroughputWrite 1
MemoryAvailable 0
MemoryTotal 0

MSFT_Volume

Name Units
CapacityAvailable 0
CapacityTotal 0
IOLatencyAverage 3
IOLatencyRead 3
IOLatencyWrite 3
IOPSRead 2
IOPSTotal 2
IOPSWrite 2
IOThroughputRead 1
IOThroughputTotal 1
IOThroughputWrite 1

See also