dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.11k stars 4.7k forks source link

Proposal: extend platform information in RuntimeInformation.OSDescription #37923

Open am11 opened 4 years ago

am11 commented 4 years ago

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 root@releng2.nyi.freebsd.org:/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 root@releng2.nyi.freebsd.org:/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
danmoseley commented 4 years ago

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"

danmoseley commented 4 years ago

cc @richlander

am11 commented 4 years ago

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.

danmoseley commented 4 years ago

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.

am11 commented 4 years ago

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:

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

am11 commented 4 years ago

@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.

eerhardt commented 4 years ago

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".

richlander commented 4 years ago

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:

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.

am11 commented 4 years ago

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:

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.

richlander commented 4 years ago

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

eerhardt commented 4 years ago

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:

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.

am11 commented 4 years ago

"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.

danmoseley commented 4 years ago

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)

eerhardt commented 4 years ago

After all the SDK today just logs OSDescription wholesale

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

am11 commented 4 years ago

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:

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
    }
}
abhitalks commented 2 years ago

Just to note that System.Runtime.InteropServices.RuntimeInformation.OSDescription returns the same string: Microsoft Windows 6.3.9600 for both Windows 8.1 and Windows Server 2012 R2.

Similarly need a way to distinguish between Win 8 and Win 2012 (both 6.2.9200) and so on.