├── .drone.yml ├── .github └── workflows │ └── CpuUsage-Tests-Matrix.yml ├── .gitignore ├── .idea └── .idea.Universe.CpuUsage │ └── .idea │ └── runConfigurations │ ├── Benchmarks_Core_2_2.xml │ └── Benchmarks_NET_4_7.xml ├── .travis.yml ├── .vscode ├── extensions.json ├── launch.json └── tasks.json ├── FULL-TIME-REPORT.sh ├── Info ├── Benchark-via-fio-on-AzurePipelines.txt ├── Benchmark.txt ├── TODO-on-Linux.txt ├── express.test.sh └── show-platform.sh ├── LICENSE ├── README.md ├── Shared ├── Targeting.props ├── Universe.CpuUsage.snk └── Version.props ├── Tests4Mono ├── On-Local-Mac-or-Linux.sh ├── Universe.CpuUsage.MonoTests.sln ├── Universe.CpuUsage.MonoTests │ ├── App.config │ ├── FirstTest.cs │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Universe.CpuUsage.MonoTests.csproj │ ├── ZeroTest.cs │ └── packages.config ├── build-the-matrix.sh ├── install-dotnet.sh ├── nuget.config └── say.include.sh ├── Universe.CpuUsage.Banchmark ├── BenchmarkProgram.cs ├── CpuUsageBenchmarks.cs ├── Universe.CpuUsage.Banchmark.csproj └── run-llvm.sh ├── Universe.CpuUsage.Tests ├── AsyncSchedulerCases.cs ├── CpuLoader.cs ├── CpuUsageAsyncWatcher_AsyncStreamTests.cs ├── CpuUsageAsyncWatcher_Tests.cs ├── CrossInfo │ └── CrossInfo.cs ├── CrossInfo_Tests.cs ├── Env.cs ├── LinuxKernelCacheFlusher.cs ├── LinuxResourcesUsage_Reader_Tests.cs ├── NUnitTestsBase.cs ├── PosixProcessPriority.cs ├── PosixResourcesUsage_Tests.cs ├── PrecisionTest.cs ├── RisingTest.cs ├── SmokeCpuUsage.cs ├── Statistica │ ├── HistogramReport.cs │ ├── PercentileCalc.cs │ └── Statistica.cs ├── Test_IsSupported.cs └── Universe.CpuUsage.Tests.csproj ├── Universe.CpuUsage.sln ├── Universe.CpuUsage ├── CpuUsage.cs ├── CpuUsageAsyncWatcher.cs ├── CpuUsageReader.cs ├── CrossInfo.cs ├── LegacyNetStandardInterop.cs ├── LinuxResourceUsageReader.cs ├── MacOsThreadInfo.cs ├── PosixResourceUsage.cs ├── Universe.CpuUsage.csproj └── WindowsCpuUsage.cs ├── Using Net Standard.url ├── appveyor.yml ├── azure-pipelines.yml ├── azure-steps-nix.yml ├── azure-steps-nix.yml-Prev ├── benchmark.linux-2.sh ├── benchmark.linux.sh ├── benchmark.win.cmd ├── build-and-test-in-docker.sh ├── build.cmd ├── images ├── CpuUsage.Icon-V2.png ├── CpuUsage.Icon.png ├── Main.Icon.pdn ├── Universe.CpuUsage-benchmark.png └── Universe.CpuUsage-implementation-table.png ├── nuget.config-ignored └── test-on-mono-only-platforms.sh /.drone.yml: -------------------------------------------------------------------------------- 1 | kind: pipeline 2 | type: docker 3 | name: 'ARM 64 bit: Build and test' 4 | 5 | platform: 6 | os: linux 7 | arch: arm64 8 | 9 | steps: 10 | - name: 'Build & Test on ARM 64 bit' 11 | image: 'debian:stretch' 12 | commands: 13 | - bash -e build-and-test-in-docker.sh 14 | 15 | --- 16 | kind: pipeline 17 | type: docker 18 | name: 'ARM 32 bit: Build and test on' 19 | 20 | platform: 21 | os: linux 22 | arch: arm 23 | 24 | steps: 25 | - name: 'Build & Test on ARM 32 bit' 26 | image: 'debian:stretch' 27 | commands: 28 | - bash -e build-and-test-in-docker.sh 29 | 30 | --- 31 | kind: pipeline 32 | type: docker 33 | name: 'x64_86: Build and test' 34 | 35 | platform: 36 | os: linux 37 | arch: amd64 38 | 39 | steps: 40 | - name: 'Build & Test on x64' 41 | image: 'debian:stretch' 42 | commands: 43 | - bash -e build-and-test-in-docker.sh 44 | 45 | -------------------------------------------------------------------------------- /.github/workflows/CpuUsage-Tests-Matrix.yml: -------------------------------------------------------------------------------- 1 | name: CPU Usage Tests Matrix 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ "master" ] 7 | 8 | defaults: 9 | run: 10 | shell: bash 11 | 12 | jobs: 13 | TestCpuUsageOnVm: 14 | name: On VM 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: 19 | - 'x64-debian-10' 20 | - 'x64-debian-11' 21 | - 'x64-debian-12' 22 | - 'x64-debian-13' 23 | - 'x64-ubuntu-22.04' 24 | - 'x64-ubuntu-24.04' 25 | - 'arm64-debian-10' 26 | - 'arm64-debian-11' 27 | - 'arm64-debian-12' 28 | - 'arm64-debian-13' 29 | - 'arm64-ubuntu-14.04' 30 | - 'arm64-ubuntu-16.04' 31 | - 'arm64-ubuntu-18.04' 32 | - 'arm64-ubuntu-20.04' 33 | - 'arm64-ubuntu-22.04' 34 | - 'arm64-ubuntu-24.04' 35 | - 'armel-debian-8' 36 | - 'armel-debian-9' 37 | - 'armel-debian-10' 38 | - 'armel-debian-11' 39 | - 'armhf-debian-8' 40 | - 'armhf-debian-9' 41 | - 'armhf-debian-10' 42 | - 'armhf-debian-11' 43 | - 'armhf-debian-12' 44 | - 'armhf-ubuntu-14.04' 45 | - 'armhf-ubuntu-16.04' 46 | - 'armhf-ubuntu-18.04' 47 | - 'armhf-ubuntu-20.04' 48 | - 'armhf-ubuntu-22.04' 49 | - 'armhf-ubuntu-24.04' 50 | - 'i386-debian-10' 51 | - 'i386-debian-11' 52 | - 'i386-debian-12' 53 | 54 | 55 | runs-on: ubuntu-24.04 56 | timeout-minutes: 15 57 | steps: 58 | - name: Checkout 59 | uses: actions/checkout@v4 60 | 61 | - name: Bootstrap (timeout and try-and-retry) 62 | run: | 63 | script=https://raw.githubusercontent.com/devizer/test-and-build/master/install-build-tools-bundle.sh; (wget -q -nv --no-check-certificate -O - $script 2>/dev/null || curl -ksSL $script) | bash > /dev/null 64 | dotnet --info 65 | Say "Installing timeout" 66 | try-and-retry sudo apt-get update -qq; 67 | try-and-retry sudo apt-get install bsdutils -y -qq; 68 | timeout --version 69 | 70 | - name: Build Tests 71 | run: | 72 | set -ue; set -o pipefail 73 | cd Universe.CpuUsage.Tests 74 | Reset-Target-Framework -fw net48 75 | time try-and-retry dotnet build -c Release -f net48 -o "$HOME/Cpu-Usage-Tests" >/dev/null 76 | 77 | - name: Pull VM 78 | run: | 79 | set -ue; set -o pipefail 80 | docker version 81 | try-and-retry docker pull "devizervlad/crossplatform-pipeline:${{ matrix.os }}" 82 | 83 | - name: Run Tests 84 | uses: nick-fields/retry@v3 85 | with: 86 | timeout_minutes: 12 87 | max_attempts: 3 88 | shell: bash 89 | retry_wait_seconds: 1 90 | on_retry_command: 'docker rm -f VM 2>/dev/null || true' 91 | command: | 92 | set -ue; set -o pipefail 93 | cat << 'EOF' > "$HOME/Cpu-Usage-Tests/lets-rock.sh" 94 | Say "STARTING CPU USAGE TESTS"; 95 | free -m; 96 | nunit3-console --inprocess --workers=1 Universe.CpuUsage.Tests.dll 2>&1 | tee /job/PRECISION.LOG; 97 | Say "TESTS COMPLETED" 98 | EOF 99 | cat "$HOME/Cpu-Usage-Tests/lets-rock.sh" 100 | timeout 666 docker run --privileged -e VM_MEM=1000M -e VM_CPUS=2 -e QEMU_TCG_ACCELERATOR=tcg --name VM --hostname VM --device /dev/fuse --cap-add SYS_ADMIN --security-opt apparmor:unconfined -t \ 101 | -v "$HOME/Cpu-Usage-Tests":/job \ 102 | "devizervlad/crossplatform-pipeline:${{ matrix.os }}" \ 103 | bash -eu -o pipefail lets-rock.sh 104 | 105 | - name: PREPARE Artifact 106 | run: | 107 | # TRIM Show_Precision_Histogram (first) ... Show_Precision_Histogram (last) lines 108 | sudo chown -R $(whoami) $HOME/Cpu-Usage-Tests 109 | cp $HOME/Cpu-Usage-Tests/PRECISION.LOG /tmp/0 110 | # sed '0,/Show_Precision_Histogram/d' - it exclides matching line 111 | cat /tmp/0 | sed -n '/Show_Precision_Histogram/,$p' > /tmp/1 112 | tac /tmp/1 > /tmp/2 113 | cat /tmp/2 | sed -n '/Show_Precision_Histogram/,$p' > /tmp/3 114 | tac /tmp/3 > $HOME/Cpu-Usage-Tests/PRECISION.LOG 115 | cat $HOME/Cpu-Usage-Tests/PRECISION.LOG 116 | 117 | mkdir -p "${{ github.workspace }}/Artifact" 118 | cp -f $HOME/Cpu-Usage-Tests/PRECISION.LOG "${{ github.workspace }}/Artifact/PRECISION on ${{ matrix.os }}.LOG" 119 | 120 | - name: Upload artifacts 121 | uses: actions/upload-artifact@v4 122 | if: always() 123 | with: 124 | name: 'Precision ${{ matrix.os }}' 125 | path: '${{ github.workspace }}/Artifact' 126 | 127 | TestCpuUsageonHost: 128 | name: On Host 129 | strategy: 130 | fail-fast: false 131 | matrix: 132 | include: 133 | - os: windows-2025 134 | - os: windows-2022 135 | - os: windows-2019 136 | - os: ubuntu-20.04 137 | - os: ubuntu-22.04 138 | - os: ubuntu-24.04 139 | - os: macos-13 140 | - os: macos-14 141 | - os: macos-15 142 | 143 | runs-on: ${{ matrix.os }} 144 | steps: 145 | - name: Checkout 146 | uses: actions/checkout@v4 147 | 148 | - name: Environment 149 | run: 'printenv | sort' 150 | 151 | - name: Setup .NET Core 152 | uses: actions/setup-dotnet@v4 153 | with: 154 | dotnet-version: 8.0 155 | 156 | - name: .NET Info 157 | run: | 158 | script=https://raw.githubusercontent.com/devizer/test-and-build/master/install-build-tools-bundle.sh; (wget -q -nv --no-check-certificate -O - $script 2>/dev/null || curl -ksSL $script) | bash > /dev/null 159 | dotnet --info 160 | 161 | - name: TEST full (and build) 162 | run: | 163 | set -eu; 164 | Say ".NET SDK LIST" 165 | dotnet --list-sdks 166 | if [[ "$(uname -s)" == Linux ]]; then 167 | Say "Linux Kernel: [$(uname -r 2>/dev/null)]" 168 | fi 169 | TARGET_FRAMEWORKS_TEST="${TARGET_FRAMEWORKS_TEST:-net8.0}" 170 | cd Universe.CpuUsage.Tests 171 | 172 | Say "Building $TARGET_FRAMEWORKS_TEST" 173 | sed -i -E 's|.*|'$TARGET_FRAMEWORKS_TEST'|' *.csproj 174 | time try-and-retry dotnet build -c Release -f $TARGET_FRAMEWORKS_TEST >/dev/null 2>&1 175 | Say "Testing" 176 | dotnet test --no-build -c Release -f $TARGET_FRAMEWORKS_TEST 177 | 178 | - name: SHOW Precision Histogram 179 | run: | 180 | set -eu; 181 | Say "Linux Kernel: [$(uname -r 2>/dev/null)]" 182 | cd Universe.CpuUsage.Tests 183 | TARGET_FRAMEWORKS_TEST="${TARGET_FRAMEWORKS_TEST:-net8.0}" 184 | mkdir -p ../Artifact 185 | dotnet test --no-build -c Release -f $TARGET_FRAMEWORKS_TEST --filter "Name ~ Show_Precision_Histogram" | tee "../Artifact/PRECISION on ${{ matrix.os }}.LOG" & 186 | 187 | - name: Upload artifacts [${{ env.THEARTIFACTS_NATIVE }}] 188 | uses: actions/upload-artifact@v4 189 | if: always() 190 | with: 191 | name: 'Precision ${{ matrix.os }}' 192 | path: '${{ github.workspace }}/Artifact' 193 | 194 | Combine: 195 | name: Combine results in a single Artifact 196 | needs: [TestCpuUsageonHost, TestCpuUsageOnVm] 197 | if: always() 198 | runs-on: ubuntu-latest 199 | steps: 200 | - name: Download All Artifacts 201 | uses: actions/download-artifact@v4 202 | with: 203 | pattern: '**' 204 | path: "${{ runner.temp }}/Combined" 205 | merge-multiple: true 206 | 207 | - name: Show Download Structure 208 | run: 'sudo apt-get update -qq; sudo apt-get install tree -y -qq; tree $RUNNER_TEMP' 209 | 210 | - name: Upload Combined System Info 211 | uses: actions/upload-artifact@v4 212 | with: 213 | name: 'Combined CPU Usage Tests' 214 | path: "${{ runner.temp }}/Combined" 215 | 216 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | backup.cmd 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015/2017 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # Visual Studio 2017 auto generated files 34 | Generated\ Files/ 35 | 36 | # MSTest test Results 37 | [Tt]est[Rr]esult*/ 38 | [Bb]uild[Ll]og.* 39 | 40 | # NUNIT 41 | *.VisualState.xml 42 | TestResult.xml 43 | 44 | # Build Results of an ATL Project 45 | [Dd]ebugPS/ 46 | [Rr]eleasePS/ 47 | dlldata.c 48 | 49 | # Benchmark Results 50 | BenchmarkDotNet.Artifacts/ 51 | 52 | # .NET Core 53 | project.lock.json 54 | project.fragment.lock.json 55 | artifacts/ 56 | **/Properties/launchSettings.json 57 | 58 | # StyleCop 59 | StyleCopReport.xml 60 | 61 | # Files built by Visual Studio 62 | *_i.c 63 | *_p.c 64 | *_i.h 65 | *.ilk 66 | *.meta 67 | *.obj 68 | *.iobj 69 | *.pch 70 | *.pdb 71 | *.ipdb 72 | *.pgc 73 | *.pgd 74 | *.rsp 75 | *.sbr 76 | *.tlb 77 | *.tli 78 | *.tlh 79 | *.tmp 80 | *.tmp_proj 81 | *.log 82 | *.vspscc 83 | *.vssscc 84 | .builds 85 | *.pidb 86 | *.svclog 87 | *.scc 88 | 89 | # Chutzpah Test files 90 | _Chutzpah* 91 | 92 | # Visual C++ cache files 93 | ipch/ 94 | *.aps 95 | *.ncb 96 | *.opendb 97 | *.opensdf 98 | *.sdf 99 | *.cachefile 100 | *.VC.db 101 | *.VC.VC.opendb 102 | 103 | # Visual Studio profiler 104 | *.psess 105 | *.vsp 106 | *.vspx 107 | *.sap 108 | 109 | # Visual Studio Trace Files 110 | *.e2e 111 | 112 | # TFS 2012 Local Workspace 113 | $tf/ 114 | 115 | # Guidance Automation Toolkit 116 | *.gpState 117 | 118 | # ReSharper is a .NET coding add-in 119 | _ReSharper*/ 120 | *.[Rr]e[Ss]harper 121 | *.DotSettings.user 122 | 123 | # JustCode is a .NET coding add-in 124 | .JustCode 125 | 126 | # TeamCity is a build add-in 127 | _TeamCity* 128 | 129 | # DotCover is a Code Coverage Tool 130 | *.dotCover 131 | 132 | # AxoCover is a Code Coverage Tool 133 | .axoCover/* 134 | !.axoCover/settings.json 135 | 136 | # Visual Studio code coverage results 137 | *.coverage 138 | *.coveragexml 139 | 140 | # NCrunch 141 | _NCrunch_* 142 | .*crunch*.local.xml 143 | nCrunchTemp_* 144 | 145 | # MightyMoose 146 | *.mm.* 147 | AutoTest.Net/ 148 | 149 | # Web workbench (sass) 150 | .sass-cache/ 151 | 152 | # Installshield output folder 153 | [Ee]xpress/ 154 | 155 | # DocProject is a documentation generator add-in 156 | DocProject/buildhelp/ 157 | DocProject/Help/*.HxT 158 | DocProject/Help/*.HxC 159 | DocProject/Help/*.hhc 160 | DocProject/Help/*.hhk 161 | DocProject/Help/*.hhp 162 | DocProject/Help/Html2 163 | DocProject/Help/html 164 | 165 | # Click-Once directory 166 | publish/ 167 | 168 | # Publish Web Output 169 | *.[Pp]ublish.xml 170 | *.azurePubxml 171 | # Note: Comment the next line if you want to checkin your web deploy settings, 172 | # but database connection strings (with potential passwords) will be unencrypted 173 | *.pubxml 174 | *.publishproj 175 | 176 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 177 | # checkin your Azure Web App publish settings, but sensitive information contained 178 | # in these scripts will be unencrypted 179 | PublishScripts/ 180 | 181 | # NuGet Packages 182 | *.nupkg 183 | # The packages folder can be ignored because of Package Restore 184 | **/[Pp]ackages/* 185 | # except build/, which is used as an MSBuild target. 186 | !**/[Pp]ackages/build/ 187 | # Uncomment if necessary however generally it will be regenerated when needed 188 | #!**/[Pp]ackages/repositories.config 189 | # NuGet v3's project.json files produces more ignorable files 190 | *.nuget.props 191 | *.nuget.targets 192 | 193 | # Microsoft Azure Build Output 194 | csx/ 195 | *.build.csdef 196 | 197 | # Microsoft Azure Emulator 198 | ecf/ 199 | rcf/ 200 | 201 | # Windows Store app package directories and files 202 | AppPackages/ 203 | BundleArtifacts/ 204 | Package.StoreAssociation.xml 205 | _pkginfo.txt 206 | *.appx 207 | 208 | # Visual Studio cache files 209 | # files ending in .cache can be ignored 210 | *.[Cc]ache 211 | # but keep track of directories ending in .cache 212 | !*.[Cc]ache/ 213 | 214 | # Others 215 | ClientBin/ 216 | ~$* 217 | *~ 218 | *.dbmdl 219 | *.dbproj.schemaview 220 | *.jfm 221 | *.pfx 222 | *.publishsettings 223 | orleans.codegen.cs 224 | 225 | # Including strong name files can present a security risk 226 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 227 | #*.snk 228 | 229 | # Since there are multiple workflows, uncomment next line to ignore bower_components 230 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 231 | #bower_components/ 232 | 233 | # RIA/Silverlight projects 234 | Generated_Code/ 235 | 236 | # Backup & report files from converting an old project file 237 | # to a newer Visual Studio version. Backup files are not needed, 238 | # because we have git ;-) 239 | _UpgradeReport_Files/ 240 | Backup*/ 241 | UpgradeLog*.XML 242 | UpgradeLog*.htm 243 | ServiceFabricBackup/ 244 | *.rptproj.bak 245 | 246 | # SQL Server files 247 | *.mdf 248 | *.ldf 249 | *.ndf 250 | 251 | # Business Intelligence projects 252 | *.rdl.data 253 | *.bim.layout 254 | *.bim_*.settings 255 | *.rptproj.rsuser 256 | 257 | # Microsoft Fakes 258 | FakesAssemblies/ 259 | 260 | # GhostDoc plugin setting file 261 | *.GhostDoc.xml 262 | 263 | # Node.js Tools for Visual Studio 264 | .ntvs_analysis.dat 265 | node_modules/ 266 | 267 | # Visual Studio 6 build log 268 | *.plg 269 | 270 | # Visual Studio 6 workspace options file 271 | *.opt 272 | 273 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 274 | *.vbw 275 | 276 | # Visual Studio LightSwitch build output 277 | **/*.HTMLClient/GeneratedArtifacts 278 | **/*.DesktopClient/GeneratedArtifacts 279 | **/*.DesktopClient/ModelManifest.xml 280 | **/*.Server/GeneratedArtifacts 281 | **/*.Server/ModelManifest.xml 282 | _Pvt_Extensions 283 | 284 | # Paket dependency manager 285 | .paket/paket.exe 286 | paket-files/ 287 | 288 | # FAKE - F# Make 289 | .fake/ 290 | 291 | # JetBrains Rider 292 | .idea/ 293 | *.sln.iml 294 | 295 | # CodeRush 296 | .cr/ 297 | 298 | # Python Tools for Visual Studio (PTVS) 299 | __pycache__/ 300 | *.pyc 301 | 302 | # Cake - Uncomment if you are using it 303 | # tools/** 304 | # !tools/packages.config 305 | 306 | # Tabs Studio 307 | *.tss 308 | 309 | # Telerik's JustMock configuration file 310 | *.jmconfig 311 | 312 | # BizTalk build output 313 | *.btp.cs 314 | *.btm.cs 315 | *.odx.cs 316 | *.xsd.cs 317 | 318 | # OpenCover UI analysis results 319 | OpenCover/ 320 | 321 | # Azure Stream Analytics local run output 322 | ASALocalRun/ 323 | 324 | # MSBuild Binary and Structured Log 325 | *.binlog 326 | 327 | # NVidia Nsight GPU debugger configuration file 328 | *.nvuser 329 | 330 | # MFractors (Xamarin productivity tool) working folder 331 | .mfractor/ 332 | -------------------------------------------------------------------------------- /.idea/.idea.Universe.CpuUsage/.idea/runConfigurations/Benchmarks_Core_2_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | -------------------------------------------------------------------------------- /.idea/.idea.Universe.CpuUsage/.idea/runConfigurations/Benchmarks_NET_4_7.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: shell 2 | 3 | git: 4 | depth: false 5 | 6 | # mono: 7 | # - latest 8 | 9 | matrix: 10 | include: 11 | 12 | # - name: Linux PowerPC 64 (mono) 13 | # os: linux 14 | # dist: xenial 15 | # env: 16 | # - MULTIARCH_IMAGE="multiarch/debian-debootstrap:ppc64el-stretch" 17 | # services: 18 | # - docker 19 | 20 | - name: OS X 10.14 (.net core & mono) 21 | os: osx 22 | osx_image: xcode10.2 23 | 24 | - name: Native ARM64 Ubuntu 18.04 (.net core & mono) 25 | os: linux 26 | dist: bionic 27 | arch: arm64 28 | 29 | - name: Linux ARM-64 (.net core & mono) 30 | os: linux 31 | dist: xenial 32 | env: 33 | - MULTIARCH_IMAGE="multiarch/debian-debootstrap:arm64-stretch" 34 | services: 35 | - docker 36 | 37 | - name: Linux ARM-v7 (mono) 38 | os: linux 39 | dist: xenial 40 | env: 41 | - MULTIARCH_IMAGE="multiarch/debian-debootstrap:armhf-buster" 42 | services: 43 | - docker 44 | 45 | - name: Linux i386 (mono) 46 | os: linux 47 | dist: xenial 48 | env: 49 | - MULTIARCH_IMAGE="multiarch/debian-debootstrap:i386-stretch" 50 | services: 51 | - docker 52 | 53 | - name: OS X 10.10 (mono) 54 | os: osx 55 | osx_image: xcode6.4 56 | 57 | # - name: OS X 10.12 58 | # os: osx 59 | # osx_image: xcode8.3 60 | # - name: OS X 10.13 61 | # os: osx 62 | # osx_image: xcode9.3 63 | 64 | # https://docs.travis-ci.com/user/caching/ 65 | before_script: 66 | - export SKIP_POSIXRESOURCESUSAGE_ASSERTS=True 67 | - 'script=https://raw.githubusercontent.com/devizer/test-and-build/master/install-build-tools-bundle.sh; (wget -q -nv --no-check-certificate -O - $script 2>/dev/null || curl -ksSL $script) | bash' 68 | - | 69 | OS_X_VER=$(sw_vers 2>/dev/null | grep BuildVer | awk '{print $2}' | cut -c1-2 || true); OS_X_VER=$((OS_X_VER-4)) 70 | 71 | - pushd Tests4Mono; . install-dotnet.sh; popd 72 | - dotnet --info || true 73 | - mono --version || true 74 | - 'mono --version; msbuild /version; echo ""; nuget 2>&1 | head -4 || true' 75 | - 'printenv | sort' 76 | - 'bash Info/show-platform.sh' 77 | 78 | script: 79 | - export DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER=0 80 | - export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1; export DOTNET_CLI_TELEMETRY_OPTOUT=1; 81 | - export CI=false 82 | - | 83 | # Restore 84 | Say "DOTNET RESTORE" 85 | dotnet restore || dotnet restore --disable-parallel || true 86 | Say "NUGET RESTORE" 87 | nuget restore -verbosity quiet || true 88 | 89 | - '[ $OS_X_VER -ge 13 ] && Say "DOTNET TEST (core 3.0, release)" && (dotnet test -f netcoreapp3.0 -c Release | cat) || true' 90 | - '[ $OS_X_VER -ge 13 ] && Say "MAC OS BENCHMARK (core 3.0, release)" && (time bash benchmark.linux.sh) || true' 91 | 92 | - | 93 | if [[ -n "$MULTIARCH_IMAGE" ]]; then 94 | set -e 95 | 96 | Say "Daemonizing ${MULTIARCH_IMAGE} image as 'tests' container" 97 | docker run --rm --privileged multiarch/qemu-user-static:register --reset 98 | docker run -d -h debian-multiarch-tests --name tests -t "${MULTIARCH_IMAGE}" bash -c 'sleep 424242' 99 | 100 | Say "Installing mono in the QEMU" 101 | docker cp Tests4Mono/install-dotnet.sh tests:/ 102 | docker exec tests bash /install-dotnet.sh 103 | 104 | Say "BUILDING MATRIX" 105 | cd Tests4Mono; source build-the-matrix.sh; echo $matrix_run; cd .. 106 | 107 | Say "Runnig test matrix" 108 | pushd $matrix 109 | docker exec tests mkdir -p $matrix 110 | docker cp ./. tests:$(pwd) 111 | sudo docker exec tests bash -ec "export SKIP_POSIXRESOURCESUSAGE_ASSERTS=$SKIP_POSIXRESOURCESUSAGE_ASSERTS; $matrix_run" 112 | 113 | else 114 | # MAC OS (check renice without sudo), both 14th and 10th 115 | echo "Starting tests using Mono in [$(pwd)]" 116 | cd Tests4Mono; set +e; source build-the-matrix.sh; echo $matrix_run 117 | # need -e -c 118 | bash -e -c "$matrix_run" 119 | fi 120 | 121 | - 'Show-System-Stat || true' 122 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "ms-dotnettools.csharp" 8 | ], 9 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 10 | "unwantedRecommendations": [ 11 | ] 12 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Benchmark (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build-release", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/Universe.CpuUsage.Banchmark/bin/Release/netcoreapp3.0/Universe.CpuUsage.Banchmark.exe", 14 | "args": ["--dry"], 15 | "cwd": "${workspaceFolder}/Universe.CpuUsage.Banchmark/bin/Release/netcoreapp3.0", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false, 19 | "logging": {"engineLogging": false} 20 | }, 21 | { 22 | "name": ".NET Core Attach", 23 | "type": "coreclr", 24 | "request": "attach", 25 | "processId": "${command:pickProcess}" 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Build using SDK 3.1 (release)", 6 | "command": "dotnet build -c Release -f netcoreapp3.1", 7 | "type": "shell", 8 | "problemMatcher": "$msCompile", 9 | "group": { 10 | "kind": "build", 11 | "isDefault": true 12 | } 13 | }, 14 | { 15 | "label": "Test using SDK 3.1 (release)", 16 | "command": "dotnet test -c Debug -f netcoreapp3.1 Universe.CpuUsage.Tests\\Universe.CpuUsage.Tests.csproj", 17 | "type": "shell", 18 | "problemMatcher": "$msCompile", 19 | "group": { 20 | "kind": "test", 21 | "isDefault": true 22 | } 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /FULL-TIME-REPORT.sh: -------------------------------------------------------------------------------- 1 | export FULL_TIME=" 2 | TIME: 3 | Elapsed real time %E 4 | Elapsed real time %e 5 | Total number of CPU-seconds that the process spent in kernel mode %S 6 | Total number of CPU-seconds that the process spent in user mode %U 7 | Percentage of the CPU that this job got %P 8 | 9 | MEMORY: 10 | Maximum resident set size of the process during its lifetime (in Kbytes) %M 11 | Average resident set size of the process (in Kbytes) %t 12 | Average total (data+stack+text) memory use of the process (in Kbytes) %K 13 | Average size of the process's unshared data area (in Kbytes) %D 14 | Average size of the process's unshared stack space (in Kbytes) %p 15 | Average size of the process's shared text space (in Kbytes) %X 16 | System's page size, in bytes. This is a per-system constant, but varies between systems. %Z 17 | Number of major page faults. %F 18 | Number of minor page faults. %R 19 | Number of times the process was swapped out of main memory. %W 20 | Number of times the process was context-switched involuntarily (because the time slice expired). %c 21 | Number of waits: times that the program was context-switched voluntarily, for instance while waiting for an I/O operation to complete. %w 22 | 23 | I/O: 24 | Number of file system inputs by the process. %I 25 | Number of file system outputs by the process. %O 26 | Number of socket messages received by the process. %r 27 | Number of socket messages sent by the process. %s 28 | Number of signals delivered to the process. %k 29 | Name and command-line arguments of the command being timed. [%C] 30 | Exit status of the command. %x 31 | " 32 | # sudo apt-get install time -y 33 | -------------------------------------------------------------------------------- /Info/Benchmark.txt: -------------------------------------------------------------------------------- 1 | 2 | Linux 4.15 3 | | Method | Mean | Error | StdDev | Rank | Gen 0 | Gen 1 | Gen 2 | Allocated | 4 | |------------ |----------:|---------:|---------:|-----:|-------:|------:|------:|----------:| 5 | | DateTimeNow | 150.24 ns | 2.346 ns | 2.194 ns | 2 | - | - | - | - | 6 | | Stopwatch | 77.27 ns | 0.473 ns | 0.419 ns | 1 | 0.0095 | - | - | 40 B | 7 | | ByProcess | 795.18 ns | 8.000 ns | 6.681 ns | 3 | - | - | - | - | 8 | | ByThread | 834.59 ns | 9.324 ns | 8.722 ns | 4 | - | - | - | - | 9 | 10 | Linux 32 bit @ ARM 3.4 (Orange PI, H3, 1200 MHz) 11 | | Method | Mean | Error | StdDev | Rank | Gen 0 | Gen 1 | Gen 2 | Allocated | 12 | |------------ |---------:|----------:|----------:|-----:|-------:|------:|------:|----------:| 13 | | DateTimeNow | 2.788 us | 0.0595 us | 0.0944 us | 2 | - | - | - | - | 14 | | Stopwatch | 1.737 us | 0.0539 us | 0.0504 us | 1 | 0.1945 | - | - | 32 B | 15 | | ByProcess | 5.552 us | 0.1662 us | 0.4900 us | 3 | - | - | - | - | 16 | | ByThread | 5.664 us | 0.1136 us | 0.1960 us | 3 | - | - | - | - | 17 | 18 | 19 | OSX 10.13 20 | | Method | Mean | Error | StdDev | Median | Rank | Gen 0 | Gen 1 | Gen 2 | Allocated | 21 | |------------ |------------:|----------:|----------:|------------:|-----:|-------:|------:|------:|----------:| 22 | | DateTimeNow | 186.70 ns | 1.233 ns | 1.093 ns | 186.79 ns | 2 | - | - | - | - | 23 | | Stopwatch | 72.02 ns | 1.474 ns | 3.560 ns | 70.45 ns | 1 | 0.0095 | - | - | 40 B | 24 | | ByProcess | 1,906.05 ns | 47.314 ns | 44.258 ns | 1,892.13 ns | 3 | - | - | - | - | 25 | | ByThread | 1,968.98 ns | 41.026 ns | 69.665 ns | 1,945.56 ns | 4 | - | - | - | - | 26 | 27 | OSX 10.13 28 | | Method | Mean | Error | StdDev | Rank | Gen 0 | Gen 1 | Gen 2 | Allocated | 29 | |------------ |------------:|----------:|----------:|-----:|-------:|------:|------:|----------:| 30 | | DateTimeNow | 73.40 ns | 1.506 ns | 2.475 ns | 1 | - | - | - | - | 31 | | Stopwatch | 79.36 ns | 1.640 ns | 1.454 ns | 2 | 0.0025 | - | - | 40 B | 32 | | ByProcess | 1,979.10 ns | 29.771 ns | 26.391 ns | 4 | - | - | - | - | 33 | | ByThread | 1,921.54 ns | 35.869 ns | 33.552 ns | 3 | - | - | - | - | 34 | 35 | Windows 36 | | Method | Mean | Error | StdDev | Rank | Gen 0 | Gen 1 | Gen 2 | Allocated | 37 | |------------ |----------:|---------:|---------:|-----:|-------:|------:|------:|----------:| 38 | | DateTimeNow | 217.08 ns | 1.667 ns | 1.559 ns | 4 | - | - | - | - | 39 | | Stopwatch | 31.12 ns | 0.203 ns | 0.169 ns | 1 | 0.0095 | - | - | 40 B | 40 | | ByProcess | 200.49 ns | 3.743 ns | 3.501 ns | 2 | - | - | - | - | 41 | | ByThread | 205.11 ns | 3.970 ns | 4.413 ns | 3 | - | - | - | - | 42 | -------------------------------------------------------------------------------- /Info/TODO-on-Linux.txt: -------------------------------------------------------------------------------- 1 | http://man7.org/linux/man-pages/man2/getrusage.2.html 2 | https://unix.stackexchange.com/questions/442969/what-exactly-are-voluntary-context-switches 3 | 4 | x64: 5 | 6 | #5 {Show_Raw_Process_Usage} @ LinuxResourcesUsage_Tests starting... 7 | getrusage returns 0 8 | GetRawUsageResources(RUSAGE_SELF): 9 | u_sec 0:11, u_usec 1:612017, k_sec 2:1, k_usec 3:767126, 10 | maxrss 4:899992, ixrss 5:0, idrss 6:0, isrss 7:0, 11 | minflt 8:693381, majflt 9:1, nswap 10:0, inblock 11:25424, 12 | oublock 12:21512, msgsnd 13:0, msgrcv 14:0, nsignals 15:0, 13 | nvcsw 16:2074, nivcsw 17:3717 14 | #5 {Show_Raw_Process_Usage} >PASSED< in 00:00:00.0101869 (cpu: 79%, 8.045 = 8.045 [user] + 0.000 [kernel] milliseconds) 15 | 16 | #6 {Show_Raw_Thread_Usage} @ LinuxResourcesUsage_Tests starting... 17 | getrusage returns 0 18 | GetRawUsageResources(RUSAGE_THREAD): 19 | u_sec 0:10, u_usec 1:521780, k_sec 2:1, k_usec 3:486560, 20 | maxrss 4:900276, ixrss 5:0, idrss 6:0, isrss 7:0, 21 | minflt 8:650550, majflt 9:0, nswap 10:0, inblock 11:18384, 22 | oublock 12:17672, msgsnd 13:0, msgrcv 14:0, nsignals 15:0, 23 | nvcsw 16:372, nivcsw 17:2248 24 | #6 {Show_Raw_Thread_Usage} >PASSED< in 00:00:00.0009432 (cpu: 71%, 0.671 = 0.671 [user] + 0.000 [kernel] milliseconds) -------------------------------------------------------------------------------- /Info/express.test.sh: -------------------------------------------------------------------------------- 1 | docker run -it --rm ubuntu bash -c 'export DEBIAN_FRONTEND=noninteractive; apt-get update; apt-get install git curl sudo mc -y; 2 | git clone https://github.com/devizer/Universe.CpuUsage; cd Universe.CpuUsage; 3 | cd Universe.CpuUsage/Universe.CpuUsage.Tests; 4 | script=https://raw.githubusercontent.com/devizer/test-and-build/master/lab/install-DOTNET.sh; (wget -q -nv --no-check-certificate -O - $script 2>/dev/null || curl -ksSL $script) | bash; 5 | dotnet test -f netcoreapp3.1; 6 | bash' 7 | 8 | -------------------------------------------------------------------------------- /Info/show-platform.sh: -------------------------------------------------------------------------------- 1 | files=' 2 | /sys/devices/virtual/dmi/id/chassis_vendor 3 | /sys/devices/virtual/dmi/id/sys_vendor 4 | /sys/devices/virtual/dmi/id/board_vendor 5 | 6 | /sys/devices/virtual/dmi/id/product_name 7 | /sys/devices/virtual/dmi/id/board_name 8 | 9 | /sys/firmware/devicetree/base/model 10 | /proc/device-tree/model 11 | ' 12 | for f in $files; do 13 | echo "$f: [$(sudo cat $f 2>/dev/null)]" 14 | done 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 devizer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Universe.CpuUsage     [![Nuget](https://img.shields.io/nuget/v/Universe.CpuUsage?label=nuget.org)](https://www.nuget.org/packages/Universe.CpuUsage/) 2 | CPU Usage for .NET Core, .NET Framework and Mono on Linux, Windows and macOS 3 | 4 | ## Crossplatform CPU Usage metrics 5 | It receives the amount of time that the current **thread(s)/process** has executed in _**kernel**_ and _**user**_ mode. 6 | 7 | Works everywhere: Linux, OSX and Windows. 8 | 9 | Targets everywhere: Net Framework 2.0+, Net Standard 1.3+, Net Core 1.0+ 10 | 11 | ## Coverage and supported OS 12 | Minimum OS requirements: Linux Kernel 2.6.26, Mac OS 10.9, Windows XP/2003 13 | 14 | CPU Usage for sub-tasks and sub-threads requires NET Core, NET Framework 4.6+ 15 | 16 | ### Autotests using .NET Core and .NET Framework cover: 17 | - Linux running on X64, arm64, armhf, armv5 and i386, kernel varies 3.16 ... 6.8 18 | - Windows Server 2016 ... 2025 19 | - macOS on X64 and Apple Silicon 20 | 21 | ### Manually tested on: 22 | - Termux on non-rooted arm64 phone. 23 | - WSL v1 and v2 24 | - Windows 10 ARM64 (.NET Core) 25 | - FreeBSD 12 (both .NET Core and Mono). 26 | 27 | 28 | | appveyor | travis-ci | 29 | |----------------------------|-------------------------------------------------------------------------------------------| 30 | | .NET Core: **Linux** x64, **Windows** x64.
Mono: **Linux** x64. | .NET Core: **macOS** 10.14, **Linux** Arm 64.
Mono: **Linux** Arm 64, Arm-v7, i386, **macOS** 10.10. | 31 | |

