Add CPU runtime counter (#23680)
authorSung Yoon Whang <suwhang@microsoft.com>
Thu, 4 Apr 2019 03:40:50 +0000 (20:40 -0700)
committerGitHub <noreply@github.com>
Thu, 4 Apr 2019 03:40:50 +0000 (20:40 -0700)
* Add cpu counter

* Fix windows build

* Make the counter just return current CPU usage as %

* Add Unix

* Fix unix build

* Some cleanup

* rename

* fixing some build errors

* some cleanup

* remove unused using

* more cleanup

* newline

* Address PR feedback

* more pr feedback

* More feedback

src/System.Private.CoreLib/System.Private.CoreLib.csproj
src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetProcessTimes.cs [new file with mode: 0644]
src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetSystemTimes.cs [new file with mode: 0644]
src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems
src/System.Private.CoreLib/src/System/Diagnostics/Eventing/RuntimeEventSource.cs
src/System.Private.CoreLib/src/System/Diagnostics/Eventing/RuntimeEventSourceHelper.Unix.cs [new file with mode: 0644]
src/System.Private.CoreLib/src/System/Diagnostics/Eventing/RuntimeEventSourceHelper.Windows.cs [new file with mode: 0644]

index e8cb0b8..3a8c2e7 100644 (file)
     <Compile Include="$(BclSourcesRoot)\System\DateTime.Unix.CoreCLR.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Globalization\GlobalizationMode.Unix.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Threading\ClrThreadPoolBoundHandle.Unix.cs" />
+    <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\RuntimeEventSourceHelper.Unix.cs" Condition="'$(FeaturePerfTracing)' == 'true'"/>
   </ItemGroup>
   <ItemGroup Condition="'$(TargetsWindows)' == 'true'">
     <Compile Include="$(BclSourcesRoot)\Internal\Runtime\InteropServices\InMemoryAssemblyLoader.cs" />
     <Compile Include="$(BclSourcesRoot)\System\ApplicationModel.Windows.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Globalization\GlobalizationMode.Windows.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Threading\ClrThreadPoolBoundHandle.Windows.cs" />
+    <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\RuntimeEventSourceHelper.Windows.cs" Condition="'$(FeaturePerfTracing)' == 'true'"/>
   </ItemGroup>
   <ItemGroup Condition="'$(FeatureAppX)' == 'true'">
     <Compile Include="$(BclSourcesRoot)\System\Threading\SynchronizationContext.Uap.cs" />
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetProcessTimes.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetProcessTimes.cs
new file mode 100644 (file)
index 0000000..22ec793
--- /dev/null
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+    internal partial class Kernel32
+    {
+        [DllImport(Libraries.Kernel32, SetLastError = true)]
+        internal extern static bool GetProcessTimes(IntPtr handleProcess, out long creation, out long exit, out long kernel, out long user);
+    }
+}
\ No newline at end of file
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetSystemTimes.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetSystemTimes.cs
new file mode 100644 (file)
index 0000000..ff26978
--- /dev/null
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+    internal partial class Kernel32
+    {
+        [DllImport(Libraries.Kernel32, SetLastError = true)]
+        internal extern static bool GetSystemTimes(out long idle, out long kernel, out long user);
+    }
+}
\ No newline at end of file
index 79a5866..f2b23c6 100644 (file)
     <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetFullPathNameW.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetLongPathNameW.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetLogicalDrives.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetProcessTimes.cs" Condition="'$(FeaturePerfTracing)' == 'true'" />
     <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetSystemDirectoryW.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetSystemInfo.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetSystemTimes.cs" Condition="'$(FeaturePerfTracing)' == 'true'" />
     <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetTempFileNameW.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetTempPathW.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetVersionExW.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.FLock.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.FSync.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.FTruncate.cs" />
-    <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.GetCpuUtilization.cs" Condition="'$(FeaturePortableThreadPool)' == 'true'" />
+    <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.GetCpuUtilization.cs" Condition="'$(FeaturePortableThreadPool)' == 'true' Or '$(FeaturePerfTracing)' == 'true'" />
     <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.GetCwd.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.GetEUid.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.GetHostName.cs" />
