├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ └── test.yml ├── .gitignore ├── Gopkg.lock ├── LICENSE ├── README.md ├── THIRD_PARTY_LICENSES.md ├── cpu ├── cpu.go ├── cpu_darwin.go ├── cpu_linux_arm64.go ├── cpu_linux_default.go ├── cpu_windows.go ├── cpu_windows_386.go ├── cpu_windows_amd64.go ├── from-lscpu-arm.py ├── lscpu_linux_arm64.go ├── util_linux.go └── util_linux_test.go ├── filesystem ├── filesystem.go ├── filesystem_common.go ├── filesystem_nix_test.go └── filesystem_windows.go ├── go.mod ├── go.sum ├── gohai.go ├── gohai_test.go ├── log.go ├── make.go ├── memory ├── memory.go ├── memory_darwin.go ├── memory_linux.go └── memory_windows.go ├── network ├── network.go └── network_common.go ├── platform ├── platform.go ├── platform_android.go ├── platform_common.go ├── platform_darwin.go ├── platform_linux.go ├── platform_nix.go ├── platform_windows.go ├── platform_windows_386.go └── platform_windows_amd64.go ├── processes ├── gops │ ├── gops.go │ ├── process_group.go │ ├── process_group_test.go │ ├── process_info.go │ ├── process_info_darwin.go │ └── process_info_linux.go ├── processes.go ├── processes_common.go └── processes_windows.go └── utils ├── cmd.go └── dict_helpers.go /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/about-codeowners/ for syntax 2 | # Rules are matched bottom-to-top, so one team can own subdirectories 3 | # and another the rest of the directory. 4 | 5 | # All file changes should be reviewed by the maintainer team unless 6 | # specified otherwise 7 | * @DataDog/agent-shared-components 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | # Maintain dependencies for GitHub Actions 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | interval: "daily" 13 | 14 | # Maintain dependencies for Go 15 | - package-ecosystem: "gomod" 16 | directory: "/" 17 | schedule: 18 | interval: "daily" 19 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '18 6 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'go' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@83b7061638ee4956cf7545a6f7efe594e5ad0247 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@d186a2a36cc67bfa1b860e6170d37fb9634742c7 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@d186a2a36cc67bfa1b860e6170d37fb9634742c7 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@d186a2a36cc67bfa1b860e6170d37fb9634742c7 72 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | jobs: 5 | test: 6 | strategy: 7 | matrix: 8 | os: [ubuntu-20.04, ubuntu-latest, macos-11, macos-latest, windows-2019, windows-latest] 9 | runs-on: ${{ matrix.os }} 10 | steps: 11 | - uses: actions/checkout@83b7061638ee4956cf7545a6f7efe594e5ad0247 12 | - uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 13 | with: 14 | go-version: 1.17.x 15 | - name: Test 16 | run: go test ./... 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.test 3 | /.vagrant 4 | /build 5 | /vendor 6 | gohai 7 | *.swp 8 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | branch = "next" 6 | digest = "1:80f75788f4df66bdfb52fe5c7714878b384ec172e9382ae01c809fe25c028e9b" 7 | name = "github.com/StackExchange/wmi" 8 | packages = ["."] 9 | pruneopts = "UT" 10 | revision = "9f32b5905fd6ce7384093f9d048437e79f7b4d85" 11 | 12 | [[projects]] 13 | digest = "1:50e893a85575fa48dc4982a279e50e2fd8b74e4f7c587860c1e25c77083b8125" 14 | name = "github.com/cihub/seelog" 15 | packages = ["."] 16 | pruneopts = "UT" 17 | revision = "d2c6e5aa9fbfdd1c624e140287063c7730654115" 18 | version = "v2.6" 19 | 20 | [[projects]] 21 | digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" 22 | name = "github.com/davecgh/go-spew" 23 | packages = ["spew"] 24 | pruneopts = "UT" 25 | revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" 26 | version = "v1.1.1" 27 | 28 | [[projects]] 29 | digest = "1:1ae488e919756729db69becc130be75a4820dfdd0775e430a85f726a84ce14ab" 30 | name = "github.com/go-ole/go-ole" 31 | packages = [ 32 | ".", 33 | "oleutil", 34 | ] 35 | pruneopts = "UT" 36 | revision = "de8695c8edbf8236f30d6e1376e20b198a028d42" 37 | 38 | [[projects]] 39 | digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" 40 | name = "github.com/pmezard/go-difflib" 41 | packages = ["difflib"] 42 | pruneopts = "UT" 43 | revision = "792786c7400a136282c1664665ae0a8db921c6c2" 44 | version = "v1.0.0" 45 | 46 | [[projects]] 47 | digest = "1:0dc15f8c051646249461ab843ed26b0126c528032ad8f0df2d291917ae502c12" 48 | name = "github.com/shirou/gopsutil" 49 | packages = [ 50 | "cpu", 51 | "host", 52 | "internal/common", 53 | "mem", 54 | "net", 55 | "process", 56 | ] 57 | pruneopts = "UT" 58 | revision = "e8f7a95747d711f34ddfe9dd9b825a84bd059dec" 59 | version = "v2.0.0" 60 | 61 | [[projects]] 62 | branch = "master" 63 | digest = "1:99c6a6dab47067c9b898e8c8b13d130c6ab4ffbcc4b7cc6236c2cd0b1e344f5b" 64 | name = "github.com/shirou/w32" 65 | packages = ["."] 66 | pruneopts = "UT" 67 | revision = "bb4de0191aa41b5507caa14b0650cdbddcd9280b" 68 | 69 | [[projects]] 70 | digest = "1:972c2427413d41a1e06ca4897e8528e5a1622894050e2f527b38ddf0f343f759" 71 | name = "github.com/stretchr/testify" 72 | packages = ["assert"] 73 | pruneopts = "UT" 74 | revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053" 75 | version = "v1.3.0" 76 | 77 | [[projects]] 78 | branch = "master" 79 | digest = "1:228f27b6ae6aa190a8082e78c76870062506763cae2526543dc8f7ac9bea0947" 80 | name = "golang.org/x/sys" 81 | packages = [ 82 | "unix", 83 | "windows", 84 | "windows/registry", 85 | ] 86 | pruneopts = "UT" 87 | revision = "77cc2087c03b9062d8eb49dd60e665cbd7c1cde2" 88 | 89 | [solve-meta] 90 | analyzer-name = "dep" 91 | analyzer-version = 1 92 | input-imports = [ 93 | "github.com/cihub/seelog", 94 | "github.com/shirou/gopsutil/mem", 95 | "github.com/shirou/gopsutil/process", 96 | "github.com/stretchr/testify/assert", 97 | "golang.org/x/sys/windows/registry", 98 | ] 99 | solver-name = "gps-cdcl" 100 | solver-version = 1 101 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2015 Kentaro Kuribayashi 3 | Copyright (c) 2014-2015, Datadog 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gohai 2 | 3 | [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](http://kentaro.mit-license.org/) 4 | 5 | Gohai is a tool which collects an inventory of system information. It aims to implement some parts of features from [facter](https://github.com/puppetlabs/facter) and [ohai](https://github.com/opscode/ohai). It's forked from Kentaro Kuribayashi's [verity](https://github.com/kentaro/verity). 6 | 7 | ## :warning: Gohai now lives in the repository of the [datadog-agent](https://github.com/DataDog/datadog-agent), under the `/pkg/gohai` directory, as an independent module :warning: 8 | 9 | Bug reports and feature requests should be addressed to the new parent repository ([datadog-agent](https://github.com/DataDog/datadog-agent)). 10 | 11 | In order to update to the most recent version of Gohai, you have to replace `github.com/DataDog/gohai` with `github.com/DataDog/datadog-agent/pkg/gohai` in your `go.mod` and your imports. 12 | 13 | Note that the API was changed so verify that the API calls that you are using did not change in the new destination. 14 | 15 | This repository will likely be archived soon. 16 | 17 | ## Usage 18 | 19 | Gohai will build and install with `go get`. We require at least Go 1.17. 20 | 21 | ```sh 22 | go get github.com/DataDog/gohai 23 | ``` 24 | 25 | Running it will dump json formatted output: 26 | 27 | ```sh 28 | $ gohai | jq . 29 | { 30 | "cpu": { 31 | "cpu_cores": "2", 32 | "family": "6", 33 | "mhz": "2600", 34 | "model": "58", 35 | "model_name": "Intel(R) Core(TM) i5-3230M CPU @ 2.60GHz", 36 | "stepping": "9", 37 | "vendor_id": "GenuineIntel" 38 | }, 39 | "filesystem": [ 40 | { 41 | "kb_size": "244277768", 42 | "mounted_on": "/", 43 | "name": "/dev/disk0s2" 44 | } 45 | ], 46 | "memory": { 47 | "swap_total": "4096.00M", 48 | "total": "8589934592" 49 | }, 50 | "network": { 51 | "ipaddress": "192.168.1.6", 52 | "ipaddressv6": "fe80::5626:96ff:fed3:5811", 53 | "macaddress": "54:26:96:d3:58:11" 54 | }, 55 | "platform": { 56 | "GOOARCH": "amd64", 57 | "GOOS": "darwin", 58 | "goV": "1.2.1", 59 | "hostname": "new-host.home", 60 | "kernel_name": "Darwin", 61 | "kernel_release": "12.5.0", 62 | "kernel_version": "Darwin Kernel Version 12.5.0: Sun Sep 29 13:33:47 PDT 2013; root:xnu-2050.48.12~1/RELEASE_X86_64", 63 | "machine": "x86_64", 64 | "os": "Darwin", 65 | "processor": "i386", 66 | "pythonV": "2.7.2" 67 | } 68 | } 69 | ``` 70 | 71 | Pipe it through eg. `jq` or `python -m json.tool` for pretty output. 72 | 73 | ## How to build 74 | 75 | Just run `go build`! 76 | 77 | ## Build with version info 78 | 79 | To build Gohai with version information, use `make.go`: 80 | 81 | ```sh 82 | go run make.go 83 | ``` 84 | 85 | It will build gohai using the `go build` command, with the version info passed through `-ldflags`. 86 | 87 | ## Updating CPU Information 88 | 89 | Some information about CPUs is drawn from the source of the `util-linux` utility `lscpu`. 90 | To update this information, such as when new processors are released, run 91 | 92 | ``` 93 | python cpu/from-lscpu-arm.py /path/to/lscpu-arm.c > cpu/lscpu_linux_arm64.go 94 | ``` 95 | 96 | ## See Also 97 | 98 | * [facter](https://github.com/puppetlabs/facter) 99 | * [ohai](https://github.com/opscode/ohai) 100 | 101 | ## Author 102 | 103 | * [Kentaro Kuribayashi](http://kentarok.org/) 104 | 105 | ## License 106 | 107 | * MIT http://kentaro.mit-license.org/ 108 | -------------------------------------------------------------------------------- /THIRD_PARTY_LICENSES.md: -------------------------------------------------------------------------------- 1 | ## gopsutil [LICENSE](https://github.com/shirou/gopsutil/blob/master/LICENSE) 2 | 3 | gopsutil is distributed under BSD license reproduced below. 4 | 5 | Copyright (c) 2014, WAKAYAMA Shirou 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without modification, 9 | are permitted provided that the following conditions are met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | * Neither the name of the gopsutil authors nor the names of its contributors 17 | may be used to endorse or promote products derived from this software without 18 | specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /cpu/cpu.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | // Package cpu regroups collecting information about the CPU 7 | package cpu 8 | 9 | import ( 10 | "fmt" 11 | "strconv" 12 | "strings" 13 | 14 | "github.com/DataDog/gohai/utils" 15 | ) 16 | 17 | // Cpu holds metadata about the host CPU 18 | // 19 | //nolint:revive 20 | type Cpu struct { 21 | // VendorId the CPU vendor ID 22 | VendorId string 23 | // ModelName the CPU model 24 | ModelName string 25 | // CpuCores the number of cores for the CPU 26 | CpuCores uint64 27 | // CpuLogicalProcessors the number of logical core for the CPU 28 | CpuLogicalProcessors uint64 29 | // Mhz the frequency for the CPU (Not available on ARM) 30 | Mhz float64 31 | // CacheSizeBytes the cache size for the CPU (Linux only) 32 | CacheSizeBytes uint64 33 | // Family the CPU family 34 | Family string 35 | // Model the CPU model name 36 | Model string 37 | // Stepping the CPU stepping 38 | Stepping string 39 | 40 | // CpuPkgs the CPU pkg count (Windows only) 41 | CpuPkgs uint64 42 | // CpuNumaNodes the CPU numa node count (Windows only) 43 | CpuNumaNodes uint64 44 | // CacheSizeL1Bytes the CPU L1 cache size (Windows only) 45 | CacheSizeL1Bytes uint64 46 | // CacheSizeL2Bytes the CPU L2 cache size (Windows only) 47 | CacheSizeL2Bytes uint64 48 | // CacheSizeL3 the CPU L3 cache size (Windows only) 49 | CacheSizeL3Bytes uint64 50 | } 51 | 52 | const name = "cpu" 53 | 54 | // Name returns the name of the package 55 | func (cpu *Cpu) Name() string { 56 | return name 57 | } 58 | 59 | // Collect collects the CPU information. 60 | // Returns an object which can be converted to a JSON or an error if nothing could be collected. 61 | // Tries to collect as much information as possible. 62 | func (cpu *Cpu) Collect() (result interface{}, err error) { 63 | result, err = getCPUInfo() 64 | return 65 | } 66 | 67 | // Get returns a CPU struct already initialized, a list of warnings and an error. The method will try to collect as much 68 | // metadata as possible, an error is returned if nothing could be collected. The list of warnings contains errors if 69 | // some metadata could not be collected. 70 | func Get() (*Cpu, []string, error) { 71 | cpuInfo, err := getCPUInfo() 72 | if err != nil { 73 | return nil, nil, err 74 | } 75 | 76 | warnings := []string{} 77 | c := &Cpu{} 78 | 79 | c.VendorId = utils.GetString(cpuInfo, "vendor_id") 80 | c.ModelName = utils.GetString(cpuInfo, "model_name") 81 | c.Family = utils.GetString(cpuInfo, "family") 82 | c.Model = utils.GetString(cpuInfo, "model") 83 | c.Stepping = utils.GetString(cpuInfo, "stepping") 84 | 85 | // We serialize int to string in the windows version of 'GetCpuInfo' and back to int here. This is less than 86 | // ideal but we don't want to break backward compatibility for now. The entire gohai project needs a rework but 87 | // for now we simply adding typed field to avoid using maps of interface.. 88 | c.CpuPkgs = utils.GetUint64(cpuInfo, "cpu_pkgs", &warnings) 89 | c.CpuNumaNodes = utils.GetUint64(cpuInfo, "cpu_numa_nodes", &warnings) 90 | c.CacheSizeL1Bytes = utils.GetUint64(cpuInfo, "cache_size_l1", &warnings) 91 | c.CacheSizeL2Bytes = utils.GetUint64(cpuInfo, "cache_size_l2", &warnings) 92 | c.CacheSizeL3Bytes = utils.GetUint64(cpuInfo, "cache_size_l3", &warnings) 93 | 94 | c.CpuCores = utils.GetUint64(cpuInfo, "cpu_cores", &warnings) 95 | c.CpuLogicalProcessors = utils.GetUint64(cpuInfo, "cpu_logical_processors", &warnings) 96 | c.Mhz = utils.GetFloat64(cpuInfo, "mhz", &warnings) 97 | 98 | // cache_size uses the format '9216 KB' 99 | cacheSizeString := strings.Split(utils.GetString(cpuInfo, "cache_size"), " ")[0] 100 | cacheSizeBytes, err := strconv.ParseUint(cacheSizeString, 10, 64) 101 | if err == nil { 102 | c.CacheSizeBytes = cacheSizeBytes * 1024 103 | } else { 104 | warnings = append(warnings, fmt.Sprintf("could not collect cache size: %s", err)) 105 | } 106 | 107 | return c, warnings, nil 108 | } 109 | -------------------------------------------------------------------------------- /cpu/cpu_darwin.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package cpu 7 | 8 | import ( 9 | "os/exec" 10 | "strconv" 11 | "strings" 12 | ) 13 | 14 | var cpuMap = map[string]string{ 15 | "machdep.cpu.vendor": "vendor_id", 16 | "machdep.cpu.brand_string": "model_name", 17 | "hw.physicalcpu": "cpu_cores", 18 | "hw.logicalcpu": "cpu_logical_processors", 19 | "hw.cpufrequency": "mhz", 20 | "machdep.cpu.family": "family", 21 | "machdep.cpu.model": "model", 22 | "machdep.cpu.stepping": "stepping", 23 | } 24 | 25 | func getCPUInfo() (cpuInfo map[string]string, err error) { 26 | cpuInfo = make(map[string]string) 27 | 28 | for option, key := range cpuMap { 29 | out, err := exec.Command("sysctl", "-n", option).Output() 30 | if err == nil { 31 | cpuInfo[key] = strings.Trim(string(out), "\n") 32 | } 33 | } 34 | 35 | if len(cpuInfo["mhz"]) != 0 { 36 | mhz, err := strconv.Atoi(cpuInfo["mhz"]) 37 | if err == nil { 38 | cpuInfo["mhz"] = strconv.Itoa(mhz / 1000000) 39 | } 40 | } 41 | 42 | return 43 | } 44 | -------------------------------------------------------------------------------- /cpu/cpu_linux_arm64.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | //go:build linux && arm64 7 | // +build linux,arm64 8 | 9 | package cpu 10 | 11 | import ( 12 | "fmt" 13 | "os" 14 | "regexp" 15 | "strconv" 16 | ) 17 | 18 | // The Linux kernel does not include much useful information in /proc/cpuinfo 19 | // for arm64, so we must dig further into the /sys tree and build a more 20 | // accurate representation of the contained data, rather than relying on the 21 | // simple analysis in cpu/cpu_linux_default.go. 22 | 23 | // nodeNRegex recognizes directories named `nodeNN` 24 | var nodeNRegex = regexp.MustCompile("^node[0-9]+$") 25 | 26 | func getCPUInfo() (cpuInfo map[string]string, err error) { 27 | cpuInfo = make(map[string]string) 28 | 29 | procCpu, err := readProcCpuInfo() 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | // we blithely assume that many of the CPU characteristics are the same for 35 | // all CPUs, so we can just use the first. 36 | firstCpu := procCpu[0] 37 | 38 | // determine vendor and model from CPU implementer / part 39 | if cpuVariantStr, ok := firstCpu["CPU implementer"]; ok { 40 | if cpuVariant, err := strconv.ParseUint(cpuVariantStr, 0, 64); err == nil { 41 | if cpuPartStr, ok := firstCpu["CPU part"]; ok { 42 | if cpuPart, err := strconv.ParseUint(cpuPartStr, 0, 64); err == nil { 43 | cpuInfo["model"] = cpuPartStr 44 | if impl, ok := hwVariant[cpuVariant]; ok { 45 | cpuInfo["vendor_id"] = impl.name 46 | if modelName, ok := impl.parts[cpuPart]; ok { 47 | cpuInfo["model_name"] = modelName 48 | } else { 49 | cpuInfo["model_name"] = cpuPartStr 50 | } 51 | } else { 52 | cpuInfo["vendor_id"] = cpuVariantStr 53 | cpuInfo["model_name"] = cpuPartStr 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | // ARM does not define a family 61 | cpuInfo["family"] = "none" 62 | 63 | // 'lscpu' represents the stepping as an rXpY string 64 | if cpuVariantStr, ok := firstCpu["CPU variant"]; ok { 65 | if cpuVariant, err := strconv.ParseUint(cpuVariantStr, 0, 64); err == nil { 66 | if cpuRevisionStr, ok := firstCpu["CPU revision"]; ok { 67 | if cpuRevision, err := strconv.ParseUint(cpuRevisionStr, 0, 64); err == nil { 68 | cpuInfo["stepping"] = fmt.Sprintf("r%dp%d", cpuVariant, cpuRevision) 69 | } 70 | } 71 | } 72 | } 73 | 74 | // Iterate over each processor and fetch additional information from /sys/devices/system/cpu 75 | cores := map[uint64]struct{}{} 76 | packages := map[uint64]struct{}{} 77 | cacheSizes := map[uint64]uint64{} 78 | for _, stanza := range procCpu { 79 | procID, err := strconv.ParseUint(stanza["processor"], 0, 64) 80 | if err != nil { 81 | continue 82 | } 83 | 84 | if coreID, ok := sysCpuInt(fmt.Sprintf("cpu%d/topology/core_id", procID)); ok { 85 | cores[coreID] = struct{}{} 86 | } 87 | 88 | if pkgID, ok := sysCpuInt(fmt.Sprintf("cpu%d/topology/physical_package_id", procID)); ok { 89 | packages[pkgID] = struct{}{} 90 | } 91 | 92 | // iterate over each cache this CPU can use 93 | i := 0 94 | for { 95 | if sharedList, ok := sysCpuList(fmt.Sprintf("cpu%d/cache/index%d/shared_cpu_list", procID, i)); ok { 96 | // we are scanning CPUs in order, so only count this cache if it's not shared with a 97 | // CPU that has already been scanned 98 | shared := false 99 | for sharedProcID := range sharedList { 100 | if sharedProcID < procID { 101 | shared = true 102 | break 103 | } 104 | } 105 | 106 | if !shared { 107 | if level, ok := sysCpuInt(fmt.Sprintf("cpu%d/cache/index%d/level", procID, i)); ok { 108 | if size, ok := sysCpuSize(fmt.Sprintf("cpu%d/cache/index%d/size", procID, i)); ok { 109 | cacheSizes[level] += size 110 | } 111 | } 112 | } 113 | } else { 114 | break 115 | } 116 | i++ 117 | } 118 | } 119 | cpuInfo["cpu_pkgs"] = strconv.Itoa(len(packages)) 120 | cpuInfo["cpu_cores"] = strconv.Itoa(len(cores)) 121 | cpuInfo["cpu_logical_processors"] = strconv.Itoa(len(procCpu)) 122 | cpuInfo["cache_size_l1"] = strconv.FormatUint(cacheSizes[1], 10) 123 | cpuInfo["cache_size_l2"] = strconv.FormatUint(cacheSizes[2], 10) 124 | cpuInfo["cache_size_l3"] = strconv.FormatUint(cacheSizes[3], 10) 125 | 126 | // cache_size uses the format '9216 KB' 127 | cpuInfo["cache_size"] = fmt.Sprintf("%d KB", (cacheSizes[1]+cacheSizes[2]+cacheSizes[3])/1024) 128 | 129 | // Count the number of NUMA nodes in /sys/devices/system/node 130 | nodes := 0 131 | if dirents, err := os.ReadDir("/sys/devices/system/node"); err == nil { 132 | for _, dirent := range dirents { 133 | if dirent.IsDir() && nodeNRegex.MatchString(dirent.Name()) { 134 | nodes++ 135 | } 136 | } 137 | } 138 | cpuInfo["cpu_numa_nodes"] = strconv.Itoa(nodes) 139 | 140 | // ARM does not make the clock speed available 141 | // cpuInfo["mhz"] 142 | 143 | return cpuInfo, nil 144 | } 145 | -------------------------------------------------------------------------------- /cpu/cpu_linux_default.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | //go:build linux && !arm64 7 | // +build linux,!arm64 8 | 9 | package cpu 10 | 11 | import ( 12 | "bufio" 13 | "os" 14 | "regexp" 15 | "strconv" 16 | ) 17 | 18 | var cpuMap = map[string]string{ 19 | "vendor_id": "vendor_id", 20 | "model name": "model_name", 21 | "cpu cores": "cpu_cores", 22 | "siblings": "cpu_logical_processors", 23 | "cpu MHz\t": "mhz", 24 | "cache size": "cache_size", 25 | "cpu family": "family", 26 | "model\t": "model", 27 | "stepping": "stepping", 28 | } 29 | 30 | // Values that need to be multiplied by the number of physical processors 31 | var perPhysicalProcValues = []string{ 32 | "cpu_cores", 33 | "cpu_logical_processors", 34 | } 35 | 36 | func getCPUInfo() (cpuInfo map[string]string, err error) { 37 | lines, err := readProcFile() 38 | if err != nil { 39 | return 40 | } 41 | 42 | cpuInfo = make(map[string]string) 43 | // Implementation of a set that holds the physical IDs 44 | physicalProcIDs := make(map[string]struct{}) 45 | 46 | for _, line := range lines { 47 | pair := regexp.MustCompile("\t: ").Split(line, 2) 48 | 49 | if pair[0] == "physical id" { 50 | physicalProcIDs[pair[1]] = struct{}{} 51 | } 52 | 53 | key, ok := cpuMap[pair[0]] 54 | if ok { 55 | cpuInfo[key] = pair[1] 56 | } 57 | } 58 | 59 | // Multiply the values that are "per physical processor" by the number of physical procs 60 | for _, field := range perPhysicalProcValues { 61 | if value, ok := cpuInfo[field]; ok { 62 | intValue, err := strconv.Atoi(value) 63 | if err != nil { 64 | continue 65 | } 66 | 67 | cpuInfo[field] = strconv.Itoa(intValue * len(physicalProcIDs)) 68 | } 69 | } 70 | 71 | return 72 | } 73 | 74 | func readProcFile() (lines []string, err error) { 75 | file, err := os.Open("/proc/cpuinfo") 76 | 77 | if err != nil { 78 | return 79 | } 80 | 81 | scanner := bufio.NewScanner(file) 82 | 83 | for scanner.Scan() { 84 | lines = append(lines, scanner.Text()) 85 | } 86 | 87 | if scanner.Err() != nil { 88 | err = scanner.Err() 89 | return 90 | } 91 | 92 | return 93 | } 94 | -------------------------------------------------------------------------------- /cpu/cpu_windows.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package cpu 7 | 8 | import ( 9 | "fmt" 10 | "regexp" 11 | "strconv" 12 | "strings" 13 | "syscall" 14 | "unsafe" 15 | 16 | "golang.org/x/sys/windows/registry" 17 | ) 18 | 19 | var getCPUInfo = GetCpuInfo 20 | 21 | // ERROR_INSUFFICIENT_BUFFER is the error number associated with the 22 | // "insufficient buffer size" error 23 | // 24 | //nolint:revive 25 | const ERROR_INSUFFICIENT_BUFFER syscall.Errno = 122 26 | 27 | const registryHive = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0" 28 | 29 | // CACHE_DESCRIPTOR contains cache related information 30 | // see https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-cache_descriptor 31 | // 32 | //nolint:unused,revive 33 | type CACHE_DESCRIPTOR struct { 34 | Level uint8 35 | Associativity uint8 36 | LineSize uint16 37 | Size uint32 38 | cacheType uint32 39 | } 40 | 41 | // SYSTEM_LOGICAL_PROCESSOR_INFORMATION describes the relationship 42 | // between the specified processor set. 43 | // see https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-system_logical_processor_information 44 | // 45 | //nolint:unused,revive 46 | type SYSTEM_LOGICAL_PROCESSOR_INFORMATION struct { 47 | ProcessorMask uintptr 48 | Relationship int // enum (int) 49 | // in the Windows header, this is a union of a byte, a DWORD, 50 | // and a CACHE_DESCRIPTOR structure 51 | dataunion [16]byte 52 | } 53 | 54 | //.const SYSTEM_LOGICAL_PROCESSOR_INFORMATION_SIZE = 32 55 | 56 | // GROUP_AFFINITY represents a processor group-specific affinity, 57 | // such as the affinity of a thread. 58 | // see https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-group_affinity 59 | // 60 | //nolint:revive 61 | type GROUP_AFFINITY struct { 62 | Mask uintptr 63 | Group uint16 64 | Reserved [3]uint16 65 | } 66 | 67 | // NUMA_NODE_RELATIONSHIP represents information about a NUMA node 68 | // in a processor group. 69 | // see https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-numa_node_relationship 70 | // 71 | //nolint:revive 72 | type NUMA_NODE_RELATIONSHIP struct { 73 | NodeNumber uint32 74 | Reserved [20]uint8 75 | GroupMask GROUP_AFFINITY 76 | } 77 | 78 | // CACHE_RELATIONSHIP describes cache attributes. 79 | // see https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-cache_relationship 80 | // 81 | //nolint:revive 82 | type CACHE_RELATIONSHIP struct { 83 | Level uint8 84 | Associativity uint8 85 | LineSize uint16 86 | CacheSize uint32 87 | CacheType int // enum in C 88 | Reserved [20]uint8 89 | GroupMask GROUP_AFFINITY 90 | } 91 | 92 | // PROCESSOR_GROUP_INFO represents the number and affinity of processors 93 | // in a processor group. 94 | // see https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-processor_group_info 95 | // 96 | //nolint:revive 97 | type PROCESSOR_GROUP_INFO struct { 98 | MaximumProcessorCount uint8 99 | ActiveProcessorCount uint8 100 | Reserved [38]uint8 101 | ActiveProcessorMask uintptr 102 | } 103 | 104 | // GROUP_RELATIONSHIP represents information about processor groups. 105 | // see https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-group_relationship 106 | // 107 | //nolint:revive 108 | type GROUP_RELATIONSHIP struct { 109 | MaximumGroupCount uint16 110 | ActiveGroupCount uint16 111 | Reserved [20]uint8 112 | // variable size array of PROCESSOR_GROUP_INFO 113 | } 114 | 115 | // PROCESSOR_RELATIONSHIP represents information about affinity 116 | // within a processor group. 117 | // see https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-processor_relationship 118 | // 119 | //nolint:unused,revive 120 | type PROCESSOR_RELATIONSHIP struct { 121 | Flags uint8 122 | EfficiencyClass uint8 123 | wReserved [20]uint8 124 | GroupCount uint16 125 | // what follows is an array of zero or more GROUP_AFFINITY structures 126 | } 127 | 128 | // SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX contains information about 129 | // the relationships of logical processors and related hardware. 130 | // https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-system_logical_processor_information_ex 131 | // 132 | //nolint:revive 133 | type SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX struct { 134 | Relationship int 135 | Size uint32 136 | // what follows is a C union of 137 | // PROCESSOR_RELATIONSHIP, 138 | // NUMA_NODE_RELATIONSHIP, 139 | // CACHE_RELATIONSHIP, 140 | // GROUP_RELATIONSHIP 141 | } 142 | 143 | // see https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getlogicalprocessorinformationex 144 | const ( 145 | // RelationProcessorCore retrieves information about logical processors 146 | // that share a single processor core. 147 | RelationProcessorCore = 0 148 | // RelationNumaNode retrieves information about logical processors 149 | // that are part of the same NUMA node. 150 | RelationNumaNode = 1 151 | // RelationCache retrieves information about logical processors 152 | // that share a cache. 153 | RelationCache = 2 154 | // RelationProcessorPackage retrieves information about logical processors 155 | // that share a physical package. 156 | RelationProcessorPackage = 3 157 | // RelationGroup retrieves information about logical processors 158 | // that share a processor group. 159 | RelationGroup = 4 160 | ) 161 | 162 | // SYSTEM_INFO contains information about the current computer system. 163 | // This includes the architecture and type of the processor, the number 164 | // of processors in the system, the page size, and other such information. 165 | // see https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info 166 | // 167 | //nolint:revive 168 | type SYSTEM_INFO struct { 169 | wProcessorArchitecture uint16 170 | wReserved uint16 171 | dwPageSize uint32 172 | lpMinApplicationAddress *uint32 173 | lpMaxApplicationAddress *uint32 174 | dwActiveProcessorMask uintptr 175 | dwNumberOfProcessors uint32 176 | dwProcessorType uint32 177 | dwAllocationGranularity uint32 178 | wProcessorLevel uint16 179 | wProcessorRevision uint16 180 | } 181 | 182 | // CPU_INFO contains information about cpu, eg. number of cores, cache size 183 | // 184 | //nolint:revive 185 | type CPU_INFO struct { 186 | numaNodeCount int // number of NUMA nodes 187 | pkgcount int // number of packages (physical CPUS) 188 | corecount int // total number of cores 189 | logicalcount int // number of logical CPUS 190 | l1CacheSize uint32 // layer 1 cache size 191 | l2CacheSize uint32 // layer 2 cache size 192 | l3CacheSize uint32 // layer 3 cache size 193 | relationGroups int // number of cpu relation groups 194 | maxProcsInGroups int // max number of processors 195 | activeProcsInGroups int // active processors 196 | 197 | } 198 | 199 | func countBits(num uint64) (count int) { 200 | count = 0 201 | for num > 0 { 202 | if (num & 0x1) == 1 { 203 | count++ 204 | } 205 | num >>= 1 206 | } 207 | return 208 | } 209 | 210 | func getSystemInfo() (si SYSTEM_INFO) { 211 | var mod = syscall.NewLazyDLL("kernel32.dll") 212 | var gsi = mod.NewProc("GetSystemInfo") 213 | 214 | // syscall does not fail 215 | //nolint:errcheck 216 | gsi.Call(uintptr(unsafe.Pointer(&si))) 217 | return 218 | } 219 | 220 | // GetCpuInfo returns map of interesting bits of information about the CPU 221 | func GetCpuInfo() (map[string]string, error) { 222 | // Initialize cpuInfo with all fields to avoid missing a field which 223 | // could be expected by the backend or by users 224 | // TODO: make sure that the backend actually works with any subset of fields 225 | cpuInfo := map[string]string{ 226 | "mhz": "0", 227 | "model_name": "", 228 | "vendor_id": "", 229 | "family": "", 230 | "cpu_pkgs": "0", 231 | "cpu_numa_nodes": "0", 232 | "cpu_cores": "0", 233 | "cpu_logical_processors": "0", 234 | "cache_size_l1": "0", 235 | "cache_size_l2": "0", 236 | "cache_size_l3": "0", 237 | "model": "0", 238 | "stepping": "0", 239 | } 240 | 241 | k, err := registry.OpenKey(registry.LOCAL_MACHINE, 242 | registryHive, 243 | registry.QUERY_VALUE) 244 | if err == nil { 245 | // ignore registry key close errors 246 | //nolint:staticcheck 247 | defer k.Close() 248 | 249 | dw, _, err := k.GetIntegerValue("~MHz") 250 | if err == nil { 251 | cpuInfo["mhz"] = strconv.Itoa(int(dw)) 252 | } 253 | 254 | s, _, err := k.GetStringValue("ProcessorNameString") 255 | if err == nil { 256 | cpuInfo["model_name"] = s 257 | } 258 | 259 | s, _, err = k.GetStringValue("VendorIdentifier") 260 | if err == nil { 261 | cpuInfo["vendor_id"] = s 262 | } 263 | 264 | s, _, err = k.GetStringValue("Identifier") 265 | if err == nil { 266 | cpuInfo["family"] = extract(s, "Family") 267 | } 268 | } 269 | 270 | cpus, err := computeCoresAndProcessors() 271 | if err == nil { 272 | cpuInfo["cpu_pkgs"] = strconv.Itoa(cpus.pkgcount) 273 | cpuInfo["cpu_numa_nodes"] = strconv.Itoa(cpus.numaNodeCount) 274 | cpuInfo["cpu_cores"] = strconv.Itoa(cpus.corecount) 275 | cpuInfo["cpu_logical_processors"] = strconv.Itoa(cpus.logicalcount) 276 | 277 | cpuInfo["cache_size_l1"] = strconv.Itoa(int(cpus.l1CacheSize)) 278 | cpuInfo["cache_size_l2"] = strconv.Itoa(int(cpus.l2CacheSize)) 279 | cpuInfo["cache_size_l3"] = strconv.Itoa(int(cpus.l3CacheSize)) 280 | } 281 | 282 | si := getSystemInfo() 283 | cpuInfo["model"] = strconv.Itoa(int((si.wProcessorRevision >> 8) & 0xFF)) 284 | cpuInfo["stepping"] = strconv.Itoa(int(si.wProcessorRevision & 0xFF)) 285 | 286 | return cpuInfo, nil 287 | } 288 | 289 | func extract(caption, field string) string { 290 | re := regexp.MustCompile(fmt.Sprintf("%s [0-9]* ", field)) 291 | return strings.Split(re.FindStringSubmatch(caption)[0], " ")[1] 292 | } 293 | -------------------------------------------------------------------------------- /cpu/cpu_windows_386.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package cpu 7 | 8 | import ( 9 | "encoding/binary" 10 | "syscall" 11 | "unsafe" 12 | ) 13 | 14 | // SYSTEM_LOGICAL_PROCESSOR_INFORMATION_SIZE is the size of 15 | // SYSTEM_LOGICAL_PROCESSOR_INFORMATION struct 16 | // 17 | //nolint:revive 18 | const SYSTEM_LOGICAL_PROCESSOR_INFORMATION_SIZE = 24 19 | 20 | func getSystemLogicalProcessorInformationSize() int { 21 | return SYSTEM_LOGICAL_PROCESSOR_INFORMATION_SIZE 22 | } 23 | 24 | func byteArrayToProcessorStruct(data []byte) (info SYSTEM_LOGICAL_PROCESSOR_INFORMATION) { 25 | info.ProcessorMask = uintptr(binary.LittleEndian.Uint32(data)) 26 | info.Relationship = int(binary.LittleEndian.Uint32(data[4:])) 27 | copy(info.dataunion[0:16], data[8:24]) 28 | return 29 | } 30 | 31 | func computeCoresAndProcessors() (cpuInfo CPU_INFO, err error) { 32 | var mod = syscall.NewLazyDLL("kernel32.dll") 33 | var getProcInfo = mod.NewProc("GetLogicalProcessorInformation") 34 | var buflen uint32 = 0 35 | err = syscall.Errno(0) 36 | // first, figure out how much we need 37 | status, _, err := getProcInfo.Call(uintptr(0), 38 | uintptr(unsafe.Pointer(&buflen))) 39 | if status == 0 { 40 | if err != ERROR_INSUFFICIENT_BUFFER { 41 | // only error we're expecing here is insufficient buffer 42 | // anything else is a failure 43 | return 44 | } 45 | } else { 46 | // this shouldn't happen. Errno won't be set (because the function) 47 | // succeeded. So just return something to indicate we've failed 48 | err = syscall.Errno(2) 49 | return 50 | } 51 | buf := make([]byte, buflen) 52 | status, _, err = getProcInfo.Call(uintptr(unsafe.Pointer(&buf[0])), 53 | uintptr(unsafe.Pointer(&buflen))) 54 | if status == 0 { 55 | return 56 | } 57 | // walk through each of the buffers 58 | 59 | for i := 0; uint32(i) < buflen; i += getSystemLogicalProcessorInformationSize() { 60 | info := byteArrayToProcessorStruct(buf[i : i+getSystemLogicalProcessorInformationSize()]) 61 | 62 | switch info.Relationship { 63 | case RelationNumaNode: 64 | cpuInfo.numaNodeCount++ 65 | 66 | case RelationProcessorCore: 67 | cpuInfo.corecount++ 68 | cpuInfo.logicalcount += countBits(uint64(info.ProcessorMask)) 69 | 70 | case RelationProcessorPackage: 71 | cpuInfo.pkgcount++ 72 | } 73 | } 74 | return 75 | } 76 | -------------------------------------------------------------------------------- /cpu/cpu_windows_amd64.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package cpu 7 | 8 | import ( 9 | "encoding/binary" 10 | "syscall" 11 | "unsafe" 12 | ) 13 | 14 | // SYSTEM_LOGICAL_PROCESSOR_INFORMATION_SIZE is the size of 15 | // SYSTEM_LOGICAL_PROCESSOR_INFORMATION struct 16 | // 17 | //nolint:revive 18 | const SYSTEM_LOGICAL_PROCESSOR_INFORMATION_SIZE = 32 19 | 20 | //nolint:unused 21 | func getSystemLogicalProcessorInformationSize() int { 22 | return SYSTEM_LOGICAL_PROCESSOR_INFORMATION_SIZE 23 | } 24 | 25 | //nolint:unused 26 | func byteArrayToProcessorStruct(data []byte) (info SYSTEM_LOGICAL_PROCESSOR_INFORMATION) { 27 | info.ProcessorMask = uintptr(binary.LittleEndian.Uint64(data)) 28 | info.Relationship = int(binary.LittleEndian.Uint64(data[8:])) 29 | copy(info.dataunion[0:16], data[16:32]) 30 | return 31 | } 32 | 33 | func byteArrayToGroupAffinity(data []byte) (affinity GROUP_AFFINITY, consumed uint32, err error) { 34 | err = nil 35 | affinity.Mask = uintptr(binary.LittleEndian.Uint64(data)) 36 | affinity.Group = binary.LittleEndian.Uint16(data[8:]) 37 | // can skip the reserved, but count it 38 | consumed = 16 39 | return 40 | 41 | } 42 | func byteArrayToProcessorInformationExStruct(data []byte) (info SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, consumed uint32, err error) { 43 | err = nil 44 | info.Relationship = int(binary.LittleEndian.Uint32(data)) 45 | info.Size = binary.LittleEndian.Uint32(data[4:]) 46 | 47 | consumed = 8 48 | return 49 | } 50 | 51 | func byteArrayToProcessorRelationshipStruct(data []byte) (proc PROCESSOR_RELATIONSHIP, groupMask []GROUP_AFFINITY, consumed uint32, err error) { 52 | err = nil 53 | proc.Flags = data[0] 54 | proc.EfficiencyClass = data[1] 55 | proc.GroupCount = uint16(binary.LittleEndian.Uint32(data[22:])) 56 | consumed = 24 57 | if proc.GroupCount != 0 { 58 | gm := make([]GROUP_AFFINITY, proc.GroupCount) 59 | 60 | for i := uint16(0); i < proc.GroupCount; i++ { 61 | var used uint32 62 | var ga GROUP_AFFINITY 63 | ga, used, err = byteArrayToGroupAffinity(data[consumed:]) 64 | if err != nil { 65 | return 66 | } 67 | gm[i] = ga 68 | consumed += used 69 | } 70 | groupMask = gm 71 | } 72 | return 73 | } 74 | 75 | func byteArrayToNumaNode(data []byte) (numa NUMA_NODE_RELATIONSHIP, consumed uint32, err error) { 76 | numa.NodeNumber = binary.LittleEndian.Uint32(data) 77 | // skip 20 bytes of reserved 78 | consumed = 24 79 | aff, used, err := byteArrayToGroupAffinity(data[consumed:]) 80 | numa.GroupMask = aff 81 | consumed += used 82 | return 83 | } 84 | 85 | func byteArrayToRelationCache(data []byte) (cache CACHE_RELATIONSHIP, consumed uint32, err error) { 86 | cache.Level = data[0] 87 | cache.Associativity = data[1] 88 | cache.LineSize = binary.LittleEndian.Uint16(data[2:]) 89 | cache.CacheSize = binary.LittleEndian.Uint32(data[4:]) 90 | cache.CacheType = int(binary.LittleEndian.Uint32(data[8:])) 91 | // skip 20 bytes 92 | consumed = 32 93 | ga, used, err := byteArrayToGroupAffinity(data[consumed:]) 94 | cache.GroupMask = ga 95 | consumed += used 96 | return 97 | 98 | } 99 | 100 | func byteArrayToRelationGroup(data []byte) (group GROUP_RELATIONSHIP, gi []PROCESSOR_GROUP_INFO, consumed uint32, err error) { 101 | group.MaximumGroupCount = binary.LittleEndian.Uint16(data) 102 | group.ActiveGroupCount = binary.LittleEndian.Uint16(data[4:]) 103 | consumed = 24 104 | if group.ActiveGroupCount > 0 { 105 | groups := make([]PROCESSOR_GROUP_INFO, group.ActiveGroupCount) 106 | for i := uint16(0); i < group.ActiveGroupCount; i++ { 107 | groups[i].MaximumProcessorCount = data[consumed] 108 | consumed++ 109 | groups[i].ActiveProcessorCount = data[consumed] 110 | consumed++ 111 | consumed += 38 // reserved 112 | groups[i].ActiveProcessorMask = uintptr(binary.LittleEndian.Uint64(data[consumed:])) 113 | consumed += 8 114 | } 115 | } 116 | return 117 | } 118 | 119 | func computeCoresAndProcessors() (CPU_INFO, error) { 120 | var cpuInfo CPU_INFO 121 | var mod = syscall.NewLazyDLL("kernel32.dll") 122 | var getProcInfo = mod.NewProc("GetLogicalProcessorInformationEx") 123 | var buflen uint32 124 | 125 | // first, figure out how much we need 126 | status, _, callErr := getProcInfo.Call(uintptr(0xFFFF), // all relationships. 127 | uintptr(0), 128 | uintptr(unsafe.Pointer(&buflen))) 129 | if status == 0 { 130 | if callErr != ERROR_INSUFFICIENT_BUFFER { 131 | // only error we're expecing here is insufficient buffer 132 | // anything else is a failure 133 | return cpuInfo, callErr 134 | } 135 | } else { 136 | // this shouldn't happen. Errno won't be set (because the function) 137 | // succeeded. So just return something to indicate we've failed 138 | return cpuInfo, syscall.Errno(1) 139 | } 140 | buf := make([]byte, buflen) 141 | status, _, callErr = getProcInfo.Call( 142 | uintptr(0xFFFF), // still want all relationships 143 | uintptr(unsafe.Pointer(&buf[0])), 144 | uintptr(unsafe.Pointer(&buflen))) 145 | if status == 0 { 146 | return cpuInfo, callErr 147 | } 148 | // walk through each of the buffers 149 | 150 | bufused := uint32(0) 151 | for bufused < buflen { 152 | info, used, decodeerr := byteArrayToProcessorInformationExStruct(buf[bufused:]) 153 | if decodeerr != nil { 154 | return cpuInfo, decodeerr 155 | } 156 | bufused += used 157 | if info.Size == 0 { 158 | break 159 | } 160 | switch info.Relationship { 161 | case RelationProcessorCore: 162 | core, groupMask, used, decodeerr := byteArrayToProcessorRelationshipStruct(buf[bufused:]) 163 | if decodeerr != nil { 164 | return cpuInfo, decodeerr 165 | } 166 | bufused += used 167 | cpuInfo.corecount++ 168 | for j := uint16(0); j < core.GroupCount; j++ { 169 | cpuInfo.logicalcount += countBits(uint64(groupMask[j].Mask)) 170 | } 171 | case RelationNumaNode: 172 | _, used, decodeerr := byteArrayToNumaNode(buf[bufused:]) 173 | if decodeerr != nil { 174 | return cpuInfo, decodeerr 175 | } 176 | cpuInfo.numaNodeCount++ 177 | bufused += used 178 | 179 | case RelationCache: 180 | cache, used, decodeerr := byteArrayToRelationCache(buf[bufused:]) 181 | if decodeerr != nil { 182 | return cpuInfo, decodeerr 183 | } 184 | bufused += used 185 | switch cache.Level { 186 | case 1: 187 | cpuInfo.l1CacheSize = cache.CacheSize 188 | case 2: 189 | cpuInfo.l2CacheSize = cache.CacheSize 190 | case 3: 191 | cpuInfo.l3CacheSize = cache.CacheSize 192 | } 193 | case RelationProcessorPackage: 194 | _, _, used, decodeerr := byteArrayToProcessorRelationshipStruct(buf[bufused:]) 195 | if decodeerr != nil { 196 | return cpuInfo, decodeerr 197 | } 198 | bufused += used 199 | cpuInfo.pkgcount++ 200 | 201 | case RelationGroup: 202 | group, groupInfo, used, decodeerr := byteArrayToRelationGroup(buf[bufused:]) 203 | if decodeerr != nil { 204 | return cpuInfo, decodeerr 205 | } 206 | bufused += used 207 | cpuInfo.relationGroups += int(group.MaximumGroupCount) 208 | for _, info := range groupInfo { 209 | cpuInfo.maxProcsInGroups += int(info.MaximumProcessorCount) 210 | cpuInfo.activeProcsInGroups += int(info.ActiveProcessorCount) 211 | } 212 | 213 | } 214 | } 215 | 216 | return cpuInfo, nil 217 | } 218 | -------------------------------------------------------------------------------- /cpu/from-lscpu-arm.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import sys, re, textwrap 4 | 5 | # This script reads a copy of lscpu-arm.c and outputs the various tables to be 6 | # included in cpu_linux_arm.go. Of course, this may need adjustment as other 7 | # changes are made to lscpu-arm.c. 8 | 9 | def main(): 10 | lines = iter(open(sys.argv[1], "r")) 11 | 12 | # part_lists are the lists of part numbers, keyed by name 13 | part_lists = {} 14 | 15 | # hw_implementer is the hw_implementer array 16 | hw_implementer = None 17 | 18 | try: 19 | while True: 20 | line = next(lines) 21 | mo = re.match(r"static const struct ([a-z0-9_]*) ([a-z0-9_]*)\[\] = {", line) 22 | if mo: 23 | if mo.group(1) == "id_part": 24 | part_lists[mo.group(2)] = parse_part_list(lines) 25 | if mo.group(1) == "hw_impl": 26 | hw_implementer = parse_hw_impl_list(lines) 27 | except StopIteration: 28 | pass # all finished! 29 | 30 | print(textwrap.dedent("""\ 31 | // Code generated by cpu/from-lscpu-arm.py; DO NOT EDIT. 32 | 33 | // +build linux 34 | // +build arm64 35 | 36 | package cpu 37 | 38 | // hwImpl defines values for a spcific "CPU Implementer" 39 | type hwImpl struct { 40 | // name of the implementer 41 | name string 42 | // part numbers (indexed by "CPU part") 43 | parts map[uint64]string 44 | } 45 | 46 | // hwVariant is based on hw_implementer in 47 | // https://github.com/util-linux/util-linux/blob/master/sys-utils/lscpu-arm.c 48 | // See cpu/from-lscpu-arm.py to regenerate this value. 49 | var hwVariant = map[uint64]hwImpl{""").replace(' '*8, '\t')) 50 | for hw_impl, ident, name in hw_implementer: 51 | print(f"\t{hw_impl}: hwImpl{{") 52 | print(f"\t\tname: {name},") 53 | print(f"\t\tparts: map[uint64]string{{") 54 | for k, v in part_lists[ident]: 55 | print(f"\t\t\t{k}: {v},") 56 | print(f"\t\t}},") 57 | print(f"\t}},") 58 | print(f"}}") 59 | 60 | 61 | def parse_part_list(lines): 62 | """Parse lines of the form `{ k, v },`, where k is a number and v is a string""" 63 | rv = [] 64 | while True: 65 | line = next(lines) 66 | mo = re.match(r'\s*{\s*([0-9a-fx]*),\s*(".*")\s*},.*', line) 67 | if mo: 68 | k, v = mo.group(1, 2) 69 | if k == "-1": 70 | continue 71 | rv.append((k,v)) 72 | else: 73 | return rv 74 | 75 | 76 | def parse_hw_impl_list(lines): 77 | """Parse lines of the form `{ k, ident, name },`, where k is a number, ident is 78 | an identifier, and name is a string""" 79 | rv = [] 80 | while True: 81 | line = next(lines) 82 | mo = re.match(r'\s*{\s*([0-9a-fx]*),\s*([a-z0-9_]*),\s*(".*")\s*},.*', line) 83 | if mo: 84 | k, ident, name = mo.group(1, 2, 3) 85 | if k == "-1": 86 | continue 87 | rv.append((k, ident, name)) 88 | else: 89 | return rv 90 | 91 | 92 | if __name__ == "__main__": 93 | main() 94 | -------------------------------------------------------------------------------- /cpu/lscpu_linux_arm64.go: -------------------------------------------------------------------------------- 1 | // Code generated by cpu/from-lscpu-arm.py; DO NOT EDIT. 2 | 3 | //go:build linux && arm64 4 | // +build linux,arm64 5 | 6 | package cpu 7 | 8 | // hwImpl defines values for a spcific "CPU Implementer" 9 | type hwImpl struct { 10 | // name of the implementer 11 | name string 12 | // part numbers (indexed by "CPU part") 13 | parts map[uint64]string 14 | } 15 | 16 | // hwVariant is based on hw_implementer in 17 | // 18 | // https://github.com/util-linux/util-linux/blob/master/sys-utils/lscpu-arm.c 19 | // 20 | // See from-lscpu-arm.py to regenerate this value. 21 | var hwVariant = map[uint64]hwImpl{ 22 | 0x41: hwImpl{ 23 | name: "ARM", 24 | parts: map[uint64]string{ 25 | 0x810: "ARM810", 26 | 0x920: "ARM920", 27 | 0x922: "ARM922", 28 | 0x926: "ARM926", 29 | 0x940: "ARM940", 30 | 0x946: "ARM946", 31 | 0x966: "ARM966", 32 | 0xa20: "ARM1020", 33 | 0xa22: "ARM1022", 34 | 0xa26: "ARM1026", 35 | 0xb02: "ARM11 MPCore", 36 | 0xb36: "ARM1136", 37 | 0xb56: "ARM1156", 38 | 0xb76: "ARM1176", 39 | 0xc05: "Cortex-A5", 40 | 0xc07: "Cortex-A7", 41 | 0xc08: "Cortex-A8", 42 | 0xc09: "Cortex-A9", 43 | 0xc0d: "Cortex-A17", 44 | 0xc0f: "Cortex-A15", 45 | 0xc0e: "Cortex-A17", 46 | 0xc14: "Cortex-R4", 47 | 0xc15: "Cortex-R5", 48 | 0xc17: "Cortex-R7", 49 | 0xc18: "Cortex-R8", 50 | 0xc20: "Cortex-M0", 51 | 0xc21: "Cortex-M1", 52 | 0xc23: "Cortex-M3", 53 | 0xc24: "Cortex-M4", 54 | 0xc27: "Cortex-M7", 55 | 0xc60: "Cortex-M0+", 56 | 0xd01: "Cortex-A32", 57 | 0xd03: "Cortex-A53", 58 | 0xd04: "Cortex-A35", 59 | 0xd05: "Cortex-A55", 60 | 0xd06: "Cortex-A65", 61 | 0xd07: "Cortex-A57", 62 | 0xd08: "Cortex-A72", 63 | 0xd09: "Cortex-A73", 64 | 0xd0a: "Cortex-A75", 65 | 0xd0b: "Cortex-A76", 66 | 0xd0c: "Neoverse-N1", 67 | 0xd0d: "Cortex-A77", 68 | 0xd0e: "Cortex-A76AE", 69 | 0xd13: "Cortex-R52", 70 | 0xd20: "Cortex-M23", 71 | 0xd21: "Cortex-M33", 72 | 0xd40: "Neoverse-V1", 73 | 0xd41: "Cortex-A78", 74 | 0xd42: "Cortex-A78AE", 75 | 0xd44: "Cortex-X1", 76 | 0xd46: "Cortex-A510", 77 | 0xd47: "Cortex-A710", 78 | 0xd48: "Cortex-X2", 79 | 0xd49: "Neoverse-N2", 80 | 0xd4a: "Neoverse-E1", 81 | 0xd4b: "Cortex-A78C", 82 | 0xd4d: "Cortex-A715", 83 | 0xd4e: "Cortex-X3", 84 | }, 85 | }, 86 | 0x42: hwImpl{ 87 | name: "Broadcom", 88 | parts: map[uint64]string{ 89 | 0x0f: "Brahma B15", 90 | 0x100: "Brahma B53", 91 | 0x516: "ThunderX2", 92 | }, 93 | }, 94 | 0x43: hwImpl{ 95 | name: "Cavium", 96 | parts: map[uint64]string{ 97 | 0x0a0: "ThunderX", 98 | 0x0a1: "ThunderX 88XX", 99 | 0x0a2: "ThunderX 81XX", 100 | 0x0a3: "ThunderX 83XX", 101 | 0x0af: "ThunderX2 99xx", 102 | }, 103 | }, 104 | 0x44: hwImpl{ 105 | name: "DEC", 106 | parts: map[uint64]string{ 107 | 0xa10: "SA110", 108 | 0xa11: "SA1100", 109 | }, 110 | }, 111 | 0x46: hwImpl{ 112 | name: "FUJITSU", 113 | parts: map[uint64]string{ 114 | 0x001: "A64FX", 115 | }, 116 | }, 117 | 0x48: hwImpl{ 118 | name: "HiSilicon", 119 | parts: map[uint64]string{ 120 | 0xd01: "Kunpeng-920", 121 | }, 122 | }, 123 | 0x49: hwImpl{ 124 | name: "Infineon", 125 | parts: map[uint64]string{}, 126 | }, 127 | 0x4d: hwImpl{ 128 | name: "Motorola/Freescale", 129 | parts: map[uint64]string{}, 130 | }, 131 | 0x4e: hwImpl{ 132 | name: "NVIDIA", 133 | parts: map[uint64]string{ 134 | 0x000: "Denver", 135 | 0x003: "Denver 2", 136 | 0x004: "Carmel", 137 | }, 138 | }, 139 | 0x50: hwImpl{ 140 | name: "APM", 141 | parts: map[uint64]string{ 142 | 0x000: "X-Gene", 143 | }, 144 | }, 145 | 0x51: hwImpl{ 146 | name: "Qualcomm", 147 | parts: map[uint64]string{ 148 | 0x00f: "Scorpion", 149 | 0x02d: "Scorpion", 150 | 0x04d: "Krait", 151 | 0x06f: "Krait", 152 | 0x201: "Kryo", 153 | 0x205: "Kryo", 154 | 0x211: "Kryo", 155 | 0x800: "Falkor V1/Kryo", 156 | 0x801: "Kryo V2", 157 | 0x803: "Kryo 3XX Silver", 158 | 0x804: "Kryo 4XX Gold", 159 | 0x805: "Kryo 4XX Silver", 160 | 0xc00: "Falkor", 161 | 0xc01: "Saphira", 162 | }, 163 | }, 164 | 0x53: hwImpl{ 165 | name: "Samsung", 166 | parts: map[uint64]string{ 167 | 0x001: "exynos-m1", 168 | }, 169 | }, 170 | 0x56: hwImpl{ 171 | name: "Marvell", 172 | parts: map[uint64]string{ 173 | 0x131: "Feroceon 88FR131", 174 | 0x581: "PJ4/PJ4b", 175 | 0x584: "PJ4B-MP", 176 | }, 177 | }, 178 | 0x61: hwImpl{ 179 | name: "Apple", 180 | parts: map[uint64]string{ 181 | 0x020: "Icestorm-T8101", 182 | 0x021: "Firestorm-T8101", 183 | 0x022: "Icestorm-T8103", 184 | 0x023: "Firestorm-T8103", 185 | 0x030: "Blizzard-T8110", 186 | 0x031: "Avalanche-T8110", 187 | 0x032: "Blizzard-T8112", 188 | 0x033: "Avalanche-T8112", 189 | }, 190 | }, 191 | 0x66: hwImpl{ 192 | name: "Faraday", 193 | parts: map[uint64]string{ 194 | 0x526: "FA526", 195 | 0x626: "FA626", 196 | }, 197 | }, 198 | 0x69: hwImpl{ 199 | name: "Intel", 200 | parts: map[uint64]string{ 201 | 0x200: "i80200", 202 | 0x210: "PXA250A", 203 | 0x212: "PXA210A", 204 | 0x242: "i80321-400", 205 | 0x243: "i80321-600", 206 | 0x290: "PXA250B/PXA26x", 207 | 0x292: "PXA210B", 208 | 0x2c2: "i80321-400-B0", 209 | 0x2c3: "i80321-600-B0", 210 | 0x2d0: "PXA250C/PXA255/PXA26x", 211 | 0x2d2: "PXA210C", 212 | 0x411: "PXA27x", 213 | 0x41c: "IPX425-533", 214 | 0x41d: "IPX425-400", 215 | 0x41f: "IPX425-266", 216 | 0x682: "PXA32x", 217 | 0x683: "PXA930/PXA935", 218 | 0x688: "PXA30x", 219 | 0x689: "PXA31x", 220 | 0xb11: "SA1110", 221 | 0xc12: "IPX1200", 222 | }, 223 | }, 224 | 0x70: hwImpl{ 225 | name: "Phytium", 226 | parts: map[uint64]string{ 227 | 0x660: "FTC660", 228 | 0x661: "FTC661", 229 | 0x662: "FTC662", 230 | 0x663: "FTC663", 231 | }, 232 | }, 233 | 0xc0: hwImpl{ 234 | name: "Ampere", 235 | parts: map[uint64]string{}, 236 | }, 237 | } 238 | -------------------------------------------------------------------------------- /cpu/util_linux.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package cpu 7 | 8 | import ( 9 | "bufio" 10 | "io/ioutil" 11 | "os" 12 | "regexp" 13 | "strconv" 14 | "strings" 15 | ) 16 | 17 | var prefix = "" // only used for testing 18 | var listRangeRegex = regexp.MustCompile("([0-9]+)-([0-9]+)$") 19 | 20 | // sysCpuInt reads an integer from a file in /sys/devices/system/cpu 21 | func sysCpuInt(path string) (uint64, bool) { 22 | content, err := ioutil.ReadFile(prefix + "/sys/devices/system/cpu/" + path) 23 | if err != nil { 24 | return 0, false 25 | } 26 | 27 | value, err := strconv.ParseUint(strings.TrimSpace(string(content)), 0, 64) 28 | if err != nil { 29 | return 0, false 30 | } 31 | 32 | return value, true 33 | } 34 | 35 | // sysCpuSize reads an value with a K/M/G suffix from a file in /sys/devices/system/cpu 36 | func sysCpuSize(path string) (uint64, bool) { 37 | content, err := ioutil.ReadFile(prefix + "/sys/devices/system/cpu/" + path) 38 | if err != nil { 39 | return 0, false 40 | } 41 | 42 | s := strings.TrimSpace(string(content)) 43 | mult := uint64(1) 44 | switch s[len(s)-1] { 45 | case 'K': 46 | mult = 1024 47 | case 'M': 48 | mult = 1024 * 1024 49 | case 'G': 50 | mult = 1024 * 1024 * 1024 51 | } 52 | if mult > 1 { 53 | s = s[:len(s)-1] 54 | } 55 | 56 | value, err := strconv.ParseUint(s, 0, 64) 57 | if err != nil { 58 | return 0, false 59 | } 60 | 61 | return value * mult, true 62 | } 63 | 64 | // sysCpuList reads a list of integers, comma-seprated with ranges (`0-5,7-11`) 65 | // from a file in /sys/devices/system/cpu. The return value is the set of 66 | // integers included in the list (for the example above, {0, 1, 2, 3, 4, 5, 7, 67 | // 8, 9, 10, 11}). 68 | func sysCpuList(path string) (map[uint64]struct{}, bool) { 69 | content, err := ioutil.ReadFile(prefix + "/sys/devices/system/cpu/" + path) 70 | if err != nil { 71 | return nil, false 72 | } 73 | 74 | result := map[uint64]struct{}{} 75 | contentStr := strings.TrimSpace(string(content)) 76 | if len(contentStr) == 0 { 77 | return result, true 78 | } 79 | 80 | for _, elt := range strings.Split(contentStr, ",") { 81 | if submatches := listRangeRegex.FindStringSubmatch(elt); submatches != nil { 82 | // Handle the NN-NN form, inserting each included integer into the set 83 | first, err := strconv.ParseUint(submatches[1], 0, 64) 84 | if err != nil { 85 | return nil, false 86 | } 87 | last, err := strconv.ParseUint(submatches[2], 0, 64) 88 | if err != nil { 89 | return nil, false 90 | } 91 | for i := first; i <= last; i++ { 92 | result[i] = struct{}{} 93 | } 94 | } else { 95 | // Handle a simple integer, just inserting it into the set 96 | i, err := strconv.ParseUint(elt, 0, 64) 97 | if err != nil { 98 | return nil, false 99 | } 100 | result[i] = struct{}{} 101 | } 102 | } 103 | 104 | return result, true 105 | } 106 | 107 | // readProcCpuInfo reads /proc/cpuinfo. The file is structured as a set of 108 | // blank-line-separated stanzas, and each stanza is a map of string to string, 109 | // with whitespace stripped. 110 | func readProcCpuInfo() ([]map[string]string, error) { 111 | file, err := os.Open(prefix + "/proc/cpuinfo") 112 | if err != nil { 113 | return nil, err 114 | } 115 | defer file.Close() 116 | 117 | var stanzas []map[string]string 118 | var stanza map[string]string 119 | 120 | scanner := bufio.NewScanner(file) 121 | 122 | for scanner.Scan() { 123 | line := strings.TrimSpace(scanner.Text()) 124 | if len(line) == 0 { 125 | stanza = nil 126 | continue 127 | } 128 | 129 | pair := strings.SplitN(line, ":", 2) 130 | if len(pair) != 2 { 131 | continue 132 | } 133 | if stanza == nil { 134 | stanza = make(map[string]string) 135 | stanzas = append(stanzas, stanza) 136 | } 137 | stanza[strings.TrimSpace(pair[0])] = strings.TrimSpace(pair[1]) 138 | } 139 | 140 | if scanner.Err() != nil { 141 | err = scanner.Err() 142 | return nil, err 143 | } 144 | 145 | // On some platforms, such as rPi, there are stanzas in this file that do 146 | // not correspond to processors. It doesn't seem this file is intended for 147 | // machine consumption! So, we filter those out. 148 | var results []map[string]string 149 | for _, stanza := range stanzas { 150 | if _, found := stanza["processor"]; !found { 151 | continue 152 | } 153 | results = append(results, stanza) 154 | } 155 | 156 | return results, nil 157 | } 158 | -------------------------------------------------------------------------------- /cpu/util_linux_test.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package cpu 7 | 8 | import ( 9 | "io/ioutil" 10 | "os" 11 | "path/filepath" 12 | "testing" 13 | 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | func TestSysCpuInt(t *testing.T) { 18 | prefix = t.TempDir() 19 | defer func() { prefix = "" }() 20 | os.MkdirAll(filepath.Join(prefix, filepath.FromSlash("sys/devices/system/cpu")), 0o777) 21 | path := filepath.Join(prefix, filepath.FromSlash("sys/devices/system/cpu/somefile")) 22 | 23 | t.Run("zero", func(t *testing.T) { 24 | ioutil.WriteFile(path, []byte("0\n"), 0o666) 25 | got, ok := sysCpuInt("somefile") 26 | require.True(t, ok) 27 | require.Equal(t, uint64(0), got) 28 | }) 29 | 30 | t.Run("dec", func(t *testing.T) { 31 | ioutil.WriteFile(path, []byte("20\n"), 0o666) 32 | got, ok := sysCpuInt("somefile") 33 | require.True(t, ok) 34 | require.Equal(t, uint64(20), got) 35 | }) 36 | 37 | t.Run("hex", func(t *testing.T) { 38 | ioutil.WriteFile(path, []byte("0x20\n"), 0o666) 39 | got, ok := sysCpuInt("somefile") 40 | require.True(t, ok) 41 | require.Equal(t, uint64(32), got) 42 | }) 43 | 44 | t.Run("invalid", func(t *testing.T) { 45 | ioutil.WriteFile(path, []byte("eleventy"), 0o666) 46 | _, ok := sysCpuInt("somefile") 47 | require.False(t, ok) 48 | }) 49 | 50 | t.Run("missing", func(t *testing.T) { 51 | _, ok := sysCpuInt("nonexistent") 52 | require.False(t, ok) 53 | }) 54 | } 55 | 56 | func TestSysCpuSize(t *testing.T) { 57 | prefix = t.TempDir() 58 | defer func() { prefix = "" }() 59 | os.MkdirAll(filepath.Join(prefix, filepath.FromSlash("sys/devices/system/cpu")), 0o777) 60 | path := filepath.Join(prefix, filepath.FromSlash("sys/devices/system/cpu/somefile")) 61 | 62 | t.Run("zero", func(t *testing.T) { 63 | ioutil.WriteFile(path, []byte("0\n"), 0o666) 64 | got, ok := sysCpuSize("somefile") 65 | require.True(t, ok) 66 | require.Equal(t, uint64(0), got) 67 | }) 68 | 69 | t.Run("no-suffix", func(t *testing.T) { 70 | ioutil.WriteFile(path, []byte("20\n"), 0o666) 71 | got, ok := sysCpuSize("somefile") 72 | require.True(t, ok) 73 | require.Equal(t, uint64(20), got) 74 | }) 75 | 76 | t.Run("K", func(t *testing.T) { 77 | ioutil.WriteFile(path, []byte("20K\n"), 0o666) 78 | got, ok := sysCpuSize("somefile") 79 | require.True(t, ok) 80 | require.Equal(t, uint64(20*1024), got) 81 | }) 82 | 83 | t.Run("M", func(t *testing.T) { 84 | ioutil.WriteFile(path, []byte("20M"), 0o666) 85 | got, ok := sysCpuSize("somefile") 86 | require.True(t, ok) 87 | require.Equal(t, uint64(20*1024*1024), got) 88 | }) 89 | 90 | t.Run("G", func(t *testing.T) { 91 | ioutil.WriteFile(path, []byte("20G"), 0o666) 92 | got, ok := sysCpuSize("somefile") 93 | require.True(t, ok) 94 | require.Equal(t, uint64(20*1024*1024*1024), got) 95 | }) 96 | 97 | t.Run("invalid", func(t *testing.T) { 98 | ioutil.WriteFile(path, []byte("eleventy"), 0o666) 99 | _, ok := sysCpuSize("somefile") 100 | require.False(t, ok) 101 | }) 102 | 103 | t.Run("missing", func(t *testing.T) { 104 | _, ok := sysCpuSize("nonexistent") 105 | require.False(t, ok) 106 | }) 107 | } 108 | 109 | func TestSysCpuList(t *testing.T) { 110 | prefix = t.TempDir() 111 | defer func() { prefix = "" }() 112 | os.MkdirAll(filepath.Join(prefix, filepath.FromSlash("sys/devices/system/cpu")), 0o777) 113 | path := filepath.Join(prefix, filepath.FromSlash("sys/devices/system/cpu/somefile")) 114 | 115 | t.Run("empty", func(t *testing.T) { 116 | ioutil.WriteFile(path, []byte("\n"), 0o666) 117 | got, ok := sysCpuList("somefile") 118 | require.True(t, ok) 119 | require.Equal(t, map[uint64]struct{}{}, got) 120 | }) 121 | 122 | t.Run("single", func(t *testing.T) { 123 | ioutil.WriteFile(path, []byte("20\n"), 0o666) 124 | got, ok := sysCpuList("somefile") 125 | require.True(t, ok) 126 | require.Equal(t, map[uint64]struct{}{20: {}}, got) 127 | }) 128 | 129 | t.Run("range", func(t *testing.T) { 130 | ioutil.WriteFile(path, []byte("5-8\n"), 0o666) 131 | got, ok := sysCpuList("somefile") 132 | require.True(t, ok) 133 | require.Equal(t, map[uint64]struct{}{ 134 | 5: {}, 135 | 6: {}, 136 | 7: {}, 137 | 8: {}, 138 | }, got) 139 | }) 140 | 141 | t.Run("combo", func(t *testing.T) { 142 | ioutil.WriteFile(path, []byte("1,5-8,10\n"), 0o666) 143 | got, ok := sysCpuList("somefile") 144 | require.True(t, ok) 145 | require.Equal(t, map[uint64]struct{}{ 146 | 1: {}, 147 | 5: {}, 148 | 6: {}, 149 | 7: {}, 150 | 8: {}, 151 | 10: {}, 152 | }, got) 153 | }) 154 | 155 | t.Run("invalid", func(t *testing.T) { 156 | ioutil.WriteFile(path, []byte("eleventy"), 0o666) 157 | _, ok := sysCpuList("somefile") 158 | require.False(t, ok) 159 | }) 160 | 161 | t.Run("missing", func(t *testing.T) { 162 | _, ok := sysCpuList("nonexistent") 163 | require.False(t, ok) 164 | }) 165 | } 166 | 167 | func TestReadProcCpuInfo(t *testing.T) { 168 | prefix = t.TempDir() 169 | defer func() { prefix = "" }() 170 | os.MkdirAll(filepath.Join(prefix, filepath.FromSlash("proc")), 0o777) 171 | path := filepath.Join(prefix, filepath.FromSlash("proc/cpuinfo")) 172 | 173 | t.Run("simple", func(t *testing.T) { 174 | ioutil.WriteFile(path, []byte(` 175 | processor: 0 176 | 177 | processor: 1 178 | `), 0o777) 179 | info, err := readProcCpuInfo() 180 | require.NoError(t, err) 181 | require.Equal(t, []map[string]string{ 182 | {"processor": "0"}, 183 | {"processor": "1"}, 184 | }, info) 185 | }) 186 | 187 | t.Run("bogus stanza", func(t *testing.T) { 188 | ioutil.WriteFile(path, []byte(` 189 | processor: 0 190 | 191 | processor: 1 192 | 193 | so much depends 194 | upon 195 | 196 | a red wheel 197 | barrow 198 | 199 | glazed with rain 200 | water 201 | 202 | beside the white 203 | chickens 204 | -- William Carlos Williams 205 | `), 0o777) 206 | info, err := readProcCpuInfo() 207 | require.NoError(t, err) 208 | require.Equal(t, []map[string]string{ 209 | {"processor": "0"}, 210 | {"processor": "1"}, 211 | }, info) 212 | }) 213 | } 214 | -------------------------------------------------------------------------------- /filesystem/filesystem.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | //go:build linux || darwin 7 | // +build linux darwin 8 | 9 | // Package filesystem returns information about available filesystems. 10 | package filesystem 11 | 12 | import ( 13 | "context" 14 | "errors" 15 | "fmt" 16 | "os/exec" 17 | "strings" 18 | "time" 19 | ) 20 | 21 | var dfCommand = "df" 22 | var dfOptions = []string{"-l", "-k"} 23 | var dfTimeout = 2 * time.Second 24 | 25 | func getFileSystemInfo() (interface{}, error) { 26 | ctx, cancel := context.WithTimeout(context.Background(), dfTimeout) 27 | defer cancel() 28 | 29 | /* Grab filesystem data from df */ 30 | cmd := exec.CommandContext(ctx, dfCommand, dfOptions...) 31 | 32 | // force output in the C locale (untranslated) so that we can recognize the headers 33 | cmd.Env = []string{"LC_ALL=C"} 34 | 35 | out, execErr := cmd.Output() 36 | var parseErr error 37 | var result []interface{} 38 | if out != nil { 39 | result, parseErr = parseDfOutput(string(out)) 40 | } 41 | 42 | // if we managed to get _any_ data, just use it, ignoring other errors 43 | if len(result) != 0 { 44 | return result, nil 45 | } 46 | 47 | // otherwise, prefer the parse error, as it is probably more detailed 48 | err := execErr 49 | if parseErr != nil { 50 | err = parseErr 51 | } 52 | if err == nil { 53 | err = errors.New("unknown error") 54 | } 55 | return nil, fmt.Errorf("df failed to collect filesystem data: %s", err) 56 | } 57 | 58 | func parseDfOutput(out string) ([]interface{}, error) { 59 | lines := strings.Split(out, "\n") 60 | if len(lines) < 2 { 61 | return nil, errors.New("no output") 62 | } 63 | var fileSystemInfo = make([]interface{}, 0, len(lines)-2) 64 | 65 | // parse the header to find the offsets for each component we need 66 | hdr := lines[0] 67 | fieldErrFunc := func(field string) error { 68 | return fmt.Errorf("could not find '%s' in `%s %s` output", 69 | field, dfCommand, strings.Join(dfOptions, " ")) 70 | } 71 | 72 | kbsizeOffset := strings.Index(hdr, "1K-blocks") 73 | if kbsizeOffset == -1 { 74 | kbsizeOffset = strings.Index(hdr, "1024-blocks") 75 | if kbsizeOffset == -1 { 76 | return nil, fieldErrFunc("`1K-blocks` or `1024-blocks`") 77 | } 78 | } 79 | 80 | mountedOnOffset := strings.Index(hdr, "Mounted on") 81 | if mountedOnOffset == -1 { 82 | return nil, fieldErrFunc("`Mounted on`") 83 | } 84 | 85 | // now parse the remaining lines using those offsets 86 | for _, line := range lines[1:] { 87 | if len(line) == 0 || len(line) < mountedOnOffset { 88 | continue 89 | } 90 | info := map[string]string{} 91 | 92 | // we assume that "Filesystem" is the leftmost field, and continues to the 93 | // beginning of "1K-blocks". 94 | info["name"] = strings.Trim(line[:kbsizeOffset], " ") 95 | 96 | // kbsize is right-aligned under "1K-blocks", so strip leading 97 | // whitespace and the discard everything after the next whitespace 98 | kbsizeAndMore := strings.TrimLeft(line[kbsizeOffset:], " ") 99 | info["kb_size"] = strings.SplitN(kbsizeAndMore, " ", 2)[0] 100 | 101 | // mounted_on is left-aligned under "Mounted on" and continues to EOL 102 | info["mounted_on"] = strings.Trim(line[mountedOnOffset:], " ") 103 | 104 | fileSystemInfo = append(fileSystemInfo, info) 105 | } 106 | return fileSystemInfo, nil 107 | } 108 | -------------------------------------------------------------------------------- /filesystem/filesystem_common.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | // Package filesystem regroups collecting information about the filesystem 7 | package filesystem 8 | 9 | // FileSystem is the Collector type of the filesystem package. 10 | type FileSystem struct{} 11 | 12 | const name = "filesystem" 13 | 14 | // Name returns the name of the package 15 | func (filesystem *FileSystem) Name() string { 16 | return name 17 | } 18 | 19 | // Collect collects the filesystem information. 20 | // Returns an object which can be converted to a JSON or an error if nothing could be collected. 21 | // Tries to collect as much information as possible. 22 | func (filesystem *FileSystem) Collect() (result interface{}, err error) { 23 | result, err = getFileSystemInfo() 24 | return 25 | } 26 | -------------------------------------------------------------------------------- /filesystem/filesystem_nix_test.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | //go:build linux || darwin 7 | // +build linux darwin 8 | 9 | package filesystem 10 | 11 | import ( 12 | "testing" 13 | "time" 14 | 15 | "github.com/stretchr/testify/require" 16 | ) 17 | 18 | func withDfCommand(t *testing.T, command ...string) { 19 | oldCommand := dfCommand 20 | oldOptions := dfOptions 21 | dfCommand = command[0] 22 | if len(command) > 1 { 23 | dfOptions = command[1:] 24 | } else { 25 | dfOptions = []string{} 26 | } 27 | t.Cleanup(func() { 28 | dfCommand = oldCommand 29 | dfOptions = oldOptions 30 | }) 31 | } 32 | 33 | func TestSlowDf(t *testing.T) { 34 | withDfCommand(t, "sleep", "5") 35 | dfTimeout = 20 * time.Millisecond // test faster 36 | defer func() { dfTimeout = 2 * time.Second }() 37 | 38 | _, err := getFileSystemInfo() 39 | require.ErrorContains(t, err, "df failed to collect filesystem data") 40 | } 41 | 42 | func TestOldMacosDf(t *testing.T) { 43 | // from https://apple.stackexchange.com/questions/263437/df-hide-ifree-iused-512-blocks-customize-column-format-dont-show-inode-info 44 | withDfCommand(t, "sh", "-c", ` 45 | echo 'Filesystem 1K-blocks Used Available Capacity iused ifree %iused Mounted on'; 46 | echo '/dev/disk0s2 975093952 719904648 254677304 74% 90052079 31834663 74% /'; 47 | echo 'devfs 368 368 0 100% 637 0 100% /dev'; 48 | echo 'map -hosts 0 0 0 100% 0 0 100% /net'; 49 | echo 'map -static 0 0 0 100% 0 0 100% /Volumes/Large'; 50 | `) 51 | 52 | out, err := getFileSystemInfo() 53 | require.NoError(t, err) 54 | require.Equal(t, []interface{}{ 55 | map[string]string{"kb_size": "975093952", "mounted_on": "/", "name": "/dev/disk0s2"}, 56 | map[string]string{"kb_size": "368", "mounted_on": "/dev", "name": "devfs"}, 57 | map[string]string{"kb_size": "0", "mounted_on": "/net", "name": "map -hosts"}, 58 | map[string]string{"kb_size": "0", "mounted_on": "/Volumes/Large", "name": "map -static"}, 59 | }, out) 60 | } 61 | 62 | func TestDfLinux(t *testing.T) { 63 | withDfCommand(t, "sh", "-c", ` 64 | echo 'Filesystem 1K-blocks Used Available Use% Mounted on'; 65 | echo '/dev/root 16197480 13252004 2929092 82% /'; 66 | echo 'devtmpfs 15381564 0 15381564 0% /dev'; 67 | echo 'tmpfs 15388388 0 15388388 0% /dev/shm'; 68 | `) 69 | 70 | out, err := getFileSystemInfo() 71 | require.NoError(t, err) 72 | require.Equal(t, []interface{}{ 73 | map[string]string{"kb_size": "16197480", "mounted_on": "/", "name": "/dev/root"}, 74 | map[string]string{"kb_size": "15381564", "mounted_on": "/dev", "name": "devtmpfs"}, 75 | map[string]string{"kb_size": "15388388", "mounted_on": "/dev/shm", "name": "tmpfs"}, 76 | }, out) 77 | } 78 | 79 | func TestDfMac(t *testing.T) { 80 | withDfCommand(t, "sh", "-c", ` 81 | echo 'Filesystem 1024-blocks Used Available Capacity iused ifree %iused Mounted on'; 82 | echo '/dev/disk1s1s1 488245288 15055192 344743840 5% 502048 3447438400 0% /'; 83 | echo '/dev/disk1s5 488245288 20 344743840 1% 2 3447438400 0% /System/Volumes/VM'; 84 | `) 85 | 86 | out, err := getFileSystemInfo() 87 | require.NoError(t, err) 88 | require.Equal(t, []interface{}{ 89 | map[string]string{"kb_size": "488245288", "mounted_on": "/", "name": "/dev/disk1s1s1"}, 90 | map[string]string{"kb_size": "488245288", "mounted_on": "/System/Volumes/VM", "name": "/dev/disk1s5"}, 91 | }, out) 92 | } 93 | 94 | func TestDfWithVolumeSpaces(t *testing.T) { 95 | withDfCommand(t, "sh", "-c", ` 96 | echo 'Filesystem 1K-blocks Used Available Use% Mounted on'; 97 | echo '/dev/disk4s3 367616 360928 6688 99% /Volumes/Firefox'; 98 | echo '/dev/disk5 307200 283136 24064 93% /Volumes/MySQL Workbench community-8.0.30'; 99 | `) 100 | 101 | out, err := getFileSystemInfo() 102 | require.NoError(t, err) 103 | require.Equal(t, []interface{}{ 104 | map[string]string{"kb_size": "367616", "mounted_on": "/Volumes/Firefox", "name": "/dev/disk4s3"}, 105 | map[string]string{"kb_size": "307200", "mounted_on": "/Volumes/MySQL Workbench community-8.0.30", "name": "/dev/disk5"}, 106 | }, out) 107 | } 108 | 109 | func TestDfWithErrors(t *testing.T) { 110 | withDfCommand(t, "sh", "-c", ` 111 | echo 'Filesystem 1K-blocks Used Available Use% Mounted on'; 112 | echo '/dev/disk4s3 367616 360928 6688 99% /Volumes/Firefox'; 113 | echo 'Some error from df'; 114 | echo '/dev/disk5 307200 283136 24064 93% /Volumes/MySQL Workbench community-8.0.30'; 115 | `) 116 | 117 | out, err := getFileSystemInfo() 118 | require.NoError(t, err) 119 | require.Equal(t, []interface{}{ 120 | map[string]string{"kb_size": "367616", "mounted_on": "/Volumes/Firefox", "name": "/dev/disk4s3"}, 121 | map[string]string{"kb_size": "307200", "mounted_on": "/Volumes/MySQL Workbench community-8.0.30", "name": "/dev/disk5"}, 122 | }, out) 123 | } 124 | 125 | func TestFaileDfWithData(t *testing.T) { 126 | // (note that this sample output is valid on both linux and darwin) 127 | withDfCommand(t, "sh", "-c", `echo "Filesystem 1K-blocks Used Available Use% Mounted on"; echo "/dev/disk1s1s1 488245288 138504332 349740956 29% /"; exit 1`) 128 | 129 | out, err := getFileSystemInfo() 130 | require.NoError(t, err) 131 | require.Equal(t, []interface{}{ 132 | map[string]string{"kb_size": "488245288", "mounted_on": "/", "name": "/dev/disk1s1s1"}, 133 | }, out) 134 | } 135 | 136 | func TestGetFileSystemInfo(t *testing.T) { 137 | out, err := getFileSystemInfo() 138 | require.NoError(t, err) 139 | outArray := out.([]interface{}) 140 | require.Greater(t, len(outArray), 0) 141 | } 142 | -------------------------------------------------------------------------------- /filesystem/filesystem_windows.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package filesystem 7 | 8 | import ( 9 | "strconv" 10 | "syscall" 11 | "unsafe" 12 | ) 13 | 14 | // Handle represents a pointer used by FindFirstVolumeW and similar functions 15 | type Handle uintptr 16 | 17 | // InvalidHandle is the value returned in case of error 18 | const InvalidHandle Handle = ^Handle(0) 19 | 20 | // ERRORMoreData is the error returned when the size is not big enough 21 | const ERRORMoreData syscall.Errno = 234 22 | 23 | // this would probably go in a common utilities rather than here 24 | 25 | func convertWindowsStringList(winput []uint16) []string { 26 | var retstrings []string 27 | var rsindex = 0 28 | 29 | retstrings = append(retstrings, "") 30 | for i := 0; i < (len(winput) - 1); i++ { 31 | if winput[i] == 0 { 32 | if winput[i+1] == 0 { 33 | return retstrings 34 | } 35 | rsindex++ 36 | retstrings = append(retstrings, "") 37 | continue 38 | } 39 | retstrings[rsindex] += string(rune(winput[i])) 40 | } 41 | return retstrings 42 | } 43 | 44 | // as would this 45 | func convertWindowsString(winput []uint16) string { 46 | var retstring string 47 | for i := 0; i < len(winput); i++ { 48 | if winput[i] == 0 { 49 | break 50 | } 51 | retstring += string(rune(winput[i])) 52 | } 53 | return retstring 54 | } 55 | 56 | func getDiskSize(vol string) (size uint64, freespace uint64) { 57 | var mod = syscall.NewLazyDLL("kernel32.dll") 58 | var getDisk = mod.NewProc("GetDiskFreeSpaceExW") 59 | var sz uint64 60 | var fr uint64 61 | 62 | volWinStr, err := syscall.UTF16PtrFromString(vol) 63 | if err != nil { 64 | return 0, 0 65 | } 66 | status, _, _ := getDisk.Call(uintptr(unsafe.Pointer(volWinStr)), 67 | uintptr(0), 68 | uintptr(unsafe.Pointer(&sz)), 69 | uintptr(unsafe.Pointer(&fr))) 70 | if status == 0 { 71 | return 0, 0 72 | } 73 | return sz, fr 74 | } 75 | 76 | func getMountPoints(vol string) []string { 77 | var mod = syscall.NewLazyDLL("kernel32.dll") 78 | var getPaths = mod.NewProc("GetVolumePathNamesForVolumeNameW") 79 | var tmp uint32 80 | var objlistsize uint32 = 0x0 81 | var retval []string 82 | 83 | volWinStr, err := syscall.UTF16PtrFromString(vol) 84 | if err != nil { 85 | return retval 86 | } 87 | status, _, errno := getPaths.Call(uintptr(unsafe.Pointer(volWinStr)), 88 | uintptr(unsafe.Pointer(&tmp)), 89 | 2, 90 | uintptr(unsafe.Pointer(&objlistsize))) 91 | 92 | if status != 0 || errno != ERRORMoreData { 93 | // unexpected 94 | return retval 95 | } 96 | 97 | buf := make([]uint16, objlistsize) 98 | status, _, _ = getPaths.Call(uintptr(unsafe.Pointer(volWinStr)), 99 | uintptr(unsafe.Pointer(&buf[0])), 100 | uintptr(objlistsize), 101 | uintptr(unsafe.Pointer(&objlistsize))) 102 | if status == 0 { 103 | return retval 104 | } 105 | return convertWindowsStringList(buf) 106 | 107 | } 108 | 109 | func getFileSystemInfo() (interface{}, error) { 110 | var mod = syscall.NewLazyDLL("kernel32.dll") 111 | var findFirst = mod.NewProc("FindFirstVolumeW") 112 | var findNext = mod.NewProc("FindNextVolumeW") 113 | var findClose = mod.NewProc("FindVolumeClose") 114 | 115 | //var findHandle Handle 116 | buf := make([]uint16, 512) 117 | var sz int32 = 512 118 | fh, _, _ := findFirst.Call(uintptr(unsafe.Pointer(&buf[0])), 119 | uintptr(sz)) 120 | var findHandle = Handle(fh) 121 | var fileSystemInfo []interface{} 122 | 123 | if findHandle != InvalidHandle { 124 | // ignore close error 125 | //nolint:errcheck 126 | defer findClose.Call(fh) 127 | moreData := true 128 | for moreData { 129 | outstring := convertWindowsString(buf) 130 | sz, _ := getDiskSize(outstring) 131 | var capacity string 132 | if 0 == sz { 133 | capacity = "Unknown" 134 | } else { 135 | capacity = strconv.FormatInt(int64(sz)/1024.0, 10) 136 | } 137 | mountpts := getMountPoints(outstring) 138 | var mountName string 139 | if len(mountpts) > 0 { 140 | mountName = mountpts[0] 141 | } 142 | iface := map[string]interface{}{ 143 | "name": outstring, 144 | "kb_size": capacity, 145 | "mounted_on": mountName, 146 | } 147 | fileSystemInfo = append(fileSystemInfo, iface) 148 | status, _, _ := findNext.Call(fh, 149 | uintptr(unsafe.Pointer(&buf[0])), 150 | uintptr(sz)) 151 | if 0 == status { 152 | moreData = false 153 | } 154 | } 155 | 156 | } 157 | 158 | return fileSystemInfo, nil 159 | } 160 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/DataDog/gohai 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/cihub/seelog v0.0.0-20151216151435-d2c6e5aa9fbf 7 | github.com/shirou/gopsutil/v3 v3.22.12 8 | github.com/stretchr/testify v1.8.2 9 | golang.org/x/sys v0.3.0 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cihub/seelog v0.0.0-20151216151435-d2c6e5aa9fbf h1:XI2tOTCBqEnMyN2j1yPBI07yQHeywUSCEf8YWqf0oKw= 2 | github.com/cihub/seelog v0.0.0-20151216151435-d2c6e5aa9fbf/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 7 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 8 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 9 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 10 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 11 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= 12 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= 13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= 16 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 17 | github.com/shirou/gopsutil/v3 v3.22.12 h1:oG0ns6poeUSxf78JtOsfygNWuEHYYz8hnnNg7P04TJs= 18 | github.com/shirou/gopsutil/v3 v3.22.12/go.mod h1:Xd7P1kwZcp5VW52+9XsirIKd/BROzbb2wdX3Kqlz9uI= 19 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 20 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 21 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 22 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 23 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 24 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 25 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= 26 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 27 | github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= 28 | github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= 29 | github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= 30 | github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= 31 | github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= 32 | github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 33 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 34 | golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 35 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 36 | golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= 37 | golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 38 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 39 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 40 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 41 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 42 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 43 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 44 | -------------------------------------------------------------------------------- /gohai.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | // Package main contains the binary related functions, 7 | // eg. cli parameters 8 | package main 9 | 10 | import ( 11 | "bytes" 12 | "encoding/json" 13 | "flag" 14 | "fmt" 15 | "os" 16 | "sort" 17 | "strings" 18 | 19 | // 3p 20 | log "github.com/cihub/seelog" 21 | 22 | // project 23 | "github.com/DataDog/gohai/cpu" 24 | "github.com/DataDog/gohai/filesystem" 25 | "github.com/DataDog/gohai/memory" 26 | "github.com/DataDog/gohai/network" 27 | "github.com/DataDog/gohai/platform" 28 | "github.com/DataDog/gohai/processes" 29 | ) 30 | 31 | // Collector represents a group of information which can be collected 32 | type Collector interface { 33 | Name() string 34 | Collect() (interface{}, error) 35 | } 36 | 37 | // SelectedCollectors represents a set of collector names 38 | type SelectedCollectors map[string]struct{} 39 | 40 | var collectors = []Collector{ 41 | &cpu.Cpu{}, 42 | &filesystem.FileSystem{}, 43 | &memory.Memory{}, 44 | &network.Network{}, 45 | &platform.Platform{}, 46 | &processes.Processes{}, 47 | } 48 | 49 | var options struct { 50 | only SelectedCollectors 51 | exclude SelectedCollectors 52 | logLevel string 53 | version bool 54 | } 55 | 56 | // version information filled in at build time 57 | var ( 58 | buildDate string 59 | gitCommit string 60 | gitBranch string 61 | goVersion string 62 | ) 63 | 64 | // Collect fills the result map with the collector information under their name key 65 | func Collect() (result map[string]interface{}, err error) { 66 | result = make(map[string]interface{}) 67 | 68 | for _, collector := range collectors { 69 | if shouldCollect(collector) { 70 | c, err := collector.Collect() 71 | if err != nil { 72 | log.Warnf("[%s] %s", collector.Name(), err) 73 | } 74 | if c != nil { 75 | result[collector.Name()] = c 76 | } 77 | } 78 | } 79 | 80 | result["gohai"] = versionMap() 81 | 82 | return 83 | } 84 | 85 | func versionMap() (result map[string]interface{}) { 86 | result = make(map[string]interface{}) 87 | 88 | result["git_hash"] = gitCommit 89 | result["git_branch"] = gitBranch 90 | result["build_date"] = buildDate 91 | result["go_version"] = goVersion 92 | 93 | return 94 | } 95 | 96 | func versionString() string { 97 | var buf bytes.Buffer 98 | 99 | if gitCommit != "" { 100 | fmt.Fprintf(&buf, "Git hash: %s\n", gitCommit) 101 | } 102 | if gitBranch != "" { 103 | fmt.Fprintf(&buf, "Git branch: %s\n", gitBranch) 104 | } 105 | if buildDate != "" { 106 | fmt.Fprintf(&buf, "Build date: %s\n", buildDate) 107 | } 108 | if goVersion != "" { 109 | fmt.Fprintf(&buf, "Go Version: %s\n", goVersion) 110 | } 111 | 112 | return buf.String() 113 | } 114 | 115 | // Implement the flag.Value interface 116 | func (sc *SelectedCollectors) String() string { 117 | collectorSlice := make([]string, 0, len(*sc)) 118 | for collectorName := range *sc { 119 | collectorSlice = append(collectorSlice, collectorName) 120 | } 121 | sort.Strings(collectorSlice) 122 | return fmt.Sprint(collectorSlice) 123 | } 124 | 125 | // Set adds the given comma-separated list of collector names to the selected set. 126 | func (sc *SelectedCollectors) Set(value string) error { 127 | for _, collectorName := range strings.Split(value, ",") { 128 | (*sc)[collectorName] = struct{}{} 129 | } 130 | return nil 131 | } 132 | 133 | // Return whether we should collect on a given collector, depending on the parsed flags 134 | func shouldCollect(collector Collector) bool { 135 | if _, ok := options.only[collector.Name()]; len(options.only) > 0 && !ok { 136 | return false 137 | } 138 | 139 | if _, ok := options.exclude[collector.Name()]; ok { 140 | return false 141 | } 142 | 143 | return true 144 | } 145 | 146 | // Will be called after all the imported packages' init() have been called 147 | // Define collector-specific flags in their packages' init() function 148 | func init() { 149 | options.only = make(SelectedCollectors) 150 | options.exclude = make(SelectedCollectors) 151 | 152 | flag.BoolVar(&options.version, "version", false, "Show version information and exit") 153 | flag.Var(&options.only, "only", "Run only the listed collectors (comma-separated list of collector names)") 154 | flag.Var(&options.exclude, "exclude", "Run all the collectors except those listed (comma-separated list of collector names)") 155 | flag.StringVar(&options.logLevel, "log-level", "info", "Log level (one of 'warn', 'info', 'debug')") 156 | } 157 | 158 | func main() { 159 | defer log.Flush() 160 | 161 | flag.Parse() 162 | 163 | err := initLogging(options.logLevel) 164 | if err != nil { 165 | panic(fmt.Sprintf("Unable to initialize logger: %s", err)) 166 | } 167 | 168 | if options.version { 169 | fmt.Printf("%s", versionString()) 170 | os.Exit(0) 171 | } 172 | 173 | gohai, err := Collect() 174 | 175 | if err != nil { 176 | panic(err) 177 | } 178 | 179 | buf, err := json.Marshal(gohai) 180 | 181 | if err != nil { 182 | panic(err) 183 | } 184 | 185 | os.Stdout.Write(buf) 186 | } 187 | -------------------------------------------------------------------------------- /gohai_test.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package main 7 | 8 | import ( 9 | "encoding/json" 10 | "net" 11 | "runtime" 12 | "testing" 13 | 14 | "github.com/stretchr/testify/assert" 15 | ) 16 | 17 | func TestSelectedCollectors_String(t *testing.T) { 18 | sc := &SelectedCollectors{ 19 | "foo": struct{}{}, 20 | "bar": struct{}{}, 21 | } 22 | assert.Equal(t, "[bar foo]", sc.String()) 23 | } 24 | 25 | // gohaiPayload defines the format we expect the gohai information 26 | // to be in. 27 | // Any change to this datastructure should be notified to the backend 28 | // team to ensure compatibility. 29 | type gohaiPayload struct { 30 | CPU struct { 31 | CPUCores string `json:"cpu_cores"` 32 | CPULogicalProcessors string `json:"cpu_logical_processors"` 33 | Family string `json:"family"` 34 | Mhz string `json:"mhz"` 35 | Model string `json:"model"` 36 | ModelName string `json:"model_name"` 37 | Stepping string `json:"stepping"` 38 | VendorID string `json:"vendor_id"` 39 | 40 | // CacheSize is only reported on Linux 41 | CacheSize string `json:"cache_size"` 42 | 43 | // On Windows, we report additional fields 44 | CacheSizeL1 string `json:"cache_size_l1"` 45 | CacheSizeL2 string `json:"cache_size_l2"` 46 | CacheSizeL3 string `json:"cache_size_l3"` 47 | CPUNumaNodes string `json:"cpu_numa_nodes"` 48 | CPUPkgs string `json:"cpu_pkgs"` 49 | } `json:"cpu"` 50 | Filesystem []struct { 51 | KbSize string `json:"kb_size"` 52 | // MountedOn can be empty on Windows 53 | MountedOn string `json:"mounted_on"` 54 | Name string `json:"name"` 55 | } `json:"filesystem"` 56 | Memory struct { 57 | // SwapTotal is not reported on Windows 58 | SwapTotal string `json:"swap_total"` 59 | Total string `json:"total"` 60 | } `json:"memory"` 61 | Network struct { 62 | Interfaces []struct { 63 | Ipv4 []string `json:"ipv4"` 64 | Ipv6 []string `json:"ipv6"` 65 | Ipv6Network string `json:"ipv6-network"` 66 | Macaddress string `json:"macaddress"` 67 | Name string `json:"name"` 68 | Ipv4Network string `json:"ipv4-network"` 69 | } `json:"interfaces"` 70 | Ipaddress string `json:"ipaddress"` 71 | Ipaddressv6 string `json:"ipaddressv6"` 72 | Macaddress string `json:"macaddress"` 73 | } `json:"network"` 74 | Platform struct { 75 | Gooarch string `json:"GOOARCH"` 76 | Goos string `json:"GOOS"` 77 | GoV string `json:"goV"` 78 | Hostname string `json:"hostname"` 79 | KernelName string `json:"kernel_name"` 80 | KernelRelease string `json:"kernel_release"` 81 | // KernelVersion is not reported on Windows 82 | KernelVersion string `json:"kernel_version"` 83 | Machine string `json:"machine"` 84 | Os string `json:"os"` 85 | Processor string `json:"processor"` 86 | // On Windows, we report additional fields 87 | Family string `json:"family"` 88 | } `json:"platform"` 89 | } 90 | 91 | func TestGohaiSerialization(t *testing.T) { 92 | gohai, err := Collect() 93 | 94 | assert.NoError(t, err) 95 | 96 | gohaiJSON, err := json.Marshal(gohai) 97 | assert.NoError(t, err) 98 | 99 | var payload gohaiPayload 100 | assert.NoError(t, json.Unmarshal(gohaiJSON, &payload)) 101 | 102 | assert.NotEmpty(t, payload.CPU.CPUCores) 103 | assert.NotEmpty(t, payload.CPU.CPULogicalProcessors) 104 | assert.NotEmpty(t, payload.CPU.Family) 105 | if runtime.GOARCH != "arm64" { 106 | // (Mhz is not defined on ARM64) 107 | assert.NotEmpty(t, payload.CPU.Mhz) 108 | } 109 | assert.NotEmpty(t, payload.CPU.Model) 110 | assert.NotEmpty(t, payload.CPU.ModelName) 111 | assert.NotEmpty(t, payload.CPU.Stepping) 112 | assert.NotEmpty(t, payload.CPU.VendorID) 113 | 114 | if runtime.GOOS == "windows" { 115 | // Additional fields that we report on Windows 116 | assert.NotEmpty(t, payload.CPU.CacheSizeL1) 117 | assert.NotEmpty(t, payload.CPU.CacheSizeL2) 118 | assert.NotEmpty(t, payload.CPU.CacheSizeL3) 119 | assert.NotEmpty(t, payload.CPU.CPUNumaNodes) 120 | assert.NotEmpty(t, payload.CPU.CPUPkgs) 121 | } 122 | 123 | if assert.NotEmpty(t, payload.Filesystem) { 124 | if runtime.GOOS != "windows" { 125 | // On Windows, MountedOn can be empty 126 | assert.NotEmpty(t, payload.Filesystem[0].MountedOn, 0) 127 | } 128 | assert.NotEmpty(t, payload.Filesystem[0].KbSize, 0) 129 | assert.NotEmpty(t, payload.Filesystem[0].Name, 0) 130 | } 131 | if runtime.GOOS != "windows" { 132 | // Not reported on Windows 133 | assert.NotEmpty(t, payload.Memory.SwapTotal) 134 | } 135 | assert.NotEmpty(t, payload.Memory.Total) 136 | 137 | if assert.NotEmpty(t, payload.Network.Interfaces) { 138 | for _, itf := range payload.Network.Interfaces { 139 | assert.NotEmpty(t, itf.Name) 140 | // Some interfaces don't have MacAddresses 141 | //assert.NotEmpty(t, itf.Macaddress) 142 | 143 | if len(itf.Ipv4) == 0 && len(itf.Ipv6) == 0 { 144 | // Disabled interfaces won't have any IP address 145 | continue 146 | } 147 | if len(itf.Ipv4) == 0 { 148 | assert.NotEmpty(t, itf.Ipv6) 149 | assert.NotEmpty(t, itf.Ipv6Network) 150 | for _, ip := range itf.Ipv6 { 151 | assert.NotNil(t, net.ParseIP(ip)) 152 | } 153 | } else { 154 | assert.NotEmpty(t, itf.Ipv4) 155 | assert.NotEmpty(t, itf.Ipv4Network) 156 | for _, ip := range itf.Ipv4 { 157 | assert.NotNil(t, net.ParseIP(ip)) 158 | } 159 | } 160 | } 161 | } 162 | assert.NotEmpty(t, payload.Network.Ipaddress) 163 | assert.NotNil(t, net.ParseIP(payload.Network.Ipaddress)) 164 | // Ipaddressv6 *can* be empty 165 | // assert.NotEmpty(t, payload.Network.Ipaddressv6) 166 | assert.NotEmpty(t, payload.Network.Macaddress) 167 | 168 | assert.NotEmpty(t, payload.Platform.Gooarch) 169 | assert.NotEmpty(t, payload.Platform.Goos) 170 | assert.NotEmpty(t, payload.Platform.GoV) 171 | assert.NotEmpty(t, payload.Platform.Hostname) 172 | assert.NotEmpty(t, payload.Platform.KernelName) 173 | assert.NotEmpty(t, payload.Platform.KernelRelease) 174 | assert.NotEmpty(t, payload.Platform.Machine) 175 | assert.NotEmpty(t, payload.Platform.Os) 176 | if runtime.GOOS != "windows" { 177 | // Not reported on Windows 178 | assert.NotEmpty(t, payload.Platform.KernelVersion) 179 | assert.NotEmpty(t, payload.Platform.Processor) 180 | } else { 181 | // Additional fields that we report on Windows 182 | assert.NotEmpty(t, payload.Platform.Family) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package main 7 | 8 | import ( 9 | "bytes" 10 | "fmt" 11 | "os" 12 | 13 | log "github.com/cihub/seelog" 14 | ) 15 | 16 | const baseStdErrLogConfig = ` 17 | 18 | 19 | 20 | 21 | 22 | 23 | ` 24 | 25 | // StdErrReceiver is a dummy receiver used to log to stderr instead of stdout. 26 | // See seelog.CustomReceiver. 27 | type StdErrReceiver struct{} 28 | 29 | // ReceiveMessage is called when the custom receiver gets seelog message from 30 | // a parent dispatcher. 31 | // See seelog.CustomReceiver. 32 | func (sr *StdErrReceiver) ReceiveMessage(message string, _ log.LogLevel, _ log.LogContextInterface) error { 33 | fmt.Fprint(os.Stderr, message) 34 | return nil 35 | } 36 | 37 | // AfterParse is called immediately after your custom receiver is instantiated by 38 | // the xml config parser. 39 | // See seelog.CustomReceiver. 40 | func (sr *StdErrReceiver) AfterParse(_ log.CustomReceiverInitArgs) error { 41 | return nil 42 | } 43 | 44 | // Flush is called when the custom receiver gets a 'flush' directive from a 45 | // parent receiver. 46 | // See seelog.CustomReceiver. 47 | func (sr *StdErrReceiver) Flush() {} 48 | 49 | // Close is called when the custom receiver gets a 'close' directive from a 50 | // parent receiver. 51 | // See seelog.CustomReceiver. 52 | func (sr *StdErrReceiver) Close() error { 53 | return nil 54 | } 55 | 56 | func initLogging(logLevel string) error { 57 | log.RegisterReceiver("stderr", &StdErrReceiver{}) 58 | 59 | logConfig := bytes.Replace([]byte(baseStdErrLogConfig), []byte("loglevel"), []byte(logLevel), 1) 60 | logger, err := log.LoggerFromConfigAsBytes(logConfig) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | log.ReplaceLogger(logger) 66 | 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /make.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | //go:build ignore 7 | // +build ignore 8 | 9 | package main 10 | 11 | import ( 12 | "fmt" 13 | "log" 14 | "os" 15 | "os/exec" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | func commandOutput(name string, args ...string) string { 21 | out, err := exec.Command(name, args...).Output() 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | 26 | return strings.TrimSpace(string(out)) 27 | } 28 | 29 | func main() { 30 | gobin := "go" 31 | if len(os.Args) > 1 { 32 | gobin = os.Args[1] 33 | } 34 | date := time.Now().Format(time.RFC3339) 35 | commit := commandOutput("git", "rev-parse", "--short", "HEAD") 36 | branch := commandOutput("git", "rev-parse", "--abbrev-ref", "HEAD") 37 | version := commandOutput(gobin, "version") 38 | 39 | // expected go_version output: go version go1.9.2 darwin/amd64 40 | versionRune := []rune(strings.Split(version, " ")[2]) 41 | goVersion := string(versionRune[2:]) 42 | 43 | var ldflags string 44 | ldflags = fmt.Sprintf("-X main.buildDate=%s -X main.gitCommit=%s -X main.gitBranch=%s -X main.goVersion=%s", date, commit, branch, goVersion) 45 | 46 | cmd := exec.Command(gobin, []string{"build", "-a", "-ldflags", ldflags}...) 47 | cmd.Env = os.Environ() 48 | cmd.Stdout = os.Stdout 49 | cmd.Stderr = os.Stderr 50 | err := cmd.Run() 51 | if err != nil { 52 | os.Exit(1) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /memory/memory.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | // Package memory regroups collecting information about the memory 7 | package memory 8 | 9 | // Memory holds memory metadata about the host 10 | type Memory struct { 11 | // TotalBytes is the total memory for the host in byte 12 | TotalBytes uint64 13 | // SwapTotalBytes is the swap memory size in byte (Unix only) 14 | SwapTotalBytes uint64 15 | } 16 | 17 | const name = "memory" 18 | 19 | // Name returns the name of the package 20 | func (memory *Memory) Name() string { 21 | return name 22 | } 23 | 24 | // Collect collects the Memory information. 25 | // Returns an object which can be converted to a JSON or an error if nothing could be collected. 26 | // Tries to collect as much information as possible. 27 | func (memory *Memory) Collect() (result interface{}, err error) { 28 | result, err = getMemoryInfo() 29 | return 30 | } 31 | 32 | // Get returns a Memory struct already initialized, a list of warnings and an error. The method will try to collect as much 33 | // metadata as possible, an error is returned if nothing could be collected. The list of warnings contains errors if 34 | // some metadata could not be collected. 35 | func Get() (*Memory, []string, error) { 36 | // Legacy code from gohai returns memory in: 37 | // - byte for Windows 38 | // - mix of byte and MB for OSX 39 | // - KB on linux 40 | // 41 | // this method being new we can align this behavior to return bytes everywhere without breaking backward 42 | // compatibility 43 | 44 | mem, swap, warnings, err := getMemoryInfoByte() 45 | if err != nil { 46 | return nil, nil, err 47 | } 48 | 49 | return &Memory{ 50 | TotalBytes: mem, 51 | SwapTotalBytes: swap, 52 | }, warnings, nil 53 | } 54 | -------------------------------------------------------------------------------- /memory/memory_darwin.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package memory 7 | 8 | import ( 9 | "fmt" 10 | "os/exec" 11 | "regexp" 12 | "strconv" 13 | "strings" 14 | ) 15 | 16 | func getMemoryInfo() (memoryInfo map[string]string, err error) { 17 | memoryInfo = make(map[string]string) 18 | 19 | out, err := exec.Command("sysctl", "-n", "hw.memsize").Output() 20 | if err == nil { 21 | memoryInfo["total"] = strings.Trim(string(out), "\n") 22 | } 23 | 24 | out, err = exec.Command("sysctl", "-n", "vm.swapusage").Output() 25 | if err == nil { 26 | swap := regexp.MustCompile("total = ").Split(string(out), 2)[1] 27 | memoryInfo["swap_total"] = strings.Split(swap, " ")[0] 28 | } 29 | 30 | return 31 | } 32 | 33 | func getMemoryInfoByte() (uint64, uint64, []string, error) { 34 | memInfo, err := getMemoryInfo() 35 | var mem, swap uint64 36 | warnings := []string{} 37 | 38 | // mem is already in bytes but `swap_total` use the format "5120,00M" 39 | if v, ok := memInfo["swap_total"]; ok { 40 | idx := strings.IndexAny(v, ",.") // depending on the locale either a comma or dot is used 41 | swapTotal, e := strconv.ParseUint(v[0:idx], 10, 64) 42 | if e == nil { 43 | swap = swapTotal * 1024 * 1024 // swapTotal is in mb 44 | } else { 45 | warnings = append(warnings, fmt.Sprintf("could not parse swap size: %s", e)) 46 | } 47 | } 48 | 49 | if v, ok := memInfo["total"]; ok { 50 | t, e := strconv.ParseUint(v, 10, 64) 51 | if e == nil { 52 | mem = t // mem is returned in bytes 53 | } else { 54 | warnings = append(warnings, fmt.Sprintf("could not parse memory size: %s", e)) 55 | } 56 | } 57 | 58 | return mem, swap, warnings, err 59 | } 60 | -------------------------------------------------------------------------------- /memory/memory_linux.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package memory 7 | 8 | import ( 9 | "bufio" 10 | "fmt" 11 | "os" 12 | "regexp" 13 | "strconv" 14 | "strings" 15 | 16 | "github.com/DataDog/gohai/utils" 17 | ) 18 | 19 | var memMap = map[string]string{ 20 | "MemTotal": "total", 21 | "SwapTotal": "swap_total", 22 | } 23 | 24 | func getMemoryInfo() (memoryInfo map[string]string, err error) { 25 | file, err := os.Open("/proc/meminfo") 26 | 27 | if err != nil { 28 | return 29 | } 30 | 31 | var lines []string 32 | scanner := bufio.NewScanner(file) 33 | 34 | for scanner.Scan() { 35 | lines = append(lines, scanner.Text()) 36 | } 37 | 38 | if scanner.Err() != nil { 39 | err = scanner.Err() 40 | return 41 | } 42 | 43 | memoryInfo = make(map[string]string) 44 | 45 | for _, line := range lines { 46 | pair := regexp.MustCompile(": +").Split(line, 2) 47 | values := regexp.MustCompile(" +").Split(pair[1], 2) 48 | 49 | key, ok := memMap[pair[0]] 50 | if ok { 51 | memoryInfo[key] = fmt.Sprintf("%s%s", values[0], values[1]) 52 | } 53 | } 54 | 55 | return 56 | } 57 | 58 | func getMemoryInfoByte() (mem uint64, swap uint64, warnings []string, err error) { 59 | memInfo, err := getMemoryInfo() 60 | if err != nil { 61 | return 62 | } 63 | 64 | memString := strings.TrimSuffix(strings.ToLower(utils.GetString(memInfo, "total")), "kb") 65 | swapString := strings.TrimSuffix(strings.ToLower(utils.GetString(memInfo, "swap_total")), "kb") 66 | 67 | t, e := strconv.ParseUint(memString, 10, 64) 68 | if e == nil { 69 | mem = t * 1024 // getMemoryInfo return values in KB 70 | } else { 71 | warnings = append(warnings, fmt.Sprintf("could not parse memory size: %s", e)) 72 | } 73 | 74 | s, e := strconv.ParseUint(swapString, 10, 64) 75 | if e == nil { 76 | swap = s * 1024 // getMemoryInfo return values in KB 77 | } else { 78 | warnings = append(warnings, fmt.Sprintf("could not parse swap size: %s", e)) 79 | } 80 | 81 | return mem, swap, warnings, err 82 | } 83 | -------------------------------------------------------------------------------- /memory/memory_windows.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package memory 7 | 8 | import ( 9 | "strconv" 10 | "syscall" 11 | "unsafe" 12 | ) 13 | 14 | // MEMORYSTATUSEX is the type of the struct expected by GlobalMemoryStatusEx 15 | // 16 | //nolint:revive 17 | type MEMORYSTATUSEX struct { 18 | dwLength uint32 // size of this structure 19 | dwMemoryLoad uint32 // number 0-100 estimating %age of memory in use 20 | ulTotalPhys uint64 // amount of physical memory 21 | ulAvailPhys uint64 // amount of physical memory that can be used w/o flush to disk 22 | ulTotalPageFile uint64 // current commit limit for system or process 23 | ulAvailPageFile uint64 // amount of memory current process can commit 24 | ulTotalVirtual uint64 // size of user-mode portion of VA space 25 | ulAvailVirtual uint64 // amount of unreserved/uncommitted memory in ulTotalVirtual 26 | ulAvailExtendedVirtual uint64 // reserved (always zero) 27 | } 28 | 29 | func getMemoryInfo() (memoryInfo map[string]string, err error) { 30 | memoryInfo = make(map[string]string) 31 | 32 | mem, _, _, err := getMemoryInfoByte() 33 | if err == nil { 34 | memoryInfo["total"] = strconv.FormatUint(mem, 10) 35 | } 36 | return 37 | } 38 | 39 | func getMemoryInfoByte() (mem uint64, swap uint64, warnings []string, err error) { 40 | var mod = syscall.NewLazyDLL("kernel32.dll") 41 | var getMem = mod.NewProc("GlobalMemoryStatusEx") 42 | 43 | var memStruct MEMORYSTATUSEX 44 | 45 | memStruct.dwLength = uint32(unsafe.Sizeof(memStruct)) 46 | 47 | status, _, err := getMem.Call(uintptr(unsafe.Pointer(&memStruct))) 48 | if status != 0 { 49 | mem = memStruct.ulTotalPhys 50 | err = nil 51 | } 52 | return 53 | } 54 | -------------------------------------------------------------------------------- /network/network.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | // Package network regroups collecting information about the network interfaces 7 | package network 8 | 9 | func getNetworkInfo() (networkInfo map[string]interface{}, err error) { 10 | networkInfo = make(map[string]interface{}) 11 | 12 | macaddress, err := macAddress() 13 | if err != nil { 14 | return networkInfo, err 15 | } 16 | networkInfo["macaddress"] = macaddress 17 | 18 | ipAddress, err := externalIPAddress() 19 | if err != nil { 20 | return networkInfo, err 21 | } 22 | networkInfo["ipaddress"] = ipAddress 23 | 24 | ipAddressV6, err := externalIpv6Address() 25 | if err != nil { 26 | return networkInfo, err 27 | } 28 | // We append an IPv6 address to the payload only if IPv6 is enabled 29 | if ipAddressV6 != "" { 30 | networkInfo["ipaddressv6"] = ipAddressV6 31 | } 32 | 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /network/network_common.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package network 7 | 8 | import ( 9 | "errors" 10 | "net" 11 | 12 | "github.com/DataDog/gohai/utils" 13 | ) 14 | 15 | // Network holds network metadata about the host 16 | // 17 | //nolint:revive 18 | type Network struct { 19 | // IpAddress is the ipv4 address for the host 20 | IpAddress string 21 | // IpAddressv6 is the ipv6 address for the host 22 | IpAddressv6 string 23 | // MacAddress is the macaddress for the host 24 | MacAddress string 25 | 26 | // TODO: the collect method also returns metadata about interfaces. They should be added to this struct. 27 | // Since it would require even more cleanup we'll do it in another PR when needed. 28 | } 29 | 30 | const name = "network" 31 | 32 | // Name returns the name of the package 33 | func (network *Network) Name() string { 34 | return name 35 | } 36 | 37 | // Collect collects the Network information. 38 | // Returns an object which can be converted to a JSON or an error if nothing could be collected. 39 | // Tries to collect as much information as possible. 40 | func (network *Network) Collect() (result interface{}, err error) { 41 | result, err = getNetworkInfo() 42 | if err != nil { 43 | return 44 | } 45 | 46 | interfaces, err := getMultiNetworkInfo() 47 | if err == nil && len(interfaces) > 0 { 48 | interfaceMap, ok := result.(map[string]interface{}) 49 | if !ok { 50 | return 51 | } 52 | interfaceMap["interfaces"] = interfaces 53 | } 54 | return 55 | } 56 | 57 | // Get returns a Network struct already initialized, a list of warnings and an error. The method will try to collect as much 58 | // metadata as possible, an error is returned if nothing could be collected. The list of warnings contains errors if 59 | // some metadata could not be collected. 60 | func Get() (*Network, []string, error) { 61 | networkInfo, err := getNetworkInfo() 62 | if err != nil { 63 | return nil, nil, err 64 | } 65 | 66 | return &Network{ 67 | IpAddress: utils.GetStringInterface(networkInfo, "ipaddress"), 68 | IpAddressv6: utils.GetStringInterface(networkInfo, "ipaddressv6"), 69 | MacAddress: utils.GetStringInterface(networkInfo, "macaddress"), 70 | }, nil, nil 71 | } 72 | 73 | func getMultiNetworkInfo() (multiNetworkInfo []map[string]interface{}, err error) { 74 | ifaces, err := net.Interfaces() 75 | 76 | if err != nil { 77 | return multiNetworkInfo, err 78 | } 79 | for _, iface := range ifaces { 80 | _iface := make(map[string]interface{}) 81 | _ipv4 := []string{} 82 | _ipv6 := []string{} 83 | if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 { 84 | // interface down or loopback interface 85 | continue 86 | } 87 | addrs, err := iface.Addrs() 88 | if err != nil { 89 | // skip this interface but try the next 90 | continue 91 | } 92 | for _, addr := range addrs { 93 | ip, network, _ := net.ParseCIDR(addr.String()) 94 | if ip == nil || ip.IsLoopback() { 95 | continue 96 | } 97 | if ip.To4() == nil { 98 | _ipv6 = append(_ipv6, ip.String()) 99 | _iface["ipv6-network"] = network.String() 100 | } else { 101 | _ipv4 = append(_ipv4, ip.String()) 102 | _iface["ipv4-network"] = network.String() 103 | } 104 | if len(iface.HardwareAddr.String()) > 0 { 105 | _iface["macaddress"] = iface.HardwareAddr.String() 106 | } 107 | } 108 | _iface["ipv4"] = _ipv4 109 | _iface["ipv6"] = _ipv6 110 | if len(_iface) > 0 { 111 | _iface["name"] = iface.Name 112 | multiNetworkInfo = append(multiNetworkInfo, _iface) 113 | } 114 | } 115 | return multiNetworkInfo, err 116 | } 117 | 118 | func externalIpv6Address() (string, error) { 119 | ifaces, err := net.Interfaces() 120 | 121 | if err != nil { 122 | return "", err 123 | } 124 | 125 | for _, iface := range ifaces { 126 | if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 { 127 | // interface down or loopback interface 128 | continue 129 | } 130 | addrs, err := iface.Addrs() 131 | if err != nil { 132 | return "", err 133 | } 134 | for _, addr := range addrs { 135 | var ip net.IP 136 | switch v := addr.(type) { 137 | case *net.IPNet: 138 | ip = v.IP 139 | case *net.IPAddr: 140 | ip = v.IP 141 | } 142 | if ip == nil || ip.IsLoopback() { 143 | continue 144 | } 145 | if ip.To4() != nil { 146 | // ipv4 address 147 | continue 148 | } 149 | return ip.String(), nil 150 | } 151 | } 152 | 153 | // We don't return an error if no IPv6 interface has been found. Indeed, 154 | // some orgs just don't have IPv6 enabled. If there's a network error, it 155 | // will pop out when getting the Mac address and/or the IPv4 address 156 | // (before this function's call; see network.go -> getNetworkInfo()) 157 | return "", nil 158 | } 159 | 160 | func externalIPAddress() (string, error) { 161 | ifaces, err := net.Interfaces() 162 | 163 | if err != nil { 164 | return "", err 165 | } 166 | 167 | for _, iface := range ifaces { 168 | if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 { 169 | // interface down or loopback interface 170 | continue 171 | } 172 | addrs, err := iface.Addrs() 173 | if err != nil { 174 | return "", err 175 | } 176 | for _, addr := range addrs { 177 | var ip net.IP 178 | switch v := addr.(type) { 179 | case *net.IPNet: 180 | ip = v.IP 181 | case *net.IPAddr: 182 | ip = v.IP 183 | } 184 | if ip == nil || ip.IsLoopback() { 185 | continue 186 | } 187 | ip = ip.To4() 188 | if ip == nil { 189 | // not an ipv4 address 190 | continue 191 | } 192 | return ip.String(), nil 193 | } 194 | } 195 | return "", errors.New("not connected to the network") 196 | } 197 | 198 | func macAddress() (string, error) { 199 | ifaces, err := net.Interfaces() 200 | 201 | if err != nil { 202 | return "", err 203 | } 204 | 205 | for _, iface := range ifaces { 206 | if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 { 207 | // interface down or loopback interface 208 | continue 209 | } 210 | addrs, err := iface.Addrs() 211 | if err != nil { 212 | return "", err 213 | } 214 | for _, addr := range addrs { 215 | var ip net.IP 216 | switch v := addr.(type) { 217 | case *net.IPNet: 218 | ip = v.IP 219 | case *net.IPAddr: 220 | ip = v.IP 221 | } 222 | if ip == nil || ip.IsLoopback() || ip.To4() == nil { 223 | continue 224 | } 225 | return iface.HardwareAddr.String(), nil 226 | } 227 | } 228 | return "", errors.New("not connected to the network") 229 | } 230 | -------------------------------------------------------------------------------- /platform/platform.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | //go:build !android 7 | // +build !android 8 | 9 | package platform 10 | 11 | import ( 12 | "runtime" 13 | "strings" 14 | 15 | "github.com/DataDog/gohai/utils" 16 | ) 17 | 18 | // Collect collects the Platform information. 19 | // Returns an object which can be converted to a JSON or an error if nothing could be collected. 20 | // Tries to collect as much information as possible. 21 | func (platform *Platform) Collect() (result interface{}, err error) { 22 | result, _, err = getPlatformInfo() 23 | return 24 | } 25 | 26 | // Get returns a Platform struct already initialized, a list of warnings and an error. The method will try to collect as much 27 | // metadata as possible, an error is returned if nothing could be collected. The list of warnings contains errors if 28 | // some metadata could not be collected. 29 | func Get() (*Platform, []string, error) { 30 | platformInfo, warnings, err := getPlatformInfo() 31 | if err != nil { 32 | return nil, nil, err 33 | } 34 | 35 | p := &Platform{} 36 | p.GoVersion = utils.GetString(platformInfo, "goV") 37 | p.GoOS = utils.GetString(platformInfo, "GOOS") 38 | p.GoArch = utils.GetString(platformInfo, "GOOARCH") 39 | p.KernelName = utils.GetString(platformInfo, "kernel_name") 40 | p.KernelRelease = utils.GetString(platformInfo, "kernel_release") 41 | p.Hostname = utils.GetString(platformInfo, "hostname") 42 | p.Machine = utils.GetString(platformInfo, "machine") 43 | p.OS = utils.GetString(platformInfo, "os") 44 | p.Family = utils.GetString(platformInfo, "family") 45 | p.KernelVersion = utils.GetString(platformInfo, "kernel_version") 46 | p.Processor = utils.GetString(platformInfo, "processor") 47 | p.HardwarePlatform = utils.GetString(platformInfo, "hardware_platform") 48 | 49 | return p, warnings, nil 50 | } 51 | 52 | func getPlatformInfo() (platformInfo map[string]string, warnings []string, err error) { 53 | 54 | // collect each portion, and allow the parts that succeed (even if some 55 | // parts fail.) For this check, it does have the (small) liability 56 | // that if both the ArchInfo() and the PythonVersion() fail, the error 57 | // from the ArchInfo() will be lost. 58 | 59 | // For this, no error check. The successful results will be added 60 | // to the return value, and the error stored. 61 | platformInfo, err = GetArchInfo() 62 | if platformInfo == nil { 63 | platformInfo = map[string]string{} 64 | } 65 | 66 | platformInfo["goV"] = strings.ReplaceAll(runtime.Version(), "go", "") 67 | platformInfo["GOOS"] = runtime.GOOS 68 | platformInfo["GOOARCH"] = runtime.GOARCH 69 | 70 | return 71 | } 72 | -------------------------------------------------------------------------------- /platform/platform_android.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | //go:build android 7 | // +build android 8 | 9 | package platform 10 | 11 | // Collects the Platform information. 12 | // Returns an object which can be converted to a JSON or an error if nothing could be collected. 13 | // Tries to collect as much information as possible. 14 | func (platform *Platform) Collect() (interface{}, error) { 15 | return nil, nil 16 | } 17 | 18 | // Get returns a Platform struct already initialized, a list of warnings and an error. The method will try to collect as much 19 | // metadata as possible, an error is returned if nothing could be collected. The list of warnings contains errors if 20 | // some metadata could not be collected. 21 | func Get() (*Platform, []string, error) { 22 | return nil, nil, nil 23 | } 24 | -------------------------------------------------------------------------------- /platform/platform_common.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | // Package platform regroups collecting information about the platform 7 | package platform 8 | 9 | // Platform holds metadata about the host 10 | type Platform struct { 11 | // GoVersion is the golang version. 12 | GoVersion string 13 | // GoOS is equal to "runtime.GOOS" 14 | GoOS string 15 | // GoArch is equal to "runtime.GOARCH" 16 | GoArch string 17 | 18 | // KernelName is the kernel name (ex: "windows", "Linux", ...) 19 | KernelName string 20 | // KernelRelease the kernel release (ex: "10.0.20348", "4.15.0-1080-gcp", ...) 21 | KernelRelease string 22 | // Hostname is the hostname for the host 23 | Hostname string 24 | // Machine the architecture for the host (is: x86_64 vs arm). 25 | Machine string 26 | // OS is the os name description (ex: "GNU/Linux", "Windows Server 2022 Datacenter", ...) 27 | OS string 28 | 29 | // Family is the OS family (Windows only) 30 | Family string 31 | 32 | // KernelVersion the kernel version, Unix only 33 | KernelVersion string 34 | // Processor is the processor type, Unix only (ex "x86_64", "arm", ...) 35 | Processor string 36 | // HardwarePlatform is the hardware name, Linux only (ex "x86_64") 37 | HardwarePlatform string 38 | } 39 | 40 | const name = "platform" 41 | 42 | // Name returns the name of the package 43 | func (platform *Platform) Name() string { 44 | return name 45 | } 46 | -------------------------------------------------------------------------------- /platform/platform_darwin.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package platform 7 | 8 | import ( 9 | "strings" 10 | 11 | log "github.com/cihub/seelog" 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | var unameOptions = []string{"-s", "-n", "-r", "-m", "-p"} 16 | 17 | // processIsTranslated detects if the process using gohai is running under the Rosetta 2 translator 18 | func processIsTranslated() (bool, error) { 19 | // https://developer.apple.com/documentation/apple_silicon/about_the_rosetta_translation_environment#3616845 20 | ret, err := unix.SysctlUint32("sysctl.proc_translated") 21 | 22 | if err == nil { 23 | return ret == 1, nil 24 | } else if err.(unix.Errno) == unix.ENOENT { 25 | return false, nil 26 | } 27 | return false, err 28 | } 29 | 30 | func updateArchInfo(archInfo map[string]string, values []string) { 31 | archInfo["kernel_name"] = values[0] 32 | archInfo["hostname"] = values[1] 33 | archInfo["kernel_release"] = values[2] 34 | archInfo["machine"] = values[3] 35 | archInfo["processor"] = strings.Trim(values[4], "\n") 36 | archInfo["os"] = values[0] 37 | 38 | if isTranslated, err := processIsTranslated(); err == nil && isTranslated { 39 | log.Debug("Running under Rosetta translator; overriding architecture values") 40 | archInfo["processor"] = "arm" 41 | archInfo["machine"] = "arm64" 42 | } else if err != nil { 43 | log.Debugf("Error when detecting Rosetta translator: %s", err) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /platform/platform_linux.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package platform 7 | 8 | import "strings" 9 | 10 | var unameOptions = []string{"-s", "-n", "-r", "-m", "-p", "-i", "-o"} 11 | 12 | func updateArchInfo(archInfo map[string]string, values []string) { 13 | archInfo["kernel_name"] = values[0] 14 | archInfo["hostname"] = values[1] 15 | archInfo["kernel_release"] = values[2] 16 | archInfo["machine"] = values[3] 17 | archInfo["processor"] = values[4] 18 | archInfo["hardware_platform"] = values[5] 19 | archInfo["os"] = strings.Trim(values[6], "\n") 20 | } 21 | -------------------------------------------------------------------------------- /platform/platform_nix.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | //go:build linux || darwin 7 | // +build linux darwin 8 | 9 | package platform 10 | 11 | import ( 12 | "os/exec" 13 | "regexp" 14 | "strings" 15 | ) 16 | 17 | // GetArchInfo returns basic host architecture information 18 | func GetArchInfo() (archInfo map[string]string, err error) { 19 | archInfo = map[string]string{} 20 | 21 | out, err := exec.Command("uname", unameOptions...).Output() 22 | if err != nil { 23 | return nil, err 24 | } 25 | line := string(out) 26 | values := regexp.MustCompile(" +").Split(line, 7) 27 | updateArchInfo(archInfo, values) 28 | 29 | out, err = exec.Command("uname", "-v").Output() 30 | if err != nil { 31 | return nil, err 32 | } 33 | archInfo["kernel_version"] = strings.Trim(string(out), "\n") 34 | 35 | return 36 | } 37 | -------------------------------------------------------------------------------- /platform/platform_windows.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package platform 7 | 8 | import ( 9 | "fmt" 10 | "os" 11 | "runtime" 12 | "strconv" 13 | "syscall" 14 | "unicode/utf16" 15 | "unsafe" 16 | 17 | "golang.org/x/sys/windows" 18 | "golang.org/x/sys/windows/registry" 19 | ) 20 | 21 | // OSVERSIONINFOEXW contains operating system version information. 22 | // From winnt.h (see https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoexw) 23 | // This is used by https://docs.microsoft.com/en-us/windows/win32/devnotes/rtlgetversion 24 | // 25 | //nolint:revive 26 | type OSVERSIONINFOEXW struct { 27 | dwOSVersionInfoSize uint32 28 | dwMajorVersion uint32 29 | dwMinorVersion uint32 30 | dwBuildNumber uint32 31 | dwPlatformId uint32 32 | szCSDVersion [128]uint16 33 | wServicePackMajor uint16 34 | wServicePackMinor uint16 35 | wSuiteMask uint16 36 | wProductType uint8 37 | wReserved uint8 38 | } 39 | 40 | var ( 41 | modNetapi32 = windows.NewLazyDLL("Netapi32.dll") 42 | procNetServerGetInfo = modNetapi32.NewProc("NetServerGetInfo") 43 | procNetAPIBufferFree = modNetapi32.NewProc("NetApiBufferFree") 44 | ntdll = windows.NewLazyDLL("Ntdll.dll") 45 | procRtlGetVersion = ntdll.NewProc("RtlGetVersion") 46 | winbrand = windows.NewLazyDLL("winbrand.dll") 47 | kernel32 = windows.NewLazyDLL("kernel32.dll") 48 | procIsWow64Process2 = kernel32.NewProc("IsWow64Process2") 49 | 50 | // ERROR_SUCCESS is the error returned in case of success 51 | // 52 | //nolint:revive 53 | ERROR_SUCCESS syscall.Errno 54 | ) 55 | 56 | // see https://learn.microsoft.com/en-us/windows/win32/api/lmserver/nf-lmserver-netserverenum 57 | // 58 | //nolint:revive 59 | const ( 60 | // SV_TYPE_WORKSTATION is for all workstations. 61 | SV_TYPE_WORKSTATION = uint32(0x00000001) 62 | // SV_TYPE_SERVER is for all computers that run the Server service. 63 | SV_TYPE_SERVER = uint32(0x00000002) 64 | // SV_TYPE_SQLSERVER is for any server that runs an instance of Microsoft SQL Server. 65 | SV_TYPE_SQLSERVER = uint32(0x00000004) 66 | // SV_TYPE_DOMAIN_CTRL is for a server that is primary domain controller. 67 | SV_TYPE_DOMAIN_CTRL = uint32(0x00000008) 68 | // SV_TYPE_DOMAIN_BAKCTRL is for any server that is a backup domain controller. 69 | SV_TYPE_DOMAIN_BAKCTRL = uint32(0x00000010) 70 | // SV_TYPE_TIME_SOURCE is for any server that runs the Timesource service. 71 | SV_TYPE_TIME_SOURCE = uint32(0x00000020) 72 | // SV_TYPE_AFP is for any server that runs the Apple Filing Protocol (AFP) file service. 73 | SV_TYPE_AFP = uint32(0x00000040) 74 | // SV_TYPE_NOVELL is for any server that is a Novell server. 75 | SV_TYPE_NOVELL = uint32(0x00000080) 76 | // SV_TYPE_DOMAIN_MEMBER is for any computer that is LAN Manager 2.x domain member. 77 | SV_TYPE_DOMAIN_MEMBER = uint32(0x00000100) 78 | // SV_TYPE_PRINTQ_SERVER is for any computer that shares a print queue. 79 | SV_TYPE_PRINTQ_SERVER = uint32(0x00000200) 80 | // SV_TYPE_DIALIN_SERVER is for any server that runs a dial-in service. 81 | SV_TYPE_DIALIN_SERVER = uint32(0x00000400) 82 | // SV_TYPE_XENIX_SERVER is for any server that is a Xenix server. 83 | SV_TYPE_XENIX_SERVER = uint32(0x00000800) 84 | // SV_TYPE_SERVER_UNIX is for any server that is a UNIX server. This is the same as the SV_TYPE_XENIX_SERVER. 85 | SV_TYPE_SERVER_UNIX = SV_TYPE_XENIX_SERVER 86 | // SV_TYPE_NT is for a workstation or server. 87 | SV_TYPE_NT = uint32(0x00001000) 88 | // SV_TYPE_WFW is for any computer that runs Windows for Workgroups. 89 | SV_TYPE_WFW = uint32(0x00002000) 90 | // SV_TYPE_SERVER_MFPN is for any server that runs the Microsoft File and Print for NetWare service. 91 | SV_TYPE_SERVER_MFPN = uint32(0x00004000) 92 | // SV_TYPE_SERVER_NT is for any server that is not a domain controller. 93 | SV_TYPE_SERVER_NT = uint32(0x00008000) 94 | // SV_TYPE_POTENTIAL_BROWSER is for any computer that can run the browser service. 95 | SV_TYPE_POTENTIAL_BROWSER = uint32(0x00010000) 96 | // SV_TYPE_BACKUP_BROWSER is for a computer that runs a browser service as backup. 97 | SV_TYPE_BACKUP_BROWSER = uint32(0x00020000) 98 | // SV_TYPE_MASTER_BROWSER is for a computer that runs the master browser service. 99 | SV_TYPE_MASTER_BROWSER = uint32(0x00040000) 100 | // SV_TYPE_DOMAIN_MASTER is for a computer that runs the domain master browser. 101 | SV_TYPE_DOMAIN_MASTER = uint32(0x00080000) 102 | // SV_TYPE_SERVER_OSF is for a computer that runs OSF/1. 103 | SV_TYPE_SERVER_OSF = uint32(0x00100000) 104 | // SV_TYPE_SERVER_VMS is for a computer that runs Open Virtual Memory System (VMS). 105 | SV_TYPE_SERVER_VMS = uint32(0x00200000) 106 | // SV_TYPE_WINDOWS is for a computer that runs Windows. 107 | SV_TYPE_WINDOWS = uint32(0x00400000) /* Windows95 and above */ 108 | // SV_TYPE_DFS is for a computer that is the root of Distributed File System (DFS) tree. 109 | SV_TYPE_DFS = uint32(0x00800000) 110 | // SV_TYPE_CLUSTER_NT is for server clusters available in the domain. 111 | SV_TYPE_CLUSTER_NT = uint32(0x01000000) 112 | // SV_TYPE_TERMINALSERVER is for a server running the Terminal Server service. 113 | SV_TYPE_TERMINALSERVER = uint32(0x02000000) 114 | // SV_TYPE_CLUSTER_VS_NT is for cluster virtual servers available in the domain. 115 | SV_TYPE_CLUSTER_VS_NT = uint32(0x04000000) 116 | // SV_TYPE_DCE is for a computer that runs IBM Directory and Security Services (DSS) or equivalent. 117 | SV_TYPE_DCE = uint32(0x10000000) 118 | // SV_TYPE_ALTERNATE_XPORT is for a computer that over an alternate transport. 119 | SV_TYPE_ALTERNATE_XPORT = uint32(0x20000000) 120 | // SV_TYPE_LOCAL_LIST_ONLY is for any computer maintained in a list by the browser. See the following Remarks section. 121 | SV_TYPE_LOCAL_LIST_ONLY = uint32(0x40000000) 122 | // SV_TYPE_DOMAIN_ENUM is for the primary domain. 123 | SV_TYPE_DOMAIN_ENUM = uint32(0x80000000) 124 | // SV_TYPE_ALL is for all servers. This is a convenience that will return all possible servers 125 | SV_TYPE_ALL = uint32(0xFFFFFFFF) /* handy for NetServerEnum2 */ 126 | ) 127 | 128 | const ( 129 | IMAGE_FILE_MACHINE_UNKNOWN = uint16(0x0) 130 | IMAGE_FILE_MACHINE_TARGET_HOST = uint16(0x0001) // Useful for indicating we want to interact with the host and not a WoW guest. Win10/2016 and above only 131 | IMAGE_FILE_MACHINE_I386 = uint16(0x014c) // Intel 386. 132 | IMAGE_FILE_MACHINE_R3000 = uint16(0x0162) // MIPS little-endian, = uint16(0x160 big-endian 133 | IMAGE_FILE_MACHINE_R4000 = uint16(0x0166) // MIPS little-endian 134 | IMAGE_FILE_MACHINE_R10000 = uint16(0x0168) // MIPS little-endian 135 | IMAGE_FILE_MACHINE_WCEMIPSV2 = uint16(0x0169) // MIPS little-endian WCE v2 136 | IMAGE_FILE_MACHINE_ALPHA = uint16(0x0184) // Alpha_AXP 137 | IMAGE_FILE_MACHINE_SH3 = uint16(0x01a2) // SH3 little-endian 138 | IMAGE_FILE_MACHINE_SH3DSP = uint16(0x01a3) 139 | IMAGE_FILE_MACHINE_SH3E = uint16(0x01a4) // SH3E little-endian 140 | IMAGE_FILE_MACHINE_SH4 = uint16(0x01a6) // SH4 little-endian 141 | IMAGE_FILE_MACHINE_SH5 = uint16(0x01a8) // SH5 142 | IMAGE_FILE_MACHINE_ARM = uint16(0x01c0) // ARM Little-Endian 143 | IMAGE_FILE_MACHINE_THUMB = uint16(0x01c2) // ARM Thumb/Thumb-2 Little-Endian 144 | IMAGE_FILE_MACHINE_ARMNT = uint16(0x01c4) // ARM Thumb-2 Little-Endian 145 | IMAGE_FILE_MACHINE_AM33 = uint16(0x01d3) 146 | IMAGE_FILE_MACHINE_POWERPC = uint16(0x01F0) // IBM PowerPC Little-Endian 147 | IMAGE_FILE_MACHINE_POWERPCFP = uint16(0x01f1) 148 | IMAGE_FILE_MACHINE_IA64 = uint16(0x0200) // Intel 64 149 | IMAGE_FILE_MACHINE_MIPS16 = uint16(0x0266) // MIPS 150 | IMAGE_FILE_MACHINE_ALPHA64 = uint16(0x0284) // ALPHA64 151 | IMAGE_FILE_MACHINE_MIPSFPU = uint16(0x0366) // MIPS 152 | IMAGE_FILE_MACHINE_MIPSFPU16 = uint16(0x0466) // MIPS 153 | IMAGE_FILE_MACHINE_AXP64 = IMAGE_FILE_MACHINE_ALPHA64 154 | IMAGE_FILE_MACHINE_TRICORE = uint16(0x0520) // Infineon 155 | IMAGE_FILE_MACHINE_CEF = uint16(0x0CEF) 156 | IMAGE_FILE_MACHINE_EBC = uint16(0x0EBC) // EFI Byte Code 157 | IMAGE_FILE_MACHINE_AMD64 = uint16(0x8664) // AMD64 (K8) 158 | IMAGE_FILE_MACHINE_M32R = uint16(0x9041) // M32R little-endian 159 | IMAGE_FILE_MACHINE_ARM64 = uint16(0xAA64) // ARM64 Little-Endian 160 | IMAGE_FILE_MACHINE_CEE = uint16(0xC0EE) 161 | ) 162 | const registryHive = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion" 163 | const productNameKey = "ProductName" 164 | const buildNumberKey = "CurrentBuildNumber" 165 | const majorKey = "CurrentMajorVersionNumber" 166 | const minorKey = "CurrentMinorVersionNumber" 167 | 168 | func netServerGetInfo() (si SERVER_INFO_101, err error) { 169 | var outdata *byte 170 | // do additional work so that we don't panic() when the library's 171 | // not there (like in a container) 172 | if err = modNetapi32.Load(); err != nil { 173 | return 174 | } 175 | if err = procNetServerGetInfo.Find(); err != nil { 176 | return 177 | } 178 | status, _, err := procNetServerGetInfo.Call(uintptr(0), uintptr(101), uintptr(unsafe.Pointer(&outdata))) 179 | if status != uintptr(0) { 180 | return 181 | } 182 | // ignore free errors 183 | //nolint:errcheck 184 | defer procNetAPIBufferFree.Call(uintptr(unsafe.Pointer(outdata))) 185 | return platGetServerInfo(outdata), nil 186 | } 187 | 188 | func fetchOsDescription() (string, error) { 189 | err := winbrand.Load() 190 | if err == nil { 191 | // From https://stackoverflow.com/a/69462683 192 | procBrandingFormatString := winbrand.NewProc("BrandingFormatString") 193 | if procBrandingFormatString.Find() == nil { 194 | // Encode the string "%WINDOWS_LONG%" to UTF-16 and append a null byte for the Windows API 195 | magicString := utf16.Encode([]rune("%WINDOWS_LONG%" + "\x00")) 196 | os, _, err := procBrandingFormatString.Call(uintptr(unsafe.Pointer(&magicString[0]))) 197 | if err == ERROR_SUCCESS { 198 | // ignore free errors 199 | //nolint:errcheck 200 | defer syscall.LocalFree(syscall.Handle(os)) 201 | // govet complains about possible misuse of unsafe.Pointer here 202 | //nolint:govet 203 | return windows.UTF16PtrToString((*uint16)(unsafe.Pointer(os))), nil 204 | } 205 | } 206 | } 207 | 208 | k, err := registry.OpenKey(registry.LOCAL_MACHINE, 209 | registryHive, 210 | registry.QUERY_VALUE) 211 | if err == nil { 212 | // ignore registry key close errors 213 | //nolint:staticcheck 214 | defer k.Close() 215 | os, _, err := k.GetStringValue(productNameKey) 216 | if err == nil { 217 | return os, nil 218 | } 219 | } 220 | 221 | return "(undetermined windows version)", err 222 | } 223 | 224 | func fetchWindowsVersion() (major uint64, minor uint64, build uint64, err error) { 225 | var osversion OSVERSIONINFOEXW 226 | status, _, _ := procRtlGetVersion.Call(uintptr(unsafe.Pointer(&osversion))) 227 | if status == 0 { 228 | major = uint64(osversion.dwMajorVersion) 229 | minor = uint64(osversion.dwMinorVersion) 230 | build = uint64(osversion.dwBuildNumber) 231 | } else { 232 | var regkey registry.Key 233 | regkey, err = registry.OpenKey(registry.LOCAL_MACHINE, 234 | registryHive, 235 | registry.QUERY_VALUE) 236 | if err != nil { 237 | return 238 | } 239 | // ignore registry key close errors 240 | //nolint:staticcheck 241 | defer regkey.Close() 242 | major, _, err = regkey.GetIntegerValue(majorKey) 243 | if err != nil { 244 | return 245 | } 246 | 247 | minor, _, err = regkey.GetIntegerValue(minorKey) 248 | if err != nil { 249 | return 250 | } 251 | 252 | var regbuild string 253 | regbuild, _, err = regkey.GetStringValue(buildNumberKey) 254 | if err != nil { 255 | return 256 | } 257 | build, err = strconv.ParseUint(regbuild, 10, 0) 258 | } 259 | return 260 | } 261 | 262 | // check to see if we're running on syswow64 on another architecture 263 | // (specifically arm) 264 | // the function we're going to use (IsWow64Process2) isn't available prior 265 | // to win10/2016. Fail gracefully, and assume we're not on wow in that 266 | // case 267 | 268 | func getNativeArchInfo() string { 269 | nativearch := "x86_64" 270 | if runtime.GOARCH == "amd64" { 271 | nativearch = "x86_64" 272 | } else { 273 | nativearch = runtime.GOARCH 274 | } 275 | var err error 276 | if err = kernel32.Load(); err == nil { 277 | if err = procIsWow64Process2.Find(); err == nil { 278 | var pmachine uint16 279 | var pnative uint16 280 | h := windows.CurrentProcess() 281 | b, _, _ := procIsWow64Process2.Call(uintptr(h), uintptr(unsafe.Pointer(&pmachine)), uintptr(unsafe.Pointer(&pnative))) 282 | if b != uintptr(0) { 283 | // check to see the native processor type. 284 | switch pnative { 285 | case IMAGE_FILE_MACHINE_AMD64: 286 | // it's already set to this 287 | nativearch = "x86_64" 288 | case IMAGE_FILE_MACHINE_ARM64: 289 | nativearch = "ARM64" 290 | } 291 | } 292 | } 293 | } 294 | return nativearch 295 | } 296 | 297 | // GetArchInfo returns basic host architecture information 298 | func GetArchInfo() (map[string]string, error) { 299 | // Initialize systemInfo with all fields to avoid missing a field which 300 | // could be expected by the backend or by users 301 | // TODO: make sure that the backend actually works with any subset of fields 302 | systemInfo := map[string]string{ 303 | "hostname": "", 304 | "machine": "", 305 | "os": "", 306 | "kernel_release": "0.0.0", 307 | "kernel_name": "", 308 | "family": "", 309 | } 310 | 311 | hostname, err := os.Hostname() 312 | if err == nil { 313 | systemInfo["hostname"] = hostname 314 | } 315 | 316 | systemInfo["machine"] = getNativeArchInfo() 317 | 318 | osDescription, err := fetchOsDescription() 319 | if err == nil { 320 | systemInfo["os"] = osDescription 321 | } 322 | 323 | maj, min, bld, err := fetchWindowsVersion() 324 | if err == nil { 325 | verstring := fmt.Sprintf("%d.%d.%d", maj, min, bld) 326 | systemInfo["kernel_release"] = verstring 327 | } 328 | 329 | systemInfo["kernel_name"] = "Windows" 330 | 331 | // do additional work so that we don't panic() when the library's 332 | // not there (like in a container) 333 | family := "Unknown" 334 | si, sierr := netServerGetInfo() 335 | if sierr == nil { 336 | if (si.sv101_type&SV_TYPE_WORKSTATION) == SV_TYPE_WORKSTATION || 337 | (si.sv101_type&SV_TYPE_SERVER) == SV_TYPE_SERVER { 338 | if (si.sv101_type & SV_TYPE_WORKSTATION) == SV_TYPE_WORKSTATION { 339 | family = "Workstation" 340 | } else if (si.sv101_type & SV_TYPE_SERVER) == SV_TYPE_SERVER { 341 | family = "Server" 342 | } 343 | if (si.sv101_type & SV_TYPE_DOMAIN_MEMBER) == SV_TYPE_DOMAIN_MEMBER { 344 | family = "Domain Joined " + family 345 | } else { 346 | family = "Standalone " + family 347 | } 348 | } else if (si.sv101_type & SV_TYPE_DOMAIN_CTRL) == SV_TYPE_DOMAIN_CTRL { 349 | family = "Domain Controller" 350 | } else if (si.sv101_type & SV_TYPE_DOMAIN_BAKCTRL) == SV_TYPE_DOMAIN_BAKCTRL { 351 | family = "Backup Domain Controller" 352 | } 353 | } 354 | systemInfo["family"] = family 355 | 356 | return systemInfo, nil 357 | } 358 | -------------------------------------------------------------------------------- /platform/platform_windows_386.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package platform 7 | 8 | import ( 9 | "encoding/binary" 10 | "unsafe" 11 | ) 12 | 13 | // WKSTA_INFO_100 contains platform-specific information 14 | // see https://learn.microsoft.com/en-us/windows/win32/api/lmwksta/ns-lmwksta-wksta_info_100 15 | // 16 | //nolint:revive 17 | type WKSTA_INFO_100 struct { 18 | wki100_platform_id uint32 19 | wki100_computername string 20 | wki100_langroup string 21 | wki100_ver_major uint32 22 | wki100_ver_minor uint32 23 | } 24 | 25 | // SERVER_INFO_101 contains server-specific information 26 | // see https://learn.microsoft.com/en-us/windows/win32/api/lmserver/ns-lmserver-server_info_101 27 | // 28 | //nolint:revive 29 | type SERVER_INFO_101 struct { 30 | sv101_platform_id uint32 31 | sv101_name string 32 | sv101_version_major uint32 33 | sv101_version_minor uint32 34 | sv101_type uint32 35 | sv101_comment string 36 | } 37 | 38 | func byteArrayToWksaInfo(data []byte) (info WKSTA_INFO_100) { 39 | info.wki100_platform_id = binary.LittleEndian.Uint32(data) 40 | 41 | // if necessary, convert the pointer to a c-string into a GO string. 42 | // Not using at this time. However, leaving as a placeholder, to 43 | // show why we're skipping 4 bytes of the buffer here... 44 | 45 | //addr := (*byte)(unsafe.Pointer(uintptr(binary.LittleEndian.Uint64(data[4:])))) 46 | //info.wki100_computername = addr 47 | 48 | // ... and again here for the lan group name. 49 | //stringptr = (*[]byte)(unsafe.Pointer(uintptr(binary.LittleEndian.Uint64(data[8:])))) 50 | //info.wki100_langroup = convertWindowsString(stringptr) 51 | 52 | info.wki100_ver_major = binary.LittleEndian.Uint32(data[12:]) 53 | info.wki100_ver_minor = binary.LittleEndian.Uint32(data[16:]) 54 | return 55 | } 56 | 57 | //nolint:unused 58 | func platGetVersion(outdata *byte) (maj uint64, min uint64, err error) { 59 | var info WKSTA_INFO_100 60 | var dataptr []byte 61 | dataptr = (*[20]byte)(unsafe.Pointer(outdata))[:] 62 | 63 | info = byteArrayToWksaInfo(dataptr) 64 | maj = uint64(info.wki100_ver_major) 65 | min = uint64(info.wki100_ver_minor) 66 | return 67 | } 68 | 69 | func platGetServerInfo(data *byte) (si101 SERVER_INFO_101) { 70 | var outdata []byte 71 | outdata = (*[24]byte)(unsafe.Pointer(data))[:] 72 | si101.sv101_platform_id = binary.LittleEndian.Uint32(outdata) 73 | 74 | //stringptr := (*[]uint16)(unsafe.Pointer(uintptr(binary.LittleEndian.Uint64(outdata[4:])))) 75 | //si101.sv101_name = convertWindowsString(*stringptr) 76 | 77 | si101.sv101_version_major = binary.LittleEndian.Uint32(outdata[8:]) 78 | si101.sv101_version_minor = binary.LittleEndian.Uint32(outdata[12:]) 79 | si101.sv101_type = binary.LittleEndian.Uint32(outdata[16:]) 80 | 81 | //stringptr = (*[]uint16)(unsafe.Pointer(uintptr(binary.LittleEndian.Uint32(outdata[20:])))) 82 | //si101.sv101_comment = convertWindowsString(*stringptr) 83 | return 84 | 85 | } 86 | -------------------------------------------------------------------------------- /platform/platform_windows_amd64.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package platform 7 | 8 | import ( 9 | "encoding/binary" 10 | "unsafe" 11 | ) 12 | 13 | // WKSTA_INFO_100 contains platform-specific information 14 | // see https://learn.microsoft.com/en-us/windows/win32/api/lmwksta/ns-lmwksta-wksta_info_100 15 | // 16 | //nolint:revive,unused 17 | type WKSTA_INFO_100 struct { 18 | wki100_platform_id uint32 19 | wki100_computername string 20 | wki100_langroup string 21 | wki100_ver_major uint32 22 | wki100_ver_minor uint32 23 | } 24 | 25 | // SERVER_INFO_101 contains server-specific information 26 | // see https://learn.microsoft.com/en-us/windows/win32/api/lmserver/ns-lmserver-server_info_101 27 | // 28 | //nolint:revive,unused 29 | type SERVER_INFO_101 struct { 30 | sv101_platform_id uint32 31 | sv101_name string 32 | sv101_version_major uint32 33 | sv101_version_minor uint32 34 | sv101_type uint32 35 | sv101_comment string 36 | } 37 | 38 | //nolint:unused 39 | func byteArrayToWksaInfo(data []byte) (info WKSTA_INFO_100) { 40 | info.wki100_platform_id = binary.LittleEndian.Uint32(data) 41 | 42 | // the specified return type of wki100_platform_id is uint32. However, 43 | // due to 64 bit packing, we actually have to skip 8 bytes. 44 | 45 | // if necessary, convert the pointer to a c-string into a GO string. 46 | // Not using at this time. However, leaving as a placeholder, to 47 | // show why we're skipping 8 bytes of the buffer here... 48 | 49 | //addr := (*byte)(unsafe.Pointer(uintptr(binary.LittleEndian.Uint64(data[8:])))) 50 | //info.wki100_computername = addr 51 | 52 | // ... and again here for the lan group name. 53 | //stringptr = (*[]byte)(unsafe.Pointer(uintptr(binary.LittleEndian.Uint64(data[16:])))) 54 | //info.wki100_langroup = convertWindowsString(stringptr) 55 | 56 | info.wki100_ver_major = binary.LittleEndian.Uint32(data[24:]) 57 | info.wki100_ver_minor = binary.LittleEndian.Uint32(data[28:]) 58 | return 59 | } 60 | 61 | //nolint:unused 62 | func platGetVersion(outdata *byte) (maj uint64, min uint64, err error) { 63 | var info WKSTA_INFO_100 64 | var dataptr []byte 65 | dataptr = (*[32]byte)(unsafe.Pointer(outdata))[:] 66 | 67 | info = byteArrayToWksaInfo(dataptr) 68 | maj = uint64(info.wki100_ver_major) 69 | min = uint64(info.wki100_ver_minor) 70 | return 71 | } 72 | 73 | func platGetServerInfo(data *byte) (si101 SERVER_INFO_101) { 74 | var outdata []byte 75 | outdata = (*[40]byte)(unsafe.Pointer(data))[:] 76 | si101.sv101_platform_id = binary.LittleEndian.Uint32(outdata) 77 | 78 | // due to 64 bit packing, skip 8 bytes to get to the name string 79 | //stringptr := *(*[]uint16)(unsafe.Pointer(uintptr(binary.LittleEndian.Uint64(outdata[8:])))) 80 | //si101.sv101_name = convertWindowsString(stringptr) 81 | 82 | si101.sv101_version_major = binary.LittleEndian.Uint32(outdata[16:]) 83 | si101.sv101_version_minor = binary.LittleEndian.Uint32(outdata[20:]) 84 | si101.sv101_type = binary.LittleEndian.Uint32(outdata[24:]) 85 | 86 | // again skip 4 more for byte packing, so start at 32 87 | //stringptr = (*[]uint16)(unsafe.Pointer(uintptr(binary.LittleEndian.Uint32(outdata[32:])))) 88 | //si101.sv101_comment = convertWindowsString(*stringptr) 89 | return 90 | } 91 | -------------------------------------------------------------------------------- /processes/gops/gops.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | //go:build linux || darwin 7 | // +build linux darwin 8 | 9 | package gops 10 | 11 | import ( 12 | "sort" 13 | ) 14 | 15 | func minInt(x, y int) int { 16 | if x < y { 17 | return x 18 | } 19 | 20 | return y 21 | } 22 | 23 | // TopRSSProcessGroups returns an ordered slice of the process groups that use the most RSS 24 | func TopRSSProcessGroups(limit int) (ProcessNameGroups, error) { 25 | procs, err := GetProcesses() 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | procGroups := ByRSSDesc{GroupByName(procs)} 31 | 32 | sort.Sort(procGroups) 33 | 34 | return procGroups.ProcessNameGroups[:minInt(limit, len(procGroups.ProcessNameGroups))], nil 35 | } 36 | -------------------------------------------------------------------------------- /processes/gops/process_group.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | //go:build linux || darwin 7 | // +build linux darwin 8 | 9 | package gops 10 | 11 | import ( 12 | "sort" 13 | ) 14 | 15 | // ProcessNameGroup represents a group of processes, grouped by name 16 | type ProcessNameGroup struct { 17 | pids []int32 18 | rss uint64 19 | pctMem float64 20 | vms uint64 21 | name string 22 | usernames map[string]bool 23 | } 24 | 25 | // ProcessNameGroups represents a list of ProcessNameGroup. 26 | type ProcessNameGroups []*ProcessNameGroup 27 | 28 | // Pids returns the list of pids in the group. 29 | func (pg *ProcessNameGroup) Pids() []int32 { 30 | return pg.pids 31 | } 32 | 33 | // Name returns the name of the group. 34 | func (pg *ProcessNameGroup) Name() string { 35 | return pg.name 36 | } 37 | 38 | // RSS returns the RSS used by the group. 39 | func (pg *ProcessNameGroup) RSS() uint64 { 40 | return pg.rss 41 | } 42 | 43 | // PctMem returns the percentage of memory used by the group. 44 | func (pg *ProcessNameGroup) PctMem() float64 { 45 | return pg.pctMem 46 | } 47 | 48 | // VMS returns the vms of the group. 49 | func (pg *ProcessNameGroup) VMS() uint64 { 50 | return pg.vms 51 | } 52 | 53 | // Usernames returns a slice of the usernames, sorted alphabetically 54 | func (pg *ProcessNameGroup) Usernames() []string { 55 | var usernameStringSlice sort.StringSlice 56 | for username := range pg.usernames { 57 | usernameStringSlice = append(usernameStringSlice, username) 58 | } 59 | 60 | sort.Sort(usernameStringSlice) 61 | 62 | return []string(usernameStringSlice) 63 | } 64 | 65 | // NewProcessNameGroup returns a new empty ProcessNameGroup 66 | func NewProcessNameGroup() *ProcessNameGroup { 67 | processNameGroup := new(ProcessNameGroup) 68 | processNameGroup.usernames = make(map[string]bool) 69 | 70 | return processNameGroup 71 | } 72 | 73 | // GroupByName groups the processInfos by name and return a slice of ProcessNameGroup 74 | func GroupByName(processInfos []*ProcessInfo) ProcessNameGroups { 75 | groupIndexByName := make(map[string]int) 76 | processNameGroups := make(ProcessNameGroups, 0, 10) 77 | 78 | for _, processInfo := range processInfos { 79 | if _, ok := groupIndexByName[processInfo.Name]; !ok { 80 | processNameGroups = append(processNameGroups, NewProcessNameGroup()) 81 | groupIndexByName[processInfo.Name] = len(processNameGroups) - 1 82 | } 83 | 84 | processNameGroups[groupIndexByName[processInfo.Name]].add(processInfo) 85 | } 86 | 87 | return processNameGroups 88 | } 89 | 90 | func (pg *ProcessNameGroup) add(p *ProcessInfo) { 91 | pg.pids = append(pg.pids, p.PID) 92 | if pg.name == "" { 93 | pg.name = p.Name 94 | } 95 | pg.rss += p.RSS 96 | pg.pctMem += p.PctMem 97 | pg.vms += p.VMS 98 | pg.usernames[p.Username] = true 99 | } 100 | 101 | // Len returns the number of groups 102 | func (s ProcessNameGroups) Len() int { 103 | return len(s) 104 | } 105 | 106 | // Swap swaps processes at index i and j 107 | func (s ProcessNameGroups) Swap(i, j int) { 108 | s[i], s[j] = s[j], s[i] 109 | } 110 | 111 | // ByRSSDesc is used to sort groups by decreasing RSS. 112 | type ByRSSDesc struct { 113 | ProcessNameGroups 114 | } 115 | 116 | // Less returns whether the group at index i uses more RSS than the one at index j. 117 | func (s ByRSSDesc) Less(i, j int) bool { 118 | return s.ProcessNameGroups[i].RSS() > s.ProcessNameGroups[j].RSS() 119 | } 120 | -------------------------------------------------------------------------------- /processes/gops/process_group_test.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | //go:build linux || darwin 7 | // +build linux darwin 8 | 9 | package gops 10 | 11 | import ( 12 | "testing" 13 | 14 | "github.com/stretchr/testify/assert" 15 | ) 16 | 17 | func TestUsernames(t *testing.T) { 18 | png := getProcessNameGroup() 19 | usernames := png.Usernames() 20 | assert.Equal(t, []string{"foo_user", "sample_user", "test_user", "user_foo"}, usernames) 21 | } 22 | 23 | func getProcessNameGroup() *ProcessNameGroup { 24 | return &ProcessNameGroup{ 25 | pids: []int32{1, 3, 56, 234, 784}, 26 | rss: uint64(54328), 27 | pctMem: 56.9, 28 | vms: uint64(2515828), 29 | name: "pgroup1", 30 | usernames: map[string]bool{ 31 | "sample_user": true, 32 | "user_foo": true, 33 | "foo_user": true, 34 | "test_user": true, 35 | }, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /processes/gops/process_info.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | //go:build linux || darwin 7 | // +build linux darwin 8 | 9 | // Package gops extracts the information on running processes from gopsutil 10 | package gops 11 | 12 | import ( 13 | "fmt" 14 | "runtime" 15 | 16 | // 3p 17 | log "github.com/cihub/seelog" 18 | 19 | // project 20 | "github.com/shirou/gopsutil/v3/mem" 21 | "github.com/shirou/gopsutil/v3/process" 22 | ) 23 | 24 | // ProcessInfo contains information about a single process 25 | type ProcessInfo struct { 26 | PID int32 27 | PPID int32 28 | Name string 29 | RSS uint64 30 | PctMem float64 31 | VMS uint64 32 | Username string 33 | } 34 | 35 | // GetProcesses returns a slice of all the processes that are running 36 | func GetProcesses() ([]*ProcessInfo, error) { 37 | processInfos := make([]*ProcessInfo, 0, 10) 38 | 39 | virtMemStat, err := mem.VirtualMemory() 40 | if err != nil { 41 | err = fmt.Errorf("error fetching system memory stats: %w", err) 42 | return nil, err 43 | } 44 | totalMem := float64(virtMemStat.Total) 45 | 46 | pids, err := process.Pids() 47 | if err != nil { 48 | err = fmt.Errorf("error fetching PIDs: %w", err) 49 | return nil, err 50 | } 51 | 52 | for _, pid := range pids { 53 | p, err := process.NewProcess(pid) 54 | if err != nil { 55 | // an error can occur here only if the process has disappeared, 56 | log.Debugf("Process with pid %d disappeared while scanning: %w", pid, err) 57 | continue 58 | } 59 | processInfo, err := newProcessInfo(p, totalMem) 60 | if err != nil { 61 | log.Debugf("Error fetching info for pid %d: %w", pid, err) 62 | continue 63 | } 64 | 65 | processInfos = append(processInfos, processInfo) 66 | } 67 | 68 | // platform-specific post-processing on the collected info 69 | postProcess(processInfos) 70 | 71 | return processInfos, nil 72 | } 73 | 74 | // Make a new ProcessInfo from a Process from gopsutil 75 | func newProcessInfo(p *process.Process, totalMem float64) (*ProcessInfo, error) { 76 | memInfo, err := p.MemoryInfo() 77 | if err != nil { 78 | return nil, err 79 | } 80 | pid := p.Pid 81 | ppid, err := p.Ppid() 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | name, err := p.Name() 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | pctMem := 100. * float64(memInfo.RSS) / totalMem 92 | 93 | var username string 94 | if runtime.GOOS != "android" { 95 | username, err = p.Username() 96 | if err != nil { 97 | return nil, err 98 | } 99 | } 100 | 101 | return &ProcessInfo{pid, ppid, name, memInfo.RSS, pctMem, memInfo.VMS, username}, nil 102 | } 103 | -------------------------------------------------------------------------------- /processes/gops/process_info_darwin.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package gops 7 | 8 | // Do nothing for now 9 | func postProcess(_ []*ProcessInfo) {} 10 | -------------------------------------------------------------------------------- /processes/gops/process_info_linux.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package gops 7 | 8 | // Build a hash pid -> ppid 9 | func buildPIDHash(processInfos []*ProcessInfo) (hash map[int32]int32) { 10 | hash = make(map[int32]int32) 11 | for _, processInfo := range processInfos { 12 | hash[processInfo.PID] = processInfo.PPID 13 | } 14 | return 15 | } 16 | 17 | // Return whether the PID is of a kernel thread, based on whether it has 18 | // the init process (PID 1) as ancestor 19 | func isKernelThread(pid int32, pidHash map[int32]int32, depth int32) bool { 20 | if pid == 1 { 21 | return false 22 | } 23 | // Once we reach this level of recursion we know it's not a kernel thread. 24 | // Also, avoids a possible infinite recursion as our process list is not 25 | // necessarily consistent. 26 | if depth > 2 { 27 | return false 28 | } 29 | 30 | ppid, ok := pidHash[pid] 31 | if !ok { 32 | return true 33 | } 34 | 35 | return isKernelThread(ppid, pidHash, depth+1) 36 | } 37 | 38 | // Name processes "kernel" if they're a kernel thread 39 | func postProcess(processInfos []*ProcessInfo) { 40 | pidHash := buildPIDHash(processInfos) 41 | for _, processInfo := range processInfos { 42 | if isKernelThread(processInfo.PID, pidHash, 0) { 43 | processInfo.Name = "kernel" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /processes/processes.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | //go:build linux || darwin 7 | // +build linux darwin 8 | 9 | // Package processes regroups collecting information about existing processes 10 | package processes 11 | 12 | import ( 13 | "strings" 14 | "time" 15 | 16 | "github.com/DataDog/gohai/processes/gops" 17 | ) 18 | 19 | // ProcessField is an untyped representation of a process group, 20 | // compatible with the legacy "processes" resource check. 21 | type ProcessField [7]interface{} 22 | 23 | // getProcesses return a JSON payload which is compatible with 24 | // the legacy "processes" resource check 25 | func getProcesses(limit int) ([]interface{}, error) { 26 | processGroups, err := gops.TopRSSProcessGroups(limit) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | snapData := make([]ProcessField, len(processGroups)) 32 | 33 | for i, processGroup := range processGroups { 34 | processField := ProcessField{ 35 | strings.Join(processGroup.Usernames(), ","), 36 | 0, // pct_cpu, requires two consecutive samples to be computed, so not fetched for now 37 | processGroup.PctMem(), 38 | processGroup.VMS(), 39 | processGroup.RSS(), 40 | processGroup.Name(), 41 | len(processGroup.Pids()), 42 | } 43 | snapData[i] = processField 44 | } 45 | 46 | return []interface{}{time.Now().Unix(), snapData}, nil 47 | } 48 | -------------------------------------------------------------------------------- /processes/processes_common.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | // Package processes regroups collecting information about running processes. 7 | package processes 8 | 9 | import "flag" 10 | 11 | var options struct { 12 | limit int 13 | } 14 | 15 | // Processes is the Collector type of the processes package. 16 | type Processes struct{} 17 | 18 | const name = "processes" 19 | 20 | func init() { 21 | flag.IntVar(&options.limit, name+"-limit", 20, "Number of process groups to return") 22 | } 23 | 24 | // Name returns the name of the package 25 | func (processes *Processes) Name() string { 26 | return name 27 | } 28 | 29 | // Collect collects the processes information. 30 | // Returns an object which can be converted to a JSON or an error if nothing could be collected. 31 | // Tries to collect as much information as possible. 32 | func (processes *Processes) Collect() (result interface{}, err error) { 33 | // even if getProcesses returns nil, simply assigning to result 34 | // will have a non-nil return, because it has a valid inner 35 | // type (more info here: https://golang.org/doc/faq#nil_error ) 36 | // so, jump through the hoop of temporarily storing the return, 37 | // and explicitly return nil if it fails. 38 | gpresult, err := getProcesses(options.limit) 39 | if gpresult == nil { 40 | return nil, err 41 | } 42 | return gpresult, err 43 | } 44 | -------------------------------------------------------------------------------- /processes/processes_windows.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package processes 7 | 8 | import "errors" 9 | 10 | func getProcesses(_ int) ([]interface{}, error) { 11 | return nil, errors.New("Not implemented on Windows") 12 | } 13 | -------------------------------------------------------------------------------- /utils/cmd.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | // Package utils contains helper functions used across the project 7 | package utils 8 | 9 | import ( 10 | "fmt" 11 | "os" 12 | "os/exec" 13 | ) 14 | 15 | // ExecCmdFunc is a function type that matches exec.Command's signature 16 | type ExecCmdFunc = func(name string, arg ...string) *exec.Cmd 17 | 18 | // fakeExecCmd is a function that initialises a new exec.Cmd, one which will 19 | // simply call the testName function rather than the command it is provided. It will 20 | // also pass through as arguments to the testName function the testRunName, the command and its 21 | // arguments. 22 | func fakeExecCmd(testName string, testRunName string, command string, args ...string) *exec.Cmd { 23 | cs := []string{fmt.Sprintf("-test.run=%s", testName), "--", testRunName} 24 | cs = append(cs, command) 25 | cs = append(cs, args...) 26 | cmd := exec.Command(os.Args[0], cs...) 27 | cmd.Env = []string{"GO_TEST_PROCESS=1"} 28 | return cmd 29 | } 30 | 31 | // BuildFakeExecCmd returns a fakeExecCmd for the testName and testRunName. 32 | // See platform/platform_common_test.go for an example of how to use it to mock exec.Cmd in tests. 33 | func BuildFakeExecCmd(testName string, testRunName string) ExecCmdFunc { 34 | return func(command string, args ...string) *exec.Cmd { 35 | return fakeExecCmd(testName, testRunName, command, args...) 36 | } 37 | } 38 | 39 | // ParseFakeExecCmdArgs parses the CLI's os.Args as passed by fakeExecCmd and returns the 40 | // testRunName, and cmdList. 41 | // Meant to be used from test functions that are called by a fakeExecCmd built with 42 | // BuildFakeExecCmd. 43 | func ParseFakeExecCmdArgs() (string, []string) { 44 | args := os.Args 45 | for len(args) > 0 { 46 | if args[0] == "--" { 47 | args = args[1:] 48 | break 49 | } 50 | args = args[1:] 51 | } 52 | 53 | if len(args) < 2 { 54 | fmt.Fprintf(os.Stderr, "No command\n") 55 | os.Exit(2) 56 | } 57 | 58 | return args[0], args[1:] 59 | } 60 | -------------------------------------------------------------------------------- /utils/dict_helpers.go: -------------------------------------------------------------------------------- 1 | // This file is licensed under the MIT License. 2 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 3 | // Copyright © 2015 Kentaro Kuribayashi 4 | // Copyright 2014-present Datadog, Inc. 5 | 6 | package utils 7 | 8 | import ( 9 | "fmt" 10 | "strconv" 11 | ) 12 | 13 | // GetString returns the dict[key] or an empty string if the entry doesn't exists 14 | func GetString(dict map[string]string, key string) string { 15 | if v, ok := dict[key]; ok { 16 | return v 17 | } 18 | return "" 19 | } 20 | 21 | // GetStringInterface returns the dict[key] or an empty string if the entry doesn't exists or the value is not a string 22 | func GetStringInterface(dict map[string]interface{}, key string) string { 23 | if v, ok := dict[key]; ok { 24 | vString, ok := v.(string) 25 | if !ok { 26 | return "" 27 | } 28 | return vString 29 | } 30 | return "" 31 | } 32 | 33 | // GetUint64 returns the dict[key] or an empty uint64 if the entry doesn't exists 34 | func GetUint64(dict map[string]string, key string, warnings *[]string) uint64 { 35 | if v, ok := dict[key]; ok { 36 | num, err := strconv.ParseUint(v, 10, 64) 37 | if err == nil { 38 | return num 39 | } 40 | *warnings = append(*warnings, fmt.Sprintf("could not parse '%s' from %s: %s", v, key, err)) 41 | return 0 42 | } 43 | return 0 44 | } 45 | 46 | // GetFloat64 returns the dict[key] or an empty uint64 if the entry doesn't exists 47 | func GetFloat64(dict map[string]string, key string, warnings *[]string) float64 { 48 | if v, ok := dict[key]; ok { 49 | num, err := strconv.ParseFloat(v, 64) 50 | if err == nil { 51 | return num 52 | } 53 | *warnings = append(*warnings, fmt.Sprintf("could not parse '%s' from %s: %s", v, key, err)) 54 | return 0 55 | } 56 | return 0 57 | } 58 | --------------------------------------------------------------------------------