[![Build status](https://ci.appveyor.com/api/projects/status/udq3dip23mqxlkjf?svg=true)](https://ci.appveyor.com/project/devizer/universe-cpuusage)

|

[![Build Status](https://travis-ci.org/devizer/Universe.CpuUsage.svg?branch=master)](https://travis-ci.org/devizer/Universe.CpuUsage)

| 32 | 33 | ### Integration tests on exotic platforms 34 | For ***mono only*** platforms (i386, ppc64, mips, arm v5/v6, etc) here is the script for integration tests: [test-on-mono-only-platforms.sh](https://raw.githubusercontent.com/devizer/Universe.CpuUsage/master/test-on-mono-only-platforms.sh) 35 | ``` 36 | url=https://raw.githubusercontent.com/devizer/Universe.CpuUsage/master/test-on-mono-only-platforms.sh; 37 | (wget -q -nv --no-check-certificate -O - $url 2>/dev/null || curl -ksSL $url) | bash 38 | ``` 39 | 40 | ## Implementation 41 | The implementation utilizes platform invocation (P/Invoke) of the corresponding system libraries depending on the OS: 42 | 43 | | OS | per thread implementation | per process implementation | library | 44 | |----------|--------------------------|------------------------|-----------------| 45 | | Linux | getrusage(RUSAGE_THREAD) | getrusage(RUSAGE_SELF) | libc.so | 46 | | Windows | GetThreadTimes() | GetProcessTimes() | kernel32.dll | 47 | | Mac OS X | thread_info() | getrusage(RUSAGE_SELF) | libSystem.dylib | 48 | 49 | ## Precision depends on 50 | Here is a summary of CPU usage precision. In general, it depends on OS and version and it does not depend on CPU performance except of FreeBSD 51 | 52 | | OS | Average Precision | 53 | |----------------------------------------------------------------------------|------------------:| 54 | | Windows Server 2025, AMD EPYC 7763 | _15,625 μs_ | 55 | | Windows Server 2019, Xeon E5-2697 v3 | _16,250 μs_ | 56 | | Linux, i5-10300, 4.0 GHz, kernel 6.8 | _1,000 μs_ | 57 | | Ubuntu, x86-64 QEMU, kernel 6.8 | _1,000 μs_ | 58 | | Debian, x86-64 QEMU, kernel 6.10 | _4,000 μs_ | 59 | | Ubuntu, arm64 QEMU, kernel 6.8 | _1,000 μs_ | 60 | | Debian, arm64 QEMU, kernel 6.10 | _4,000 μs_ | 61 | | Linux, Xeon E5-2697 v3 @ 2.60GHz, kernel 5.0 | _4,000 μs_ | 62 | | Linux, ARMv7 H3 CPU, 1.50GHz, kernel 3.4 | _10,000 μs_ | 63 | | Linux, ARMv7 H3 CPU, 1.10GHz, kernel 4.19 | _4,000 μs_ | 64 | | Linux, ARMv7 H3 CPU, 1.30GHz, kernel 6.13 | _4,000 μs_ | 65 | | Mac OS 10.14, Xeon E5-2697 v2 @ 2.70GHz | 14.0 μs | 66 | | Mac OS 15, Apple M1 | 3.0 μs | 67 | | FreeBSD 12, .NET Core, Xeon E3-1270 v2 @ 3.50GHz, pseudo kernel 2.6.32 | 3.0 μs | 68 | | FreeBSD 12, Mono 5.1, Xeon E3-1270 v2 @ 3.50GHz, native BSD 12 | 1.9 μs | 69 | 70 | Detailed histograms of precision are produced by PrecisionTest.cs 71 | 72 | ## Low level API: class CpuUsage 73 | ```csharp 74 | var onStart = CpuUsage.GetByThread(); 75 | ... 76 | var onEnd = CpuUsage.GetByThread(); 77 | Console.WriteLine("CPU Usage: " + (onEnd-onStart)); 78 | ``` 79 | 80 | ## High level API: class CpuUsageAsyncWatcher 81 | ```csharp 82 | public void Configure(IApplicationBuilder app) 83 | { 84 | // Here is a "middleware" that displays total CPU usage 85 | // of all the Tasks executed by ASP.NET Core pipeline during each http request 86 | app.Use(async (context, next) => 87 | { 88 | CpuUsageAsyncWatcher watcher = new CpuUsageAsyncWatcher(); 89 | await next.Invoke(); 90 | watcher.Stop(); 91 | Console.WriteLine($"Cpu Usage by http request is {watcher.GetSummaryCpuUsage()}"); 92 | }); 93 | } 94 | ``` 95 | 96 | ## Benchmark 97 | Just for illustration here is a comparison to well known DateTime.Now and Stopwatch using benchmark.net. All of them are taken using .NET Core 3.0 runtime. 98 | 99 | #### Linux x64, kernel 4.15 100 | | Method | Mean | Error | StdDev | Rank | Gen 0 | Gen 1 | Gen 2 | Allocated | 101 | |------------ |----------:|---------:|---------:|-----:|-------:|------:|------:|----------:| 102 | | DateTime.Now() | 150.24 ns | 2.346 ns | 2.194 ns | 2 | - | - | - | - | 103 | | Stopwatch | 77.27 ns | 0.473 ns | 0.419 ns | 1 | 0.0095 | - | - | 40 B | 104 | | Process CPU Usage | 795.18 ns | 8.000 ns | 6.681 ns | 3 | - | - | - | - | 105 | | Thread CPU Usage| 834.59 ns | 9.324 ns | 8.722 ns | 4 | - | - | - | - | 106 | 107 | #### Linux 32 bit @ ARM, kernel 3.4 (Orange PI, H3, 1500 MHz) 108 | | Method | Mean | Error | StdDev | Rank | Gen 0 | Gen 1 | Gen 2 | Allocated | 109 | |------------ |---------:|----------:|----------:|-----:|-------:|------:|------:|----------:| 110 | | DateTime.Now | 2.788 μs | 0.0595 μs | 0.0944 μs | 2 | - | - | - | - | 111 | | Stopwatch | 1.737 μs | 0.0539 μs | 0.0504 μs | 1 | 0.1945 | - | - | 32 B | 112 | | Process CPU Usage | 5.552 μs | 0.1662 μs | 0.4900 μs | 3 | - | - | - | - | 113 | | Thread CPU Usage | 5.664 μs | 0.1136 μs | 0.1960 μs | 3 | - | - | - | - | 114 | 115 | #### macOS 10.14 116 | | Method | Mean | Error | StdDev | Rank | Gen 0 | Gen 1 | Gen 2 | Allocated | 117 | |------------ |------------:|----------:|----------:|-----:|-------:|------:|------:|----------:| 118 | | DateTime.Now() | 73.40 ns | 1.506 ns | 2.475 ns | 1 | - | - | - | - | 119 | | Stopwatch | 79.36 ns | 1.640 ns | 1.454 ns | 2 | 0.0025 | - | - | 40 B | 120 | | Process CPU Usage | 1,979.10 ns | 29.771 ns | 26.391 ns | 4 | - | - | - | - | 121 | | Thread CPU Usage | 1,921.54 ns | 35.869 ns | 33.552 ns | 3 | - | - | - | - | 122 | 123 | #### Windows Server 2016 124 | | Method | Mean | Error | StdDev | Rank | Gen 0 | Gen 1 | Gen 2 | Allocated | 125 | |------------ |----------:|---------:|---------:|-----:|-------:|------:|------:|----------:| 126 | | DateTime.Now() | 217.08 ns | 1.667 ns | 1.559 ns | 4 | - | - | - | - | 127 | | Stopwatch | 31.12 ns | 0.203 ns | 0.169 ns | 1 | 0.0095 | - | - | 40 B | 128 | | Process CPU Usage | 200.49 ns | 3.743 ns | 3.501 ns | 2 | - | - | - | - | 129 | | Thread CPU Usage | 205.11 ns | 3.970 ns | 4.413 ns | 3 | - | - | - | - | 130 | 131 | ##### Legend 132 | - Stopwatch: `var sw = new Stopwatch(); var ticks = sw.ElapsedTicks;` 133 | - Process CPU Usage: `CpuUsageReader.GetByProcess();` 134 | - Thread CPU Usage: `CpuUsageReader.GetByThread();` 135 | - ns - nanosecond, μs - microsecond 136 | -------------------------------------------------------------------------------- /Shared/Universe.CpuUsage.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devizer/Universe.CpuUsage/5c5c67794ad0592460f15d972a8afe76362c4a9f/Shared/Universe.CpuUsage.snk -------------------------------------------------------------------------------- /Shared/Version.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 1.42 5 | 1.42 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Tests4Mono/On-Local-Mac-or-Linux.sh: -------------------------------------------------------------------------------- 1 | work=$HOME/build/devizer; mkdir -p $work; cd $work; rm -rf $work/Universe.CpuUsage; git clone https://github.com/devizer/Universe.CpuUsage; cd Universe.CpuUsage/Tests4Mono; 2 | git pull; set +e; source build-the-matrix.sh; echo $matrix_run; bash -e -c "$matrix_run" 3 | -------------------------------------------------------------------------------- /Tests4Mono/Universe.CpuUsage.MonoTests.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29326.143 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Universe.CpuUsage.MonoTests", "Universe.CpuUsage.MonoTests\Universe.CpuUsage.MonoTests.csproj", "{CCCAD110-24BE-4056-B1E0-F87F80280195}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {CCCAD110-24BE-4056-B1E0-F87F80280195}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {CCCAD110-24BE-4056-B1E0-F87F80280195}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {CCCAD110-24BE-4056-B1E0-F87F80280195}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {CCCAD110-24BE-4056-B1E0-F87F80280195}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {8A4C2E86-6430-4C7F-B905-69CD89BEF0D8} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Tests4Mono/Universe.CpuUsage.MonoTests/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Tests4Mono/Universe.CpuUsage.MonoTests/FirstTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Mono.Cecil; 4 | using NUnit.Framework; 5 | 6 | namespace Universe.CpuUsage.MonoTests 7 | { 8 | [TestFixture] 9 | public class FirstTest 10 | { 11 | [Test] 12 | public void Smoke_Test() 13 | { 14 | Show_CpuUsage_Version(); 15 | CpuUsage? byThread = CpuUsage.GetByThread(); 16 | CpuUsage? byProcess = CpuUsage.GetByProcess(); 17 | TestContext.Progress.WriteLine($"Process's CPU Usage : {byProcess}"); 18 | TestContext.Progress.WriteLine($"Thread's CPU Usage : {byThread}"); 19 | } 20 | 21 | [Test] 22 | public void Show_CpuUsage_Version() 23 | { 24 | var asm = typeof(CpuUsage).Assembly; 25 | TestContext.Progress.WriteLine($"CpuUsage Version : {asm.GetName().Version}"); 26 | TestContext.Progress.WriteLine($"Its Target Framework : '{GetTargetFramework(asm.Location)}'"); 27 | } 28 | 29 | 30 | 31 | [Test/*, Ignore("Experimental")*/] 32 | public void Show_TargetFramework_ByCecil() 33 | { 34 | var fileName = typeof(CpuUsage).Assembly.Location; 35 | 36 | TryAndForget("AssemblyDefinition.ReadAssembly()", () => 37 | { 38 | var asmDef = AssemblyDefinition.ReadAssembly(fileName); 39 | ShowProperties(asmDef, 3); 40 | TestContext.Progress.WriteLine(" "); 41 | 42 | foreach (var attr in asmDef.CustomAttributes) 43 | { 44 | TestContext.Progress.WriteLine($"Attribute {attr.AttributeType}"); 45 | ShowProperties(attr, 6); 46 | foreach (var prop in attr.Properties) 47 | { 48 | TestContext.Progress.WriteLine($" Property [{prop.Argument.Type}] {prop.Name}: '{prop.Argument.Value}'"); 49 | } 50 | TestContext.Progress.WriteLine(" "); 51 | } 52 | 53 | 54 | }); 55 | } 56 | 57 | [Test, Ignore("Experimental")] 58 | public void Show_Metatdata_ByCecil() 59 | { 60 | var fileName = typeof(CpuUsage).Assembly.Location; 61 | TestContext.Progress.WriteLine($"CpuUsage Location: {fileName}"); 62 | 63 | TryAndForget("ModuleDefinition.ReadModule()", () => 64 | { 65 | ModuleDefinition module = ModuleDefinition.ReadModule(fileName); 66 | ShowProperties(module, 4); 67 | TestContext.Progress.WriteLine(" "); 68 | 69 | 70 | foreach (CustomAttribute moduleCustomAttribute in module.CustomAttributes) 71 | { 72 | TestContext.Progress.WriteLine($"Attribute: {moduleCustomAttribute.GetType()}"); 73 | ShowProperties(moduleCustomAttribute, 4); 74 | TestContext.Progress.WriteLine(" "); 75 | } 76 | }); 77 | } 78 | 79 | void ShowProperties(object obj, int indent) 80 | { 81 | if (obj == null) return; 82 | string pre = indent > 0 ? new string(' ', indent) : ""; 83 | var props = obj.GetType().GetProperties(); 84 | foreach (var propertyInfo in props) 85 | { 86 | TryAndForget(null, () => 87 | { 88 | TestContext.Progress.WriteLine($"{pre}{propertyInfo.Name}: {propertyInfo.GetValue(obj)}"); 89 | }); 90 | } 91 | } 92 | 93 | void TryAndForget(string caption, Action action) 94 | { 95 | try 96 | { 97 | action(); 98 | } 99 | catch (Exception ex) 100 | { 101 | if (caption != null) 102 | Console.WriteLine($"Fail. {caption}. {ex.GetType().Name}: {ex.Message}"); 103 | } 104 | 105 | } 106 | 107 | string GetTargetFramework(string fileName) 108 | { 109 | var asmDef = AssemblyDefinition.ReadAssembly(fileName); 110 | const string attrName = "System.Runtime.Versioning.TargetFrameworkAttribute"; 111 | var tfa = asmDef.CustomAttributes.FirstOrDefault(x => x.AttributeType.FullName == attrName); 112 | return tfa?.Properties.FirstOrDefault().Argument.Value?.ToString(); 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /Tests4Mono/Universe.CpuUsage.MonoTests/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Console = System.Console; 7 | 8 | namespace Universe.CpuUsage.MonoTests 9 | { 10 | class Program 11 | { 12 | static void Main(string[] args) 13 | { 14 | Console.WriteLine($"The Platform: {CrossInfo.ThePlatform}"); 15 | Console.WriteLine($"Process: {IntPtr.Size * 8} bits"); 16 | Console.WriteLine($"CPU Usage by Process: {CpuUsage.GetByProcess()}"); 17 | Console.WriteLine($"CPU Usage by Thread: {CpuUsage.GetByThread()}"); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Tests4Mono/Universe.CpuUsage.MonoTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Universe.CpuUsage.MonoTests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Universe.CpuUsage.MonoTests")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("cccad110-24be-4056-b1e0-f87f80280195")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Tests4Mono/Universe.CpuUsage.MonoTests/Universe.CpuUsage.MonoTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Debug 8 | AnyCPU 9 | {CCCAD110-24BE-4056-B1E0-F87F80280195} 10 | Exe 11 | Universe.CpuUsage.MonoTests 12 | Universe.CpuUsage.MonoTests 13 | v4.7 14 | 512 15 | true 16 | true 17 | 18 | 19 | 20 | 21 | AnyCPU 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | 30 | 31 | AnyCPU 32 | pdbonly 33 | true 34 | bin\Release\ 35 | TRACE 36 | prompt 37 | 4 38 | 39 | 40 | 41 | ..\packages\Mono.Cecil.0.11.1\lib\net40\Mono.Cecil.dll 42 | 43 | 44 | ..\packages\Mono.Cecil.0.11.1\lib\net40\Mono.Cecil.Mdb.dll 45 | 46 | 47 | ..\packages\Mono.Cecil.0.11.1\lib\net40\Mono.Cecil.Pdb.dll 48 | 49 | 50 | ..\packages\Mono.Cecil.0.11.1\lib\net40\Mono.Cecil.Rocks.dll 51 | 52 | 53 | ..\packages\NUnit.3.12.0\lib\net45\nunit.framework.dll 54 | 55 | 56 | ..\packages\ParallelExtensionsExtras.1.2.0.0\lib\net40\ParallelExtensionsExtras.dll 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | ..\bin\net46\Universe.CpuUsage.dll 68 | 69 | 70 | 71 | 72 | LinkedTests\AsyncSchedulerCases.cs 73 | 74 | 75 | LinkedTests\CpuLoader.cs 76 | 77 | 78 | LinkedTests\CpuUsageAsyncWatcher_AsyncStreamTests.cs 79 | 80 | 81 | LinkedTests\CpuUsageAsyncWatcher_Tests.cs 82 | 83 | 84 | FullCrossInfo\CrossInfo.cs 85 | 86 | 87 | LinkedTests\CrossInfo_Tests.cs 88 | 89 | 90 | LinkedTests\LinuxKernelCacheFlusher.cs 91 | 92 | 93 | LinkedTests\LinuxResourcesUsage_Reader_Tests.cs 94 | 95 | 96 | LinkedTests\NUnitTestsBase.cs 97 | 98 | 99 | LinkedTests\PosixProcessPriority.cs 100 | 101 | 102 | LinkedTests\PosixResourcesUsage_Tests.cs 103 | 104 | 105 | LinkedTests\PrecisionTest.cs 106 | 107 | 108 | LinkedTests\RisingTest.cs 109 | 110 | 111 | LinkedTests\SmokeCpuUsage.cs 112 | 113 | 114 | LinkedTests\Statistica\HistogramReport.cs 115 | 116 | 117 | LinkedTests\Statistica\PercentileCalc.cs 118 | 119 | 120 | LinkedTests\Statistica\Statistica.cs 121 | 122 | 123 | LinkedTests\Test_IsSupported.cs 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /Tests4Mono/Universe.CpuUsage.MonoTests/ZeroTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace Universe.CpuUsage.MonoTests 4 | { 5 | 6 | [TestFixture] 7 | public class ZeroTest 8 | { 9 | [Test] 10 | public void DoNothing() 11 | { 12 | 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Tests4Mono/Universe.CpuUsage.MonoTests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Tests4Mono/build-the-matrix.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # work=$HOME/build/devizer; mkdir -p $work; cd $work; rm -rf Universe.CpuUsage; git clone https://github.com/devizer/Universe.CpuUsage; cd Universe.CpuUsage/Tests4Mono; source build-the-matrix.sh; echo $matrix_run; bash -c "$matrix_run" | tee ~/cpu-usage-tests.log 3 | 4 | # set -e 5 | 6 | # pushd "$(dirname "$0")" >/dev/null; SCRIPT="$(pwd)"; popd >/dev/null 7 | SCRIPT="$(pwd)" 8 | SayScript="$(pwd)/say.include.sh" 9 | pushd ..; StartFrom=$(pwd); popd 10 | source "$SayScript" 11 | 12 | echo ' 13 | 14 | 15 | 16 | 17 | 18 | ' > nuget.config 19 | 20 | 21 | matrix=~/build/devizer/MATRIX-Universe.CpuUsage 22 | mkdir -p bin $matrix 23 | bin_path=$(pwd)/bin 24 | restore_path=$(pwd)/obj/RESTORE 25 | mkdir -p $restore_path; rm -rf $restore_path/* || true 26 | Say "Matrix Path: $matrix" 27 | Say "Library path: $bin_path" 28 | 29 | cd $restore_path 30 | Say "Nuget Restore Universe.CpuUsage to [$(pwd)]" 31 | nuget install Universe.CpuUsage -verbosity quiet || nuget install Universe.CpuUsage -verbosity quiet || true 32 | pushd Universe.CpuUsage*/lib 33 | copyto=$(pwd) 34 | popd 35 | RAOT_VER=3.0.2 36 | Say "Nuget Restore Theraot.Core ${RAOT_VER} to $(pwd)" 37 | nuget install Theraot.Core -version $RAOT_VER -verbosity quiet || nuget install Theraot.Core -version $RAOT_VER -verbosity quiet || true 38 | cd Theraot.Core*/lib 39 | for subdir in $(ls -1); do 40 | # Say "TRY ${copyto}/${subdir}" 41 | if [[ -d "${copyto}/${subdir}" ]]; then 42 | echo -e "COPY [Theraot.Core $RAOT_VER] @ ${subdir} to\n [${copyto}/${subdir}]" 43 | cp -r ${subdir}/. "${copyto}/${subdir}" 44 | fi 45 | done 46 | # popd 47 | 48 | # Say "LIST OF THE [$(pwd)]" 49 | # find . 50 | 51 | 52 | # cd Universe.CpuUsage*/lib 53 | cd $copyto 54 | Say "CpuUsage libraries: {$(pwd)}" 55 | rm -rf net47 net472 net48 netcoreapp3.0 netstandard2.1 56 | # mkdir -p ../../../../Tests4Linux/bin 57 | cp -r ./. $bin_path/ 58 | 59 | cd $SCRIPT 60 | Say "RESTORE for [$(pwd)] Universe.CpuUsage.MonoTests.sln" 61 | nuget restore Universe.CpuUsage.MonoTests.sln -verbosity quiet || nuget restore Universe.CpuUsage.MonoTests.sln -verbosity quiet 62 | # pushd ~/build/devizer/Universe.CpuUsage; find . ; popd 63 | msbuild /t:Rebuild /p:Configuration=Release /v:q 64 | 65 | errors=0; 66 | proj=Universe.CpuUsage.MonoTests/Universe.CpuUsage.MonoTests.csproj 67 | cp -f ${proj} ${proj}-bak 68 | cat "$SayScript" > $matrix/run.sh 69 | echo 'success=0; errors=0; Say "RUNNING MATRIX. current is [$(pwd)]. Machine is [$(uname -m)-$(uname -s)]"' >> $matrix/run.sh 70 | matrix_run="cd $matrix && bash run.sh" 71 | for target_dir in $(ls -d bin/*/); do 72 | target=$(basename $target_dir) 73 | echo "pushd job-${target} >/dev/null" >> $matrix/run.sh 74 | echo 'echo ""; Say "JOB ['${target}'] for [$(uname -m)-$(uname -s)] in [$(pwd)]"' >> $matrix/run.sh 75 | 76 | 77 | Say "Mono Tests: msbuild rebuild for [$target]" 78 | sed_cmd="s/\.\.\\bin\\net46\\Universe/\.\.\\bin\\${target}\\Universe/g" 79 | sed_cmd="s/net46/${target}/g" 80 | 81 | cp -f ${proj}-bak ${proj} 82 | sed "$sed_cmd" ${proj}-bak > ${proj} 83 | echo "REF: {$(cat $proj | grep $target | grep HintPath)}" 84 | 85 | cfg=Debug 86 | msbuild /noLogo /t:Rebuild /p:Configuration=$cfg /v:q 87 | echo "mono ./Universe.CpuUsage.MonoTests/bin/$cfg/Universe.CpuUsage.MonoTests.exe" >> $matrix/run.sh 88 | 89 | # Say "Mono Tests: Run Tests for [$target]" 90 | echo ' 91 | pushd packages/NUnit.ConsoleRunner*/tools >/dev/null; runner=$(pwd)/nunit3-console.exe; popd >/dev/null 92 | echo "Runner for the '$target' target is [$runner]" 93 | pushd Universe.CpuUsage.MonoTests/bin/'$cfg' >/dev/null 94 | mono $runner --workers=1 Universe.CpuUsage.MonoTests.exe && { Say "Success ['$target'] for [$(uname -m)-$(uname -s)]"; success=$((success+1)); } || { Say "ERROR: TESTING ['$target'] for [$(uname -m)-$(uname -s)]"; errors=$((errors+1)); } 95 | popd >/dev/null 96 | ' >> $matrix/run.sh 97 | 98 | mkdir -p $matrix/job-${target} 99 | cp -r ./. $matrix/job-${target} 100 | 101 | printf 'echo ""; popd >/dev/null\n\n' >> $matrix/run.sh 102 | done 103 | 104 | echo 'Say "Target Summary. Success: $success, Errors: $errors"; exit $errors' >> $matrix/run.sh 105 | chmod +x $matrix/run.sh 106 | 107 | 108 | has_dot_net="no"; command -v dotnet >/dev/null 2>&1 && { has_dot_net=yes; } || true 109 | if [[ $has_dot_net == yes && "$(uname -m)" == "aarch64" ]]; then 110 | pushd $StartFrom; 111 | cd .. 112 | Say "dotnet RESTORE for [$(pwd)]" 113 | time dotnet restore --disable-parallel || true 114 | pushd Universe.CpuUsage.Tests 115 | Say "dotnet TEST -f netcoreapp2.2 for [$(pwd)]" 116 | (dotnet test -f netcoreapp2.2 -c Release || exit 1) | cat 117 | popd 118 | popd 119 | fi 120 | 121 | # set +e -------------------------------------------------------------------------------- /Tests4Mono/install-dotnet.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # script=https://raw.githubusercontent.com/devizer/Universe.CpuUsage/master/Tests4Mono/install-dotnet.sh; (wget -q -nv --no-check-certificate -O - $script 2>/dev/null || curl -ksSL $script) | bash 3 | set -e 4 | OS_X_VER=$(sw_vers 2>/dev/null | grep BuildVer | awk '{print $2}' | cut -c1-2 || true); OS_X_VER=$((OS_X_VER-4)) 5 | 6 | script=https://raw.githubusercontent.com/devizer/test-and-build/master/install-build-tools.sh; (wget -q -nv --no-check-certificate -O - $script 2>/dev/null || curl -ksSL $script) | bash 7 | Say "Hello. Installing mono on Linux/OSX. Non-x64 arch is fully supported" 8 | 9 | # OSX? 10 | if [[ "$(uname -s)" == "Darwin" ]]; then 11 | MONO_Url=https://download.mono-project.com/archive/6.4.0/macos-10-universal/MonoFramework-MDK-6.4.0.198.macos10.xamarin.universal.pkg 12 | MONO_Url=https://download.mono-project.com/archive/6.8.0/macos-10-universal/MonoFramework-MDK-6.8.0.123.macos10.xamarin.universal.pkg 13 | curl -o ~/mono.pkg $MONO_Url 14 | sudo installer -verbose -pkg ~/mono.pkg -target / 15 | rm ~/mono.pkg 16 | export PATH="/Library/Frameworks/Mono.framework/Versions/Current/Commands:$PATH" 17 | else 18 | 19 | cmd1="try-and-retry apt-get -qq update >/dev/null" 20 | cmd2="try-and-retry apt-get install -y -qq git sudo jq tar bzip2 gzip curl lsb-release procps gnupg apt-transport-https dirmngr ca-certificates >/dev/null" 21 | for cmd in "$cmd1" "$cmd2"; do 22 | echo "eval [$cmd]" 23 | sudo true >/dev/null 2>&1 && time eval "sudo $cmd" || time eval "$cmd" 24 | done 25 | 26 | Say "Configure apt for missed signatures" 27 | echo 'Acquire::Check-Valid-Until "0";' | sudo tee /etc/apt/apt.conf.d/10no--check-valid-until 28 | echo 'APT::Get::Assume-Yes "true";' | sudo tee /etc/apt/apt.conf.d/11assume-yes 29 | echo 'APT::Get::AllowUnauthenticated "true";' | sudo tee /etc/apt/apt.conf.d/12allow-unauth 30 | 31 | # echo net.ipv6.conf.all.disable_ipv6=1 | sudo tee /etc/sysctl.d/disableipv6.conf || true 32 | 33 | if [[ "$(command -v mono)" == "" ]]; then 34 | # export MONO_ENV_OPTIONS=-O=-aot 35 | export MONO_USE_LLVM=0 36 | 37 | try-and-retry sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys A6A19B38D3D831EF 38 | # sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF 39 | source /etc/os-release 40 | def="deb https://download.mono-project.com/repo/$ID stable-$(lsb_release -s -c) main" 41 | if [[ "$ID" == "raspbian" ]]; then def="deb https://download.mono-project.com/repo/debian stable-raspbian$(lsb_release -cs) main"; fi 42 | echo "$def" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list >/dev/null 43 | echo "Official mono repo: /etc/apt/sources.list.d/mono-official-stable.list" 44 | echo $def 45 | try-and-retry sudo apt-get -qq update; 46 | Say "Final mono installation" 47 | time try-and-retry sudo apt-get -qq install mono-complete nuget msbuild -y 48 | # --allow-unauthenticated ? 49 | fi 50 | set -e 51 | mono --version 52 | 53 | fi 54 | 55 | echo "uname -m: $(uname -m)" 56 | 57 | need_core=false 58 | if [[ "$(uname -m)" == "aarch64" || "$(uname -m)" == "x86_64" ]] || [ $OS_X_VER -ge 13 ]; then need_core=true; fi 59 | if [[ $OS_X_VER -le 12 && $OS_X_VER -gt 0 ]] ; then need_core=false; fi 60 | if [[ $need_core == true ]]; then 61 | DOTNET_Url=https://dot.net/v1/dotnet-install.sh 62 | cmd_curl_dotnet_install="curl -ksSL -o /tmp/dotnet-install.sh $DOTNET_Url" 63 | cmd_dotnet_22="bash /tmp/dotnet-install.sh -c 2.2 -i ~/.dotnet" 64 | cmd_dotnet_30="bash /tmp/dotnet-install.sh -c 3.0 -i ~/.dotnet" 65 | cmd_dotnet_31="bash /tmp/dotnet-install.sh -c 3.1 -i ~/.dotnet" 66 | for cmd in "$cmd_curl_dotnet_install" "$cmd_dotnet_22" "$cmd_dotnet_30" "$cmd_dotnet_31"; do 67 | echo "|# $cmd" 68 | time (eval "$cmd" || eval "$cmd" || eval "$cmd") 69 | done 70 | 71 | echo '#!/usr/bin/env bash 72 | mkdir -p $HOME/.dotnet/tools $HOME/.dotnet 73 | export PATH="$HOME/.dotnet:$PATH:$HOME/.dotnet/tools" 74 | export DOTNET_ROOT="$HOME/.dotnet" 75 | ' >> ~/.dotnet-env 76 | chmod +x ~/.dotnet-env 77 | . ~/.dotnet-env 78 | echo ' 79 | . ~/.dotnet-env 80 | ' >> ~/.bashrc 81 | dotnet --info || true 82 | dotnet tool install -g BenchmarkDotNet.Tool || true 83 | fi 84 | 85 | set +e 86 | -------------------------------------------------------------------------------- /Tests4Mono/nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Tests4Mono/say.include.sh: -------------------------------------------------------------------------------- 1 | counter=0; 2 | function header() { 3 | startAt=${startAt:-$(date +%s)}; elapsed=$(date +%s); elapsed=$((elapsed-startAt)); 4 | if [[ $(uname -s) != Darwin ]]; then 5 | elapsed=$(TZ=UTC date -d "@${elapsed}" "+%_H:%M:%S" 2>/dev/null); 6 | else 7 | elapsed=$(TZ=UTC date -r "${elapsed}" "+%_H:%M:%S" 2>/dev/null); 8 | fi 9 | LightGreen='\033[1;32m'; Yellow='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'; LightGray='\033[1;2m'; 10 | printf "${LightGray}${elapsed:-}${NC} ${LightGreen}$1${NC} ${Yellow}$2${NC}\n"; 11 | } 12 | function Say() { 13 | echo ""; 14 | counter=$((counter+1)); 15 | header "STEP $counter" "$1"; 16 | } 17 | Say "" >/dev/null; 18 | counter=0; 19 | -------------------------------------------------------------------------------- /Universe.CpuUsage.Banchmark/BenchmarkProgram.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Configs; 2 | using BenchmarkDotNet.Environments; 3 | using BenchmarkDotNet.Jobs; 4 | using BenchmarkDotNet.Reports; 5 | using BenchmarkDotNet.Running; 6 | 7 | namespace Universe.CpuUsage.Banchmark 8 | { 9 | class BenchmarkProgram 10 | { 11 | public static void Main(string[] args) 12 | { 13 | // var run = Job.ShortRun; 14 | var run = Job.MediumRun; 15 | IConfig config = ManualConfig.Create(DefaultConfig.Instance); 16 | // Job jobLlvm = Job.InProcess; 17 | Job jobLlvm = run.With(Jit.Llvm).With(MonoRuntime.Default).WithId("LLVM-ON").WithWarmupCount(3); 18 | Job jobNoLlvm = run.With(MonoRuntime.Default).WithId("LLVM-OFF").WithWarmupCount(3); 19 | Job jobCore21 = run.With(CoreRuntime.Core21).WithId("NET-CORE").WithWarmupCount(3); 20 | Job jobCore50 = run.With(CoreRuntime.Core50).WithId("NET-CORE").WithWarmupCount(3); 21 | Job jobFW47 = run.With(ClrRuntime.Net47).WithId("NETFW-47").WithWarmupCount(3); 22 | config = config.With(new[] { jobLlvm, jobNoLlvm, jobCore21, jobCore50}); 23 | Summary summary = BenchmarkRunner.Run(config); 24 | } 25 | 26 | 27 | } 28 | } -------------------------------------------------------------------------------- /Universe.CpuUsage.Banchmark/CpuUsageBenchmarks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using BenchmarkDotNet.Attributes; 6 | using BenchmarkDotNet.Jobs; 7 | 8 | namespace Universe.CpuUsage.Banchmark 9 | { 10 | /*[SimpleJob(RuntimeMoniker.NetCoreApp30)]*/ 11 | [RankColumn] 12 | [MemoryDiagnoser] 13 | public class CpuUsageBenchmarks 14 | { 15 | [Benchmark] 16 | public int ManagedThreadId() 17 | { 18 | return Thread.CurrentThread.ManagedThreadId; 19 | } 20 | 21 | [Benchmark] 22 | public long CustomThreadId() 23 | { 24 | return LegacyNetStandardInterop.GetThreadId(); 25 | } 26 | 27 | [Benchmark] 28 | public void DateTimeNow() 29 | { 30 | var now = DateTime.Now; 31 | } 32 | 33 | [Benchmark] 34 | public void Stopwatch() 35 | { 36 | System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew(); 37 | var ticks = sw.ElapsedTicks; 38 | } 39 | 40 | [Benchmark] 41 | public void ByProcess() 42 | { 43 | CpuUsage.GetByProcess(); 44 | } 45 | 46 | [Benchmark] 47 | public void ByThread() 48 | { 49 | CpuUsage.GetByProcess(); 50 | } 51 | 52 | [Benchmark] 53 | public async Task CpuUsageAsyncMinimal() 54 | { 55 | CpuUsageAsyncWatcher watcher = new CpuUsageAsyncWatcher(); 56 | await Task.Run(() => "nothing to do"); 57 | var totals = watcher.Totals.GetSummaryCpuUsage(); 58 | } 59 | 60 | } 61 | } -------------------------------------------------------------------------------- /Universe.CpuUsage.Banchmark/Universe.CpuUsage.Banchmark.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net47;netcoreapp3.0;netcoreapp2.2;netcoreapp3.1;net5.0 6 | 7 | 8 | 9 | 1701;1702;CS0162;CS0414;NU1701 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Universe.CpuUsage.Banchmark/run-llvm.sh: -------------------------------------------------------------------------------- 1 | # work=$HOME/build/devizer; mkdir -p $work; cd $work; git clone https://github.com/devizer/Universe.CpuUsage; cd Universe.CpuUsage; git pull; cd Universe.CpuUsage.Banchmark; bash run-llvm.sh 2 | 3 | pushd .. 4 | nuget restore || true; dotnet restore || true 5 | popd 6 | msbuild *.csproj /t:rebuild /p:Configuration=Release /v:q 7 | cd bin/Release/net47 8 | mono --llvm Universe.CpuUsage.Banchmark.exe 9 | cd .. 10 | cd .. 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Universe.CpuUsage.Tests/AsyncSchedulerCases.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using System.Threading.Tasks.Schedulers; 4 | 5 | namespace Universe.CpuUsage.Tests 6 | { 7 | public class AsyncSchedulerCases 8 | { 9 | public static IEnumerable Schedulers 10 | { 11 | get 12 | { 13 | var defaultScheduler = TaskScheduler.Default; 14 | yield return new AsyncSchedulerCase("Default .NET Scheduler", new TaskFactory(defaultScheduler), defaultScheduler); 15 | 16 | // works on OSX 10.10 on mono and windows, may not work on dotnet except of ConcurrentTest 17 | // May fail CpuUsageAsyncWatcher_Tests.ParallelTests and CpuUsageAsyncWatcher_Tests.SimpleTest on Linux 18 | ThreadPerTaskScheduler tpt = new ThreadPerTaskScheduler(); 19 | yield return new AsyncSchedulerCase("Thread Per Task Scheduler", new TaskFactory(tpt), tpt); 20 | 21 | LimitedConcurrencyLevelTaskScheduler lc = new LimitedConcurrencyLevelTaskScheduler(16); 22 | yield return new AsyncSchedulerCase("Limited Concurrency Scheduler, up to 16 threads", new TaskFactory(lc), lc); 23 | 24 | LimitedConcurrencyLevelTaskScheduler lc1 = new LimitedConcurrencyLevelTaskScheduler(1); 25 | yield return new AsyncSchedulerCase("Limited Concurrency Scheduler. Single Thread", new TaskFactory(lc1), lc1); 26 | 27 | if (CrossInfo.ThePlatform == CrossInfo.Platform.Windows) 28 | { 29 | QueuedTaskScheduler s = new QueuedTaskScheduler(1); 30 | yield return new AsyncSchedulerCase("QueuedTaskScheduler, Single Thread", new TaskFactory(s), s); 31 | 32 | // May fail CpuUsageAsyncWatcher_Tests.ParallelTests only 33 | QueuedTaskScheduler s2 = new QueuedTaskScheduler(); 34 | yield return new AsyncSchedulerCase("QueuedTaskScheduler, Unlimited Threads", new TaskFactory(s2), s2); 35 | } 36 | 37 | } 38 | } 39 | } 40 | 41 | public class AsyncSchedulerCase 42 | { 43 | public string Title; 44 | public TaskFactory Factory; 45 | public TaskScheduler Scheduler; 46 | 47 | public AsyncSchedulerCase(string title, TaskFactory factory, TaskScheduler scheduler) 48 | { 49 | Title = title; 50 | Factory = factory; 51 | Scheduler = scheduler; 52 | } 53 | 54 | public override string ToString() 55 | { 56 | return Title; 57 | } 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /Universe.CpuUsage.Tests/CpuLoader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace Universe.CpuUsage.Tests 6 | { 7 | public class CpuLoader 8 | { 9 | // contains a value of each CpuUsage increment 10 | public readonly List Population = new List(64000/8); 11 | 12 | public int IncrementsCount => Population.Count; 13 | 14 | // MILLI seconds 15 | public static CpuLoader Run(int minDuration = 1, int minCpuUsage = 1, bool needKernelLoad = true) 16 | { 17 | CpuLoader ret = new CpuLoader(); 18 | ret.LoadCpu(minDuration, minCpuUsage, needKernelLoad); 19 | return ret; 20 | } 21 | 22 | // MILLI seconds 23 | private void LoadCpu(int minDuration, int minCpuUsage, bool needKernelLoad) 24 | { 25 | Stopwatch sw = Stopwatch.StartNew(); 26 | CpuUsage firstUsage = CpuUsage.GetByThread().Value; 27 | CpuUsage prev = firstUsage; 28 | bool isFirstChange = true; 29 | bool isDone = false; 30 | while (!isDone) 31 | { 32 | if (needKernelLoad) 33 | { 34 | var ptr = Marshal.AllocHGlobal(2*1024); 35 | Marshal.FreeHGlobal(ptr); 36 | } 37 | 38 | CpuUsage nextUsage = CpuUsage.GetByThread().Value; 39 | if (nextUsage.TotalMicroSeconds != prev.TotalMicroSeconds) 40 | { 41 | var increment = CpuUsage.Substruct(nextUsage, prev).TotalMicroSeconds; 42 | if (!isFirstChange) Population.Add(increment); 43 | isFirstChange = false; 44 | } 45 | 46 | prev = nextUsage; 47 | 48 | isDone = 49 | sw.ElapsedMilliseconds >= minDuration 50 | && (nextUsage - firstUsage).TotalMicroSeconds >= minCpuUsage * 1000L; 51 | } 52 | } 53 | 54 | } 55 | } -------------------------------------------------------------------------------- /Universe.CpuUsage.Tests/CpuUsageAsyncWatcher_AsyncStreamTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using NUnit.Framework; 7 | using Tests; 8 | 9 | namespace Universe.CpuUsage.Tests 10 | { 11 | #if NETCOREAPP3_0 || NETCOREAPP3_1 || NETCOREAPP5_0 12 | [TestFixture] 13 | public class CpuUsageAsyncWatcher_AsyncStreamTests : NUnitTestsBase 14 | { 15 | // https://blog.jetbrains.com/dotnet/2019/09/16/async-streams-look-new-language-features-c-8/ 16 | // https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/generate-consume-asynchronous-stream 17 | // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/async-streams 18 | [Test, TestCaseSource(typeof(AsyncSchedulerCases), nameof(AsyncSchedulerCases.Schedulers))] 19 | public async Task AwaitForEachTests(AsyncSchedulerCase testEnvironment) 20 | { 21 | // Pre JIT 22 | await testEnvironment.Factory.StartNew(() => CpuLoader.Run(minDuration: 1, minCpuUsage: 1, needKernelLoad: true)); 23 | // Act (durations are for debugging) 24 | CpuUsageAsyncWatcher watcher = new CpuUsageAsyncWatcher(); 25 | long expectedMicroseconds = 0; 26 | await foreach (var milliseconds in GetLoadings()) 27 | { 28 | await testEnvironment.Factory.StartNew(() => CpuLoader.Run(minDuration: milliseconds, minCpuUsage: milliseconds, needKernelLoad: true)); 29 | expectedMicroseconds += milliseconds * 1000L; 30 | } 31 | 32 | var totals = watcher.Totals; 33 | long actualMicroseconds = totals.GetSummaryCpuUsage().TotalMicroSeconds; 34 | Console.WriteLine($"Expected usage: {(expectedMicroseconds/1000d):n3}, Actual usage: {(actualMicroseconds/1000d):n3} milliseconds"); 35 | Console.WriteLine(totals.ToHumanString(taskDescription:"ParallelTests()")); 36 | 37 | // Assert 38 | Assert.GreaterOrEqual(totals.Count, 8, "Number of context switches should be 8 at least"); 39 | // 0.15 for qemu armhf, less for rest 40 | Assert.AreEqual(expectedMicroseconds, actualMicroseconds, Env.CpuUsagePrecision * expectedMicroseconds, "Actual CPU Usage should be about as expected."); 41 | } 42 | 43 | static async IAsyncEnumerable GetLoadings([EnumeratorCancellation] CancellationToken token = default) 44 | { 45 | foreach (var ret in new[] {250, 450, 650, 850}) 46 | { 47 | yield return ret; 48 | await Task.Delay(10); 49 | } 50 | } 51 | 52 | } 53 | #endif 54 | } -------------------------------------------------------------------------------- /Universe.CpuUsage.Tests/CpuUsageAsyncWatcher_Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using System.Threading.Tasks.Schedulers; 7 | using NUnit.Framework; 8 | using Tests; 9 | 10 | namespace Universe.CpuUsage.Tests 11 | { 12 | [TestFixture] 13 | public class CpuUsageAsyncWatcher_Tests : NUnitTestsBase 14 | { 15 | [OneTimeSetUp] 16 | public void SetUp() 17 | { 18 | PreJit().Wait(); 19 | } 20 | 21 | [Test, TestCaseSource(typeof(AsyncSchedulerCases), nameof(AsyncSchedulerCases.Schedulers))] 22 | public async Task SimpleTests(AsyncSchedulerCase testEnvironment) 23 | { 24 | if (!IsSupported()) return; 25 | 26 | // Act (durations are for debugging) 27 | CpuUsageAsyncWatcher watch = new CpuUsageAsyncWatcher(); 28 | await testEnvironment.Factory.StartNew(() => LoadCpu(milliseconds: 200)); 29 | await testEnvironment.Factory.StartNew(() => LoadCpu(milliseconds: 500)); 30 | await testEnvironment.Factory.StartNew(() => LoadCpu(milliseconds: 800)); 31 | await NotifyFinishedTasks(); 32 | watch.Stop(); 33 | var totals = watch.Totals; 34 | 35 | // Assert 36 | long actualMicroseconds = totals.GetSummaryCpuUsage().TotalMicroSeconds; 37 | long expectedMicroseconds = 1000L * (200 + 500 + 800); 38 | Console.WriteLine($"Expected usage: {(expectedMicroseconds/1000d):n3}, Actual usage: {(actualMicroseconds/1000d):n3} milliseconds"); 39 | Console.WriteLine(watch.ToHumanString(taskDescription:"SimpleTests()")); 40 | 41 | Assert.GreaterOrEqual(totals.Count, 6, "Number of context switches should be 6 at least"); 42 | Assert.AreEqual(expectedMicroseconds, actualMicroseconds, Env.CpuUsagePrecision * expectedMicroseconds, "Actual CPU Usage should be about as expected."); 43 | } 44 | 45 | [Test, TestCaseSource(typeof(AsyncSchedulerCases), nameof(AsyncSchedulerCases.Schedulers))] 46 | public async Task ParallelTests(AsyncSchedulerCase testEnvironment) 47 | { 48 | if (!IsSupported()) return; 49 | 50 | // Act (durations are for debugging) 51 | CpuUsageAsyncWatcher watcher = new CpuUsageAsyncWatcher(); 52 | TaskFactory tf = new TaskFactory(); 53 | var task4 = testEnvironment.Factory.StartNew(() => LoadCpu(milliseconds: 2400)); 54 | var task3 = testEnvironment.Factory.StartNew(() => LoadCpu(milliseconds: 2100)); 55 | var task2 = testEnvironment.Factory.StartNew(() => LoadCpu(milliseconds: 1800)); 56 | var task1 = testEnvironment.Factory.StartNew(() => LoadCpu(milliseconds: 1500)); 57 | await Task.WhenAll(task1, task2, task3, task4); 58 | await NotifyFinishedTasks(); 59 | watcher.Stop(); 60 | var totals = watcher.Totals; 61 | 62 | // Assert 63 | long actualMicroseconds = totals.GetSummaryCpuUsage().TotalMicroSeconds; 64 | long expectedMicroseconds = 1000L * (2400 + 2100 + 1800 + 1500); 65 | Console.WriteLine($"Expected usage: {(expectedMicroseconds/1000d):n3}, Actual usage: {(actualMicroseconds/1000d):n3} milliseconds"); 66 | Console.WriteLine(totals.ToHumanString(taskDescription:"ParallelTests()")); 67 | // 7 for windows 8 cores, rarely 6 for slow 2 core machine, but 7 for single core armv5 68 | Assert.GreaterOrEqual(totals.Count, 6, "Number of context switches should be 6 at least"); 69 | Assert.AreEqual(expectedMicroseconds, actualMicroseconds, Env.CpuUsagePrecision * expectedMicroseconds, "Actual CPU Usage should be about as expected."); 70 | } 71 | 72 | [Test, TestCaseSource(typeof(AsyncSchedulerCases), nameof(AsyncSchedulerCases.Schedulers))] 73 | public async Task ConcurrentTest(AsyncSchedulerCase testEnvironment) 74 | { 75 | if (!IsSupported()) return; 76 | // second context switch is lost for ThreadPerTaskScheduler 77 | if (testEnvironment.Scheduler is ThreadPerTaskScheduler) return; 78 | 79 | IList tasks = new List(); 80 | int errors = 0; 81 | int maxThreads = Math.Min(Environment.ProcessorCount + 9, 16); 82 | // hypothesis: each thread load should be exponential 83 | for (int i = 1; i <= maxThreads; i++) 84 | { 85 | var iCopy = i; 86 | tasks.Add( testEnvironment.Factory.StartNew(async () => 87 | { 88 | var expectedMilliseconds = iCopy * 400; 89 | if (!await CheckExpectedCpuUsage(expectedMilliseconds, testEnvironment.Factory)) 90 | Interlocked.Increment(ref errors); 91 | 92 | }, TaskCreationOptions.LongRunning).Unwrap()); 93 | } 94 | 95 | await Task.WhenAll(tasks); 96 | Assert.IsTrue(errors == 0, "Concurrent CpuUsageAsyncWatchers should not infer on each other. See details above"); 97 | } 98 | 99 | async Task CheckExpectedCpuUsage(int expectedMilliseconds, TaskFactory factory) 100 | { 101 | // Act 102 | CpuUsageAsyncWatcher watcher = new CpuUsageAsyncWatcher(); 103 | await factory.StartNew(() => LoadCpu(expectedMilliseconds)); 104 | watcher.Stop(); 105 | Console.WriteLine(watcher.ToHumanString(taskDescription: $"'Expected CPU Load is {expectedMilliseconds} milli-seconds'")); 106 | 107 | Assert.AreEqual(2, watcher.Totals.Count, "The CheckExpectedCpuUsage should produce exact 2 context switches"); 108 | // Assert: CpuUsage should be expectedMilliseconds 109 | var actualSeconds = watcher.GetSummaryCpuUsage().TotalMicroSeconds / 1000000d; 110 | bool isOk = actualSeconds >= expectedMilliseconds / 1000d; 111 | return isOk; 112 | } 113 | 114 | // Load CPU by number of milliseconds 115 | private void LoadCpu(int milliseconds = 42) => CpuLoader.Run(minDuration: milliseconds, minCpuUsage: milliseconds, needKernelLoad: true); 116 | 117 | private async Task PreJit() 118 | { 119 | Console.WriteLine("Pre-Jitting CpuUsageAsyncWatcher class"); 120 | CpuUsageAsyncWatcher watch = new CpuUsageAsyncWatcher(); 121 | await Task.Run(() => LoadCpu(1)); 122 | await NotifyFinishedTasks(); 123 | watch.Stop(); 124 | watch.ToHumanString(); 125 | Console.WriteLine("Pre-Jitted CpuUsageAsyncWatcher class"); 126 | } 127 | 128 | async Task NotifyFinishedTasks() 129 | { 130 | // v1 131 | // await Task.Run(() => "Nothing to do"); 132 | 133 | // v2 134 | // await Task.Delay(0); 135 | 136 | // v3 137 | for (int i = 0; i < 7; i++) 138 | { 139 | await Task.Run(() => Thread.Sleep(1)); 140 | await Task.Delay(1); 141 | } 142 | } 143 | 144 | bool IsSupported() 145 | { 146 | if (!CpuUsageAsyncWatcher.IsSupported) 147 | { 148 | Console.WriteLine("Skipping. The CpuUsageAsyncWatcher class requires NET Framework 4.6+, Net Core 1.0+ or NetStandard 2.0+"); 149 | } 150 | 151 | return CpuUsageAsyncWatcher.IsSupported; 152 | } 153 | 154 | 155 | } 156 | 157 | } -------------------------------------------------------------------------------- /Universe.CpuUsage.Tests/CrossInfo_Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | using Tests; 4 | 5 | namespace Universe.CpuUsage.Tests 6 | { 7 | [TestFixture] 8 | public class CrossInfo_Tests : NUnitTestsBase 9 | { 10 | 11 | [Test] 12 | public void Show_The_Platform() 13 | { 14 | Console.WriteLine($"The Platform: {CrossInfo.ThePlatform}"); 15 | Console.WriteLine($"Process: {IntPtr.Size * 8} bits"); 16 | Console.WriteLine($"Environment.OSVersion.Platform: {Environment.OSVersion.Platform}"); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Universe.CpuUsage.Tests/Env.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace Universe.CpuUsage.Tests 5 | { 6 | public class Env 7 | { 8 | // qemu emulator: 0.2, otherwise 0.1 9 | public static double CpuUsagePrecision => _CpuUsagePrecision.Value; 10 | 11 | private static Lazy _CpuUsagePrecision = new Lazy(() => 12 | { 13 | double ret = 0.1d; 14 | var raw = Environment.ExpandEnvironmentVariables("%CpuUsagePrecisionForAssert%"); 15 | var enUS = new CultureInfo("en-US"); 16 | if (!string.IsNullOrEmpty(raw)) 17 | if (double.TryParse(raw, NumberStyles.AllowDecimalPoint, enUS, out ret)) 18 | { 19 | } 20 | 21 | ret = ret > 0 ? ret : 0.1d; 22 | Console.WriteLine($"CpuUsagePrecisionForAssert: {ret}, raw: [{raw}]"); 23 | return ret; 24 | }); 25 | } 26 | } -------------------------------------------------------------------------------- /Universe.CpuUsage.Tests/LinuxKernelCacheFlusher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | 5 | namespace KernelManagementJam.Benchmarks 6 | { 7 | 8 | public class LinuxKernelCacheFlusher 9 | { 10 | // 60 seconds is an acceptable timeout for a build server 11 | private static readonly int WriteBuffersFlushingTimout = 60000; 12 | private static readonly int ReadBuffersFlushTimeout = 60000; 13 | 14 | // skipping is used for integration tests only 15 | private static bool SkipFlushing => Environment.GetEnvironmentVariable("SKIP_FLUSHING") == "true"; 16 | public static void Sync() 17 | { 18 | if (SkipFlushing) return; 19 | SyncWriteBuffer(); 20 | FlushReadBuffers(); 21 | } 22 | 23 | public static void SyncWriteBuffer() 24 | { 25 | StartAndIgnore("sync", "", WriteBuffersFlushingTimout); 26 | } 27 | 28 | public static void FlushReadBuffers() 29 | { 30 | bool isDropOk = false; 31 | try 32 | { 33 | File.WriteAllText("/proc/sys/vm/drop_caches", "3"); 34 | // sudo on Linux: ok 35 | // non-sudo on Linux: fail 36 | // macOS - fail 37 | isDropOk = true; 38 | } 39 | catch 40 | { 41 | } 42 | 43 | if (!isDropOk) 44 | { 45 | StartAndIgnore("sudo", "sh -c \"echo 1 > /proc/sys/vm/drop_caches; purge;\"", ReadBuffersFlushTimeout); 46 | } 47 | } 48 | 49 | static int StartAndIgnore(string executableName, string args, int timeoutMilliseconds) 50 | { 51 | Stopwatch sw = Stopwatch.StartNew(); 52 | string info = executableName == "sudo" ? args : executableName; 53 | 54 | try 55 | { 56 | ProcessStartInfo si = new ProcessStartInfo(executableName, args); 57 | if (string.IsNullOrEmpty(args)) si = new ProcessStartInfo(executableName); 58 | si.RedirectStandardError = true; 59 | si.RedirectStandardOutput = true; 60 | si.UseShellExecute = false; 61 | using (Process p = Process.Start(si)) 62 | { 63 | p.Start(); 64 | bool isOk = p.WaitForExit(timeoutMilliseconds); 65 | if (!isOk) 66 | { 67 | Console.WriteLine($"Warning! Flush command [{info}] is not completed in {sw.ElapsedMilliseconds:n0} milliseconds"); 68 | } 69 | #if DEBUG 70 | if (isOk) 71 | { 72 | Console.WriteLine($"Info: Flush command [{info}] successfully finished in {sw.ElapsedMilliseconds:n0} milliseconds"); 73 | } 74 | #endif 75 | return p.ExitCode; 76 | } 77 | } 78 | catch(Exception ex) 79 | { 80 | #if DEBUG 81 | Console.WriteLine($"Info: Process [{info}] failed in {sw.ElapsedMilliseconds:n0} milliseconds. {ex.GetType().Name} {ex.Message}"); 82 | #endif 83 | return -1; 84 | } 85 | } 86 | 87 | } 88 | } -------------------------------------------------------------------------------- /Universe.CpuUsage.Tests/LinuxResourcesUsage_Reader_Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Linq; 4 | using System.Text; 5 | using NUnit.Framework; 6 | using Tests; 7 | using Universe.CpuUsage.Interop; 8 | 9 | namespace Universe.CpuUsage.Tests 10 | { 11 | // git pull; time dotnet test --filter LinuxResourcesUsage 12 | [TestFixture] 13 | public class LinuxResourcesUsage_Reader_Tests : NUnitTestsBase 14 | { 15 | [Test] 16 | public void Is_Supported() 17 | { 18 | Console.WriteLine($"LinuxResourceUsageReader.IsSupported: {LinuxResourceUsageReader.IsSupported}"); 19 | 20 | bool expectedSupported = CrossInfo.ThePlatform == CrossInfo.Platform.Linux || 21 | CrossInfo.ThePlatform == CrossInfo.Platform.MacOSX; 22 | 23 | if (expectedSupported && !LinuxResourceUsageReader.IsSupported) 24 | Assert.Fail("On Linux 2.6.26+ the value of LinuxResourceUsageReader.IsSupported should be true"); 25 | } 26 | 27 | [Test] 28 | public void Grow_Usage() 29 | { 30 | if (CrossInfo.ThePlatform == CrossInfo.Platform.Windows) return; 31 | 32 | CpuLoader.Run(1, 1, true); 33 | CpuUsageScope scope = CrossInfo.ThePlatform == CrossInfo.Platform.MacOSX 34 | ? CpuUsageScope.Process 35 | : CpuUsageScope.Thread; 36 | 37 | Console.WriteLine($"Supported scope: {scope}"); 38 | LinuxResourceUsageReader.GetByScope(scope); 39 | var prev = LinuxResourceUsageReader.GetByScope(scope); 40 | for (int i = 0; i < 10; i++) 41 | { 42 | CpuLoader.Run(1, 1, true); 43 | CpuUsage? next = LinuxResourceUsageReader.GetByScope(scope); 44 | Console.WriteLine($" {i} -> {next}"); 45 | Assert.GreaterOrEqual(next.Value.KernelUsage.TotalMicroSeconds, prev.Value.KernelUsage.TotalMicroSeconds); 46 | Assert.GreaterOrEqual(next.Value.UserUsage.TotalMicroSeconds, prev.Value.UserUsage.TotalMicroSeconds); 47 | prev = next; 48 | } 49 | } 50 | 51 | [Test] 52 | public void Get_Process_Usage() 53 | { 54 | if (CrossInfo.ThePlatform == CrossInfo.Platform.Windows) 55 | { 56 | Console.WriteLine($"LinuxResourceUsageReader is not supported on platform {CrossInfo.ThePlatform}"); 57 | return; 58 | } 59 | 60 | var usage = LinuxResourceUsageReader.GetByProcess(); 61 | Console.WriteLine($"LinuxResourceUsageReader.GetByProcess(): {usage}"); 62 | } 63 | 64 | 65 | [Test] 66 | public void Get_Thread_Usage() 67 | { 68 | if (CrossInfo.ThePlatform == CrossInfo.Platform.Windows) 69 | { 70 | Console.WriteLine($"LinuxResourceUsageReader is not supported on platform {CrossInfo.ThePlatform}"); 71 | return; 72 | } 73 | 74 | var usage = LinuxResourceUsageReader.GetByThread(); 75 | Console.WriteLine($"LinuxResourceUsageReader.GetByThread(): {usage}"); 76 | } 77 | 78 | [Test] 79 | public void Show_Raw_Thread_Usage() 80 | { 81 | if (CrossInfo.ThePlatform == CrossInfo.Platform.Windows) 82 | { 83 | Console.WriteLine($"LinuxResourceUsageReader is not supported on platform {CrossInfo.ThePlatform}"); 84 | return; 85 | } 86 | 87 | var resources = LinuxResourceUsageInterop.GetRawUsageResources(LinuxResourceUsageInterop.RUSAGE_THREAD); 88 | Console.WriteLine($"GetRawUsageResources(RUSAGE_THREAD):{Environment.NewLine}{AsString(resources)}"); 89 | } 90 | 91 | [Test] 92 | public void Show_Raw_Process_Usage() 93 | { 94 | if (CrossInfo.ThePlatform == CrossInfo.Platform.Windows) 95 | { 96 | Console.WriteLine($"LinuxResourceUsageReader is not supported on platform {CrossInfo.ThePlatform}"); 97 | return; 98 | } 99 | 100 | var resources = LinuxResourceUsageInterop.GetRawUsageResources(LinuxResourceUsageInterop.RUSAGE_SELF); 101 | Console.WriteLine($"GetRawUsageResources(RUSAGE_SELF):{Environment.NewLine}{AsString(resources)}"); 102 | } 103 | 104 | static string AsString(IEnumerable arr) 105 | { 106 | if (arr == null) return ""; 107 | 108 | if (IntPtr.Size == 8 && CrossInfo.ThePlatform == CrossInfo.Platform.MacOSX) 109 | { 110 | var copy = arr.OfType().ToArray(); 111 | // microseconds on mac os are 4 bytes integers 112 | copy[1] = Convert.ToInt64(copy[1]) & 0xFFFFFFFF; 113 | copy[3] = Convert.ToInt64(copy[3]) & 0xFFFFFFFF; 114 | arr = copy; 115 | } 116 | 117 | const string rawNames = "u_sec u_usec k_sec k_usec maxrss ixrss idrss isrss minflt majflt nswap inblock oublock msgsnd msgrcv nsignals nvcsw nivcsw"; 118 | var names = rawNames.Split(' '); 119 | 120 | var count = arr.OfType().Count(); 121 | StringBuilder b = new StringBuilder(); 122 | var vLength = arr.OfType().Select(x => Convert.ToString(x).Length).Max(); 123 | int n = 0; 124 | foreach (var v in arr) 125 | { 126 | var comma = n + 1 < count ? "," : ""; 127 | var name = n < names.Length ? (names[n] + " ") : ""; 128 | name += n; 129 | name = name.PadLeft(11); 130 | b.AppendFormat("{0,-" + (11+1+vLength+2) + "}", $"{name}:{v}{comma}"); 131 | n++; 132 | b.Append(" "); 133 | if (n % 4 == 0) b.Append(Environment.NewLine); 134 | } 135 | 136 | return b.ToString(); 137 | } 138 | 139 | } 140 | } -------------------------------------------------------------------------------- /Universe.CpuUsage.Tests/NUnitTestsBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | using NUnit.Framework; 8 | using Universe.CpuUsage; 9 | 10 | namespace Tests 11 | { 12 | public class NUnitTestsBase 13 | { 14 | protected static TextWriter OUT; 15 | private Stopwatch StartAt; 16 | private CpuUsage? _CpuUsage_OnStart; 17 | private int TestCounter = 0; 18 | private static int GlobalTestCounter = 0; 19 | private int GlobalThisTestCounter = 0; 20 | 21 | 22 | [SetUp] 23 | public void BaseSetUp() 24 | { 25 | TestConsole.Setup(); 26 | Environment.SetEnvironmentVariable("SKIP_FLUSHING", null); 27 | StartAt = Stopwatch.StartNew(); 28 | _CpuUsage_OnStart = GetCpuUsage(); 29 | Interlocked.Increment(ref TestCounter); 30 | GlobalThisTestCounter = Interlocked.Increment(ref GlobalTestCounter); 31 | 32 | var testClassName = TestContext.CurrentContext.Test.ClassName; 33 | testClassName = testClassName.Split('.').LastOrDefault(); 34 | Console.WriteLine($"#{GlobalThisTestCounter}.{TestCounter} {{{TestContext.CurrentContext.Test.Name}}} @ {testClassName} starting..."); 35 | } 36 | 37 | CpuUsage? GetCpuUsage() 38 | { 39 | try 40 | { 41 | // return LinuxResourceUsageReader.GetByThread(); 42 | return CpuUsage.Get(CpuUsageScope.Thread); 43 | } 44 | catch 45 | { 46 | } 47 | 48 | return null; 49 | } 50 | 51 | [TearDown] 52 | public void BaseTearDown() 53 | { 54 | TimeSpan elapsed = StartAt.Elapsed; 55 | string cpuUsage = ""; 56 | if (_CpuUsage_OnStart.HasValue) 57 | { 58 | var onEnd = GetCpuUsage(); 59 | if (onEnd != null) 60 | { 61 | var delta = CpuUsage.Substruct(onEnd.Value, _CpuUsage_OnStart.Value); 62 | double user = delta.UserUsage.TotalMicroSeconds / 1000d; 63 | double kernel = delta.KernelUsage.TotalMicroSeconds / 1000d; 64 | double perCents = (user + kernel) / 1000d / elapsed.TotalSeconds; 65 | cpuUsage = $" (cpu: {(perCents*100):f0}%, {(user+kernel):n3} = {user:n3} [user] + {kernel:n3} [kernel] milliseconds)"; 66 | } 67 | } 68 | 69 | Console.WriteLine($"#{GlobalThisTestCounter}.{TestCounter} {{{TestContext.CurrentContext.Test.Name}}} >{TestContext.CurrentContext.Result.Outcome.Status.ToString().ToUpper()}< in {elapsed}{cpuUsage}{Environment.NewLine}"); 70 | } 71 | 72 | private static bool SetupDone; 73 | [OneTimeSetUp] 74 | public void BaseOneTimeSetUp() 75 | { 76 | TestConsole.Setup(); 77 | #if NETCORE || NETCOREAPP3_1 || NETCOREAPP5_0 78 | var osArch = System.Runtime.InteropServices.RuntimeInformation.OSArchitecture.ToString(); 79 | var processArch = System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture.ToString(); 80 | #else 81 | var osArch = "Not Available"; 82 | var processArch = "Not Available"; 83 | #endif 84 | if (!SetupDone) 85 | { 86 | SetupDone = true; 87 | Console.WriteLine($@"Before Any Tests: 88 | - Console.IsInputRedirected .... {Console.IsInputRedirected} 89 | - Console.IsOutputRedirected ... {Console.IsOutputRedirected} 90 | - Console.IsErrorRedirected .... {Console.IsErrorRedirected} 91 | - Environment.OSVersion.Platform {System.Environment.OSVersion.Platform} 92 | - CrossInfo.ThePlatform ........ {Universe.CrossInfo.ThePlatform} 93 | - OS Architecture .............. {osArch} 94 | - Process Architecture ......... {processArch} 95 | "); 96 | } 97 | 98 | ThreadPool.GetMinThreads(out var minWorker, out var minCompletion); 99 | ThreadPool.GetMaxThreads(out var maxWorker, out var maxCompletion); 100 | minWorker = 64; 101 | minCompletion = 64; 102 | ThreadPool.SetMinThreads(minWorker, minCompletion); 103 | } 104 | 105 | [OneTimeTearDown] 106 | public void BaseOneTimeTearDown() 107 | { 108 | // nothing todo 109 | } 110 | 111 | protected static bool IsDebug 112 | { 113 | get 114 | { 115 | #if DEBUG 116 | return true; 117 | #else 118 | return false; 119 | #endif 120 | } 121 | } 122 | 123 | public class TestConsole 124 | { 125 | static bool Done = false; 126 | public static void Setup() 127 | { 128 | if (!Done) 129 | { 130 | Done = true; 131 | Console.SetOut(new TW()); 132 | } 133 | } 134 | 135 | class TW : TextWriter 136 | { 137 | public override Encoding Encoding { get; } 138 | 139 | public override void WriteLine(string value) 140 | { 141 | // TestContext.Progress.Write(string.Join(",", value.Select(x => ((int)x).ToString("X2"))) ); 142 | // if (value.Length > Environment.NewLine.Length && value.EndsWith(Environment.NewLine)) 143 | // value = value.Substring(0, value.Length - Environment.NewLine.Length); 144 | 145 | 146 | TestContext.Progress.WriteLine(value); 147 | try 148 | { 149 | // TestContext.Error.WriteLine(value); // .WriteLine(); 150 | } 151 | catch 152 | { 153 | } 154 | } 155 | 156 | } 157 | } 158 | 159 | } 160 | 161 | 162 | [SetUpFixture] 163 | public class GlobalCleanUp 164 | { 165 | 166 | [OneTimeSetUp] 167 | public void RunBeforeAnyTests() 168 | { 169 | } 170 | 171 | [OneTimeTearDown] 172 | public void RunAfterAnyTests() 173 | { 174 | } 175 | } 176 | 177 | 178 | } -------------------------------------------------------------------------------- /Universe.CpuUsage.Tests/PosixProcessPriority.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Diagnostics; 5 | using System.Threading; 6 | 7 | namespace Universe.CpuUsage.Tests 8 | { 9 | class PosixProcessPriority 10 | { 11 | public static int WAIT_FOR_TIMEOUT = 5000; 12 | public static void ApplyMyPriority(ProcessPriorityClass priority) 13 | { 14 | // AppVeyor: Windows - OK, linux: need sudo 15 | // Travis: OSX 10.10 & 10.14 - need sudo, Ubuntu - ?! 16 | bool isOk = SetMyProcessPriority(priority); 17 | if (!isOk) 18 | { 19 | Console.WriteLine( 20 | $"WARNING! Process priority can not be changed. Permission is required"); 21 | } 22 | 23 | if (priority == ProcessPriorityClass.AboveNormal) 24 | Thread.CurrentThread.Priority = ThreadPriority.AboveNormal; 25 | else if (priority == ProcessPriorityClass.Normal) 26 | Thread.CurrentThread.Priority = ThreadPriority.Normal; 27 | else if (priority == ProcessPriorityClass.BelowNormal) 28 | Thread.CurrentThread.Priority = ThreadPriority.BelowNormal; 29 | else if (priority == ProcessPriorityClass.High) 30 | Thread.CurrentThread.Priority = ThreadPriority.Highest; 31 | else if (priority == ProcessPriorityClass.RealTime) 32 | Thread.CurrentThread.Priority = ThreadPriority.Highest; 33 | else if (priority == ProcessPriorityClass.Idle) 34 | Thread.CurrentThread.Priority = ThreadPriority.Lowest; 35 | else 36 | throw new NotSupportedException($"Priority {priority} is not supported for a thread"); 37 | } 38 | 39 | // For consistency with build-in Process.GetCurrentProcess().PriorityClass 40 | // Niceness is the same as by Net Core and Mono 41 | private static readonly Dictionary map = new Dictionary 42 | { 43 | {ProcessPriorityClass.AboveNormal, -6}, 44 | {ProcessPriorityClass.Normal, 0}, 45 | {ProcessPriorityClass.BelowNormal, 10}, 46 | {ProcessPriorityClass.High, -11}, 47 | // Bad idea - process may be frozen 48 | {ProcessPriorityClass.Idle, 19}, 49 | // Actually the is a separated API for RealTime priority management, 50 | // but it is not used by 51 | {ProcessPriorityClass.RealTime, -19}, 52 | }; 53 | 54 | 55 | public static bool SetMyProcessPriority(ProcessPriorityClass priority) 56 | { 57 | 58 | 59 | try 60 | { 61 | Process.GetCurrentProcess().PriorityClass = priority; 62 | return true; 63 | } 64 | catch (Win32Exception) 65 | { 66 | if (Environment.OSVersion.Platform == PlatformID.Win32NT) 67 | { 68 | // on windows we cant increase priority 69 | return false; 70 | } 71 | 72 | // Either Linux, OSX, FreeBSD, etc - all is ok 73 | // the package name is bsdutils 74 | map.TryGetValue(priority, out var niceness); 75 | ProcessStartInfo si = new ProcessStartInfo("sudo", $"renice -n {niceness} -p {Process.GetCurrentProcess().Id}"); 76 | // we need to mute output of both streams to console 77 | si.RedirectStandardError = true; 78 | si.RedirectStandardOutput = true; 79 | Process p; 80 | try 81 | { 82 | p = Process.Start(si); 83 | } 84 | catch 85 | { 86 | // Not a case for build servers 87 | Debug.WriteLine("It seems sudo is not installed or current user is not authorized"); 88 | return false; 89 | } 90 | 91 | using (p) 92 | { 93 | // sudo may require input of password 94 | // 5 seconds if for ARM under a high load 95 | bool isExited = p.WaitForExit(WAIT_FOR_TIMEOUT); 96 | if (!isExited) 97 | { 98 | try 99 | { 100 | p.Kill(); 101 | } 102 | catch 103 | { 104 | } 105 | } 106 | 107 | return p.ExitCode == 0; 108 | } 109 | } 110 | } 111 | 112 | public static int? GetNicenessOfCurrentProcess() 113 | { 114 | var pid = Process.GetCurrentProcess().Id.ToString(); 115 | ProcessStartInfo si = new ProcessStartInfo("ps", $"ax ax -o pid,ni"); 116 | // we need to mute output of both streams to console 117 | si.RedirectStandardError = true; 118 | si.RedirectStandardOutput = true; 119 | Process p = Process.Start(si); 120 | var rawOutput = p.StandardOutput.ReadToEnd(); 121 | var rawRows = rawOutput.Split(new[] {'\r', '\n'}, StringSplitOptions.RemoveEmptyEntries); 122 | foreach (var rawRow in rawRows) 123 | { 124 | var rawArr = rawRow.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries); 125 | if (rawArr.Length >= 2 && rawArr[0] == pid) 126 | { 127 | if (int.TryParse(rawArr[1], out var ret)) 128 | return ret; 129 | } 130 | } 131 | 132 | return null; 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /Universe.CpuUsage.Tests/PosixResourcesUsage_Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Threading; 5 | using KernelManagementJam.Benchmarks; 6 | using NUnit.Framework; 7 | using Tests; 8 | 9 | namespace Universe.CpuUsage.Tests 10 | { 11 | [TestFixture] 12 | public class PosixResourcesUsage_Tests : NUnitTestsBase 13 | { 14 | private string FileName = "IO-Metrics-Tests-" + Guid.NewGuid().ToString() + ".tmp"; 15 | 16 | // Works at home PC and AppVeyor linux, 17 | // Does not work on multi arch docker container on travis-ci 18 | private bool SkipPosixResourcesUsageAsserts => 19 | Environment.GetEnvironmentVariable("SKIP_POSIXRESOURCESUSAGE_ASSERTS") == "True"; 20 | 21 | private void WriteFile(int size) 22 | { 23 | Random rnd = new Random(42); 24 | byte[] content = new byte[size]; 25 | rnd.NextBytes(content); 26 | using (FileStream fs = new FileStream(FileName, FileMode.Create, FileAccess.Write, FileShare.ReadWrite, 128, FileOptions.WriteThrough)) 27 | { 28 | fs.Write(content, 0, content.Length); 29 | } 30 | } 31 | 32 | private void ReadFile() 33 | { 34 | var bytes = File.ReadAllBytes(FileName); 35 | } 36 | 37 | [TearDown] 38 | public void TearDown_IO_Metrics() 39 | { 40 | if (File.Exists(FileName)) File.Delete(FileName); 41 | } 42 | 43 | [Test] 44 | public void Is_Supported() 45 | { 46 | Console.WriteLine($"PosixResourceUsage.IsSupported: {PosixResourceUsage.IsSupported}"); 47 | } 48 | 49 | [Test] 50 | [TestCase(CpuUsageScope.Thread)] 51 | [TestCase(CpuUsageScope.Process)] 52 | public void Smoke_Test(CpuUsageScope scope) 53 | { 54 | if (!PosixResourceUsage.IsSupported) return; 55 | Console.WriteLine($"PosixResourceUsage.GetByScope({scope}): {PosixResourceUsage.GetByScope(scope)}"); 56 | } 57 | 58 | 59 | [Test] 60 | [TestCase(CpuUsageScope.Thread,1,9)] 61 | [TestCase(CpuUsageScope.Process,1,9)] 62 | // Actually max it is 99, but extra slow qemu sometimes needs 110 63 | [TestCase(CpuUsageScope.Thread,42, 242)] 64 | [TestCase(CpuUsageScope.Process,42,242)] 65 | public void ContextSwitch_Test(CpuUsageScope scope, int expectedSwitchCount, int expectedSwitchCountMax) 66 | { 67 | if (!PosixResourceUsage.IsSupported) return; 68 | if (scope == CpuUsageScope.Thread && CrossInfo.ThePlatform != CrossInfo.Platform.Linux) return; 69 | // Jit 70 | var jit = PosixResourceUsage.GetByScope(scope).Value; 71 | 72 | // Act 73 | PosixResourceUsage before = PosixResourceUsage.GetByScope(scope).Value; 74 | for (int i = 0; i < expectedSwitchCount; i++) 75 | { 76 | // CpuLoader.Run(1, 0, true); 77 | Stopwatch sw = Stopwatch.StartNew(); 78 | while (sw.ElapsedMilliseconds < 1) ; 79 | // on some build server sometimes fails (Azure - sometimes fail, AppVeyor - OK) 80 | Thread.Sleep(3); 81 | } 82 | 83 | PosixResourceUsage after = PosixResourceUsage.GetByScope(scope).Value; 84 | // var delta = PosixResourceUsage.Substruct(after, before); 85 | var delta = after - before; 86 | Console.WriteLine($"delta.InvoluntaryContextSwitches = {delta.InvoluntaryContextSwitches}"); 87 | Console.WriteLine($"delta.VoluntaryContextSwitches = {delta.VoluntaryContextSwitches}"); 88 | 89 | // Assert 90 | if (CrossInfo.ThePlatform != CrossInfo.Platform.Linux) return; 91 | if (SkipPosixResourcesUsageAsserts) return; 92 | Assert.IsTrue( 93 | delta.VoluntaryContextSwitches >= expectedSwitchCount && delta.VoluntaryContextSwitches <= expectedSwitchCountMax, 94 | $"VoluntaryContextSwitches: Expected {expectedSwitchCount} ... {expectedSwitchCountMax}; actual {delta.VoluntaryContextSwitches}"); 95 | // if (scope == CpuUsageScope.Thread) 96 | // Assert.IsTrue(expectedSwitchCount == delta.VoluntaryContextSwitches || expectedSwitchCount == delta.VoluntaryContextSwitches - 1); 97 | // else 98 | // Assert.GreaterOrEqual(delta.VoluntaryContextSwitches, expectedSwitchCount); 99 | } 100 | 101 | [Test] 102 | [TestCase(CpuUsageScope.Thread)] 103 | [TestCase(CpuUsageScope.Process)] 104 | // Does not work on drone.io in container 105 | public void IO_Reads_Test(CpuUsageScope scope) 106 | { 107 | if (!PosixResourceUsage.IsSupported) return; 108 | if (scope == CpuUsageScope.Thread && CrossInfo.ThePlatform != CrossInfo.Platform.Linux) return; 109 | 110 | // Arrange 111 | var numBytes = 20*1048*1024; 112 | WriteFile(numBytes); 113 | LinuxKernelCacheFlusher.Sync(); 114 | 115 | // Act 116 | PosixResourceUsage before = PosixResourceUsage.GetByScope(scope).Value; 117 | ReadFile(); 118 | PosixResourceUsage after = PosixResourceUsage.GetByScope(scope).Value; 119 | var delta = PosixResourceUsage.Substruct(after, before); 120 | Console.WriteLine($"Read Operations. Before: [{before.ReadOps}]. After: [{after.ReadOps}]"); 121 | Console.WriteLine($"Operation: Read {numBytes:n0} bytes. ReadOps = {delta.ReadOps}. WriteOps = {delta.WriteOps}"); 122 | 123 | // Assert 124 | if (CrossInfo.ThePlatform != CrossInfo.Platform.Linux) return; 125 | if (SkipPosixResourcesUsageAsserts || !IsIoReadsWritesSupported()) return; 126 | Assert.Greater(delta.ReadOps, 0); 127 | } 128 | 129 | [Test] 130 | [TestCase(CpuUsageScope.Thread)] 131 | [TestCase(CpuUsageScope.Process)] 132 | public void IO_Write_Test(CpuUsageScope scope) 133 | { 134 | if (!PosixResourceUsage.IsSupported) return; 135 | if (scope == CpuUsageScope.Thread && CrossInfo.ThePlatform != CrossInfo.Platform.Linux) return; 136 | 137 | // Arrange: nothing to do 138 | 139 | // Act 140 | PosixResourceUsage before = PosixResourceUsage.GetByScope(scope).Value; 141 | var numBytes = 10*512*1024; 142 | WriteFile(numBytes); 143 | PosixResourceUsage after = PosixResourceUsage.GetByScope(scope).Value; 144 | var delta = PosixResourceUsage.Substruct(after, before); 145 | Console.WriteLine($"Operation: write {numBytes:n0} bytes. ReadOps = {delta.ReadOps}. WriteOps = {delta.WriteOps}"); 146 | 147 | // Assert 148 | if (CrossInfo.ThePlatform != CrossInfo.Platform.Linux) return; 149 | if (SkipPosixResourcesUsageAsserts || !IsIoReadsWritesSupported()) return; 150 | Assert.Greater(delta.WriteOps, 0); 151 | } 152 | 153 | bool IsIoReadsWritesSupported() 154 | { 155 | try 156 | { 157 | return File.Exists("/proc/self/io"); 158 | } 159 | catch 160 | { 161 | // termux on non-rooted android phone. May be supported but we cant know 162 | return false; 163 | } 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /Universe.CpuUsage.Tests/PrecisionTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Text; 5 | using NUnit.Framework; 6 | using Tests; 7 | using Universe.CpuUsage.Tests.Statistica; 8 | 9 | namespace Universe.CpuUsage.Tests 10 | { 11 | [TestFixture] 12 | public class PrecisionTest : NUnitTestsBase 13 | { 14 | public class PrecisionCase 15 | { 16 | public ProcessPriorityClass Priority; 17 | public bool IncludeKernelLoad; 18 | 19 | public PrecisionCase(ProcessPriorityClass priority, bool includeKernelLoad) 20 | { 21 | Priority = priority; 22 | IncludeKernelLoad = includeKernelLoad; 23 | } 24 | 25 | public override string ToString() 26 | { 27 | return $"{Priority} priority, {(IncludeKernelLoad ? "WITH KERNEL Load" : "without kernel load")}"; 28 | } 29 | } 30 | 31 | public static PrecisionCase[] GetGranularityCases() 32 | { 33 | var fullCases = new[] 34 | { 35 | new PrecisionCase(ProcessPriorityClass.AboveNormal, false), 36 | new PrecisionCase(ProcessPriorityClass.AboveNormal, true), 37 | new PrecisionCase(ProcessPriorityClass.Normal, false), 38 | new PrecisionCase(ProcessPriorityClass.Normal, true), 39 | new PrecisionCase(ProcessPriorityClass.BelowNormal, false), 40 | new PrecisionCase(ProcessPriorityClass.BelowNormal, true), 41 | }; 42 | 43 | if (CrossFullInfo.IsMono) 44 | return new[] 45 | { 46 | new PrecisionCase(ProcessPriorityClass.AboveNormal, false), 47 | new PrecisionCase(ProcessPriorityClass.AboveNormal, true), 48 | }; 49 | 50 | return fullCases; 51 | } 52 | 53 | 54 | [Test] 55 | [TestCaseSource(typeof(PrecisionTest), nameof(GetGranularityCases))] 56 | public void Show_Precision_Histogram(PrecisionCase precisionCase) 57 | { 58 | PosixProcessPriority.ApplyMyPriority(precisionCase.Priority); 59 | var preJit = CpuLoader.Run(11, 1, true).IncrementsCount; 60 | Console.WriteLine($"OS: {CrossFullInfo.OsDisplayName}"); 61 | Console.WriteLine($"CPU: {CrossFullInfo.ProcessorName}"); 62 | var actualCase = new PrecisionCase(Process.GetCurrentProcess().PriorityClass, precisionCase.IncludeKernelLoad); 63 | Console.WriteLine($"Granularity[{actualCase}] (it may vary if Intel SpeedStep, TurboBoost, etc are active):"); 64 | int count = CrossFullInfo.IsMono ? 2 : 9; 65 | for (int i = 1; i <= count; i++) 66 | { 67 | var cpuLoadResult = CpuLoader.Run(minDuration: 1000, needKernelLoad: precisionCase.IncludeKernelLoad); 68 | long granularity = cpuLoadResult.IncrementsCount; 69 | double microSeconds = 1000000d / granularity; 70 | Console.WriteLine($" #{i}: {granularity} increments a second, eg {microSeconds:n1} microseconds in average"); 71 | 72 | // Percentile report 73 | PercentileCalc percentileCalc = new PercentileCalc(cpuLoadResult.Population, x => x, x => x.ToString("n3")); 74 | double[] percents = new[] {1d, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99}; 75 | // double[] percents = new[] {1d, 5, 10, 90, 95, 99}.Reverse().ToArray(); 76 | StringBuilder pcReport = new StringBuilder(); 77 | for(int p=0; p stat = new Statistica(cpuLoadResult.Population, x => (double) x, x => x, x => x.ToString("n3")); 87 | var histogram = stat.BuildReport(12, 3); 88 | Console.WriteLine(histogram.ToConsole("CPU Usage increments (microseconds)", 42)); 89 | } 90 | 91 | } 92 | 93 | 94 | } 95 | } 96 | 97 | -------------------------------------------------------------------------------- /Universe.CpuUsage.Tests/RisingTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Runtime.InteropServices; 5 | using NUnit.Framework; 6 | using Tests; 7 | 8 | namespace Universe.CpuUsage.Tests 9 | { 10 | [TestFixture] 11 | public class RisingTest: NUnitTestsBase 12 | { 13 | [Test] 14 | public void Test_Thread() 15 | { 16 | for(int i=1; i<=9; i++) 17 | Load(CpuUsageScope.Thread, 256); 18 | } 19 | 20 | [Test] 21 | public void Test_Process() 22 | { 23 | for(int i=1; i<=9; i++) 24 | Load(CpuUsageScope.Process, 256); 25 | } 26 | 27 | void Load(CpuUsageScope scope, int milliseconds) 28 | { 29 | CpuUsage? prev = CpuUsage.Get(scope); 30 | Assert.IsTrue(prev.HasValue, "Prev should has value"); 31 | Stopwatch sw = Stopwatch.StartNew(); 32 | while (sw.ElapsedMilliseconds <= milliseconds) 33 | { 34 | EatSomeCpu(); 35 | EatSomeMem(); 36 | } 37 | 38 | GC.Collect(); 39 | GC.WaitForPendingFinalizers(); 40 | GC.Collect(); 41 | 42 | CpuUsage? next = CpuUsage.Get(scope); 43 | double microSeconds = sw.ElapsedTicks * 1000000d / Stopwatch.Frequency; 44 | Assert.IsTrue(next.HasValue, "Next should has value"); 45 | 46 | 47 | var delta = CpuUsage.Substruct(next.Value, prev.Value); 48 | Assert.GreaterOrEqual(next.Value.KernelUsage.TotalMicroSeconds, prev.Value.KernelUsage.TotalMicroSeconds, "Kernel usage should be greater or equal"); 49 | Assert.GreaterOrEqual(next.Value.UserUsage.TotalMicroSeconds, prev.Value.UserUsage.TotalMicroSeconds, "User usage should be greater or equal"); 50 | 51 | string message = string.Format("Duration: {0:f3} of {4}, CPU@{3}: {1:f3} = {2}", 52 | microSeconds/1000, 53 | (delta.KernelUsage.MicroSeconds + delta.UserUsage.MicroSeconds)/1000d, 54 | delta, 55 | scope, 56 | milliseconds); 57 | 58 | Console.WriteLine(message); 59 | } 60 | 61 | void EatSomeMem() 62 | { 63 | Stopwatch sw = Stopwatch.StartNew(); 64 | List list = new List(); 65 | List saved = new List(); 66 | 67 | for (int i = 0; i < 100 && sw.ElapsedMilliseconds < 1000; i++) 68 | { 69 | list.Add(Marshal.AllocHGlobal(1 * 1000 * 1000)); 70 | saved.Add(new byte[1*1000*1000]); 71 | } 72 | 73 | foreach (IntPtr ptr in list) 74 | Marshal.FreeHGlobal(ptr); 75 | } 76 | 77 | void EatSomeCpu() 78 | { 79 | Stopwatch sw = Stopwatch.StartNew(); 80 | while (sw.ElapsedMilliseconds < 11) ; 81 | } 82 | 83 | } 84 | } -------------------------------------------------------------------------------- /Universe.CpuUsage.Tests/SmokeCpuUsage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | using Tests; 4 | 5 | namespace Universe.CpuUsage.Tests 6 | { 7 | [TestFixture] 8 | public class SmokeCpuUsage: NUnitTestsBase 9 | { 10 | 11 | [Test] 12 | public void Does_Not_Fail_ByThread() 13 | { 14 | CpuUsage? usage = CpuUsage.GetByThread(); 15 | Console.WriteLine($"Thread's CPU Usage: {usage}"); 16 | } 17 | 18 | [Test] 19 | public void Is_Not_Null_ByThread() 20 | { 21 | var usage = CpuUsage.GetByThread(); 22 | Console.WriteLine($"Thread's CPU Usage: {usage}"); 23 | Assert.IsTrue(usage.HasValue); 24 | } 25 | 26 | [Test] 27 | public void Does_Not_Fail_ByProcess() 28 | { 29 | CpuUsage? usage = CpuUsage.GetByProcess(); 30 | Console.WriteLine($"Process's CPU Usage: {usage}"); 31 | } 32 | 33 | [Test] 34 | public void Is_Not_Null_ByProcess() 35 | { 36 | var usage = CpuUsage.GetByProcess(); 37 | Console.WriteLine($"Process's CPU Usage: {usage}"); 38 | Assert.IsTrue(usage.HasValue); 39 | // cool! 40 | var _ = usage.Value.KernelUsage.ToString() + usage.Value.UserUsage.ToString(); 41 | } 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /Universe.CpuUsage.Tests/Statistica/HistogramReport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Universe.CpuUsage.Tests.Statistica 7 | { 8 | public class HistogramReport 9 | { 10 | public readonly Func IntervalAsString; 11 | 12 | public double Min { get; private set; } 13 | public double Max { get; private set; } 14 | public double Mean { get; private set; } 15 | public double Variance { get; private set; } 16 | public double FilteredMin { get; private set; } 17 | public double FilteredMax { get; private set; } 18 | 19 | public long LowerOutliers { get; private set; } 20 | public ICollection Histogram { get; private set; } 21 | public long HighOutliers { get; private set; } 22 | 23 | public HistogramReport(Func intervalAsString, double min, double max, double mean, double variance, double filteredMin, double filteredMax, long lowerOutliers, ICollection histogram, long highOutliers) 24 | { 25 | IntervalAsString = intervalAsString; 26 | Min = min; 27 | Max = max; 28 | Mean = mean; 29 | Variance = variance; 30 | FilteredMin = filteredMin; 31 | FilteredMax = filteredMax; 32 | LowerOutliers = lowerOutliers; 33 | Histogram = histogram; 34 | HighOutliers = highOutliers; 35 | } 36 | 37 | public string ToConsole(string caption, int barWidth = 42, int indent = 5) 38 | { 39 | string pre = indent > 0 ? new string(' ', indent) : ""; 40 | var maxCount = Math.Max(LowerOutliers, HighOutliers); 41 | maxCount = Math.Max(maxCount, Histogram.Max(x => x.Count)); 42 | 43 | var sumCount = LowerOutliers + HighOutliers + Histogram.Sum(x => x.Count); 44 | 45 | var allFormattedValues = Histogram.Select(x => IntervalAsString(x.From)).Concat(Histogram.Select(x => IntervalAsString(x.To))); 46 | var maxFormattedValueLength = allFormattedValues.Max(x => x.Length); 47 | 48 | 49 | List captions = new List(); 50 | List bars = new List(); 51 | 52 | Action addBar = count => 53 | { 54 | var lenD = count * 1d * barWidth / maxCount; 55 | var len = (int) lenD; 56 | var perCents = count * 100d / sumCount; 57 | // var perCentsAsString = count == 0 ? "--" : (" " + perCents.ToString("f1") + "%"); 58 | var perCentsAsString = count == 0 ? "" : perCents.ToString("f1") + "%"; 59 | 60 | bars.Add(String.Format(" {0,6} | {1}", 61 | perCentsAsString, 62 | len > 0 ? new string('@', len) : "" 63 | )); 64 | }; 65 | 66 | if (LowerOutliers > 0) 67 | { 68 | captions.Add($"Lower Outliers"); 69 | addBar(LowerOutliers); 70 | } 71 | 72 | foreach (var rangePart in Histogram) 73 | { 74 | Func valueToString = v => String.Format("{0,-" + maxFormattedValueLength + "}", IntervalAsString(v)); 75 | captions.Add($"{valueToString(rangePart.From)} ... {valueToString(rangePart.To)}"); 76 | addBar(rangePart.Count); 77 | } 78 | 79 | if (LowerOutliers > 0) 80 | { 81 | captions.Add($"Higher Outliers"); 82 | addBar(HighOutliers); 83 | } 84 | 85 | // var maxFormattedValue = 86 | var maxCaptionWidth = captions.Max(x => x.Length); 87 | maxCaptionWidth = Math.Max(2, maxCaptionWidth); 88 | 89 | StringBuilder ret = new StringBuilder(); 90 | ret.AppendLine($"{pre}Histogram '{caption}'"); 91 | 92 | for (int i = 0; i < captions.Count; i++) 93 | { 94 | ret.AppendFormat("{2}{0,-" + maxCaptionWidth + "} {1}", captions[i], bars[i], pre).AppendLine(); 95 | } 96 | 97 | return ret.ToString(); 98 | } 99 | 100 | 101 | public class RangePart 102 | { 103 | public int No { get; private set; } 104 | public double From { get; private set; } 105 | public double To { get; private set; } 106 | public long Count { get; internal set; } 107 | 108 | public RangePart(int no, double @from, double to, long count) 109 | { 110 | No = no; 111 | From = @from; 112 | To = to; 113 | Count = count; 114 | } 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /Universe.CpuUsage.Tests/Statistica/PercentileCalc.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Universe.CpuUsage.Tests.Statistica 6 | { 7 | public class PercentileCalc 8 | { 9 | public readonly ICollection Source; 10 | public readonly Func AsNumber; 11 | public readonly Func HistogramIntervalAsString; 12 | 13 | private List Sorted = new List(); 14 | 15 | public PercentileCalc(ICollection source, Func asNumber, Func histogramIntervalAsString) 16 | { 17 | Source = source; 18 | AsNumber = asNumber; 19 | HistogramIntervalAsString = histogramIntervalAsString; 20 | Sorted = source.Select(x => AsNumber(x)).OrderBy(x => x).ToList(); 21 | } 22 | 23 | public double this[double percentile] 24 | { 25 | get 26 | { 27 | if (percentile < 0 || percentile>100) throw new ArgumentException(); 28 | int pos = (int) (Sorted.Count / 100d * percentile); 29 | pos = Math.Min(pos, Sorted.Count - 1); 30 | return Sorted[pos]; 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Universe.CpuUsage.Tests/Statistica/Statistica.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Universe.CpuUsage.Tests.Statistica 6 | { 7 | public class Statistica 8 | { 9 | public readonly ICollection Source; 10 | public readonly Func AsNumber; 11 | public readonly Func RoundHistogramInterval; 12 | public readonly Func HistogramIntervalAsString; 13 | 14 | // public static Statistica Create() 15 | 16 | public Statistica(ICollection source, Func asNumber, Func roundHistogramInterval, Func histogramIntervalAsString) 17 | { 18 | Source = source; 19 | AsNumber = asNumber; 20 | RoundHistogramInterval = roundHistogramInterval; 21 | HistogramIntervalAsString = histogramIntervalAsString; 22 | } 23 | 24 | public HistogramReport BuildReport(int intervalCount = 15, double variances = 3) 25 | { 26 | var doubles = Source.Select(x => AsNumber(x)); 27 | var count = doubles.Count(); 28 | var min = doubles.Min(); 29 | var max = doubles.Max(); 30 | var avg = doubles.Average(); 31 | var variance = doubles.Aggregate(0d, (result, item) => result + Math.Pow(item - avg, 2), result => Math.Sqrt(result) / count); 32 | var filteredMin = min; 33 | var filteredMax = max; 34 | var filteredDistance = filteredMax - filteredMin; 35 | 36 | var lowerOutliersCount = doubles.Count(x => (x - filteredMin) <= -Double.Epsilon); 37 | var highOutliersCount = doubles.Count(x => (x - filteredMax) >= Double.Epsilon); 38 | 39 | var filteredDoubles = doubles.Where(x => (x - filteredMin) >= -Double.Epsilon && (x - filteredMax) <= Double.Epsilon).ToArray(); 40 | List.RangePart> intervals = new List.RangePart>(); 41 | for (int i = 0; i < intervalCount; i++) 42 | { 43 | double from = filteredMin + i * filteredDistance / intervalCount; 44 | double to = filteredMin + (i + 1) * filteredDistance / intervalCount; 45 | // var filtered4interval = filteredDoubles.Where(x => (x - from) >= -Double.Epsilon && (x - to) <= Double.Epsilon).ToArray(); 46 | // long perIntervalCount = filtered4interval.Length; 47 | // double perCents = intervalCount * 100d / filteredDoubles.Length; 48 | // var rangePart = new RangePart(i, @from, to, perIntervalCount); 49 | var rangePart = new HistogramReport.RangePart(i, @from, to, 0); 50 | intervals.Add(rangePart); 51 | } 52 | 53 | foreach (var filteredDouble in filteredDoubles) 54 | { 55 | int intervalIndex = 0; 56 | var i = 0; 57 | foreach (var rangePart in intervals) 58 | { 59 | if (filteredDouble - rangePart.From >= -Double.Epsilon && filteredDouble - rangePart.To <= 0d) 60 | { 61 | intervalIndex = i; 62 | break; 63 | } 64 | 65 | i++; 66 | } 67 | 68 | intervals[intervalIndex].Count = intervals[intervalIndex].Count + 1; 69 | } 70 | 71 | int keepFrom = 0; 72 | while (keepFrom < intervals.Count && intervals[keepFrom].Count == 0) 73 | keepFrom++; 74 | 75 | int keepTo = intervals.Count - 1; 76 | while (keepTo >= 0 && intervals[keepTo].Count == 0) 77 | keepTo--; 78 | 79 | var ret = intervals.AsEnumerable(); 80 | if (keepFrom > 0) ret.Skip(keepFrom); 81 | ret = ret.Take(keepTo - keepFrom + 1); 82 | 83 | return new HistogramReport( 84 | HistogramIntervalAsString, 85 | min, 86 | max, 87 | avg, 88 | variance, 89 | filteredMin, 90 | filteredMax, 91 | lowerOutliersCount, 92 | ret.ToArray(), 93 | highOutliersCount); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Universe.CpuUsage.Tests/Test_IsSupported.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | using Tests; 5 | 6 | namespace Universe.CpuUsage.Tests 7 | { 8 | [TestFixture] 9 | public class Test_IsSupported: NUnitTestsBase 10 | { 11 | // [Test] 12 | // public void ForcedFail() 13 | // { 14 | // Console.WriteLine("IT Fails"); 15 | // Assert.Fail("It is for a sandbox only"); 16 | // } 17 | 18 | [Test] 19 | public void OnWindows() 20 | { 21 | if (CrossInfo.ThePlatform == CrossInfo.Platform.Windows) 22 | { 23 | Assert.IsTrue(WindowsCpuUsage.IsSupported); 24 | } 25 | } 26 | 27 | [Test] 28 | public void OnMacOS() 29 | { 30 | if (CrossInfo.ThePlatform == CrossInfo.Platform.MacOSX) 31 | { 32 | Assert.IsTrue(MacOsThreadInfo.IsSupported); 33 | } 34 | } 35 | 36 | [Test] 37 | public void OnLinux() 38 | { 39 | if (CrossInfo.ThePlatform == CrossInfo.Platform.Linux) 40 | { 41 | Assert.IsTrue(LinuxResourceUsageReader.IsSupported); 42 | } 43 | } 44 | 45 | [Test] 46 | public void Show_Is_Supported() 47 | { 48 | Console.WriteLine($"CpuUsage.IsSupported ..................... : {CpuUsage.IsSupported}"); 49 | Console.WriteLine($"CpuUsageAsyncWatcher.IsSupported ......... : {CpuUsageAsyncWatcher.IsSupported}"); 50 | Console.WriteLine($"LinuxResourceUsageReader.IsSupported ..... : {LinuxResourceUsageReader.IsSupported}"); 51 | Console.WriteLine($"MacOsThreadInfo.IsSupported .............. : {MacOsThreadInfo.IsSupported}"); 52 | Console.WriteLine($"WindowsCpuUsage.IsSupported .............. : {WindowsCpuUsage.IsSupported}"); 53 | Console.WriteLine($"CrossInfo.ThePlatform .................... : {CrossInfo.ThePlatform}"); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Universe.CpuUsage.Tests/Universe.CpuUsage.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Full 7 | 8 | 9 | 10 | net5.0;netcoreapp3.0;netcoreapp3.1;netcoreapp2.2;net47;net46 11 | false 12 | default 13 | 14 | 15 | 16 | 1701;1702;CS0162;CS0414;NU1701 17 | 18 | 19 | 20 | 21 | 22 | 23 | all 24 | runtime; build; native; contentfiles; analyzers; buildtransitive 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Universe.CpuUsage.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29326.143 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Universe.CpuUsage", "Universe.CpuUsage\Universe.CpuUsage.csproj", "{A0382B2F-018E-4728-BE9F-D22C257D1833}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Universe.CpuUsage.Tests", "Universe.CpuUsage.Tests\Universe.CpuUsage.Tests.csproj", "{9EBDDFB8-CB54-491F-918C-A0F50D7CD0CC}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Universe.CpuUsage.Banchmark", "Universe.CpuUsage.Banchmark\Universe.CpuUsage.Banchmark.csproj", "{F7992DC0-0F19-4BD8-86D3-CA45A070EF6C}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {A0382B2F-018E-4728-BE9F-D22C257D1833}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {A0382B2F-018E-4728-BE9F-D22C257D1833}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {A0382B2F-018E-4728-BE9F-D22C257D1833}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {A0382B2F-018E-4728-BE9F-D22C257D1833}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {9EBDDFB8-CB54-491F-918C-A0F50D7CD0CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {9EBDDFB8-CB54-491F-918C-A0F50D7CD0CC}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {9EBDDFB8-CB54-491F-918C-A0F50D7CD0CC}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {9EBDDFB8-CB54-491F-918C-A0F50D7CD0CC}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {F7992DC0-0F19-4BD8-86D3-CA45A070EF6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {F7992DC0-0F19-4BD8-86D3-CA45A070EF6C}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {F7992DC0-0F19-4BD8-86D3-CA45A070EF6C}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {F7992DC0-0F19-4BD8-86D3-CA45A070EF6C}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {074EF3FD-2447-4FB9-9CF2-DC32AC30738A} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /Universe.CpuUsage/CpuUsage.cs: -------------------------------------------------------------------------------- 1 | namespace Universe.CpuUsage 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Runtime.InteropServices; 6 | 7 | 8 | // Supported by kernel 2.6.26+, mac OS 10.9+, Windows XP/2003 and above 9 | [StructLayout(LayoutKind.Sequential)] 10 | public struct CpuUsage 11 | { 12 | 13 | public TimeValue UserUsage { get; set; } 14 | public TimeValue KernelUsage { get; set; } 15 | 16 | public static CpuUsage? GetByProcess() 17 | { 18 | return Get(CpuUsageScope.Process); 19 | } 20 | 21 | public static bool IsSupported => CpuUsageReader.IsSupported; 22 | 23 | // for intellisense 24 | public static CpuUsage? GetByThread() 25 | { 26 | return Get(CpuUsageScope.Thread); 27 | } 28 | 29 | // for intellisense 30 | public static CpuUsage? SafeGet(CpuUsageScope scope) 31 | { 32 | return CpuUsageReader.SafeGet(scope); 33 | } 34 | 35 | // for intellisense 36 | public static CpuUsage? Get(CpuUsageScope scope) 37 | { 38 | return CpuUsageReader.Get(scope); 39 | } 40 | 41 | 42 | public CpuUsage(long userMicroseconds, long kernelMicroseconds) 43 | { 44 | const long _1M = 1000000L; 45 | UserUsage = new TimeValue() {Seconds = userMicroseconds / _1M, MicroSeconds = userMicroseconds % _1M}; 46 | KernelUsage = new TimeValue() {Seconds = kernelMicroseconds / _1M, MicroSeconds = kernelMicroseconds % _1M}; 47 | } 48 | 49 | 50 | public long TotalMicroSeconds => UserUsage.TotalMicroSeconds + KernelUsage.TotalMicroSeconds; 51 | 52 | public override string ToString() 53 | { 54 | var user = UserUsage.TotalMicroSeconds; 55 | var kernel = KernelUsage.TotalMicroSeconds; 56 | return $"{{{(user + kernel) / 1000d:n3} = {user / 1000d:n3} [user] + {kernel / 1000d:n3} [kernel] milliseconds}}"; 57 | // return $"{{User: {UserUsage}, Kernel: {KernelUsage}}}"; 58 | } 59 | 60 | public static CpuUsage Substruct(CpuUsage onEnd, CpuUsage onStart) 61 | { 62 | var user = onEnd.UserUsage.TotalMicroSeconds - onStart.UserUsage.TotalMicroSeconds; 63 | var system = onEnd.KernelUsage.TotalMicroSeconds - onStart.KernelUsage.TotalMicroSeconds; 64 | return new CpuUsage(user, system); 65 | } 66 | 67 | public static CpuUsage Sum(IEnumerable list) 68 | { 69 | if (list == null) throw new ArgumentNullException(nameof(list)); 70 | long user = 0; 71 | long system = 0; 72 | foreach (var item in list) 73 | { 74 | user += item.UserUsage.TotalMicroSeconds; 75 | system += item.KernelUsage.TotalMicroSeconds; 76 | } 77 | 78 | return new CpuUsage(user, system); 79 | } 80 | 81 | public static CpuUsage? Sum(IEnumerable list) 82 | { 83 | if (list == null) throw new ArgumentNullException(nameof(list)); 84 | CpuUsage? ret = null; 85 | foreach (var item in list) 86 | { 87 | if (ret.HasValue || item.HasValue) 88 | ret = Add(ret.GetValueOrDefault(), item.GetValueOrDefault()); 89 | } 90 | 91 | return ret; 92 | } 93 | 94 | 95 | public static CpuUsage Add(CpuUsage one, CpuUsage two) 96 | { 97 | long user = one.UserUsage.TotalMicroSeconds + two.UserUsage.TotalMicroSeconds; 98 | long system = one.KernelUsage.TotalMicroSeconds + two.KernelUsage.TotalMicroSeconds; 99 | return new CpuUsage(user, system); 100 | } 101 | 102 | public static CpuUsage operator -(CpuUsage onEnd, CpuUsage onStart) 103 | { 104 | return Substruct(onEnd, onStart); 105 | } 106 | 107 | public static CpuUsage? operator -(CpuUsage? onEnd, CpuUsage? onStart) 108 | { 109 | if (onEnd.HasValue || onStart.HasValue) 110 | return Substruct(onEnd.GetValueOrDefault(), onStart.GetValueOrDefault()); 111 | 112 | return null; 113 | } 114 | 115 | public static CpuUsage operator +(CpuUsage one, CpuUsage two) 116 | { 117 | return Add(one, two); 118 | } 119 | 120 | public static CpuUsage? operator +(CpuUsage? one, CpuUsage? two) 121 | { 122 | if (one.HasValue || two.HasValue) 123 | return Add(one.GetValueOrDefault(), two.GetValueOrDefault()); 124 | 125 | return null; 126 | } 127 | 128 | } 129 | 130 | // replacing it to long will limit usage by 3,170,979 years 131 | [StructLayout(LayoutKind.Sequential)] 132 | public struct TimeValue 133 | { 134 | public long Seconds { get; set; } 135 | public long MicroSeconds { get; set; } 136 | 137 | public long TotalMicroSeconds => Seconds * 1000000 + MicroSeconds; 138 | public double TotalSeconds => Seconds + MicroSeconds / 1000000d; 139 | 140 | public TimeValue(long totalMicroseconds) 141 | { 142 | const long _1M = 1000000L; 143 | Seconds = totalMicroseconds / _1M; 144 | MicroSeconds = totalMicroseconds % _1M; 145 | } 146 | 147 | 148 | public override string ToString() 149 | { 150 | return $"{TotalMicroSeconds / 1000d:n3} milliseconds"; 151 | } 152 | } 153 | 154 | public enum CpuUsageScope 155 | { 156 | Thread, 157 | Process, 158 | } 159 | 160 | } -------------------------------------------------------------------------------- /Universe.CpuUsage/CpuUsageAsyncWatcher.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable PossibleInvalidOperationException 2 | 3 | using System.Diagnostics; 4 | 5 | namespace Universe.CpuUsage 6 | { 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Text; 10 | using System.Threading; 11 | using System.Linq; 12 | 13 | public class CpuUsageAsyncWatcher 14 | { 15 | private volatile bool IsRunning = true; 16 | public ICollection Totals 17 | { 18 | get 19 | { 20 | // always not null 21 | if (_Log == null) return new List(); 22 | lock (_Log) 23 | { 24 | return new List(_Log); 25 | } 26 | } 27 | } 28 | 29 | public CpuUsage GetSummaryCpuUsage() 30 | { 31 | return Totals.GetSummaryCpuUsage(); 32 | } 33 | 34 | private List _Log = new List(); 35 | public class ContextSwitchMetrics 36 | { 37 | public double Duration { get; internal set; } 38 | public CpuUsage CpuUsage { get; internal set; } 39 | } 40 | 41 | public void Stop() 42 | { 43 | IsRunning = false; 44 | #if !NET20 && !NET30 && !NET35 && !NET40 && !NET45 45 | ContextItem.Value = null; 46 | if (_ContextSwitchListener != null) _ContextSwitchListener.Value = null; 47 | _ContextSwitchListener = null; 48 | #endif 49 | // _Log = null; it is the results 50 | } 51 | 52 | #if NETSTANDARD1_3 || NETSTANDARD1_4 || NETSTANDARD1_5 || NETSTANDARD1_6 53 | public static bool IsSupported => CpuUsageReader.IsSupported && LegacyNetStandardInterop.IsSupported && IsFrameworkSupported; 54 | static long GetThreadId() => LegacyNetStandardInterop.GetThreadId(); 55 | #else 56 | static long GetThreadId() => Thread.CurrentThread.ManagedThreadId; 57 | public static bool IsSupported => CpuUsageReader.IsSupported && IsFrameworkSupported; 58 | #endif 59 | 60 | 61 | // legacy net framework [2.0 ... 4.6) is not supported 62 | #if NETCOREAPP || NETSTANDARD2_0 || NETSTANDARD2_1 || NET48 || NET472 || NET471 || NET47 || NET462 || NET461 || NET46 || NETSTANDARD1_3 || NETSTANDARD1_4 || NETSTANDARD1_5 || NETSTANDARD1_6 63 | 64 | const bool IsFrameworkSupported = true; 65 | 66 | private class ContextSwitchInfo 67 | { 68 | public long ThreadId; 69 | public Stopwatch StartAt; 70 | public CpuUsage UsageOnStart; 71 | } 72 | 73 | private static ThreadLocal ContextItem = new ThreadLocal(); 74 | 75 | private AsyncLocal _ContextSwitchListener; 76 | 77 | private void ContextChangedHandler(AsyncLocalValueChangedArgs args) 78 | { 79 | // if (!args.ThreadContextChanged) return; 80 | if (!IsRunning) return; 81 | 82 | long tid = GetThreadId(); 83 | if (args.PreviousValue == null) 84 | { 85 | ContextItem.Value = new ContextSwitchInfo() 86 | { 87 | ThreadId = tid, 88 | StartAt = Stopwatch.StartNew(), 89 | UsageOnStart = CpuUsageReader.GetByThread().Value, 90 | }; 91 | } 92 | else if (args.CurrentValue == null) 93 | { 94 | ContextSwitchInfo contextOnStart = ContextItem.Value; 95 | ContextItem.Value = null; 96 | if (contextOnStart == null) 97 | { 98 | // .Stop() lost, Force Stop() 99 | IsRunning = false; 100 | Stop(); 101 | return; 102 | // throw new InvalidOperationException("CpuUsageAsyncWatcher.OnEnd: Missing contextOnStart. Please report"); 103 | } 104 | 105 | if (tid != contextOnStart.ThreadId) 106 | throw new InvalidOperationException( 107 | $"CpuUsageAsyncWatcher.OnEnd: ContextItem.Value.ThreadId is not as expected." 108 | + $"Thread.CurrentThread.ManagedThreadId is {tid}. " 109 | + $"contextOnStart.ThreadId is {contextOnStart.ThreadId}. Please report"); 110 | 111 | var ticks = contextOnStart.StartAt.ElapsedTicks; 112 | var duration = ticks / (double) Stopwatch.Frequency; 113 | var usageOnEnd = CpuUsageReader.GetByThread().Value; 114 | var cpuUsage = CpuUsage.Substruct(usageOnEnd, contextOnStart.UsageOnStart); 115 | ContextSwitchMetrics logRow = new ContextSwitchMetrics() 116 | { 117 | Duration = duration, 118 | CpuUsage = cpuUsage 119 | }; 120 | 121 | lock (_Log) _Log.Add(logRow); 122 | } 123 | 124 | #if DEBUG 125 | Console.ForegroundColor = ConsoleColor.Cyan; 126 | string AsString(object value) => value == null ? "off" : Convert.ToString(value); 127 | Console.WriteLine($"Value Changed {(args.ThreadContextChanged ? $"WITH context #{tid}" : $"WITHOUT context #{tid}")}: {AsString(args.PreviousValue)} => {AsString(args.CurrentValue)}"); 128 | #endif 129 | } 130 | 131 | public CpuUsageAsyncWatcher() 132 | { 133 | _ContextSwitchListener = new AsyncLocal(ContextChangedHandler); 134 | _ContextSwitchListener.Value = new object(); // "Online" does not occupies additional memory 135 | } 136 | 137 | #else 138 | const bool IsFrameworkSupported = false; 139 | public CpuUsageAsyncWatcher() 140 | { 141 | } 142 | 143 | #endif 144 | 145 | } 146 | 147 | public static class CpuUsageAsyncWatcherExtensions 148 | { 149 | public static CpuUsage GetSummaryCpuUsage(this IEnumerable log) 150 | { 151 | return log == null 152 | ? new CpuUsage() 153 | : CpuUsage.Sum(log.Select(x => x.CpuUsage)); 154 | } 155 | 156 | public static string ToHumanString(this ICollection log, int indent = 2, string taskDescription = "") 157 | { 158 | if (log == null) throw new ArgumentNullException(nameof(log)); 159 | if (indent < 0) throw new ArgumentException("indent should be zero or positive number", nameof(indent)); 160 | taskDescription = taskDescription ?? string.Empty; 161 | string pre = indent > 0 ? new string(' ', indent) : ""; 162 | StringBuilder ret = new StringBuilder(); 163 | ret.AppendLine($"Total Cpu Usage {(taskDescription?.Length > 0 ? $"of {taskDescription} " : "")}is {log.GetSummaryCpuUsage()}. Thread switches are:"); 164 | int n = 0; 165 | int posLength = log.Count.ToString().Length; 166 | string posFormat = "{0,-" + posLength + "}"; 167 | foreach (var l in log) 168 | { 169 | var delta = l.CpuUsage; 170 | double elapsed = l.Duration; 171 | double user = delta.UserUsage.TotalMicroSeconds / 1000d; 172 | double kernel = delta.KernelUsage.TotalMicroSeconds / 1000d; 173 | double perCents = (user + kernel) / 1000d / elapsed; 174 | var cpuUsageInfo = $"{(elapsed*1000):n3} (cpu: {(perCents*100):f0}%, {(user+kernel):n3} = {user:n3} [user] + {kernel:n3} [kernel] milliseconds)"; 175 | string posInfo = string.Format(posFormat, ++n); 176 | ret.AppendLine($"{pre}{posInfo}: {cpuUsageInfo}"); 177 | } 178 | 179 | return ret.ToString(); 180 | } 181 | 182 | public static string ToHumanString(this CpuUsageAsyncWatcher watcher, int indent = 2, string taskDescription = "") 183 | { 184 | if (watcher == null) throw new ArgumentNullException(nameof(watcher)); 185 | return ToHumanString(watcher.Totals, indent, taskDescription); 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /Universe.CpuUsage/CpuUsageReader.cs: -------------------------------------------------------------------------------- 1 | namespace Universe.CpuUsage 2 | { 3 | using System; 4 | 5 | internal class CpuUsageReader 6 | { 7 | public static bool IsSupported => _IsSupported.Value; 8 | 9 | public static CpuUsage? GetByProcess() 10 | { 11 | return Get(CpuUsageScope.Process); 12 | } 13 | 14 | public static CpuUsage? GetByThread() 15 | { 16 | return Get(CpuUsageScope.Thread); 17 | } 18 | 19 | public static CpuUsage? SafeGet(CpuUsageScope scope) 20 | { 21 | try 22 | { 23 | return Get(scope); 24 | } 25 | catch 26 | { 27 | return null; 28 | } 29 | } 30 | 31 | public static CpuUsage? Get(CpuUsageScope scope) 32 | { 33 | var platform = CrossInfo.ThePlatform; 34 | if (scope == CpuUsageScope.Process) 35 | { 36 | if (platform == CrossInfo.Platform.Linux || platform == CrossInfo.Platform.MacOSX) 37 | return LinuxResourceUsageReader.GetByProcess(); 38 | else 39 | return WindowsCpuUsage.Get(CpuUsageScope.Process); 40 | } 41 | 42 | if (platform == CrossInfo.Platform.Linux) 43 | return LinuxResourceUsageReader.GetByThread(); 44 | 45 | else if (platform == CrossInfo.Platform.MacOSX) 46 | return MacOsThreadInfo.GetByThread(); 47 | 48 | else if (platform == CrossInfo.Platform.Windows) 49 | // throw new NotImplementedException("CPU Usage in the scope of the thread is not yet implemented for Windows"); 50 | return WindowsCpuUsage.Get(CpuUsageScope.Thread); 51 | 52 | throw new InvalidOperationException($"CPU usage in the scope of {scope} is a kind of an unknown on the {platform}"); 53 | } 54 | 55 | private static readonly Lazy _IsSupported = new Lazy(() => 56 | { 57 | try 58 | { 59 | GetByProcess(); 60 | GetByThread(); 61 | return true; 62 | } 63 | catch 64 | { 65 | return false; 66 | } 67 | }); 68 | 69 | } 70 | } -------------------------------------------------------------------------------- /Universe.CpuUsage/CrossInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Universe 2 | { 3 | using System; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Runtime.InteropServices; 7 | using System.Text; 8 | 9 | public static class CrossInfo 10 | { 11 | 12 | public static Platform ThePlatform 13 | { 14 | get { return _Platform.Value; } 15 | } 16 | 17 | public enum Platform 18 | { 19 | Windows, 20 | Linux, 21 | MacOSX, 22 | FreeBSD, 23 | Unknown, 24 | } 25 | 26 | private static Lazy _Platform = new Lazy(() => 27 | { 28 | #if (NETCOREAPP || NETSTANDARD) && !NETFRAMEWORK 29 | if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 30 | return Platform.MacOSX; 31 | 32 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 33 | return Platform.Windows; 34 | 35 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 36 | return Platform.Linux; 37 | 38 | // Community ports Core to FreeBSD 39 | else 40 | return GetPlatform_OnLinux_OSX_BSD(); 41 | #else 42 | if (Environment.OSVersion.Platform == PlatformID.MacOSX) 43 | return Platform.MacOSX; 44 | 45 | else if (Environment.OSVersion.Platform == PlatformID.Unix) 46 | { 47 | return GetPlatform_OnLinux_OSX_BSD(); 48 | } 49 | 50 | else if (Environment.OSVersion.Platform == PlatformID.Win32NT) 51 | { 52 | return Platform.Windows; 53 | } 54 | 55 | else 56 | return Platform.Linux; 57 | #endif 58 | }); 59 | 60 | 61 | 62 | private static Platform GetPlatform_OnLinux_OSX_BSD() 63 | { 64 | #if NETSTANDARD1_3 || NETSTANDARD1_4 || NETSTANDARD1_6 65 | return IsMacOsX_On_OldNetStandard() ? Platform.MacOSX : Platform.Linux; 66 | #else 67 | var sName = ExecUName("-s"); 68 | if ("Linux".Equals(sName, StringComparison.OrdinalIgnoreCase)) 69 | return Platform.Linux; 70 | else if ("Darwin".Equals(sName, StringComparison.OrdinalIgnoreCase)) 71 | return Platform.MacOSX; 72 | else 73 | { 74 | // BSD-like with linux compatibility layer 75 | return Platform.Linux; 76 | } 77 | #endif 78 | } 79 | 80 | static bool IsMacOsX_On_OldNetStandard() 81 | { 82 | const string systemLibPath = "/usr/lib/libSystem.dylib"; 83 | if (!File.Exists(systemLibPath)) return false; 84 | using (FileStream fs = new FileStream(systemLibPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 85 | { 86 | byte[] buffer = new byte[4]; 87 | int n = fs.Read(buffer, 0, 4); 88 | if (n == 4) 89 | { 90 | return buffer[0] == 0xCA && buffer[1] == 0xFE && buffer[2] == 0xBA && buffer[3] == 0xBE; 91 | } 92 | 93 | return false; 94 | } 95 | } 96 | 97 | // static readonly StringComparison IgnoreCaseComparision = ; 98 | 99 | #if ! (NETSTANDARD1_3 || NETSTANDARD1_4 || NETSTANDARD1_6) 100 | static string ExecUName(string arg) 101 | { 102 | string ret; 103 | int exitCode; 104 | HiddenExec("uname", arg, out ret, out exitCode); 105 | if (ret != null) 106 | ret = ret.Trim('\r', '\n', '\t', ' '); 107 | 108 | return exitCode == 0 ? ret : ret; 109 | } 110 | 111 | public static void HiddenExec(string command, string args, out string output, out int exitCode) 112 | { 113 | 114 | ProcessStartInfo si = new ProcessStartInfo(command, args) 115 | { 116 | CreateNoWindow = true, 117 | RedirectStandardError = true, 118 | RedirectStandardOutput = true, 119 | StandardErrorEncoding = Encoding.UTF8, 120 | StandardOutputEncoding = Encoding.UTF8, 121 | // WindowStyle = ProcessWindowStyle.Hidden, 122 | UseShellExecute = false, 123 | }; 124 | 125 | 126 | Process p = new Process() {StartInfo = si}; 127 | using (p) 128 | { 129 | p.Start(); 130 | output = p.StandardOutput.ReadToEnd(); 131 | p.WaitForExit(); 132 | exitCode = p.ExitCode; 133 | } 134 | } 135 | #endif 136 | 137 | static void Trace_WriteLine(object message) 138 | { 139 | 140 | #if NETSTANDARD || NETCOREAPP 141 | Debug.WriteLine(message); 142 | #else 143 | Trace.WriteLine(message); 144 | #endif 145 | } 146 | 147 | } 148 | 149 | 150 | } 151 | 152 | -------------------------------------------------------------------------------- /Universe.CpuUsage/LegacyNetStandardInterop.cs: -------------------------------------------------------------------------------- 1 | namespace Universe.CpuUsage 2 | { 3 | using System; 4 | using System.Runtime.InteropServices; 5 | using Universe.CpuUsage.Interop; 6 | 7 | // for netstandard < v2.0 only 8 | public class LegacyNetStandardInterop 9 | { 10 | public static bool IsSupported => _IsSupported.Value; 11 | public static long GetThreadId() 12 | { 13 | if (CrossInfo.ThePlatform == CrossInfo.Platform.Linux) 14 | { 15 | return pthread_self_onLinux().ToInt64(); 16 | } 17 | 18 | else if (CrossInfo.ThePlatform == CrossInfo.Platform.MacOSX) 19 | { 20 | return MacOsThreadInfoInterop.mach_thread_self(); 21 | } 22 | 23 | else if (CrossInfo.ThePlatform == CrossInfo.Platform.Windows) 24 | { 25 | return WindowsCpuUsageInterop.GetCurrentThread().ToInt64(); 26 | } 27 | 28 | throw new NotSupportedException($"Platform '{CrossInfo.ThePlatform}' is not supported"); 29 | } 30 | 31 | [DllImport("libc", SetLastError = true, EntryPoint = "pthread_self")] 32 | static extern IntPtr pthread_self_onLinux(); 33 | 34 | static Lazy _IsSupported => new Lazy(() => { 35 | try 36 | { 37 | return GetThreadId() != 0; 38 | } 39 | catch 40 | { 41 | return false; 42 | } 43 | 44 | }); 45 | 46 | } 47 | } -------------------------------------------------------------------------------- /Universe.CpuUsage/LinuxResourceUsageReader.cs: -------------------------------------------------------------------------------- 1 | namespace Universe.CpuUsage 2 | { 3 | using System; 4 | using System.Collections; 5 | using System.Runtime.InteropServices; 6 | using Universe.CpuUsage.Interop; 7 | 8 | public class LinuxResourceUsageReader 9 | { 10 | 11 | public const int RESOURCE_USAGE_FIELDS_COUNT = 18; 12 | 13 | public static bool IsSupported => _IsSupported.Value; 14 | 15 | public static CpuUsage? GetByScope(CpuUsageScope scope) 16 | { 17 | var s = scope == CpuUsageScope.Process ? LinuxResourceUsageInterop.RUSAGE_SELF : LinuxResourceUsageInterop.RUSAGE_THREAD; 18 | return GetLinuxCpuUsageByScope(s); 19 | } 20 | 21 | public static CpuUsage? GetByProcess() 22 | { 23 | return GetLinuxCpuUsageByScope(LinuxResourceUsageInterop.RUSAGE_SELF); 24 | } 25 | 26 | // returns null on mac os x 27 | public static CpuUsage? GetByThread() 28 | { 29 | return GetLinuxCpuUsageByScope(LinuxResourceUsageInterop.RUSAGE_THREAD); 30 | } 31 | 32 | static Lazy _IsSupported = new Lazy(() => 33 | { 34 | try 35 | { 36 | GetByScope(CpuUsageScope.Process); 37 | GetByScope(CpuUsageScope.Thread); 38 | return true; 39 | } 40 | catch 41 | { 42 | return false; 43 | } 44 | }); 45 | 46 | internal static unsafe PosixResourceUsage? GetResourceUsageByScope(int scope) 47 | { 48 | if (IntPtr.Size == 4) 49 | { 50 | int* rawResourceUsage = stackalloc int[RESOURCE_USAGE_FIELDS_COUNT]; 51 | int result = LinuxResourceUsageInterop.getrusage_heapless(scope, new IntPtr(rawResourceUsage)); 52 | if (result != 0) return null; 53 | return new PosixResourceUsage() 54 | { 55 | UserUsage = new TimeValue() {Seconds = *rawResourceUsage, MicroSeconds = rawResourceUsage[1]}, 56 | KernelUsage = new TimeValue() {Seconds = rawResourceUsage[2], MicroSeconds = rawResourceUsage[3]}, 57 | MaxRss = rawResourceUsage[4], 58 | SoftPageFaults = rawResourceUsage[8], 59 | HardPageFaults = rawResourceUsage[9], 60 | Swaps = rawResourceUsage[10], 61 | ReadOps = rawResourceUsage[11], 62 | WriteOps = rawResourceUsage[12], 63 | SentIpcMessages = rawResourceUsage[13], 64 | ReceivedIpcMessages = rawResourceUsage[14], 65 | ReceivedSignals = rawResourceUsage[15], 66 | VoluntaryContextSwitches = rawResourceUsage[16], 67 | InvoluntaryContextSwitches = rawResourceUsage[17], 68 | }; 69 | } 70 | else 71 | { 72 | long* rawResourceUsage = stackalloc long[RESOURCE_USAGE_FIELDS_COUNT]; 73 | int result = LinuxResourceUsageInterop.getrusage_heapless(scope, new IntPtr(rawResourceUsage)); 74 | if (result != 0) return null; 75 | // microseconds are 4 bytes length on mac os and 8 bytes on linux 76 | return new PosixResourceUsage() 77 | { 78 | UserUsage = new TimeValue() {Seconds = *rawResourceUsage, MicroSeconds = rawResourceUsage[1] & 0xFFFFFFFF}, 79 | KernelUsage = new TimeValue() {Seconds = rawResourceUsage[2], MicroSeconds = rawResourceUsage[3] & 0xFFFFFFFF}, 80 | MaxRss = rawResourceUsage[4], 81 | SoftPageFaults = rawResourceUsage[8], 82 | HardPageFaults = rawResourceUsage[9], 83 | Swaps = rawResourceUsage[10], 84 | ReadOps = rawResourceUsage[11], 85 | WriteOps = rawResourceUsage[12], 86 | SentIpcMessages = rawResourceUsage[13], 87 | ReceivedIpcMessages = rawResourceUsage[14], 88 | ReceivedSignals = rawResourceUsage[15], 89 | VoluntaryContextSwitches = rawResourceUsage[16], 90 | InvoluntaryContextSwitches = rawResourceUsage[17], 91 | }; 92 | } 93 | } 94 | 95 | private static unsafe CpuUsage? GetLinuxCpuUsageByScope(int scope) 96 | { 97 | if (IntPtr.Size == 4) 98 | { 99 | int* rawResourceUsage = stackalloc int[RESOURCE_USAGE_FIELDS_COUNT]; 100 | int result = LinuxResourceUsageInterop.getrusage_heapless(scope, new IntPtr(rawResourceUsage)); 101 | if (result != 0) return null; 102 | return new CpuUsage() 103 | { 104 | UserUsage = new TimeValue() {Seconds = *rawResourceUsage, MicroSeconds = rawResourceUsage[1]}, 105 | KernelUsage = new TimeValue() {Seconds = rawResourceUsage[2], MicroSeconds = rawResourceUsage[3]}, 106 | }; 107 | } 108 | else 109 | { 110 | long* rawResourceUsage = stackalloc long[RESOURCE_USAGE_FIELDS_COUNT]; 111 | int result = LinuxResourceUsageInterop.getrusage_heapless(scope, new IntPtr(rawResourceUsage)); 112 | if (result != 0) return null; 113 | // microseconds are 4 bytes length on mac os and 8 bytes on linux 114 | return new CpuUsage() 115 | { 116 | UserUsage = new TimeValue() {Seconds = *rawResourceUsage, MicroSeconds = rawResourceUsage[1] & 0xFFFFFFFF}, 117 | KernelUsage = new TimeValue() {Seconds = rawResourceUsage[2], MicroSeconds = rawResourceUsage[3] & 0xFFFFFFFF}, 118 | }; 119 | } 120 | } 121 | } 122 | 123 | namespace Interop 124 | { 125 | public class LinuxResourceUsageInterop 126 | { 127 | 128 | // For integration tests only 129 | public static IList GetRawUsageResources(int scope = RUSAGE_THREAD) 130 | { 131 | if (IntPtr.Size == 4) 132 | { 133 | RawLinuxResourceUsage_32 ret = new RawLinuxResourceUsage_32(); 134 | ret.Raw = new int[18]; 135 | int result = getrusage32(scope, ref ret); 136 | if (result != 0) return null; 137 | Console.WriteLine($"getrusage returns {result}"); 138 | return ret.Raw; 139 | } 140 | else 141 | { 142 | RawLinuxResourceUsage_64 ret = new RawLinuxResourceUsage_64(); 143 | ret.Raw = new long[18]; 144 | int result = getrusage64(scope, ref ret); 145 | if (result != 0) return null; 146 | Console.WriteLine($"getrusage returns {result}"); 147 | return ret.Raw; 148 | } 149 | } 150 | 151 | public const int RUSAGE_SELF = 0; 152 | public const int RUSAGE_CHILDREN = -1; 153 | public const int RUSAGE_BOTH = -2; /* sys_wait4() uses this */ 154 | public const int RUSAGE_THREAD = 1; /* only the calling thread */ 155 | 156 | 157 | [DllImport("libc", SetLastError = true, EntryPoint = "getrusage")] 158 | public static extern int getrusage32(int who, ref RawLinuxResourceUsage_32 resourceUsage); 159 | 160 | [DllImport("libc", SetLastError = true, EntryPoint = "getrusage")] 161 | public static extern int getrusage32_heapless(int who, IntPtr resourceUsage); 162 | 163 | [DllImport("libc", SetLastError = true, EntryPoint = "getrusage")] 164 | public static extern int getrusage64(int who, ref RawLinuxResourceUsage_64 resourceUsage); 165 | 166 | [DllImport("libc", SetLastError = true, EntryPoint = "getrusage")] 167 | 168 | public static extern int getrusage64_heapless(int who, IntPtr resourceUsage); 169 | 170 | [DllImport("libc", SetLastError = true, EntryPoint = "getrusage")] 171 | public static extern int getrusage_heapless(int who, IntPtr resourceUsage); 172 | } 173 | 174 | // https://github.com/mono/mono/issues?utf8=%E2%9C%93&q=getrusage 175 | // https://elixir.bootlin.com/linux/v2.6.24/source/include/linux/time.h#L19 176 | // https://stackoverflow.com/questions/1468443/per-thread-cpu-statistics-in-linux 177 | // ! http://man7.org/linux/man-pages/man2/getrusage.2.html 178 | // 179 | 180 | 181 | [StructLayout(LayoutKind.Sequential)] 182 | public struct RawLinuxResourceUsage_64 183 | { 184 | [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I8, SizeConst = 18)] 185 | public long[] Raw; 186 | } 187 | 188 | [StructLayout(LayoutKind.Sequential)] 189 | public struct RawLinuxResourceUsage_32 190 | { 191 | [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I4, SizeConst = 18)] 192 | public int[] Raw; 193 | } 194 | } 195 | } -------------------------------------------------------------------------------- /Universe.CpuUsage/MacOsThreadInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Universe.CpuUsage 2 | { 3 | using System; 4 | using System.Runtime.InteropServices; 5 | using Universe.CpuUsage.Interop; 6 | 7 | public class MacOsThreadInfo 8 | { 9 | public static bool IsSupported => _IsSupported.Value; 10 | 11 | public static CpuUsage? GetByThread() 12 | { 13 | return Get(); 14 | } 15 | 16 | static CpuUsage? Get() 17 | { 18 | int threadId = MacOsThreadInfoInterop.mach_thread_self(); 19 | try 20 | { 21 | if (threadId == 0) return null; 22 | 23 | var ret = MacOsThreadInfoInterop.GetThreadCpuUsageInfo(threadId); 24 | return ret; 25 | } 26 | finally 27 | { 28 | int? self = null; 29 | int kResult2 = -424242; 30 | int kResult1 = MacOsThreadInfoInterop.mach_port_deallocate(threadId, threadId); 31 | 32 | // https://opensource.apple.com/source/xnu/xnu-792/osfmk/mach/kern_return.h 33 | // KERN_INVALID_TASK 16: target task isn't an active task. 34 | if (kResult1 != 0) 35 | { 36 | self = MacOsThreadInfoInterop.mach_thread_self(); 37 | kResult2 = MacOsThreadInfoInterop.mach_port_deallocate(self.Value, threadId); 38 | } 39 | #if DEBUG 40 | if (kResult1 != 0 && kResult2 != 0) 41 | { 42 | Console.WriteLine($@"{(kResult1 == 0 || kResult2 == 0 ? "Info" : "Warning!!!!!")} 43 | mach_port_deallocate({threadId}, {threadId}) returned {kResult1} 44 | mach_port_deallocate(mach_thread_self() == {self}, {threadId}) returned {kResult2}"); 45 | } 46 | #endif 47 | } 48 | 49 | } 50 | 51 | private static Lazy _IsSupported = new Lazy(() => 52 | { 53 | try 54 | { 55 | GetByThread(); 56 | return true; 57 | } 58 | catch 59 | { 60 | return false; 61 | } 62 | }); 63 | 64 | } 65 | 66 | namespace Interop 67 | { 68 | public class MacOsThreadInfoInterop 69 | { 70 | private const int THREAD_BASIC_INFO_FIELDS_COUNT = 10; 71 | private const int THREAD_BASIC_INFO_SIZE = THREAD_BASIC_INFO_FIELDS_COUNT * 4; 72 | private const int THREAD_BASIC_INFO = 3; 73 | 74 | [DllImport("libc", SetLastError = false, EntryPoint = "mach_thread_self")] 75 | public static extern int mach_thread_self(); 76 | 77 | // mach_port_deallocate 78 | [DllImport("libc", SetLastError = false, EntryPoint = "mach_port_deallocate")] 79 | public static extern int mach_port_deallocate(int threadId, int materializedThreadId); 80 | 81 | [DllImport("libc", SetLastError = true, EntryPoint = "thread_info")] 82 | public static extern int thread_info_custom(int threadId, int flavor, IntPtr threadInfo, ref int count); 83 | 84 | public static unsafe CpuUsage? GetThreadCpuUsageInfo(int threadId) 85 | { 86 | int* ptr = stackalloc int[THREAD_BASIC_INFO_FIELDS_COUNT]; 87 | { 88 | int count = THREAD_BASIC_INFO_SIZE; 89 | IntPtr threadInfo = new IntPtr(ptr); 90 | int result = thread_info_custom(threadId, THREAD_BASIC_INFO, threadInfo, ref count); 91 | if (result != 0) return null; 92 | return new CpuUsage() 93 | { 94 | UserUsage = new TimeValue() {Seconds = *ptr, MicroSeconds = *(ptr + 1)}, 95 | KernelUsage = new TimeValue() {Seconds = *(ptr + 2), MicroSeconds = *(ptr + 3)}, 96 | }; 97 | } 98 | } 99 | 100 | public static unsafe int[] GetRawThreadInfo_ForTests(int threadId) 101 | { 102 | int[] raw = new int[THREAD_BASIC_INFO_FIELDS_COUNT]; 103 | fixed (int* ptr = &raw[0]) 104 | { 105 | int count = THREAD_BASIC_INFO_SIZE; 106 | IntPtr threadInfo = new IntPtr(ptr); 107 | int result = thread_info_custom(threadId, THREAD_BASIC_INFO, threadInfo, ref count); 108 | Console.WriteLine($"thread_info return value: {result}"); 109 | return raw; 110 | } 111 | } 112 | } 113 | 114 | } 115 | } -------------------------------------------------------------------------------- /Universe.CpuUsage/PosixResourceUsage.cs: -------------------------------------------------------------------------------- 1 | namespace Universe.CpuUsage 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using Universe.CpuUsage.Interop; 6 | 7 | // for linux and mac os only 8 | public struct PosixResourceUsage 9 | { 10 | 11 | public static bool IsSupported => LinuxResourceUsageReader.IsSupported; 12 | 13 | public TimeValue UserUsage { get; set; } 14 | public TimeValue KernelUsage { get; set; } 15 | 16 | public long MaxRss { get; set; } 17 | 18 | public long SoftPageFaults { get; set; } 19 | public long HardPageFaults { get; set; } 20 | public long Swaps { get; set; } 21 | public long ReadOps { get; set; } 22 | public long WriteOps { get; set; } 23 | // mac os only 24 | public long SentIpcMessages { get; set; } 25 | // mac os only 26 | public long ReceivedIpcMessages { get; set; } 27 | // mac os only 28 | public long ReceivedSignals { get; set; } 29 | 30 | // usually to await availability of a resource 31 | public long VoluntaryContextSwitches { get; set; } 32 | // a higher priority process becoming runnable or because the current process exceeded its time slice. 33 | public long InvoluntaryContextSwitches { get; set; } 34 | 35 | public static PosixResourceUsage? GetByScope(CpuUsageScope scope) 36 | { 37 | int kernelScope = scope == CpuUsageScope.Process ? LinuxResourceUsageInterop.RUSAGE_SELF : LinuxResourceUsageInterop.RUSAGE_THREAD; 38 | return LinuxResourceUsageReader.GetResourceUsageByScope(kernelScope); 39 | } 40 | 41 | public static PosixResourceUsage Substruct(PosixResourceUsage onEnd, PosixResourceUsage onStart) 42 | { 43 | var user = onEnd.UserUsage.TotalMicroSeconds - onStart.UserUsage.TotalMicroSeconds; 44 | var system = onEnd.KernelUsage.TotalMicroSeconds - onStart.KernelUsage.TotalMicroSeconds; 45 | return new PosixResourceUsage() 46 | { 47 | UserUsage = new TimeValue(user), 48 | KernelUsage = new TimeValue(system), 49 | MaxRss = Math.Max(onEnd.MaxRss, onStart.MaxRss), 50 | SoftPageFaults = onEnd.SoftPageFaults - onStart.SoftPageFaults, 51 | HardPageFaults = onEnd.HardPageFaults - onStart.HardPageFaults, 52 | Swaps = onEnd.Swaps - onStart.Swaps, 53 | ReadOps = onEnd.ReadOps - onStart.ReadOps, 54 | WriteOps = onEnd.WriteOps - onStart.WriteOps, 55 | SentIpcMessages = onEnd.SentIpcMessages - onStart.SentIpcMessages, 56 | ReceivedIpcMessages = onEnd.InvoluntaryContextSwitches - onStart.InvoluntaryContextSwitches, 57 | ReceivedSignals = onEnd.ReceivedSignals - onStart.ReceivedSignals, 58 | VoluntaryContextSwitches = onEnd.VoluntaryContextSwitches - onStart.VoluntaryContextSwitches, 59 | InvoluntaryContextSwitches = onEnd.InvoluntaryContextSwitches - onStart.InvoluntaryContextSwitches, 60 | }; 61 | } 62 | 63 | public static PosixResourceUsage Add(PosixResourceUsage one, PosixResourceUsage two) 64 | { 65 | long user = one.UserUsage.TotalMicroSeconds + two.UserUsage.TotalMicroSeconds; 66 | long system = one.KernelUsage.TotalMicroSeconds + two.KernelUsage.TotalMicroSeconds; 67 | return new PosixResourceUsage() 68 | { 69 | UserUsage = new TimeValue(user), 70 | KernelUsage = new TimeValue(system), 71 | MaxRss = Math.Max(two.MaxRss, one.MaxRss), 72 | SoftPageFaults = two.SoftPageFaults + one.SoftPageFaults, 73 | HardPageFaults = two.HardPageFaults + one.HardPageFaults, 74 | Swaps = two.Swaps + one.Swaps, 75 | ReadOps = two.ReadOps + one.ReadOps, 76 | WriteOps = two.WriteOps + one.WriteOps, 77 | SentIpcMessages = two.SentIpcMessages + one.SentIpcMessages, 78 | ReceivedIpcMessages = two.InvoluntaryContextSwitches + one.InvoluntaryContextSwitches, 79 | ReceivedSignals = two.ReceivedSignals + one.ReceivedSignals, 80 | VoluntaryContextSwitches = two.VoluntaryContextSwitches + one.VoluntaryContextSwitches, 81 | InvoluntaryContextSwitches = two.InvoluntaryContextSwitches + one.InvoluntaryContextSwitches, 82 | }; 83 | } 84 | 85 | public static PosixResourceUsage Sum(IEnumerable list) 86 | { 87 | if (list == null) throw new ArgumentNullException(nameof(list)); 88 | PosixResourceUsage ret = new PosixResourceUsage(); 89 | foreach (var item in list) 90 | ret = Add(ret, item); 91 | 92 | return ret; 93 | } 94 | 95 | public static PosixResourceUsage? Sum(IEnumerable list) 96 | { 97 | if (list == null) throw new ArgumentNullException(nameof(list)); 98 | PosixResourceUsage? ret = null; 99 | foreach (var item in list) 100 | { 101 | if (ret.HasValue || item.HasValue) 102 | ret = Add(ret.GetValueOrDefault(), item.GetValueOrDefault()); 103 | } 104 | 105 | return ret; 106 | } 107 | 108 | public static PosixResourceUsage operator -(PosixResourceUsage onEnd, PosixResourceUsage onStart) 109 | { 110 | return Substruct(onEnd, onStart); 111 | } 112 | 113 | public static PosixResourceUsage? operator -(PosixResourceUsage? onEnd, PosixResourceUsage? onStart) 114 | { 115 | if (onEnd.HasValue || onStart.HasValue) 116 | return Substruct(onEnd.GetValueOrDefault(), onStart.GetValueOrDefault()); 117 | 118 | return null; 119 | } 120 | 121 | public static PosixResourceUsage operator +(PosixResourceUsage one, PosixResourceUsage two) 122 | { 123 | return Add(one, two); 124 | } 125 | 126 | public static PosixResourceUsage? operator +(PosixResourceUsage? one, PosixResourceUsage? two) 127 | { 128 | if (one.HasValue || two.HasValue) 129 | return Add(one.GetValueOrDefault(), two.GetValueOrDefault()); 130 | 131 | return null; 132 | } 133 | 134 | } 135 | } -------------------------------------------------------------------------------- /Universe.CpuUsage/Universe.CpuUsage.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Full 14 | 15 | 16 | 17 | net5.0;netcoreapp3.1;netcoreapp3.0;netcoreapp2.2;netcoreapp2.1;netcoreapp2.0;netcoreapp1.1;netcoreapp1.0;netstandard2.1;netstandard2.0;netstandard1.6;netstandard1.4;netstandard1.3;net48;net472;net47;net46;net45;net40;net35;net20; 18 | true 19 | snupkg 20 | 21 | 22 | 23 | net30;$(CommonTargetFrameworks) 24 | 25 | 26 | 27 | $(CommonTargetFrameworks) 28 | 29 | 30 | 31 | true 32 | Library 33 | true 34 | 35 | 36 | 37 | Universe.CpuUsage 38 | Universe.CpuUsage 39 | Volodymyr Hoida 40 | Universe.CpuUsage 41 | Cpu Usage, the amount of time that the current thread/process has executed in kernel and user mode including async/await scenarios. Works everywhere: Linux, OSX and Windows. Does not require any special permissions. 42 | Targets everywhere: Net Framework 2.0-4.8, Net Standard 1.3-2.1, Net Core 1.0+ ... 43 | https://github.com/devizer/Universe.CpuUsage 44 | https://github.com/devizer/Universe.CpuUsage 45 | MIT 46 | https://raw.githubusercontent.com/devizer/Universe.CpuUsage/master/images/CpuUsage.Icon-V2.png 47 | metrics kernel kernel-time user-time getrusage thread_info GetProcessTimes GetThreadTimes cpu-usage linux windows osx 48 | latest 49 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 50 | 51 | 52 | 53 | 1701;1702;CS0162;CS0414;NU1701 54 | 55 | 56 | 57 | 58 | 59 | <_Parameter1>.NETFramework,Version=v3.5 60 | 61 | 62 | 63 | 64 | <_Parameter1>.NETFramework,Version=v3.0 65 | 66 | 67 | 68 | 69 | <_Parameter1>.NETFramework,Version=v2.0 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /Universe.CpuUsage/WindowsCpuUsage.cs: -------------------------------------------------------------------------------- 1 | namespace Universe.CpuUsage 2 | { 3 | using System; 4 | using System.Runtime.InteropServices; 5 | using Universe.CpuUsage.Interop; 6 | 7 | public static class WindowsCpuUsage 8 | { 9 | public static bool IsSupported => _IsSupported.Value; 10 | 11 | 12 | public static CpuUsage? Get(CpuUsageScope scope) 13 | { 14 | long kernelMicroseconds; 15 | long userMicroseconds; 16 | bool isOk; 17 | if (scope == CpuUsageScope.Thread) 18 | isOk = WindowsCpuUsageInterop.GetThreadTimes(out kernelMicroseconds, out userMicroseconds); 19 | else 20 | isOk = WindowsCpuUsageInterop.GetProcessTimes(out kernelMicroseconds, out userMicroseconds); 21 | 22 | if (!isOk) 23 | return null; 24 | 25 | const long m = 1000000L; 26 | return new CpuUsage() 27 | { 28 | KernelUsage = new TimeValue() { Seconds = kernelMicroseconds / m, MicroSeconds = kernelMicroseconds % m}, 29 | UserUsage = new TimeValue() { Seconds = userMicroseconds / m, MicroSeconds = userMicroseconds % m}, 30 | }; 31 | 32 | } 33 | 34 | static Lazy _IsSupported = new Lazy(() => 35 | { 36 | try 37 | { 38 | Get(CpuUsageScope.Process); 39 | Get(CpuUsageScope.Thread); 40 | return true; 41 | } 42 | catch 43 | { 44 | return false; 45 | } 46 | }); 47 | 48 | } 49 | 50 | namespace Interop 51 | { 52 | public class WindowsCpuUsageInterop 53 | { 54 | [DllImport("kernel32.dll")] 55 | internal static extern IntPtr GetCurrentThread(); 56 | 57 | [DllImport("kernel32.dll", SetLastError = true)] 58 | static extern bool GetThreadTimes(IntPtr hThread, out long lpCreationTime, 59 | out long lpExitTime, out long lpKernelTime, out long lpUserTime); 60 | 61 | [DllImport("kernel32.dll")] 62 | static extern IntPtr GetCurrentProcess(); 63 | 64 | 65 | [DllImport("kernel32.dll", SetLastError = true)] 66 | static extern bool GetProcessTimes(IntPtr hThread, out long lpCreationTime, 67 | out long lpExitTime, out long lpKernelTime, out long lpUserTime); 68 | 69 | public static bool GetThreadTimes(out long kernelMicroseconds, out long userMicroseconds) 70 | { 71 | long ignored; 72 | long kernel; 73 | long user; 74 | if (GetThreadTimes(GetCurrentThread(), out ignored, out ignored, out kernel, out user)) 75 | { 76 | // Console.WriteLine($"kernel: {kernel}, user: {user}"); 77 | kernelMicroseconds = kernel / 10L; 78 | userMicroseconds = user / 10L; 79 | return true; 80 | } 81 | else 82 | { 83 | kernelMicroseconds = -1; 84 | userMicroseconds = -1; 85 | return false; 86 | } 87 | 88 | } 89 | 90 | public static bool GetProcessTimes(out long kernelMicroseconds, out long userMicroseconds) 91 | { 92 | long ignored; 93 | long kernel; 94 | long user; 95 | if (GetProcessTimes(GetCurrentProcess(), out ignored, out ignored, out kernel, out user)) 96 | { 97 | // Console.WriteLine($"kernel: {kernel}, user: {user}"); 98 | kernelMicroseconds = kernel / 10L; 99 | userMicroseconds = user / 10L; 100 | return true; 101 | } 102 | else 103 | { 104 | kernelMicroseconds = -1; 105 | userMicroseconds = -1; 106 | return false; 107 | } 108 | 109 | } 110 | 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /Using Net Standard.url: -------------------------------------------------------------------------------- 1 | [InternetShortCut] 2 | Url=https://weblog.west-wind.com/posts/2019/Feb/19/Using-NET-Standard-with-Full-Framework-NET -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '2.2.{build}' 2 | image: 3 | - Visual Studio 2019 4 | - Ubuntu1804 5 | 6 | skip_commits: 7 | files: 8 | - .circleci/* 9 | - '.travis.yml' 10 | - '*.md' 11 | 12 | clone_depth: 99999999 13 | 14 | dotnet_csproj: 15 | patch: true 16 | file: '**\Version.props' 17 | version: '{version}.%COMMIT_COUNT%' 18 | package_version: '{version}.%COMMIT_COUNT%' 19 | assembly_version: '{version}.%COMMIT_COUNT%' 20 | file_version: '{version}.%COMMIT_COUNT%' 21 | informational_version: '{version}.%COMMIT_COUNT%' 22 | 23 | platform: Any CPU 24 | init: 25 | - sh: script=https://raw.githubusercontent.com/devizer/test-and-build/master/install-build-tools-bundle.sh; (wget -q -nv --no-check-certificate -O - $script 2>/dev/null || curl -ksSL $script) | bash 26 | - sh: | 27 | Say "Memory" 28 | free -m 29 | Say "Mounted Volumes" 30 | df -h -T 31 | Say "All partitions and disks" 32 | sudo fdisk -l 33 | true 34 | - sh: | 35 | Say "Installing .NET Core" 36 | script=https://raw.githubusercontent.com/devizer/test-and-build/master/lab/install-DOTNET.sh; (wget -q -nv --no-check-certificate -O - $script 2>/dev/null || curl -ksSL $script) | bash 37 | sudo ln -f -s /usr/share/dotnet/dotnet /usr/local/bin/dotnet 38 | dotnet --info 39 | 40 | - ps: | 41 | 42 | # copy paste it 43 | function Say { param( [string] $message ) 44 | Write-Host "$(Get-Elapsed) " -NoNewline -ForegroundColor Magenta 45 | Write-Host "$message" -ForegroundColor Yellow 46 | } 47 | 48 | function Get-Elapsed 49 | { 50 | if ($Global:startAt -eq $null) { $Global:startAt = [System.Diagnostics.Stopwatch]::StartNew(); } 51 | [System.String]::Concat("[", (new-object System.DateTime(0)).AddMilliseconds($Global:startAt.ElapsedMilliseconds).ToString("mm:ss"), "]"); 52 | }; Get-Elapsed | out-null; 53 | 54 | # Display OS and CPU 55 | if ($isWindows) { 56 | $currentVersion=Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' 57 | $win_10_release_id = $currentVersion.ReleaseId; if (! $win_10_release_id) { $win_10_release_id = $currentVersion.CurrentBuildNumber } 58 | $win_name = $(Get-WMIObject win32_operatingsystem -EA SilentlyContinue).Caption 59 | Say "$($win_name): Release [$win_10_release_id], powershell [$($PSVersionTable.PSVersion)]" 60 | $cpu=Get-WmiObject Win32_Processor; Say "CPU: $($cpu.Name), $([System.Environment]::ProcessorCount) Cores"; 61 | } 62 | 63 | 64 | #- sh: | 65 | # function header() { 66 | # if [[ $(uname -s) != Darwin ]]; then 67 | # startAt=${startAt:-$(date +%s)}; elapsed=$(date +%s); elapsed=$((elapsed-startAt)); elapsed=$(TZ=UTC date -d "@${elapsed}" "+%_H:%M:%S"); 68 | # fi 69 | # LightGreen='\033[1;32m'; Yellow='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'; LightGray='\033[1;2m'; 70 | # printf "${LightGray}${elapsed:-}${NC} ${LightGreen}$1${NC} ${Yellow}$2${NC}\n"; 71 | # } 72 | # counter=0; function Say() { echo ""; counter=$((counter+1)); header "STEP $counter" "$1"; }; Say "" >/dev/null; counter=0; 73 | # 74 | 75 | install: 76 | - ps: '$Env:ARTIFACT = "$($Env:APPVEYOR_BUILD_FOLDER)$([IO.Path]::DirectorySeparatorChar)Artifact"; mkdir $Env:ARTIFACT | out-null; Say "ARTIFACT folder: $($Env:ARTIFACT)"' 77 | - ps: '$Env:SQL_SETUP_LOG_FOLDER = $Env:ARTIFACT; Say "SQL_SETUP_LOG_FOLDER is [$($Env:SQL_SETUP_LOG_FOLDER)]"' 78 | - ps: Start-Transcript "Artifact\Detailed-Build.log" -Force 79 | - git submodule update --init --recursive 80 | - ps: | 81 | 82 | $commitsRaw = & { set TZ=GMT; git log -n 999999 --date=raw --pretty=format:"%cd" } 83 | $lines = $commitsRaw.Split([Environment]::NewLine) 84 | $ENV:COMMIT_COUNT = $lines.Length 85 | Say "COMMIT_COUNT: $($ENV:COMMIT_COUNT)" 86 | 87 | Say "Install Complete" 88 | 89 | # - sh: 'mono --version || true; dotnet --info || true; ' 90 | 91 | 92 | before_build: 93 | - ps: | 94 | 95 | # Upgrade-PSReadLine 96 | 97 | build_script: 98 | - ps: | 99 | Say "RESTORE and BUILD" 100 | & dotnet restore 101 | 102 | #- sh: | 103 | # Say "Remove net 4.8 target for msbuild on linux" 104 | # sed -i 's/net48;//g' Universe.CpuUsage/Universe.CpuUsage.csproj 105 | # # cat Universe.CpuUsage/Universe.CpuUsage.csproj 106 | 107 | - ps: | 108 | 109 | Say "nuget restore for '$(pwd)'" 110 | & nuget restore -verbosity quiet 111 | #Say "Test using netcoreapp3.0" 112 | #& dotnet test -f netcoreapp3.0 -v n --test-adapter-path:. --logger:Appveyor 113 | pushd Universe.CpuUsage 114 | if ($isWindows) { 115 | Say "Rebuild and pack Release for windows" 116 | & msbuild /t:"rebuild;pack" /p:Configuration=Release /v:q 117 | } else { 118 | Say "Rebuild and pack Release for linux" 119 | & dotnet build -c Release -v q 120 | } 121 | popd 122 | 123 | after_build: 124 | 125 | test_script: 126 | - ps: | 127 | if ($isWindows) { 128 | Say ".NET Framework Tests" 129 | dotnet build -f net47 -c Debug 130 | pushd "Universe.CpuUsage.Tests\bin\Debug\net47" 131 | nunit3-console Universe.CpuUsage.Tests.dll --workers=1 "--result=Universe.CpuUsage.Tests.Result.xml;format=AppVeyor" 132 | popd 133 | } 134 | 135 | - ps: | 136 | Say ".NET Core Tests" 137 | pushd Universe.CpuUsage.Tests 138 | if ($isWindows) { 139 | dotnet test -f netcoreapp3.0 -c Release --test-adapter-path:. --logger:Appveyor -- NUnit.NumberOfTestWorkers=1 140 | } else { 141 | dotnet test -f netcoreapp3.0 -c Release --test-adapter-path:. --logger:Appveyor -- NUnit.NumberOfTestWorkers=1 142 | } 143 | popd 144 | 145 | - sh: | 146 | echo "Starting tests using Mono in [$(pwd)]" 147 | pushd Tests4Mono; set +e; source build-the-matrix.sh; echo $matrix_run 148 | # need -e -c 149 | sudo bash -e -c "$matrix_run" || true 150 | popd 151 | 152 | - ps: | 153 | 154 | Say "Pack working folder as the 'AppVeyor Build Folder.7z' artifact" 155 | & "7z" a -t7z -mx=3 -ms=on "AppVeyor Build Folder.7z" . 156 | Say "Bye-bye" 157 | 158 | after_test: 159 | # do not publish nuget to feeds on LINUX 160 | - sh: Say "removing [s]nupkg in $(pwd)" 161 | - sh: shopt -s globstar; rm -f **/*.nupkg; rm -f **/*.snupkg; 162 | - sh: 'find . -name "*.nupkg" -exec bash -c "DELETING {}; rm -f {}" \;' 163 | - sh: 'find . -name "*.snupkg" -exec bash -c "DELETING {}; rm -f {}" \;' 164 | - sh: 'Show-System-Stat || true' 165 | - ps: Stop-Transcript; 166 | # - cmd: bash -c 'time echo "Hello from $(bash --version)"' 167 | 168 | nuget: 169 | account_feed: false 170 | project_feed: true 171 | disable_publish_on_pr: true # disable publishing of .nupkg artifacts to account/project feeds for pull request builds 172 | 173 | # Error uploading artifact the storage: The underlying connection was closed: An unexpected error occurred on a send. 174 | artifacts: 175 | - path: Artifact 176 | - path: 'AppVeyor Build Folder.7z' 177 | - path: '**\Release\*.nupkg' 178 | - path: '**\Release\*.snupkg' 179 | 180 | # on_success: 181 | # - cmd: 'appveyor PushArtifact "Universe.CpuUsage\bin\Any CPU\Release\*.nupkg"' 182 | 183 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | buildConfiguration: "Debug" 3 | testProject: "Universe.CpuUsage.Tests/Universe.CpuUsage.Tests.csproj" 4 | solution: "Universe.CpuUsage.sln" 5 | 6 | jobs: 7 | 8 | - job: Virtual 9 | displayName: 'Virtual' 10 | pool: 11 | vmImage: ubuntu-latest 12 | timeoutInMinutes: 45 13 | strategy: 14 | maxParallel: 6 15 | matrix: 16 | 'armel-debian-8': 17 | QEMU_IMAGE_ID: armel-debian-8 18 | 'armel-debian-11': 19 | QEMU_IMAGE_ID: armel-debian-11 20 | 'armhf-debian-12': 21 | QEMU_IMAGE_ID: armhf-debian-12 22 | 'arm64-debian-13': 23 | QEMU_IMAGE_ID: arm64-debian-13 24 | 'x64-debian-11': 25 | QEMU_IMAGE_ID: x64-debian-11 26 | 'i386-debian-10': 27 | QEMU_IMAGE_ID: i386-debian-10 28 | 29 | steps: 30 | - script: | 31 | set -eu; set -o pipefail 32 | script=https://raw.githubusercontent.com/devizer/test-and-build/master/install-build-tools-bundle.sh; (wget -q -nv --no-check-certificate -O - $script 2>/dev/null || curl -ksSL $script) | bash >/dev/null 33 | Say --Reset-Stopwatch 34 | Say "CPU: $(Get-CpuName)" 35 | # sudo apt-get update -qq; sudo apt-get install fuse -y -qq | grep "Unpack\|Setting" || true 36 | printenv | sort; 37 | displayName: 'Variables' 38 | 39 | - script: | 40 | set -eu; set -o pipefail 41 | Configuration=Debug 42 | FW=net48 43 | Say "CPU: $(Get-CpuName)" 44 | Reset-Target-Framework -fw $FW -l latest 45 | cd Universe.CpuUsage.Tests 46 | for p in NUnit3TestAdapter Appveyor.TestLogger Microsoft.NET.Test.Sdk; do dotnet remove package $p; done 47 | time try-and-retry dotnet build -c $Configuration -v:q Universe.CpuUsage.Tests.csproj 48 | cd bin/$Configuration/$FW 49 | ls -lah 50 | Say "Pull devizervlad/crossplatform-pipeline:$QEMU_IMAGE_ID" 51 | try-and-retry docker pull -q devizervlad/crossplatform-pipeline:$QEMU_IMAGE_ID 52 | Say "Test on [$QEMU_IMAGE_ID]" 53 | cmd="export NUNIT_TEST_RUNNER_VERSION=3.12.0; curl -sfSL https://raw.githubusercontent.com/devizer/glist/master/bin/NUnit.ConsoleRunner/install.sh | bash; nunit3-console --workers=1 Universe.CpuUsage.Tests.dll" 54 | docker run -e VM_VARIABLES="VAR42;Hello" -e VAR42="42" -e Hello="Hello World" -v $(pwd):/job --privileged --device /dev/fuse --cap-add SYS_ADMIN --security-opt apparmor:unconfined -t --hostname $QEMU_IMAGE_ID devizervlad/crossplatform-pipeline:$QEMU_IMAGE_ID bash -c "echo; $cmd" 55 | displayName: 'Test' 56 | 57 | 58 | 59 | - job: Pool 60 | displayName: 'Pool' 61 | pool: 62 | name: $(POOL_NAME) 63 | timeoutInMinutes: 45 64 | strategy: 65 | maxParallel: 8 66 | matrix: 67 | 'ARM64 Windows': 68 | POOL_NAME: 'ARM64Win-pool' 69 | OS: Windows 70 | SKIP_SDK_INSTALL: 'True' 71 | 'ARMv7 Linux': 72 | POOL_NAME: 'armv7-pool' 73 | OS: Linux 74 | 'ARM64 Linux': 75 | POOL_NAME: 'arm64-pool' 76 | OS: Linux 77 | steps: 78 | - template: azure-steps-nix.yml 79 | 80 | 81 | - job: Hosted 82 | displayName: 'Hosted' 83 | pool: 84 | vmImage: $(VMIMAGE) 85 | timeoutInMinutes: 45 86 | strategy: 87 | maxParallel: 9 88 | matrix: 89 | 'Ubuntu 20.04': 90 | VMIMAGE: 'ubuntu-20.04' 91 | OS: Linux 92 | 'Ubuntu 24.04': 93 | VMIMAGE: 'ubuntu-24.04' 94 | OS: Linux 95 | 'MacOS Latest': 96 | VMIMAGE: 'macOS-latest' 97 | OS: OSX 98 | 'MacOS 12': 99 | VMIMAGE: 'macOS-12' 100 | OS: OSX 101 | 'Win 2019': 102 | VMIMAGE: 'windows-2019' 103 | OS: Windows 104 | 'Win Latest': 105 | VMIMAGE: 'windows-latest' 106 | OS: Windows 107 | steps: 108 | - template: azure-steps-nix.yml 109 | 110 | 111 | trigger: 112 | - master 113 | 114 | pr: 115 | branches: 116 | exclude: 117 | - '*' 118 | 119 | 120 | -------------------------------------------------------------------------------- /benchmark.linux-2.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # work=$HOME/build/devizer; mkdir -p $work; cd $work; git clone https://github.com/devizer/Universe.CpuUsage; cd Universe.CpuUsage; git pull 3 | # dotnet tool install -g BenchmarkDotNet.Tool 4 | pushd Universe.CpuUsage.Banchmark 5 | rm -rf bin/benchmark 6 | dotnet publish -o bin/benchmark -c Release -f net5.0 --self-contained -r linux-arm64 7 | pushd bin/benchmark 8 | ./Universe.CpuUsage.Banchmark 9 | popd 10 | popd 11 | 12 | -------------------------------------------------------------------------------- /benchmark.linux.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # work=$HOME/build/devizer; mkdir -p $work; cd $work; git clone https://github.com/devizer/Universe.CpuUsage; cd Universe.CpuUsage; git pull 3 | # dotnet tool install -g BenchmarkDotNet.Tool 4 | pushd Universe.CpuUsage.Banchmark 5 | dotnet publish -o bin/benchmark -c Release -f net5.0 6 | pushd bin/benchmark 7 | dotnet benchmark Universe.CpuUsage.Banchmark.dll --filter *CpuUsageBenchmarks* 8 | popd 9 | popd 10 | 11 | -------------------------------------------------------------------------------- /benchmark.win.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | pushd Universe.CpuUsage.Banchmark 3 | set fw=net47 4 | set fw=net5.0 5 | rd /q /s "bin\benchmark" 6 | dotnet publish -o bin/benchmark -c Release -f %fw% 7 | pushd bin\benchmark 8 | rem dotnet benchmark Universe.CpuUsage.Banchmark.dll --runtimes %fw% --filter *CpuUsageBenchmarks* 9 | dotnet Universe.CpuUsage.Banchmark.dll --filter *CpuUsageBenchmarks* 10 | rem dotnet benchmark Universe.CpuUsage.Banchmark.dll --warmupCount 2 --unrollFactor 8 --monoPath "C:\Program Files\Mono\bin\mono.exe" --runtimes Mono --filter *ByThread* 11 | rem --warmupCount 2 --unrollFactor 8 12 | popd 13 | popd 14 | 15 | -------------------------------------------------------------------------------- /build-and-test-in-docker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | export SKIP_POSIXRESOURCESUSAGE_ASSERTS=True 3 | 4 | set -e 5 | apt-get update -q; apt-get install -y wget p7zip-full sudo procps bsdutils util-linux lshw; 6 | script=https://raw.githubusercontent.com/devizer/test-and-build/master/install-build-tools-bundle.sh; (wget -q -nv --no-check-certificate -O - $script 2>/dev/null || curl -ksSL $script) | bash; 7 | Say "ENVIRONMENT"; printenv | sort 8 | Say "MEMORY INFO"; free -m; 9 | Say "BLOCK STORAGE DEVICES"; sudo fdisk -l || true; 10 | Say "MOUNTS"; df -h -T 11 | Say "LS CPU"; lscpu || true 12 | Say "7Z BENCHMARK"; 13 | if [[ $(uname -m) == armv7* ]]; then 14 | 7z -mmt1 b || true 15 | else 16 | 7z b || true 17 | fi 18 | 19 | if [[ $(uname -m) == armv8l ]]; then 20 | Say "ABORT! armel arch is not supported by .NET CORE" 21 | exit 0; 22 | fi 23 | 24 | 25 | apt-get install -yq locales systemd apt-utils apt-transport-https ca-certificates curl libcurl3 gnupg2 software-properties-common htop mc lsof unzip net-tools bsdutils sudo p7zip-full wget git time ncdu tree procps p7zip-full jq pv; 26 | apt-get clean; 27 | if [[ $(uname -m) != armv8l ]]; then 28 | script=https://raw.githubusercontent.com/devizer/test-and-build/master/lab/install-DOTNET.sh; (wget -q -nv --no-check-certificate -O - $script 2>/dev/null || curl -ksSL $script) | bash; 29 | else 30 | Say "net core for arm?" 31 | DOTNET_Url=https://dot.net/v1/dotnet-install.sh; 32 | try-and-retry curl -o /tmp/_dotnet-install.sh -ksSL $DOTNET_Url 33 | for v in 2.1 2.2 3.0 3.1; do 34 | Say ".NET Core $v for arm 32 bit" 35 | time try-and-retry timeout 666 sudo -E bash /tmp/_dotnet-install.sh -c $v -i /usr/share/dotnet --architecture arm 36 | done 37 | fi 38 | sudo ln -f -s /usr/share/dotnet/dotnet /usr/local/bin/dotnet || true 39 | file /usr/share/dotnet/dotnet || true 40 | file /usr/local/bin/dotnet || true 41 | 42 | Say "TESTING Universe.CpuUsage" 43 | dotnet test -f netcoreapp2.2 44 | 45 | Show-System-Stat -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | C:\Apps\VS2019C\MSBuild\Current\Bin\amd64\msbuild.exe /t:rebuild /p:Configuration=Release /v:q 2 | C:\Apps\VS2019C\MSBuild\Current\Bin\amd64\msbuild.exe /t:pack /p:Configuration=Debug /v:q 3 | -------------------------------------------------------------------------------- /images/CpuUsage.Icon-V2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devizer/Universe.CpuUsage/5c5c67794ad0592460f15d972a8afe76362c4a9f/images/CpuUsage.Icon-V2.png -------------------------------------------------------------------------------- /images/CpuUsage.Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devizer/Universe.CpuUsage/5c5c67794ad0592460f15d972a8afe76362c4a9f/images/CpuUsage.Icon.png -------------------------------------------------------------------------------- /images/Main.Icon.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devizer/Universe.CpuUsage/5c5c67794ad0592460f15d972a8afe76362c4a9f/images/Main.Icon.pdn -------------------------------------------------------------------------------- /images/Universe.CpuUsage-benchmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devizer/Universe.CpuUsage/5c5c67794ad0592460f15d972a8afe76362c4a9f/images/Universe.CpuUsage-benchmark.png -------------------------------------------------------------------------------- /images/Universe.CpuUsage-implementation-table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devizer/Universe.CpuUsage/5c5c67794ad0592460f15d972a8afe76362c4a9f/images/Universe.CpuUsage-implementation-table.png -------------------------------------------------------------------------------- /nuget.config-ignored: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test-on-mono-only-platforms.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e; #v15 3 | 4 | if [[ "$(command -v Reset-Target-Framework)" == "" ]]; then 5 | script=https://raw.githubusercontent.com/devizer/test-and-build/master/install-build-tools-bundle.sh; 6 | (wget -q -nv --no-check-certificate -O - $script 2>/dev/null || curl -ksSL $script) | bash 7 | fi 8 | Say --Reset-Stopwatch 9 | 10 | if [[ "$(command -v nunit3-console)" == "" ]]; then 11 | export XFW_VER=net47 NET_TEST_RUNNERS_INSTALL_DIR=/opt/net-test-runners; 12 | script=https://raw.githubusercontent.com/devizer/test-and-build/master/lab/NET-TEST-RUNNERS-build.sh; 13 | cmd="(wget -q -nv --no-check-certificate -O - $script 2>/dev/null || curl -ksSL $script) | sudo -E bash" 14 | eval "$cmd" || eval "$cmd" || eval "$cmd" 15 | fi 16 | 17 | NETFW=net461 18 | work=/transient-builds/src 19 | mkdir -p $work && cd $work 20 | Say "git clone|pull" 21 | test ! -d Universe.CpuUsage && git clone https://github.com/devizer/Universe.CpuUsage || true 22 | cd Universe.CpuUsage 23 | git reset --hard; git pull 24 | Say "Reset target framework to [$NETFW]" 25 | Reset-Target-Framework --framework $NETFW --language latest 26 | Say "Restore Dependencies" 27 | cd Universe.CpuUsage.Tests 28 | time msbuild /t:Restore -v:m 29 | Say "Build Release for Universe.CpuUsage.Tests" 30 | time msbuild /t:Build /p:Configuration=Release -v:m 31 | cd bin/Release/$NETFW 32 | Say "Run Integration Tests" 33 | time nunit3-console --workers=1 Universe.CpuUsage.Tests.dll 34 | Say "Successfully completed" 35 | --------------------------------------------------------------------------------