index 010c2ea..31effe3 100644 (file)
@@ -20,6 +20,7 @@ namespace System.Diagnostics.Tracing
         private IncrementingPollingCounter _gen1GCCounter;
         private IncrementingPollingCounter _gen2GCCounter;
         private IncrementingPollingCounter _exceptionCounter;
+        private PollingCounter _cpuTimeCounter;
 
         private const int EnabledPollingIntervalMilliseconds = 1000; // 1 second
 
@@ -41,6 +42,7 @@ namespace System.Diagnostics.Tracing
                 // overhead by at all times even when counters aren't enabled.
 
                 // On disable, PollingCounters will stop polling for values so it should be fine to leave them around.
+                _cpuTimeCounter = _cpuTimeCounter ?? new PollingCounter("CPU Usage", this, () => RuntimeEventSourceHelper.GetCpuUsage());
                 _gcHeapSizeCounter = _gcHeapSizeCounter ?? new PollingCounter("GC Heap Size", this, () => GC.GetTotalMemory(false));
                 _gen0GCCounter = _gen0GCCounter ?? new IncrementingPollingCounter("Gen 0 GC Count", this, () => GC.CollectionCount(0));
                 _gen1GCCounter = _gen1GCCounter ?? new IncrementingPollingCounter("Gen 1 GC Count", this, () => GC.CollectionCount(1));
diff --git a/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/RuntimeEventSourceHelper.Unix.cs b/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/RuntimeEventSourceHelper.Unix.cs
new file mode 100644 (file)
index 0000000..ff1eff4
--- /dev/null
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+
+namespace System.Diagnostics.Tracing
+{
+    internal sealed class RuntimeEventSourceHelper
+    {
+        private static Interop.Sys.ProcessCpuInformation cpuInfo;
+
+        internal static int GetCpuUsage()
+        {
+            return Interop.Sys.GetCpuUtilization(ref cpuInfo);
+        }
+    }
+}
diff --git a/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/RuntimeEventSourceHelper.Windows.cs b/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/RuntimeEventSourceHelper.Windows.cs
new file mode 100644 (file)
index 0000000..778961e
--- /dev/null
@@ -0,0 +1,51 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using System;
+
+
+namespace System.Diagnostics.Tracing
+{
+    internal sealed class RuntimeEventSourceHelper
+    {
+        private static long prevProcUserTime = 0;
+        private static long prevProcKernelTime = 0;
+        private static long prevSystemUserTime = 0;
+        private static long prevSystemKernelTime = 0;
+
+        internal static int GetCpuUsage()
+        {
+            // Returns the current process' CPU usage as a percentage
+
+            int cpuUsage;
+
+            if (!Interop.Kernel32.GetProcessTimes(Interop.Kernel32.GetCurrentProcess(), out _, out _, out long procKernelTime, out long procUserTime))
+            {
+                return 0;
+            }
+
+            if (!Interop.Kernel32.GetSystemTimes(out _, out long systemUserTime, out long systemKernelTime))
+            {
+                return 0;
+            }
+
+            if (prevSystemUserTime == 0 && prevSystemKernelTime == 0) // These may be 0 when we report CPU usage for the first time, in which case we should just return 0. 
+            {
+                cpuUsage = 0;
+            }
+            else
+            {
+                long totalProcTime = (procUserTime - prevProcUserTime) + (procKernelTime - prevProcKernelTime);
+                long totalSystemTime = (systemUserTime - prevSystemUserTime) + (systemKernelTime - prevSystemKernelTime);
+                cpuUsage = (int)(totalProcTime * 100 / totalSystemTime);
+            }
+
+            prevProcUserTime = procUserTime;
+            prevProcKernelTime = procKernelTime;
+            prevSystemUserTime = systemUserTime;
+            prevSystemKernelTime = systemKernelTime;
+
+            return cpuUsage;
+        }
+    }
+}