Runtime: Proposal: extend platform information in RuntimeInformation.OSDescription

Created on 15 Jun 2020  路  15Comments  路  Source: dotnet/runtime

Based on the discussion thread #37831, this proposal is to extend the output of RuntimeInformation.OSDescription string and include more information about the current platform.

The purpose is to let consumer obtained a more identifiable information about the platform from an existing OSDescription API in a consistent manner, for analytics and logging like scenarios.

For example, dotnet-sdk uses such information for dotnet --info and telemetry (src: https://github.com/dotnet/sdk/blob/3595e2a/src/Cli/Microsoft.DotNet.Cli.Utils/RuntimeEnvironment.cs). Most of that code is also replicated in a test utility method in runtime repo: https://github.com/dotnet/runtime/blob/579d8831e15fcff60a821d6fee554b7f26bba96f/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Unix.cs#L155

Currently, the output string on various platforms look like this:
| Platform | RuntimeInformation.OSDescription |
|---|---|
| FreeBSD 11 | FreeBSD 11.0-RELEASE-p1 FreeBSD 11.0-RELEASE-p1 #0 r306420: Thu Sep 29 01:43:23 UTC 2016 [email protected]:/usr/obj/usr/src/sys/GENERIC |
| Gentoo | Linux 4.19.104-gentoo #1 SMP Wed Feb 19 06:37:35 UTC 2020 |
| macOS 10.14.6 | Darwin 18.7.0 Darwin Kernel Version 18.7.0: Mon Feb 10 21:08:05 PST 2020; root:xnu-4903.278.28~1/RELEASE_X86_64 |
| SmartOS 2020 | SunOS 5.11 joyent_20200408T231825Z |
| Solaris 11.3 | SunOS 5.11 11.3 |
| Ubuntu 18.04 | Linux 4.15.0-106-generic #107-Ubuntu SMP Thu Jun 4 11:27:52 UTC 2020 |
| Windows 10 | Microsoft Windows 10.0.19635 |

This proposal is about adding a few more bits of information: name (value of PAL_UNIX_NAME or Windows), version and distro ID. For example, in some cases runtime/hosts only care about major version, so we can just use that. Expected output for the above list is:

| Platform | RuntimeInformation.OSDescription |
|---|---|
| FreeBSD 11 | FreeBSD 11.0-RELEASE-p1 FreeBSD 11.0-RELEASE-p1 #0 r306420: Thu Sep 29 01:43:23 UTC 2016 [email protected]:/usr/obj/usr/src/sys/GENERIC. Platform: FreeBSD, Version 11 |
| Gentoo | Linux 4.19.104-gentoo #1 SMP Wed Feb 19 06:37:35 UTC 2020. Platform: Linux, ID: gentoo

(distro does not provide version information) |
| macOS 10.14.6 | Darwin 18.7.0 Darwin Kernel Version 18.7.0: Mon Feb 10 21:08:05 PST 2020; root:xnu-4903.278.28~1/RELEASE_X86_64. Platform: OSX, Version: 10.14, ID: Mac OS X

(this version and ID information can be obtained by invoking sw_vers -productVersion and sw_vers -productName respectively, or by reading the XML file directly, which sw_vers uses: /System/Library/CoreServices/SystemVersion.plist) |
| SmartOS 2020 | SunOS 5.11 joyent_20200408T231825Z. Platform: illumos, Version 2020, ID: SmartOS |
| Solaris 11.3 | SunOS 5.11 11.3. Platform: Solaris, Version 11 |
| Ubuntu 18.04 | Linux 4.15.0-106-generic #107-Ubuntu SMP Thu Jun 4 11:27:52 UTC 2020. Platform: Linux, Version: 18.04, ID: ubuntu |
| Windows 10 | Microsoft Windows 10.0.19635. Platform: Windows, Version: 10.0.19635 |

Design Discussion area-System.Runtime enhancement

Most helpful comment

After all the SDK today just logs OSDescription wholesale

That's not all it does. It also does:

All 15 comments

Is anyone _parsing_ these today (as opposed to displaying/logging them)? If there's hand rolled parsers out there, do we have to take care about extending the strings? Akin to the user-agent mess in browsers.

I assume "not many" and "it's an acceptable change"

cc @richlander

Is anyone parsing these today (as opposed to displaying/logging them)?

For instance, dotnet-sdk parses that information here: https://github.com/dotnet/sdk/blob/3595e2a/src/Cli/Microsoft.DotNet.Cli.Utils/RuntimeEnvironment.cs#L77. It will continue to work as is. For Windows and macOS, it uses Environment.OSVersion. With the proposed extension, we can update the SDK to parse out this information in a platform agnostic manner.

Right, but one could imagine someone else parsing in such a way that extending the string breaks them. I think that's probably OK just pointing it out. I think it's unfortunate that we're in a position of returning a string from an API that is in a non standard format and gets parsed by user code.
cc @eerhardt in case he knows of other folks parsing it.

Ah right. I was thinking if someone is using .Contains("Linux") or .IndexOf(minimumLinuxKernelVersion) etc. with OSDescription API, this change will not break them as the first part of description has been kept intact. If they were matching the verbatim string, OSDescription == "Linux 4.15.0-106-generic #107-Ubuntu SMP Thu Jun 4 11:27:52 UTC 2020", that will break but such usage pattern is highly unlikely.

As for the proposed usage of this change; if we add the trailing comma to the last component, it would be a one-liner to extract the individual components; Platform, Version and ID using C#'s slicing feature without extra checks, e.g. for SDK's use-case:

```c#
using static System.Runtime.InteropServices.RuntimeInformation;
...
var platformName = OSDescription.Contains("Platform: ")
? OSDescription[(OSDescription.IndexOf("Platform: ") + "Platform: ".Length)..OSDescription.IndexOf(",", OSDescription.IndexOf("Platform: "))]
: null;
var version = OSDescription.Contains("Version: ")
? OSDescription[(OSDescription.IndexOf("Version: ") + "Version: ".Length)..OSDescription.IndexOf(",", OSDescription.IndexOf("Version: "))]
: null;
var distroName = OSDescription.Contains("ID: ")
? OSDescription[(OSDescription.IndexOf("ID: ") + "ID: ".Length)..OSDescription.IndexOf(",", OSDescription.IndexOf("ID: "))]
: null;

var suitablePlatformName = distroName ?? platformName;
Console.WriteLine("Platform: {0}, Version: {1}", suitablePlatformName, version);
```
(notice the trailing commas in the strings below)

Input: "Linux 4.15.0-106-generic #107-Ubuntu SMP Thu Jun 4 11:27:52 UTC 2020. Platform: Linux, Version: 18.04, ID: ubuntu,"

Result: Platform: ubuntu, Version: 18.04

Input: "Darwin 18.7.0 Darwin Kernel Version 18.7.0: Mon Feb 10 21:08:05 PST 2020; root:xnu-4903.278.28~1/RELEASE_X86_64. Platform: OSX, Version: 10.14, ID: Mac OS X,"

Result: Platform: Mac OS X, Version: 10.14

Input: "Microsoft Windows 10.0.19635. Platform: Windows, Version: 10.0.19635,"

Result: Platform: Windows, Version: 10.0.19635

@eerhardt, @danmosemsft, any remarks on the overall idea?

I have prototyped it for the supported platforms: https://github.com/dotnet/runtime/compare/master...am11:feature/more-info-in-OSDescription, each one has different way of retrieving information from system environment. The overall OSDescription output has additional distro-like information for consumers to display or parse out.

In the worse-case scenario Platform: <value>, part will always be available on all platforms and rest of the parts (Version: and ID:) may or may not be available during the run time; even on the system which provides it under normal circumstances. i.e. someone, for example, removes /etc/os-release file on a Linux-based distro, and runs an app which calls OSDescription, that will only exclude Version and ID parts from the output.

I'd like to get members of @dotnet/fxdc opinions on this proposal. Technically this isn't a change in the API, but it is a change in the data returned from an API.

If our goal is to make this information parseable - would it be better to introduce explicit APIs for this information? Instead of putting the information into a string and then saying "this is how you parse the string".

I'm in favor of improving this information. I've noticed multiple times that it isn't quite what I wanted.

I don't want to make this more of a project than it needs to be, but I'd really like a persisted spec on this topic. In short, I'd like it describe the following:

  • Information we want.
  • The information that is typically available.
  • What's the best join between those two things.
  • How to handle containers, which report the kernel of the host, when people often really want information about the guest. I think this is the most important topic.

I'm happy to work directly with you on this @am11. I'll be your PM!

That said, we need to act fast. I'm also one of the people wanting to starting pressing on the breaks for 5.0. It's almost time to lock in the release and claim victory. If this ends up being a 6.0 change, we shouldn't worry.

Thank you @richlander. I am willing to work on this with you. Surfacing the useful bits of system information in some form (new API or modifying the output of OSDescription) in 5.0 time-frame would be the best case scenario; as many new OS are being supported with this release.

My primary interest was to make SDK code more platform agnostic for .NET 5 onwards. However, I understand that it alone does not warrant such instrumentation in the runtime's public API. On the other hand, as you also noticed, the existing information provided by OSDescription is something which is not quite useful for many consumer use-cases.

The information that is typically available.

For demonstration, if we represent all this heterogeneous information as separate APIs, it would look something like this:

```c#
namespace System.Runtime.InteropServices
{
public static partial class RuntimeInformation
{
// the name which is used by IsOSPlatform(string) API to compare the parameter value (not exposed anywhere)
// e.g. Windows, Browser, or value of PAL_UNIX_NAME (FreeBSD, illumos, Linux, OSX, Solaris)
public static readonly string PlatformName; // build-time constant; never null

    // Linux and illumos specific
    //     e.g. Alpine, Android, Gentoo, Ubuntu, SmartOS, OpenIndiana
    public static readonly string? DistroId; // null on all platforms other than Linux and illumos derived ones

    // Friendly version, which is distro version in case of Linux and illumos, and OS friendly version in case of macOS
    public static readonly Version? ProductVersion; // null on Browser (webassembly)

    // Friendly name per version. available on macOS and some Linux distros, such as Ubuntu and Android
    //     e.g. Mojave, Bionic, Lollipop
    public static readonly string? ProductVersionFriendlyName; // null on majority of platforms

    // Windows specific
    public static readonly string? ServicePack;  // null on all non-Windows platforms
}

}
```

This information can be reflected in OSDescription (in separate lines or key-values as proposed above), if additional API surface is not an option.

How to handle containers

DistroId will be set to the corresponding Linux container's OS name. On other non-docker container-like scenarios (lxc containers, FreeBSD jails and Solaris/illumos zones), either DistroId (in case of lxc and zones) or PlatformName (in case of FreeBSD) will provide name of the guest OS. Moreover, ProductVersion will be (friendly) version of guest OS.

Want to DM me on twitter and then we can figure out the best way to collaborate? Me: https://twitter.com/runfaster2000

One thought I had was to create separate GetXXXInformation() APIs, which would be OS specific. That way we wouldn't have to try to generalize the information and try to make it match across operating systems.

For example, we could have:

```C#
namespace System.Runtime.InteropServices
{
public static class RuntimeInformation
{
public static WindowsInformation GetWindowsInformation() {}

    public static LinuxInformation GetLinuxInformation() {}

    public static OSXInformation GetOSXInformation() {}

   ....
}

}
```

And then could design the WindowsInformation, LinuxInformation, OSXInformation types independently as makes sense for that OS. And if you called GetLinuxInformation() on Windows, or OSX (or any OS platform where IsOSPlatform(OSPlatform.Linux) returned false) it would throw a PlatformNotSupportedException.

This way we don't have to come up with answers to "What should DistroId or ProductVersionFriendlyName mean/do on Windows?" or "What should SerivcePack mean on Ubuntu?". Instead, each OS platform can make available the information that makes sense for that platform. And all the other platforms' information simply throws PNSE.

"What should DistroId or ProductVersionFriendlyName mean/do on Windows?" or "What should SerivcePack mean on Ubuntu?"

These are indeed confusing. Perhaps, we can try to explore neutral names and otherwise throw PNSE from irrelevant properties, e.g., from ServicePack on non-Windows? This way we will not need a new public type for each supported platform.

If the purpose (per top post) is analytics and logging, then maybe it does need to be too strongly typed/fine grained. (After all the SDK today just logs OSDescription wholesale)

After all the SDK today just logs OSDescription wholesale

That's not all it does. It also does:

Some libraries tests also detect libc flavor. If it is considered as a useful bit of information about platform, perhaps an API can revel it as an enum:

```c#
namespace System.Runtime.InteropServices
{
public static class RuntimeInformation
{
public static LibCFlavor LibC { get; }
}

public enum LibCFlavor
{
    Bionic,     // Android
    Bsd,        // macOS, FreeBSD
    Emscripten, // WASM
    Gnu,        // GNU Linux distros
    MSCrt,      // Windows
    Muscle,     // Alpine Linux, Void Linux
    Posix,      // Oracle Solaris, illumos and others
}

}
```

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sahithreddyk picture sahithreddyk  路  3Comments

jzabroski picture jzabroski  路  3Comments

omariom picture omariom  路  3Comments

aggieben picture aggieben  路  3Comments

GitAntoinee picture GitAntoinee  路  3Comments