├── .bonsai.yml ├── .github └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── .old.travis.yml ├── BUILDING.md ├── Gopkg.lock ├── Gopkg.toml ├── LICENSE ├── Makefile ├── README.md ├── TESTING.md ├── cmd ├── check_cpu │ ├── README.md │ ├── cmd │ │ └── root.go │ └── main.go ├── check_file_exists │ ├── README.md │ ├── cmd │ │ ├── root.go │ │ └── root_test.go │ └── main.go ├── check_http │ ├── README.md │ ├── cmd │ │ └── root.go │ └── main.go ├── check_memory │ ├── README.md │ ├── cmd │ │ └── root.go │ └── main.go ├── check_performance_counter │ ├── README.md │ ├── cmd │ │ └── root.go │ └── main.go ├── check_port │ ├── README.md │ ├── cmd │ │ ├── root.go │ │ └── root_test.go │ └── main.go ├── check_process │ ├── README.md │ ├── cmd │ │ ├── root.go │ │ ├── root_linux.go │ │ └── root_win.go │ └── main.go ├── check_process_cpu │ ├── README.md │ ├── cmd │ │ └── root.go │ └── main.go ├── check_process_memory │ ├── README.md │ ├── cmd │ │ └── root.go │ └── main.go ├── check_service │ ├── README.md │ ├── cmd │ │ ├── root.go │ │ ├── root_linux.go │ │ └── root_win.go │ └── main.go ├── check_uptime │ ├── README.md │ ├── cmd │ │ └── root.go │ └── main.go ├── check_user_group │ ├── README.md │ ├── cmd │ │ └── root.go │ └── main.go └── initcmd │ ├── initcmd.go │ └── initcmd_test.go ├── go.mod ├── go.sum ├── godel └── config │ ├── check-plugin.yml │ ├── dist-plugin.yml │ ├── format-plugin.yml │ ├── godel.properties │ ├── godel.yml │ ├── license-plugin.yml │ └── test-plugin.yml ├── godelw ├── lib ├── app │ └── nagiosfoundation │ │ ├── check_cpu.go │ │ ├── check_cpu_test.go │ │ ├── check_file_exists.go │ │ ├── check_file_exists_test.go │ │ ├── check_http.go │ │ ├── check_http_test.go │ │ ├── check_memory.go │ │ ├── check_memory_test.go │ │ ├── check_performance_counter.go │ │ ├── check_performance_counter_test.go │ │ ├── check_performance_counter_win.go │ │ ├── check_port.go │ │ ├── check_port_test.go │ │ ├── check_process.go │ │ ├── check_process_cpu.go │ │ ├── check_process_cpu_test.go │ │ ├── check_process_linux.go │ │ ├── check_process_memory.go │ │ ├── check_process_memory_test.go │ │ ├── check_process_test.go │ │ ├── check_process_win.go │ │ ├── check_uptime.go │ │ ├── check_uptime_test.go │ │ ├── check_user_group.go │ │ ├── check_user_group_test.go │ │ ├── resultmessage.go │ │ ├── resutmessage_test.go │ │ ├── servicestatus.go │ │ ├── servicestatus_test.go │ │ ├── servicestatuslinux.go │ │ └── servicestatuswin.go └── pkg │ ├── cpu │ ├── cpu.go │ ├── cpu_test.go │ ├── cpulinux.go │ ├── cpulinux_test.go │ ├── cpulinuxcore.go │ ├── cpulinuxcore_test.go │ ├── cpuwin.go │ └── cpuwin_test.go │ ├── memory │ ├── memory.go │ ├── memory_test.go │ ├── memorylinux.go │ ├── memorylinux_test.go │ ├── memorywin.go │ └── memorywin_test.go │ ├── nagiosformatters │ ├── nagiosformatters.go │ └── nagiosformatters_test.go │ ├── perfcounters │ ├── perfcounters.go │ └── perfcounters_test.go │ └── process │ ├── process.go │ └── process_test.go └── scripts ├── inject-name-version.sh └── validate-version.sh /.bonsai.yml: -------------------------------------------------------------------------------- 1 | --- 2 | description: "#{repo}" 3 | builds: 4 | - platform: "linux" 5 | arch: "amd64" 6 | asset_filename: "#{repo}-linux-amd64-#{version}.tgz" 7 | sha_filename: "#{repo}-linux-amd64-#{version}-sha512.txt" 8 | filter: 9 | - "entity.system.os == 'linux'" 10 | - "entity.system.arch == 'amd64'" 11 | 12 | - platform: "linux" 13 | arch: "386" 14 | asset_filename: "#{repo}-linux-386-#{version}.tgz" 15 | sha_filename: "#{repo}-linux-386-#{version}-sha512.txt" 16 | filter: 17 | - "entity.system.os == 'linux'" 18 | - "entity.system.arch == '386'" 19 | 20 | - platform: "Windows" 21 | arch: "amd64" 22 | asset_filename: "#{repo}-windows-amd64-#{version}.tgz" 23 | sha_filename: "#{repo}-windows-amd64-#{version}-sha512.txt" 24 | filter: 25 | - "entity.system.os == 'windows'" 26 | - "entity.system.arch == 'amd64'" 27 | 28 | - platform: "Windows" 29 | arch: "386" 30 | asset_filename: "#{repo}-windows-386-#{version}.tgz" 31 | sha_filename: "#{repo}-windows-386-#{version}-sha512.txt" 32 | filter: 33 | - "entity.system.os == 'windows'" 34 | - "entity.system.arch == '386'" 35 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | test: 9 | strategy: 10 | matrix: 11 | go-version: [1.20.x] 12 | os: [ubuntu-20.04] 13 | runs-on: ${{ matrix.os }} 14 | steps: 15 | - name: Install Go 16 | uses: actions/setup-go@v4 17 | with: 18 | go-version: ${{ matrix.go-version }} 19 | - name: Checkout code 20 | uses: actions/checkout@v2 21 | - name: VersionCheck 22 | id: versions 23 | run: | 24 | ./godelw version 25 | ./godelw project-version 26 | echo "::set-output name=tag_name::$(./godelw project-version)" 27 | - name: Package 28 | run: | 29 | make clean 30 | make package 31 | 32 | - name: Install GitHub CLI 33 | run: | 34 | sudo apt update 35 | sudo apt install gh 36 | 37 | - name: Upload Release Asset(s) 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | run: | 41 | for asset_file in ./out/package/*; do 42 | gh release upload "${{ steps.versions.outputs.tag_name }}" "$asset_file" --clobber 43 | done 44 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Testing 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | test: 8 | strategy: 9 | matrix: 10 | go-version: [1.20.x] 11 | os: [ubuntu-latest, windows-latest] 12 | runs-on: ${{ matrix.os }} 13 | steps: 14 | - name: Install Go 15 | uses: actions/setup-go@v4 16 | with: 17 | go-version: ${{ matrix.go-version }} 18 | - name: Checkout code 19 | uses: actions/checkout@v2 20 | - name: Test 21 | run: | 22 | make test 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | .DS_Store 3 | build.sh 4 | out 5 | bin 6 | vendor 7 | _vendor* 8 | coverage.* -------------------------------------------------------------------------------- /.old.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.12.x 5 | 6 | # Only clone the most recent commit. 7 | git: 8 | depth: 1 9 | 10 | install: 11 | - | 12 | if [ ! -d /home/travis/gopath/src/github.com/ncr-devops-platform ]; then 13 | mv /home/travis/gopath/src/github.com/* /home/travis/gopath/src/github.com/ncr-devops-platform 14 | fi 15 | - curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 16 | - dep ensure -v 17 | - git checkout . 18 | - ./godelw version 19 | - ./godelw project-version 20 | 21 | script: 22 | - go test -race -coverprofile=coverage.txt -covermode=atomic -v ./... && bash <(curl -s https://codecov.io/bash) 23 | - make clean 24 | - make package 25 | - scripts/validate-version.sh 26 | 27 | deploy: 28 | provider: releases 29 | api_key: 30 | secure: V31VHHJF+4yzDrsouJ65iEuaUJL6ZweYnb0sCDKUn3BEjot5otLzXmfOxSaDY1GsVvUxdjfQqBhdTGmXc9qxj6dCrc6wN3lnA/gD8cx+X7SX4r+lvh5xgFzjTwS9iEAj0DhIxBWZPApfcDzi4Ie8lslHwAQ0kaKACFacWZJC6dASbk9anE9z7f0pNi1Rvr7rtcemtW6mJmdA9oo8E01csn1YnfXhtKRLHXiJZgifhTrUowIOTK/qS+XwniWqwbo8ebX26cVcwOXRnuvB7ixdx6uF/JsGekPElLDiFwVYmz9Yv3QN/bfBY0Q/EAGbdnqTPCe6DOe6mxD3Ai3Drk8zxGjCa70jUPVXzQ6TyZ5Dkkul2unUnE3Ffb++SmQUArNO4ARgnl29AsVOmZiwPBEe43RhN37oVan1Ln9FWMmN8pURzlqW3knQAM8KJAhLredtK08se3EMtzrOVLwFZIrMGf9bUA5wDkEIZqsCzdXWbXfOqql7zL4MyFVpr/MZM+yatt0Xjc8WsoiOqEo7tf03bzubAJLLdesbC/q2APKjOK1L8S7ZXaGzEDbb34Yv07mXlz1AtPYWjrqZ5yrfRKJwcA1BSNdhi5ZRogXOtsMMz0hRii5O/6gNL3qhShMoL3ya4ZqZ+dtb0McLGNlqlgbtoCPlYVWc3uZeIdn/KeiEiwc= 31 | file_glob: true 32 | file: out/package/* 33 | skip_cleanup: true 34 | draft: true 35 | on: 36 | tags: true 37 | -------------------------------------------------------------------------------- /BUILDING.md: -------------------------------------------------------------------------------- 1 | # Building nagiosfoundation 2 | In addition to __Go__ and __Make__, building requires a couple of utilities. 3 | * [__gödel__](https://github.com/palantir/godel) build tool 4 | * [__dep__](https://github.com/golang/dep) dependency management tool 5 | 6 | During the build gödel will automatically install itself at `$HOME/.godel` but dep must be installed beforehand using the [dep installation instructions](https://github.com/golang/dep#installation) or follow the steps below for Linux. 7 | 8 | ### Create workspace 9 | ``` 10 | cd $GOPATH 11 | mkdir -p src/github.com/ncr-devops-platform/ 12 | cd src/github.com/ncr-devops-platform/ 13 | ``` 14 | 15 | ### Clone project 16 | ``` 17 | git clone https://github.com/ncr-devops-platform/nagios-foundation.git nagiosfoundation 18 | ``` 19 | 20 | ### Set project directory 21 | ``` 22 | cd nagiosfoundation 23 | ``` 24 | 25 | ### Install dep 26 | For Linux. Use the [dep installation instructions](https://github.com/golang/dep#installation) for other platforms. 27 | ``` 28 | mkdir $GOPATH/bin 29 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 30 | ``` 31 | 32 | ### Update dependencies 33 | ``` 34 | $GOPATH/bin/dep ensure -v 35 | ``` 36 | 37 | ### Build 38 | ``` 39 | make 40 | ``` 41 | 42 | ### Artifacts 43 | The build artifacts can be found in `out/build`. 44 | 45 | ## Releases 46 | The `Makefile` uses [`ghr`](https://github.com/tcnksm/ghr) to create the GitHub Release and upload artifacts. This would normally be done by the project owner and requires a GitHub API token. Refer to the [ghr project](https://github.com/tcnksm/ghr) for more information and install `ghr` with 47 | ``` 48 | go get -u github.com/tcnksm/ghr 49 | ``` 50 | The Makefile target is `release` and is used when lauching make with 51 | ``` 52 | make release 53 | ``` 54 | 55 | ## Individual Commands 56 | When developing it may be desirable to build a single command rather than the entire project. There are a few ways of doing this. Choose the one appropriate for you. 57 | 58 | ### Gödel 59 | The `Makefile` uses Gödel for building so this method is much like the `Makefile` method. 60 | 61 | To build a command for all `os-archs` as listed in the `godel/config/dist-plugin.yml` configuration. 62 | ``` 63 | ./godelw build check_process 64 | ``` 65 | 66 | ### Go Build 67 | The native `go build` command can also be used to build a command for a single operating system (default is your native OS). Note this command will drop the executable into the current directory. To change this, execute `go help build` and look for the `-o` option. 68 | ``` 69 | go build github.com/ncr-devops-platform/nagiosfoundation/cmd/check_process 70 | ``` 71 | 72 | To build for a different OS, use the `GOOS` and `GOARCH` environment variables. For example, to build an executable for Windows on Linux, use: 73 | ``` 74 | GOOS=windows GOARCH=amd64 go build -o check_process.exe github.com/ncr-devops-platform/nagiosfoundation/cmd/check_process 75 | ``` 76 | 77 | ### Adding a New Check 78 | 79 | This project uses a `Makefile` for builds. In turn, the `Makefile` uses [Gödel](https://github.com/palantir/godel) as the next step in the chain. Adding a new check consists of three main steps. Keep in mind these are general guidelines and don't necessarily need to be done in order. 80 | 81 | 1. Add the main logic for your check to `lib/app/nagiosfoundation`. Consider this directory as containing the APIs. As APIs, any code placed here should: gracefully handle all errors, not exit (such as calling `os.Exit()`), and not send output to the terminal. Input into the API will generally originate from command-line options and parameters from the user interface and passed as parameters into the API. Output from the API consists of a result check text string using `resultMesssage()` or one of the functions in the `nagiosformatters` package as well as a numeric representation of the check result which should be one of `statusTextOK`, `statusTextWarning`, or `statusTextCritical` which are `const`s found in `lib/app/nagiosfoundation/resultmessage.go`. Bonus points for implementing unit tests for new API code. Tests for the API code are especially helpful for us maintainers as it helps the CI process verify defects haven't been introduced. Even just exercising the code is helpful. 82 | 83 | 2. Add user facing logic to `cmd/`. Directories in `cmd/` contain the user interfaces which in this case consist of a command-line interface implemented by the [Cobra framework](https://github.com/spf13/cobra). User input consists of command-line options and parameters, with command output being the check result text received from the API and sent to the terminal, and the error code received from the API and returned to the shell with `os.Exit()`. The new check should output version information on demand. Make sure this is enabled with a call to `initcmd.AddVersionCommand()` because the Travis builds check this with `scripts/validate-version.sh` and will fail the build if a proper version is not output. Unit tests for this code is desireable but not as important as for API code. 84 | 85 | 3. Add the new check to the build process by editing `godel/config/dist-plugin.yaml` and adding your new check. Generally this is as simple as copying the entry for an existing check and making the obvious changes to the check names and perhaps build targets. -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | digest = "1:e3983013d82da8c49fb6776c6317d0da4b9cb926f1bb11af37f8c975889db042" 6 | name = "github.com/PaesslerAG/gval" 7 | packages = ["."] 8 | pruneopts = "UT" 9 | revision = "0ce847fc5e6163ee05ae770a441ba497fc77dcaa" 10 | version = "v1.0.1" 11 | 12 | [[projects]] 13 | digest = "1:e92f5581902c345eb4ceffdcd4a854fb8f73cf436d47d837d1ec98ef1fe0a214" 14 | name = "github.com/StackExchange/wmi" 15 | packages = ["."] 16 | pruneopts = "UT" 17 | revision = "5d049714c4a64225c3c79a7cf7d02f7fb5b96338" 18 | version = "1.0.0" 19 | 20 | [[projects]] 21 | digest = "1:440028f55cb322d8cb5b9d5ebec298a00b7d74690a658fe6b1c0c0b44341bfae" 22 | name = "github.com/go-ole/go-ole" 23 | packages = [ 24 | ".", 25 | "oleutil", 26 | ] 27 | pruneopts = "UT" 28 | revision = "97b6244175ae18ea6eef668034fd6565847501c9" 29 | version = "v1.2.4" 30 | 31 | [[projects]] 32 | digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be" 33 | name = "github.com/inconshreveable/mousetrap" 34 | packages = ["."] 35 | pruneopts = "UT" 36 | revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" 37 | version = "v1.0" 38 | 39 | [[projects]] 40 | digest = "1:5f81e80915d9312397c603b6da6f751a4ae36ae2b4d4f0474a34241d95738686" 41 | name = "github.com/pbnjay/memory" 42 | packages = ["."] 43 | pruneopts = "UT" 44 | revision = "974d429e7ae40c89e7dcd41cfcc22a0bfbe42510" 45 | 46 | [[projects]] 47 | digest = "1:7395b855a6078ad2e6c40311402a057a91125fb9f32cf228e1b32cdc57c33538" 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 = "8048a2e9c5773235122027dd585cf821b2af1249" 59 | version = "v2.18.07" 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:9fb64875be1386e75492979fbec6a89ebf1639fefd08ca85f37868944acf4ab6" 71 | name = "github.com/spf13/cobra" 72 | packages = ["."] 73 | pruneopts = "UT" 74 | revision = "ba1052d4cbce7aac421a96de820558f75199ccbc" 75 | 76 | [[projects]] 77 | digest = "1:c1b1102241e7f645bc8e0c22ae352e8f0dc6484b6cb4d132fa9f24174e0119e2" 78 | name = "github.com/spf13/pflag" 79 | packages = ["."] 80 | pruneopts = "UT" 81 | revision = "298182f68c66c05229eb03ac171abe6e309ee79a" 82 | version = "v1.0.3" 83 | 84 | [[projects]] 85 | digest = "1:48702a7b31ffeb1ccce5db38ba75998906a74479ec0c7f50546a58105ba44972" 86 | name = "github.com/thedevsaddam/gojsonq" 87 | packages = ["."] 88 | pruneopts = "UT" 89 | revision = "69113724147bdb0973fc7b64d71de08e6cd476cb" 90 | version = "v2.2.2" 91 | 92 | [[projects]] 93 | digest = "1:a8226dc96780af51cd3c9d03c5bfec3e5716cc1f3e5bbb88009025ecf078da15" 94 | name = "golang.org/x/sys" 95 | packages = [ 96 | "unix", 97 | "windows", 98 | "windows/svc", 99 | "windows/svc/mgr", 100 | ] 101 | pruneopts = "UT" 102 | revision = "90b0e4468f9980bf79a2290394adaf7f045c5d24" 103 | 104 | [solve-meta] 105 | analyzer-name = "dep" 106 | analyzer-version = 1 107 | input-imports = [ 108 | "github.com/PaesslerAG/gval", 109 | "github.com/StackExchange/wmi", 110 | "github.com/pbnjay/memory", 111 | "github.com/shirou/gopsutil/host", 112 | "github.com/spf13/cobra", 113 | "github.com/thedevsaddam/gojsonq", 114 | "golang.org/x/sys/windows", 115 | "golang.org/x/sys/windows/svc", 116 | "golang.org/x/sys/windows/svc/mgr", 117 | ] 118 | solver-name = "gps-cdcl" 119 | solver-version = 1 120 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | [prune] 28 | go-tests = true 29 | unused-packages = true 30 | 31 | [[constraint]] 32 | name = "github.com/StackExchange/wmi" 33 | version = "1.0.0" 34 | 35 | [[constraint]] 36 | name = "golang.org/x/sys" 37 | revision = "90b0e4468f9980bf79a2290394adaf7f045c5d24" 38 | 39 | [[constraint]] 40 | name = "github.com/spf13/cobra" 41 | revision = "ba1052d4cbce7aac421a96de820558f75199ccbc" 42 | 43 | [[constraint]] 44 | name = "github.com/pbnjay/memory" 45 | revision = "974d429e7ae40c89e7dcd41cfcc22a0bfbe42510" 46 | 47 | [[constraint]] 48 | name = "github.com/thedevsaddam/gojsonq" 49 | version = "2.2.2" 50 | 51 | [[constraint]] 52 | name = "github.com/PaesslerAG/gval" 53 | version = "1.0.1" 54 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # godel_version is only used to ensure all the godel bits 2 | # have been downloaded before determining the project-version. 3 | godel_version := $(shell ./godelw version) 4 | version := $(shell ./godelw project-version) 5 | package_path = ./out/package 6 | package_version = $(package_path)/$(version) 7 | platforms = windows-amd64 linux-amd64 windows-386 linux-386 8 | 9 | package: $(platforms) 10 | 11 | clean: 12 | ./godelw clean 13 | rm -rf $(package_path) 14 | rm -f coverage.txt coverage.html 15 | 16 | test: 17 | go test -v ./... 18 | 19 | test-godel: 20 | ./godelw test 21 | 22 | coverage: 23 | go test -race -coverprofile=coverage.txt -covermode=atomic ./... 24 | go tool cover -html=coverage.txt -o coverage.html 25 | 26 | release: clean package 27 | ghr $(version) $(package_path) 28 | 29 | build: 30 | ./godelw build 31 | 32 | $(platforms): build 33 | $(eval package_bin = $(package_version)/$@/bin) 34 | mkdir -p $(package_bin) 35 | ln ./out/build/*/$(version)*/$@/* $(package_bin)/. 36 | tar -zcvf $(package_path)/nagiosfoundation-$@-$(version).tgz -C $(package_version)/$@ bin 37 | (cd $(package_path) && sha512sum nagiosfoundation-$@-$(version).tgz) > $(package_path)/nagiosfoundation-$@-$(version)-sha512.txt 38 | rm -rf $(package_version) 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nagiosfoundation 2 | 3 | [![Build Status](https://travis-ci.org/ncr-devops-platform/nagiosfoundation.svg?branch=master)](https://travis-ci.org/ncr-devops-platform/nagiosfoundation) 4 | [![codecov](https://codecov.io/gh/ncr-devops-platform/nagiosfoundation/branch/master/graph/badge.svg)](https://codecov.io/gh/ncr-devops-platform/nagiosfoundation) 5 | 6 | A suite of Nagios style checks and metrics covering the basic needs for monitoring in a Sensu-like system. 7 | 8 | 9 | ## List of Checks 10 | * [CPU](https://github.com/ncr-devops-platform/nagios-foundation/blob/master/cmd/check_cpu/README.md) 11 | * [File Exists](https://github.com/ncr-devops-platform/nagios-foundation/blob/master/cmd/check_file_exists/README.md) 12 | * [HTTP](https://github.com/ncr-devops-platform/nagios-foundation/blob/master/cmd/check_http/README.md) 13 | * [Memory](https://github.com/ncr-devops-platform/nagios-foundation/blob/master/cmd/check_memory/README.md) 14 | * [Performance Counter](https://github.com/ncr-devops-platform/nagios-foundation/blob/master/cmd/check_performance_counter/README.md) 15 | * [Port](https://github.com/ncr-devops-platform/nagios-foundation/blob/master/cmd/check_port/README.md) 16 | * [Process](https://github.com/ncr-devops-platform/nagios-foundation/blob/master/cmd/check_process/README.md) 17 | * [Service](https://github.com/ncr-devops-platform/nagios-foundation/blob/master/cmd/check_service/README.md) 18 | * [Uptime](https://github.com/ncr-devops-platform/nagios-foundation/blob/master/cmd/check_uptime/README.md) 19 | * [User and Group](https://github.com/ncr-devops-platform/nagios-foundation/blob/master/cmd/check_user_group/README.md) 20 | * [Process CPU](https://github.com/ncr-devops-platform/nagios-foundation/blob/master/cmd/check_process_cpu/README.md) 21 | * [Process Memory](https://github.com/ncr-devops-platform/nagios-foundation/blob/master/cmd/check_process_memory/README.md) 22 | 23 | ## Using 24 | Use this collection of applications as [Sensu Go Checks](https://docs.sensu.io/sensu-go/5.5/reference/checks/) in your Sensu deployment. For example, to check every 60 seconds that the signage application is running on a remote kiosk where the Sensu Agent is subscribed to `signage`, run: 25 | 26 | ``` 27 | $ cat << EOF | sensuctl create 28 | { 29 | "type": "Asset", 30 | "api_version": "core/v2", 31 | "metadata": { 32 | "name": "nagiosfoundation-0.1.0", 33 | "namespace": "default" 34 | }, 35 | "spec": { 36 | "url": "https://github.com/ncr-devops-platform/nagios-foundation/releases/download/0.1.0/nagiosfoundation-linux-amd64-0.1.0.tgz", 37 | "sha512": "5cf2c7e7ec6a003da0c7a509efec64b75a952467bfe3494800ce9dd6f44a773c2a413968bffe3362287820e7c637a1aca8c3b743b0e8d29675fcb8e87db8a2cc" 38 | } 39 | } 40 | { 41 | "type": "CheckConfig", 42 | "api_version": "core/v2", 43 | "metadata": { 44 | "name": "process_signage", 45 | "namespace": "default" 46 | }, 47 | "spec": { 48 | "command": "check_process --name signage_app", 49 | "interval": 60, 50 | "publish": true, 51 | "runtime_assets": [ 52 | "nagiosfoundation-0.1.0" 53 | ], 54 | "subscriptions": [ 55 | "signage" 56 | ] 57 | } 58 | } 59 | EOF 60 | ``` 61 | 62 | --- 63 | 64 | ## Building and Contributing 65 | See [Build Instructions](https://github.com/ncr-devops-platform/nagios-foundation/blob/master/BUILDING.md) 66 | 67 | ## Testing and Code Coverage 68 | See [Testing](https://github.com/ncr-devops-platform/nagios-foundation/blob/master/TESTING.md) 69 | -------------------------------------------------------------------------------- /TESTING.md: -------------------------------------------------------------------------------- 1 | # Testing nagiosfoundation 2 | Unit tests for code submitted to the project are encouraged. Every code submission is subject to unit tests, verified for a successful build and test coverage from the unit tests is submitted for review. The `Makefile` contains a couple of test targets to encourage the use of unit tests and make them easy to execute. Before submitting code, verify unit tests are successful and the code has adequate test coverage. 3 | 4 | ## Executing Go Tests 5 | Go tests can be launched with the `Makefile` using the `test` target. The output is visual. 6 | ``` 7 | make test 8 | ``` 9 | 10 | ## Code Coverage 11 | Code coverage is launched with the `Makefile` by using the `coverage` target. Test output goes to `coverage.txt` and `coverage.html` files. View the html file using a browser to inspect the coverage. 12 | ``` 13 | make coverage 14 | ``` -------------------------------------------------------------------------------- /cmd/check_cpu/README.md: -------------------------------------------------------------------------------- 1 | # CPU Check 2 | The CPU check retrieves the CPU load as a percentage and if it is over `--critical` (default 95%), will output a `CRITICAL` response. If a `CRITICAL` response is not output, it compares it to `--warning` (default 85%) and if over, will output a `WARNING` response. Anything else will output an `OK` response. 3 | 4 | The `--critical` and `--warning` flags can be set on the command line as desired. 5 | 6 | A `--metric_name` flag can also be specified. This string is output in the response message in a Nagios format and is suitable for machine parsing. The default is `pct_processor_time`. 7 | 8 | ## Examples 9 | Default check values 10 | ``` 11 | check_cpu 12 | ``` 13 | 14 | Warning level set to 70% 15 | ``` 16 | check_cpu --warning 70 17 | ``` 18 | 19 | Override the metric name 20 | ``` 21 | check_cpu --metric_name cpu_percentage 22 | ``` 23 | -------------------------------------------------------------------------------- /cmd/check_cpu/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/ncr-devops-platform/nagiosfoundation/cmd/initcmd" 8 | "github.com/ncr-devops-platform/nagiosfoundation/lib/app/nagiosfoundation" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // Execute runs the root command 13 | func Execute() { 14 | var warning, critical int 15 | var metricName string 16 | 17 | var rootCmd = &cobra.Command{ 18 | Use: "check_cpu", 19 | Short: "Check the CPU usage.", 20 | Long: `Perform a CPU check by getting the usage percentage and if 21 | above --critical percentage, issue a CRITICAL response, else if it is 22 | above --warning percentage, issue a WARNING response. If it's below both 23 | of these, an OK response is issued.`, 24 | Run: func(cmd *cobra.Command, args []string) { 25 | cmd.ParseFlags(os.Args) 26 | msg, retval := nagiosfoundation.CheckCPU(warning, critical, metricName) 27 | 28 | fmt.Println(msg) 29 | os.Exit(retval) 30 | }, 31 | } 32 | 33 | initcmd.AddVersionCommand(rootCmd) 34 | 35 | rootCmd.Flags().IntVarP(&warning, "warning", "w", 85, "the average cpu threshold to issue a warning alert") 36 | rootCmd.Flags().IntVarP(&critical, "critical", "c", 95, "the average cpu threshold to issue a critical alert") 37 | rootCmd.Flags().StringVarP(&metricName, "metric_name", "m", "pct_processor_time", "the name of the metric generated by this check") 38 | 39 | if err := rootCmd.Execute(); err != nil { 40 | fmt.Println(err) 41 | os.Exit(1) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /cmd/check_cpu/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/ncr-devops-platform/nagiosfoundation/cmd/check_cpu/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /cmd/check_file_exists/README.md: -------------------------------------------------------------------------------- 1 | # File Exists Check 2 | 3 | Tests for the existence of one or more files matching the specified globbing pattern: 4 | 5 | `OK`: One or more files matched the globbing pattern. 6 | 7 | `CRITICAL`: No files matched the globbing pattern. 8 | 9 | When the `--negate` flag is used, the test logic is inverted: 10 | 11 | `OK`: No files matched the globbing pattern. 12 | 13 | `CRITICAL`: One or more files matched the globbing pattern. 14 | 15 | ## Options 16 | 17 | * `--pattern (-p)`: Filepath or globbing pattern to check for one or more existing file 18 | * `--negate (-n)`: If set, asserts filepath or globbing pattern should not match an existing file 19 | 20 | ## Globbing patterns 21 | 22 | [File globbing patterns specify sets of filenames using wildcard characters.][1] 23 | 24 | This plugin currently uses golang's `filepath.Glob` and therefore [does not support the ** double-star or globstar operator][2]. 25 | 26 | ## Examples 27 | 28 | Return OK if `/etc/resolv.conf` exists: 29 | 30 | `check_file_exists --pattern /etc/resolv.conf` 31 | 32 | Return a critical if files with `.dmp` extension exist in `/var/tmp`: 33 | 34 | `check_file_exists --pattern '/var/tmp/*.dmp' --negate` 35 | 36 | [1]: https://en.wikipedia.org/wiki/Glob_%28programming%29 37 | [2]: https://www.client9.com/golang-globs-and-the-double-star-glob-operator/ -------------------------------------------------------------------------------- /cmd/check_file_exists/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/ncr-devops-platform/nagiosfoundation/cmd/initcmd" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | // Execute runs the root command 12 | func Execute(apiCheckFileExists func(string, bool) (string, int)) int { 13 | var exitCode int 14 | var pattern string 15 | var negate bool 16 | 17 | var rootCmd = &cobra.Command{ 18 | Use: "check_file_exists", 19 | Short: "Check for the existence of one or more files matching specific filepath or globbing patterns.", 20 | Run: func(cmd *cobra.Command, args []string) { 21 | cmd.ParseFlags(os.Args) 22 | msg, retval := apiCheckFileExists(pattern, negate) 23 | 24 | fmt.Println(msg) 25 | exitCode = retval 26 | }, 27 | } 28 | 29 | initcmd.AddVersionCommand(rootCmd) 30 | 31 | rootCmd.Flags().StringVarP(&pattern, "pattern", "p", "", "Filepath or globbing pattern to check for one or more existing files") 32 | rootCmd.Flags().BoolVarP(&negate, "negate", "n", false, "Asserts filepath or globbing pattern should NOT match any existing file") 33 | 34 | if err := rootCmd.Execute(); err != nil { 35 | fmt.Fprintln(os.Stdout, err) 36 | exitCode = 1 37 | } 38 | 39 | return exitCode 40 | } 41 | -------------------------------------------------------------------------------- /cmd/check_file_exists/cmd/root_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestCheckFileExistsCmd(t *testing.T) { 9 | type testItem struct { 10 | description string 11 | arguments []string 12 | exitCode int 13 | } 14 | 15 | var expectedExitCode int 16 | 17 | testList := []testItem{ 18 | { 19 | description: "Invalid commmand", 20 | arguments: []string{"check_file_exists", "invalidcommand"}, 21 | exitCode: 1, 22 | }, 23 | { 24 | description: "Valid command, exit code 0", 25 | arguments: []string{"check_file_exists"}, 26 | exitCode: 0, 27 | }, 28 | { 29 | description: "Valid command, exit code 2", 30 | arguments: []string{"check_file_exists"}, 31 | exitCode: 2, 32 | }, 33 | } 34 | 35 | apiCheckFileExists := func(pattern string, negate bool) (string, int) { 36 | return "Test Message", expectedExitCode 37 | } 38 | 39 | savedArgs := os.Args 40 | 41 | for _, i := range testList { 42 | os.Args = i.arguments 43 | expectedExitCode = i.exitCode 44 | actualExitCode := Execute(apiCheckFileExists) 45 | if actualExitCode != i.exitCode { 46 | t.Errorf("%s: Expected Code: %d, Actual Code: %d", i.description, i.exitCode, actualExitCode) 47 | } 48 | } 49 | 50 | os.Args = savedArgs 51 | } 52 | -------------------------------------------------------------------------------- /cmd/check_file_exists/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/ncr-devops-platform/nagiosfoundation/cmd/check_file_exists/cmd" 7 | "github.com/ncr-devops-platform/nagiosfoundation/lib/app/nagiosfoundation" 8 | ) 9 | 10 | func main() { 11 | os.Exit(cmd.Execute(nagiosfoundation.CheckFileExists)) 12 | } 13 | -------------------------------------------------------------------------------- /cmd/check_http/README.md: -------------------------------------------------------------------------------- 1 | # HTTP Check 2 | Performs an HTTP GET request and returns a result based on the HTTP response code and if requested, an expected value or expression. 3 | 4 | - `OK`: HTTP response code was not >= 300 and if requested, there was a match on the expected value or expression 5 | - `WARNING`: HTTP response code was >= 300 and < 400 6 | - `CRITICAL`: Connection failed or HTTP response code was >= 400 or on a failed match if an expected value or expression was supplied 7 | 8 | ## Options 9 | - `--url` (`-u`): The URL to check. Required. 10 | - `--insecure` (`-k`): Do not validate the server's certificate 11 | - `--redirect` (`-r`): If set, follow redirects. Default is do not follow redirects. 12 | - `--timeout` (`-t`): Timeout in seconds to wait for HTTP server response. Default is 15 seconds. 13 | - `--path` (`-p`) and `--expression`: Used together. A json path and expression value to compare. Use this rather than `--path` and `--expectedValue` for making comparisons. 14 | - `--path` (`-p`) and `--expectedValue` (`-e`): Used together. `--path` is the json path for retrieving a value and `--expectedValue` is the value to expected at the path. Use `--expression` instead for a more consistent interface. 15 | 16 | ## Examples 17 | Check `example.com` for good response 18 | ``` 19 | check_http --url http://www.example.com 20 | ``` 21 | 22 | Check `example.com` for good response within 2 seconds 23 | ``` 24 | check_http --url http://www.example.com --timeout 2 25 | ``` 26 | 27 | ## Using Expressions 28 | Use expressions (`--expression`) for the ability to make comparisons other than simple string equality to a json field. 29 | 30 | This option implements the [Gval expression evaluation package](https://github.com/PaesslerAG/gval) to compare against a json field, yielding the ability to use `==`, `!=`, `>`, `<`, `>=`, `<=` and probably more. These comparisons will work strings and numbers. The string or number comparison made depends on the use of double-quotes after the comparison operand. 31 | 32 | Some examples. `== "comparetothistring"` gives a string comparison while `== 1337` gives a number comparison. Note the use of these quotes is important because `"1337" < "500"` (string comparison) will yield `true` while `1337 < 500` (number comparison) will yield `false`. 33 | 34 | Consider using `--expression` rather than `--expectedValue`. 35 | 36 | Some examples of how to use this `--expression` option... 37 | 38 | The body returned for the example URL used below is 39 | 40 | ``` 41 | $ curl -s -H "Accept: application/json" https://icanhazdadjoke.com/j/HeaFdiyIJe | jq '.' 42 | { 43 | "id": "HeaFdiyIJe", 44 | "joke": "What kind of magic do cows believe in? MOODOO.", 45 | "status": 200 46 | } 47 | ``` 48 | 49 | For starters, here's how the `--expectedValue` and `--expression` options can be equally used. 50 | 51 | ``` 52 | check_http --url https://icanhazdadjoke.com/j/HeaFdiyIJe --format json --path id --expectedValue HeaFdiyIJe 53 | 54 | check_http --url https://icanhazdadjoke.com/j/HeaFdiyIJe --format json --path id --expression '== "HeaFdiyIJe"' 55 | ``` 56 | 57 | Note the use of double-quotes above. That's because strings are being compared. If strings were not used, it would give: 58 | 59 | ``` 60 | check_http --url https://icanhazdadjoke.com/j/HeaFdiyIJe --format json --path id --expression '== HeaFdiyIJe' 61 | CheckHttp CRITICAL - Url https://icanhazdadjoke.com/j/HeaFdiyIJe responded with 200. The value found at id with value HeaFdiyIJe does not match expression "== HeaFdiyIJe" 62 | ``` 63 | 64 | More examples using other operators (continue to note the use of double-quotes for strings): 65 | 66 | ``` 67 | check_http --url https://icanhazdadjoke.com/j/HeaFdiyIJe --format json --path id --expression '<= "IeaFdiyIJe"' 68 | CheckHttp OK - Url https://icanhazdadjoke.com/j/HeaFdiyIJe responded with 200. The value found at id with value HeaFdiyIJe and expression "<= "IeaFdiyIJe"" yields true 69 | 70 | check_http --url https://icanhazdadjoke.com/j/HeaFdiyIJe --format json --path id --expression '>= "HeaFdiyIJd"' 71 | CheckHttp OK - Url https://icanhazdadjoke.com/j/HeaFdiyIJe responded with 200. The value found at id with value HeaFdiyIJe and expression ">= "HeaFdiyIJd"" yields true 72 | 73 | check_http --url https://icanhazdadjoke.com/j/HeaFdiyIJe --format json --path id --expression '!= "notequal"' 74 | CheckHttp OK - Url https://icanhazdadjoke.com/j/HeaFdiyIJe responded with 200. The value found at id with value HeaFdiyIJe and expression "!= "notequal"" yields true 75 | ``` 76 | 77 | And examples using number comparisons (double-quotes not used) 78 | 79 | ``` 80 | check_http --url https://icanhazdadjoke.com/j/HeaFdiyIJe --format json --path status --expression '== 200' 81 | CheckHttp OK - Url https://icanhazdadjoke.com/j/HeaFdiyIJe responded with 200. The value found at status with value 200 and expression "== 200" yields true 82 | 83 | check_http --url https://icanhazdadjoke.com/j/HeaFdiyIJe --format json --path status --expression '>= 200' 84 | CheckHttp OK - Url https://icanhazdadjoke.com/j/HeaFdiyIJe responded with 200. The value found at status with value 200 and expression ">= 200" yields true 85 | 86 | check_http --url https://icanhazdadjoke.com/j/HeaFdiyIJe --format json --path status --expression '< 400' 87 | CheckHttp OK - Url https://icanhazdadjoke.com/j/HeaFdiyIJe responded with 200. The value found at status with value 200 and expression "< 400" yields true 88 | ``` 89 | 90 | Finally, an example using string comparison especially for Windows users at the Command Prompt shell. Notice the `--expression` option is contained in double-quotes and the double-quotes inside the option are given by using two double-quotes. 91 | 92 | ``` 93 | check_http.exe --url https://icanhazdadjoke.com/j/HeaFdiyIJe --format json --path id --expression "== ""HeaFdiyIJe""" 94 | CheckHttp OK - Url https://icanhazdadjoke.com/j/HeaFdiyIJe responded with 200. The value found at id with value HeaFdiyIJe and expression "== "HeaFdiyIJe"" yields true 95 | ``` 96 | -------------------------------------------------------------------------------- /cmd/check_http/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/ncr-devops-platform/nagiosfoundation/cmd/initcmd" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | // Execute runs the root command 12 | func Execute(apiCheckHTTP func(string, bool, bool, string, int, string, string, string, string) (string, int)) int { 13 | var redirect, insecure bool 14 | var exitCode, timeout int 15 | var url, format, path, expectedValue, expression, host string 16 | 17 | var rootCmd = &cobra.Command{ 18 | Use: "check_http", 19 | Short: "Check the response code of an http request.", 20 | Long: `Perform an HTTP get request and assert whether it is OK, warning or critical.`, 21 | Run: func(cmd *cobra.Command, args []string) { 22 | cmd.ParseFlags(os.Args) 23 | msg, retval := apiCheckHTTP(url, redirect, insecure, host, timeout, format, path, expectedValue, expression) 24 | 25 | fmt.Println(msg) 26 | exitCode = retval 27 | }, 28 | } 29 | 30 | initcmd.AddVersionCommand(rootCmd) 31 | 32 | rootCmd.Flags().StringVarP(&url, "url", "u", "http://127.0.0.1", "the URL to check") 33 | rootCmd.Flags().BoolVarP(&redirect, "redirect", "r", false, "follow redirects?") 34 | rootCmd.Flags().BoolVarP(&insecure, "insecure", "k", false, "do not validate certificate") 35 | rootCmd.Flags().IntVarP(&timeout, "timeout", "t", 15, "timeout in seconds") 36 | rootCmd.Flags().StringVarP(&host, "host", "H", "", "The host header for the request") 37 | rootCmd.Flags().StringVarP(&format, "format", "f", "", "The expected response format: json") 38 | rootCmd.Flags().StringVarP(&path, "path", "p", "", "The path in the return value data to test against the expected value") 39 | rootCmd.Flags().StringVarP(&expectedValue, "expectedValue", "e", "", "The expected response data value") 40 | rootCmd.Flags().StringVarP(&expression, "expression", "", "", "Expression to evaluate against response data value") 41 | 42 | if err := rootCmd.Execute(); err != nil { 43 | fmt.Fprintln(os.Stdout, err) 44 | exitCode = 1 45 | } 46 | 47 | return exitCode 48 | } 49 | -------------------------------------------------------------------------------- /cmd/check_http/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/ncr-devops-platform/nagiosfoundation/cmd/check_http/cmd" 7 | "github.com/ncr-devops-platform/nagiosfoundation/lib/app/nagiosfoundation" 8 | ) 9 | 10 | func main() { 11 | os.Exit(cmd.Execute(nagiosfoundation.CheckHTTP)) 12 | } 13 | -------------------------------------------------------------------------------- /cmd/check_memory/README.md: -------------------------------------------------------------------------------- 1 | # Memory Check 2 | The memory check (`check_memory`) checks the available memory as reported by the OS. It queries the OS for the amount of available memory, the amount of free memory, then calculates a memory used percentage. That memory used percentage is then compared against the `--warning` and `--critical` thresholds and an appropriate check result is output. 3 | 4 | ## Flags 5 | * `--warning`: The percentage of used memory required to trigger a warning condition. Default `85`. 6 | * `--critical`: The percentage of used memory required to trigger a critical condition. Default `95`. 7 | * `--metric_name`: The name used in the nagios portion of the message output. Default `memory_used_percentage`. 8 | 9 | ## Examples 10 | Issue a warning if memory usage is over 50% and critical if usage is over the default of 95%. 11 | ``` 12 | check_memory --warning 50 13 | ``` -------------------------------------------------------------------------------- /cmd/check_memory/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/ncr-devops-platform/nagiosfoundation/cmd/initcmd" 8 | "github.com/ncr-devops-platform/nagiosfoundation/lib/app/nagiosfoundation" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // Execute runs the root command 13 | func Execute() { 14 | var warning, critical int 15 | var metricName string 16 | 17 | var rootCmd = &cobra.Command{ 18 | Use: "check_memory", 19 | Short: "Determine if memory used exceeds percentage threshold.", 20 | Long: `Determines the percentage of memory used and if over the --critical 21 | threshold issue a CRITICAL response, then check if over the --warning threshold, 22 | issue a WARNING response. Otherwise, an OK response is issued.`, 23 | Run: func(cmd *cobra.Command, args []string) { 24 | cmd.ParseFlags(os.Args) 25 | msg, retval := nagiosfoundation.CheckMemory("", warning, critical, metricName) 26 | 27 | fmt.Println(msg) 28 | os.Exit(retval) 29 | }, 30 | } 31 | 32 | initcmd.AddVersionCommand(rootCmd) 33 | 34 | rootCmd.Flags().IntVarP(&warning, "warning", "w", 85, "the memory threshold to issue a warning alert") 35 | rootCmd.Flags().IntVarP(&critical, "critical", "c", 95, "the memory threshold to issue a critical alert") 36 | rootCmd.Flags().StringVarP(&metricName, "metric_name", "m", "available_memory_percent", "the name of the metric generated by this check") 37 | 38 | if err := rootCmd.Execute(); err != nil { 39 | fmt.Println(err) 40 | os.Exit(1) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /cmd/check_memory/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/ncr-devops-platform/nagiosfoundation/cmd/check_memory/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /cmd/check_performance_counter/README.md: -------------------------------------------------------------------------------- 1 | # Performance Counter Check 2 | The performance counter check is Windows only. It retreives a Windows Performance Counter (`--counter_name`) and compares it to `--critical` and `--warning` then outputs an appropriate responsed based on the check. Many flags make this check quite configurable. 3 | 4 | The defaults for this check have the `--critical` and `--warning` values set to `0`, and the counter value retrieved is compared to be lesser than those values. Generally a counter value will be `> 0`, causing this check to generally emit an `OK` response when using these defaults. 5 | 6 | * `--counter_name (-n)`: The Performance Counter to fetch. No default and therefore it must be specified. This can take multiple counters as an input and generates the data for the given counters. Multiple counters can be passed seperated by a comma as shown in the example below. 7 | When passing multiple counters, if the flags like `-m or -g or -w or -c` are passed to the perf counter then it throws an error. Therefore, multiple performance counter is used for data retrival but it doesn't support the comparison of warning and critical values just like single performance counter. 8 | 9 | * `--greater_than (-g)`: If set, the Performance Counter value is compared to be greater than the `--critical` and `--warning (-w)` values. The default value of `false` causes the comparison to be less than the `--critical` and `--warning` values. Do not specify a flag argument when using. 10 | * `--critical (-c)`: The value the performance counter is compared with to determine a `CRITICAL` response. The default is `0`. 11 | * `--warning (-w)`: The value the performance counter is compared with to determine a `WARNING` response. The default is `0`. 12 | * `--polling_attempts (-a)`: The number of times to attempt retrieval of the Performance Counter. Default is `2`. 13 | * `--polling_delay (-d)`: The delay, in seconds, between polling attempts. Default is `1`. 14 | * `--metric_name (-m)`: String output in the response message in a Nagios format and is suitable for machine parsing. There is no default. 15 | 16 | ## Examples 17 | Specify only the required counter name 18 | ``` 19 | check_performance_counter --counter_name "\IPv4\Datagrams/sec" 20 | ``` 21 | 22 | ## Example for passing multiple inputs for performance counter 23 | 24 | Pass all the commands for which you want to get the data 25 | ``` 26 | check_performance_counter -n "\LogicalDisk(C:)\% Free Space, \LogicalDisk(C:)\FreeMegaBytes, \system\system up time, \TCPv4\ConnectionsEstablished" 27 | ``` 28 | This will print us the data(values) for the above commands in the order. 29 | If all the counters defined are available, it prints the values and then the overall check status will be a success(0) 30 | If any of the counters defined are not available, it displays the error and overall check status will be critical(2) 31 | 32 | ## Helpful Hints 33 | To get a list of Performance Counters available on a system use 34 | ``` 35 | TypePerf.exe –q 36 | ``` 37 | -------------------------------------------------------------------------------- /cmd/check_performance_counter/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/ncr-devops-platform/nagiosfoundation/cmd/initcmd" 9 | "github.com/ncr-devops-platform/nagiosfoundation/lib/app/nagiosfoundation" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | // Execute runs the root command 14 | func Execute() { 15 | var greaterThan bool 16 | var warning, critical float64 17 | var pollingAttempts, pollingDelay int 18 | var metricName string 19 | var counterName []string 20 | var finalretval int 21 | finalretval = 0 22 | 23 | var rootCmd = &cobra.Command{ 24 | Use: "check_performance_counter", 25 | Short: "Retrieve and compare values on a performance counter.", 26 | Long: `The performance counter check is Windows only. It retrieves a Windows Performance Counter 27 | (--counter_name) and compares it to --critical and --warning then outputs an appropriate response 28 | based on the check. Many flags make this check quite configurable. 29 | 30 | The defaults for this check have the --critical and --warning values set to 0, and the counter value 31 | retrieved is compared to be lesser than those values. Generally a counter value will be > 0, causing 32 | this check to emit an OK response when using these defaults.`, 33 | Run: func(cmd *cobra.Command, args []string) { 34 | if len(counterName) != 0 && !(len(counterName) > 1 && (metricName != "" || warning != 0 || critical != 0 || greaterThan)) { 35 | var empty_cntr bool 36 | empty_cntr = false 37 | for _, counter := range counterName { 38 | counter = strings.TrimSpace(counter) 39 | if len(counter) == 0 { 40 | empty_cntr = true 41 | continue 42 | } 43 | msg, retval := nagiosfoundation.CheckPerformanceCounter(warning, critical, greaterThan, pollingAttempts, 44 | pollingDelay, metricName, counter) 45 | fmt.Println(msg) 46 | if finalretval < retval { 47 | finalretval = retval 48 | } 49 | } 50 | if empty_cntr { 51 | fmt.Println("Input given is an empty counter, pass a valid counter name") 52 | os.Exit(2) 53 | } 54 | os.Exit(finalretval) 55 | } else { 56 | fmt.Println("When passing multiple counters to the performance counter -m(--metricname) -w(--warning) -c(--critical) -g(greaterthan) flags cannot be used.") 57 | os.Exit(2) 58 | } 59 | }, 60 | } 61 | 62 | initcmd.AddVersionCommand(rootCmd) 63 | 64 | const counterNameFlag = "counter_name" 65 | rootCmd.Flags().StringSliceVarP(&counterName, counterNameFlag, "n", nil, "the name of the performance counter to check") 66 | rootCmd.MarkFlagRequired(counterNameFlag) 67 | rootCmd.Flags().StringVarP(&metricName, "metric_name", "m", "", "the name of the metric generated by this check") 68 | rootCmd.Flags().Float64VarP(&warning, "warning", "w", 0.0, "the threshold to issue a warning alert") 69 | rootCmd.Flags().Float64VarP(&critical, "critical", "c", 0.0, "the threshold to issue a critical alert") 70 | rootCmd.Flags().BoolVarP(&greaterThan, "greater_than", "g", false, "issue warnings if the metric is greater than the expected thresholds (default false)") 71 | rootCmd.Flags().IntVarP(&pollingAttempts, "polling_attempts", "a", 2, "the number of times to fetch and average the performance counter") 72 | rootCmd.Flags().IntVarP(&pollingDelay, "polling_delay", "d", 1, "the number of seconds to delay between polling attempty_cntrs") 73 | 74 | if err := rootCmd.Execute(); err != nil { 75 | fmt.Println(err) 76 | os.Exit(1) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /cmd/check_performance_counter/main.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/ncr-devops-platform/nagiosfoundation/cmd/check_performance_counter/cmd" 7 | ) 8 | 9 | func main() { 10 | cmd.Execute() 11 | } 12 | -------------------------------------------------------------------------------- /cmd/check_port/README.md: -------------------------------------------------------------------------------- 1 | # Port Check 2 | The port check attempts to connect to an address specified with the `--address` or `-a` flag and port specified with the `--port` or `-p` flag. 3 | 4 | The check can be further refined by using the `--timeout` or `-t` flag to customize the connection wait time and the `--invert` or `-i` flag to return success when a port is not being listened on. Execute `check_port --help` for more details. 5 | 6 | With `--invert` set to the default of `false`, `check_port` will return `OK` (exit code of `0`) if the address and port connection is successful. Otherwise it will return `CRITICAL` (exit code `2`). 7 | 8 | The Nagios metric name defaults to `listening_port` but can be changed with the `--metric_name` or `-m` option. 9 | 10 | ## Listener on Address and Port 11 | ``` 12 | $ check_port --address www.google.com --port 443 13 | CheckPort OK - port 443 on www.google.com using tcp | listening_port=0 14 | ``` 15 | 16 | ## No Listener on Address and Port 17 | ``` 18 | $ check_port --address www.google.com --port 81 --timeout 2 19 | CheckPort CRITICAL - port 81 on www.google.com using tcp (dial tcp 216.58.192.228:81: i/o timeout) | listening_port=2 20 | ``` 21 | 22 | ## Inverted No Listener on Address and Port 23 | ``` 24 | $ check_port --address www.google.com --port 81 --timeout 2 --invert 25 | CheckPort OK - port 81 on www.google.com using tcp (dial tcp 216.58.192.228:81: i/o timeout) | listening_port=0 26 | ``` 27 | -------------------------------------------------------------------------------- /cmd/check_port/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/ncr-devops-platform/nagiosfoundation/cmd/initcmd" 8 | nf "github.com/ncr-devops-platform/nagiosfoundation/lib/app/nagiosfoundation" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | const ( 13 | addressFlag = "address" 14 | portFlag = "port" 15 | ) 16 | 17 | // Execute runs the root command 18 | func Execute(apiCheckPort func(nf.CheckPortProtocol, string, int, int, bool, string) (string, int)) int { 19 | var machine, metricName string 20 | var port, timeout, exitCode int 21 | var invert bool 22 | 23 | var rootCmd = &cobra.Command{ 24 | Use: "check_port", 25 | Short: "Check for a listening port on a machine.", 26 | Long: `Check for a listening port on a networked machine by attempting to connect to it.`, 27 | Run: func(cmd *cobra.Command, args []string) { 28 | cmd.ParseFlags(os.Args) 29 | msg, retval := apiCheckPort(nf.CheckPortProtocolTCP, machine, port, timeout, invert, metricName) 30 | 31 | fmt.Println(msg) 32 | exitCode = retval 33 | }, 34 | } 35 | 36 | initcmd.AddVersionCommand(rootCmd) 37 | 38 | rootCmd.Flags().StringVarP(&machine, addressFlag, "a", "", "the address to check") 39 | rootCmd.MarkFlagRequired(addressFlag) 40 | rootCmd.Flags().IntVarP(&port, portFlag, "p", 0, "port number to check") 41 | rootCmd.MarkFlagRequired(portFlag) 42 | rootCmd.Flags().IntVarP(&timeout, "timeout", "t", 30, "timeout in seconds") 43 | rootCmd.Flags().BoolVarP(&invert, "invert", "i", false, "return OK on failure to connect") 44 | rootCmd.Flags().StringVarP(&metricName, "metric_name", "m", "listening_port", "the name of the metric generated by this check") 45 | 46 | if err := rootCmd.Execute(); err != nil { 47 | fmt.Fprintln(os.Stdout, err) 48 | exitCode = 1 49 | } 50 | 51 | return exitCode 52 | } 53 | -------------------------------------------------------------------------------- /cmd/check_port/cmd/root_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | nf "github.com/ncr-devops-platform/nagiosfoundation/lib/app/nagiosfoundation" 8 | ) 9 | 10 | func TestCheckPortCmd(t *testing.T) { 11 | var actualExitCode, expectedExitCode int 12 | checkName := "check_port" 13 | 14 | apiCheckPort := func(protocol nf.CheckPortProtocol, address string, port, timeout int, invert bool, metricName string) (string, int) { 15 | return "Test Message", 0 16 | } 17 | 18 | savedArgs := os.Args 19 | 20 | os.Args = []string{checkName, "--address", "localhost", "--port", "80"} 21 | expectedExitCode = 0 22 | actualExitCode = Execute(apiCheckPort) 23 | if actualExitCode != expectedExitCode { 24 | t.Errorf("%s: Expected Code: %d, Actual Code: %d", "check_port_1", expectedExitCode, actualExitCode) 25 | } 26 | 27 | os.Args = []string{checkName, "invalidcommand"} 28 | expectedExitCode = 1 29 | actualExitCode = Execute(apiCheckPort) 30 | if actualExitCode != expectedExitCode { 31 | t.Errorf("%s: Expected Code: %d, Actual Code: %d", "check_port_2", expectedExitCode, actualExitCode) 32 | } 33 | 34 | os.Args = savedArgs 35 | } 36 | -------------------------------------------------------------------------------- /cmd/check_port/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/ncr-devops-platform/nagiosfoundation/cmd/check_port/cmd" 7 | "github.com/ncr-devops-platform/nagiosfoundation/lib/app/nagiosfoundation" 8 | ) 9 | 10 | func main() { 11 | os.Exit(cmd.Execute(nagiosfoundation.CheckPort)) 12 | } 13 | -------------------------------------------------------------------------------- /cmd/check_process/README.md: -------------------------------------------------------------------------------- 1 | # Process Check 2 | The process check attempts to find a process by name specified with the `--name (-n) flag`. The result of the check depends on the value of the `--type (-t)` flag. If the `--type` flag is not specified, the default is `running`. Valid types are: 3 | * `running`: If the process is found, the check returns an `OK` result, otherwise it returns `CRITICAL`. 4 | * `notrunning` If the flag is not found, the check returns `OK` result, otherwise it returns `CRITICAL`. 5 | 6 | ## Process Running 7 | ``` 8 | check_process --name bash --type running 9 | ``` 10 | 11 | ## Process Not Running 12 | ``` 13 | check_process --name invalidname --type notrunning 14 | ``` 15 | -------------------------------------------------------------------------------- /cmd/check_process/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/ncr-devops-platform/nagiosfoundation/cmd/initcmd" 8 | "github.com/ncr-devops-platform/nagiosfoundation/lib/app/nagiosfoundation" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // Execute runs the root command 13 | func Execute() { 14 | var name, checkType, metricName string 15 | 16 | var rootCmd = &cobra.Command{ 17 | Use: "check_process", 18 | Short: "Determine if a process is running.", 19 | Long: `Perform a check for a process by name to determine if the process 20 | is running or not running. The default is to check for a running process. 21 | 22 | The --name (-n) option is always required. 23 | ` + getHelpOsConstrained(), 24 | Run: func(cmd *cobra.Command, args []string) { 25 | cmd.ParseFlags(os.Args) 26 | msg, retcode := nagiosfoundation.CheckProcess(name, checkType, metricName) 27 | 28 | fmt.Println(msg) 29 | os.Exit(retcode) 30 | }, 31 | } 32 | 33 | initcmd.AddVersionCommand(rootCmd) 34 | 35 | const nameFlag = "name" 36 | rootCmd.Flags().StringVarP(&name, nameFlag, "n", "", "process name") 37 | rootCmd.MarkFlagRequired(nameFlag) 38 | rootCmd.Flags().StringVarP(&checkType, "type", "t", "running", "Supported types are \"running\" and \"notrunning\"") 39 | rootCmd.Flags().StringVarP(&metricName, "metric_name", "m", "process_state", "the name of the metric generated by this check") 40 | 41 | if err := rootCmd.Execute(); err != nil { 42 | fmt.Println(err) 43 | os.Exit(1) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /cmd/check_process/cmd/root_linux.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package cmd 4 | 5 | func getHelpOsConstrained() string { 6 | return ` 7 | Note: Process names in POSIX systems are case sensitive.` 8 | } 9 | -------------------------------------------------------------------------------- /cmd/check_process/cmd/root_win.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package cmd 4 | 5 | func getHelpOsConstrained() string { 6 | return ` 7 | Note: Process names in Windows are not case sensitive.` 8 | } 9 | -------------------------------------------------------------------------------- /cmd/check_process/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/ncr-devops-platform/nagiosfoundation/cmd/check_process/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /cmd/check_process_cpu/README.md: -------------------------------------------------------------------------------- 1 | # Process CPU check 2 | The process cpu check (`check_process_cpu`) checks the CPU utilization of a process as reported by OS. Retrieved percentage is compared against `--warning` and `--critical` thresholds and an appropriate check result is output. 3 | 4 | ## Linux process CPU check 5 | On linux machines there are 2 modes of operation supported: 6 | - Global CPU utilization of a process (global usage on OS) 7 | - Per-core CPU utilization of a process 8 | 9 | ### Global CPU check 10 | Global CPU check uses `top` command in batch mode to find out CPU utilization by a process. Multiple samples will be taken to determine utilization. Returned percentage is normalized for core count (i.e. range is always `[0; 100]`). 11 | > First sample of the `top` command will be ignored as it reports process' lifetime stats on old machines. 12 | 13 | ### Per-core CPU check 14 | Per-core CPU check uses `pidstat` command. Multiple samples will be taken. Core utilization will be averaged based on amount of non-0 per-core values reported. 15 | 16 | For example: 17 | - sample 0: core 0 - 20.0% 18 | - sample 1: core 0 - 10.0%, core 2 - 30.0% 19 | - sample 2: core 1 - 15.0% 20 | 21 | Results in: 22 | - core 0 - 15.0% 23 | - core 1 - 15.0% 24 | - core 2 - 30.0% 25 | 26 | 30.0% will be reported by the check (as the highest core utilization). 27 | 28 | ## Windows process CPU check 29 | On windows machines only global CPU utilization of a process can be retrieved. Windows Management Instrumentation (WMI) will be used and multiple samples will be taken to calculate average CPU utilization of a process. Returned percentage is normalized for core count (i.e. range is always `[0; 100]`). 30 | 31 | ## Flags 32 | > Run this check with help command to get latest information, i.e. `check_process_cpi help` 33 | * `--warning`: The percentage of CPU utilization required to trigger a warning condition. Default: `85`. 34 | * `--critical`: The percentage of CPU utilization required to trigger a critical condition. Default: `95`. 35 | * `--metric_name`: The name used in the nagios portion of the message output. Default `process_cpu_percentage`. 36 | * `--process_name`: **Required.** The name of the process for which to query CPU usage. Process name is **case-sensitive**. On windows use executable name without extension (e.g. `chrome`). On linux use command name (e.g. `cbdaemon`) 37 | * `--core`: When included, check will return highest CPU **Core** utilization instead of global utilization. *Only linux machines support this flag; on windows machines this flag will be ignored.* 38 | 39 | ## Examples 40 | Issue a warning if global CPU utilization is over 50% for chrome 41 | ``` 42 | check_process_cpu --process_name chrome --warning 50 43 | ``` 44 | 45 | Issue a critical alert if per-core CPU utilization is over 80% for chrome 46 | ``` 47 | check_process_cpu --process_name chrome --critical 80 --core 48 | ``` -------------------------------------------------------------------------------- /cmd/check_process_cpu/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/ncr-devops-platform/nagiosfoundation/cmd/initcmd" 8 | "github.com/ncr-devops-platform/nagiosfoundation/lib/app/nagiosfoundation" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // Execute runs the root command 13 | func Execute() { 14 | var warning, critical int 15 | var processName, metricName string 16 | var usePerCoreCalculation bool 17 | 18 | var rootCmd = &cobra.Command{ 19 | Use: "check_process_cpu", 20 | Short: "Checks the CPU usage of a process", 21 | Long: `Performs a CPU utilization check by getting the usage percentage of a process and its children. 22 | Multiple samples are taken and final value is averaged. 23 | If using --core flag, then average utilization will be calculated per-core and highest number will be returned (Linux only). 24 | If returned value is above --critical percentage, issue a CRITICAL response. 25 | If above --warning percentage, issue WARNING response. 26 | If below both, issue OK response.`, 27 | Run: func(cmd *cobra.Command, args []string) { 28 | cmd.ParseFlags(os.Args) 29 | msg, retval := nagiosfoundation.CheckProcessCPU(warning, critical, processName, metricName, usePerCoreCalculation) 30 | 31 | fmt.Println(msg) 32 | os.Exit(retval) 33 | }, 34 | } 35 | 36 | initcmd.AddVersionCommand(rootCmd) 37 | 38 | rootCmd.Flags().IntVarP(&warning, "warning", "w", 85, "The average highest CPU core threshold to issue a warning alert") 39 | rootCmd.Flags().IntVarP(&critical, "critical", "c", 95, "The average highest CPU core threshold to issue a critical alert") 40 | rootCmd.Flags().StringVarP(&metricName, "metric_name", "m", "process_cpu_percentage", "The name of the metric generated by this check") 41 | rootCmd.Flags().StringVarP(&processName, "process_name", "n", "", "The name of the process to check CPU for") 42 | rootCmd.MarkFlagRequired("process_name") 43 | rootCmd.Flags().BoolVar(&usePerCoreCalculation, "core", false, "Set to use per-core calculation") 44 | 45 | if err := rootCmd.Execute(); err != nil { 46 | fmt.Println(err) 47 | os.Exit(1) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /cmd/check_process_cpu/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/ncr-devops-platform/nagiosfoundation/cmd/check_process_cpu/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /cmd/check_process_memory/README.md: -------------------------------------------------------------------------------- 1 | # Process memory check 2 | The process memory check (`check_process_memory`) checks the amount of memory consumed by a process as reported by OS. Retrieved memory percentage is compared against `--warning` and `--critical` thresholds and an appropriate check result is output. 3 | 4 | ## Linux process memory check 5 | On linux machines `ps` command will be used to retrieve memory percentage. Valid PIDs will be discovered based on `/proc/[pid]/stat` files. 6 | 7 | ## Windows process memory check 8 | On windows machines Windows Management Instrumentation (WMI) will be used. WMI will be queried for each running process memory consumption. Processes matching `.exe` **and children** of those processes will be added to the calculation. 9 | 10 | > NOTE: It is possible that parent id of the process refers to a different (reused) PID, which theoretically may yield incorrect results. 11 | 12 | > This check uses WorkingSetSize field of a process to calculate percentage. Sum of all processes' WorkingSetSize should be equal to what is observed in Task Manager as total used memory. Sum of a particular processes' WorkingSetSize MIGHT NOT be equal to what is reported as total consumed memory of a process in Task Manager. 13 | 14 | ## Flags 15 | > Run this check with help command to get latest information, i.e. `check_process_memory help` 16 | * `--warning`: The percentage of used memory required to trigger a warning condition. Default `85`. 17 | * `--critical`: The percentage of used memory required to trigger a critical condition. Default `95`. 18 | * `--metric_name`: The name used in the nagios portion of the message output. Default `memory_used_process_percentage` 19 | * `--process_name`: **Required.** The name of the process for which to query memory usage. Process name is **case-sensitive**. 20 | 21 | ## Examples 22 | Issue a warning if memory usage is over 50% for chrome 23 | ``` 24 | check_process_memory --process_name chrome --warning 50 25 | ``` -------------------------------------------------------------------------------- /cmd/check_process_memory/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/ncr-devops-platform/nagiosfoundation/cmd/initcmd" 8 | "github.com/ncr-devops-platform/nagiosfoundation/lib/app/nagiosfoundation" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // Execute runs the root command 13 | func Execute() { 14 | var warning, critical int 15 | var processName, metricName string 16 | 17 | var rootCmd = &cobra.Command{ 18 | Use: "check_process_memory", 19 | Short: "Checks the memory usage of a process", 20 | Long: `Determines the percentage of memory used by a process. 21 | On linux this value will match the value reported in 'top'. 22 | On windows this value is calculated from WorkingSetSize field of a process. 23 | Sum of WorkingSetSize for all processes should sum up to a total memory used as reported by task manager. Sum for a single process might not be equal to memory as reported on Processes tab. 24 | If returned value is above --critical percentage, issue a CRITICAL response. 25 | If above --warning percentage, issue WARNING response. 26 | If below both, issue OK response.`, 27 | Run: func(cmd *cobra.Command, args []string) { 28 | cmd.ParseFlags(os.Args) 29 | msg, retval := nagiosfoundation.CheckProcessMemory(warning, critical, processName, metricName) 30 | 31 | fmt.Println(msg) 32 | os.Exit(retval) 33 | }, 34 | } 35 | 36 | initcmd.AddVersionCommand(rootCmd) 37 | 38 | rootCmd.Flags().IntVarP(&warning, "warning", "w", 85, "The memory threshold to issue a warning alert") 39 | rootCmd.Flags().IntVarP(&critical, "critical", "c", 95, "The memory threshold to issue a critical alert") 40 | rootCmd.Flags().StringVarP(&metricName, "metric_name", "m", "memory_used_process_percentage", "The name of the metric generated by this check") 41 | rootCmd.Flags().StringVarP(&processName, "process_name", "n", "", "The name of the process to check memory consumption for") 42 | rootCmd.MarkFlagRequired("process_name") 43 | 44 | if err := rootCmd.Execute(); err != nil { 45 | fmt.Println(err) 46 | os.Exit(1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /cmd/check_process_memory/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/ncr-devops-platform/nagiosfoundation/cmd/check_process_memory/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /cmd/check_service/README.md: -------------------------------------------------------------------------------- 1 | # Service Check 2 | The service check is used to perform various checks against a service on an operating system. Until this functionality is brought to parity, the checks supported are different between Linux and Windows. 3 | 4 | Linux supports different Service Managers (`systemd`, `init`, etc). Currently `systemd` is the only supported Service Manager and is the default. 5 | 6 | ## Common Checks 7 | Both Linux and Windows support checking that a named service is running and output of the current state in a nagios format. 8 | 9 | ### Linux 10 | This check supports 11 | * Verify a service is running 12 | * Return the state of the service as a nagios formatted result 13 | 14 | The functionality depends on the command line flags used and can be easily inferred based on the flags present. 15 | * `--name (-n)` : The service name. Required. 16 | * `--current-state (-c)` : Output the service state in nagios output. 17 | * `--metric-name (-m)` : The outputted metric name. Default is `service_state`. 18 | 19 | ### Service Running 20 | To verify a service is running, use the `--name (-n)` option. Note that `--name (-n)` is required. 21 | ``` 22 | check_service --name sshd 23 | ``` 24 | 25 | ### Return the State of a Service 26 | Use the `--current_state (-c)` along with the `--name (-n)` option to return the state of a service. Linux supports two states: 27 | * 0 - Not running 28 | * 1 - Running 29 | 30 | Some examples are 31 | 32 | **Service Running** 33 | 34 | ``` 35 | $ check_service --name sshd --current_state 36 | CheckService OK - sshd in a running state | service_state=1 service_name=sshd 37 | ``` 38 | 39 | **Service Not Running** 40 | 41 | ``` 42 | $ check_service --name sshd --current_state 43 | CheckService CRITICAL - sshd not in a running state (State: inactive) | service_state=0 service_name=sshd 44 | ``` 45 | 46 | **Service Running, Metric Name Changed** 47 | 48 | ``` 49 | $ check_service --name sshd --current_state --metric_name sshd_service_state 50 | CheckService OK - sshd in a running state | sshd_service_state=1 service_name=sshd 51 | ``` 52 | 53 | ### Windows 54 | Various states are supported and the `--state (-s)` flag is used to specify them. The default is to check that the service exists. 55 | ``` 56 | check_service.exe --name audiosrv --state running 57 | ``` 58 | 59 | ## Windows Extended Features 60 | The Windows version of this check supports several additional features. 61 | * A service exists, outputting the current service state and user. 62 | * A service exists and is in a specified state. 63 | * A service exists and is started by a specified user. 64 | * A service exists, is in a specified state, and is started by a specified user. 65 | * Returning the state of a service as a nagios formatted result 66 | 67 | The functionality depends on the command line flags used and can be easily inferred based on the flags present. 68 | * `--name (-n)` : The service name. Required. 69 | * `--state (-s)` : Validate the service is in the named state. 70 | * `--user (-u)` : Validate the service is started by the named user. 71 | * `--current-state (-c)` : Output the Windows service state in nagios output. 72 | * `--win_svc_mgr (-w)` : Set to `true` to use Windows Control Manager. Default is `false` which uses WMI. 73 | * `--metric-name (-m)` : The outputted metric name. Default is `service_state`. 74 | 75 | #### Note regarding `--current_state` flag 76 | By default, `--current_state` will always exit with a return code of `0`. To return a failing state with return code `2`, use the `--state` flag alongside `--current_state` and specify the desired state. When using both flags, the return code will be `0` if the service is in the desired state (running, stopped, etc). Otherwise, it will be `2`. 77 | 78 | ## Windows Service Manager 79 | The Windows version of this check supports two methods of retrieving service data. 80 | 81 | **`wmi`**: Uses [Windows Management Instrumentation](https://docs.microsoft.com/en-us/windows/desktop/wmisdk/wmi-start-page) to retrieve service data. This method does not require any special user privileges and is the default. 82 | 83 | **`svcmgr`**: Uses the [Windows Control Manager](https://docs.microsoft.com/en-us/windows/desktop/services/service-control-manager) to retrieve service data. This method requires sufficient user privileges to access the control manager. 84 | 85 | ### Service Exists 86 | ``` 87 | check_service.exe --name audiosrv 88 | ``` 89 | ### Service Exists and in State 90 | ``` 91 | check_service.exe --name audiosrv --state stopped 92 | ``` 93 | ### Service Exists and Started by User 94 | ``` 95 | check_service.exe --name audiosrv --user "NT AUTHORITY\LocalService" 96 | ``` 97 | ### Service Exists, in State, and Started by User 98 | ``` 99 | check_service.exe --name audiosrv --state running --user "NT AUTHORITY\LocalService" 100 | ``` 101 | 102 | ### Return the State of a Service 103 | ``` 104 | ./check_service.exe --name audiosrv --current_state 105 | CheckService OK - audiosrv is in a Running state | service_state=0 service_name=audiosrv 106 | ``` 107 | 108 | ### Return the State of Non-existing Service 109 | ``` 110 | ./check_service.exe --name fakeservice --current_state 111 | CheckService OK - fakeservice does not exist | service_state=255 service_name=fakeservice 112 | ``` 113 | 114 | ## Service State Numbers 115 | The state numbers returned using the `--current_state (-c)` option are: 116 | * 0 - Running 117 | * 1 - Paused 118 | * 2 - Start Pending 119 | * 3 - Pause Pending 120 | * 4 - Continue Pending 121 | * 5 - Stop pending 122 | * 6 - Stopped 123 | * 7 - Unknown 124 | * 255 - No such service -------------------------------------------------------------------------------- /cmd/check_service/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/ncr-devops-platform/nagiosfoundation/cmd/initcmd" 8 | "github.com/ncr-devops-platform/nagiosfoundation/lib/app/nagiosfoundation" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | const serviceManagerFlag = "win_svc_mgr" 13 | const currentStateWantedFlag = "current_state" 14 | 15 | var state, user, metricName string 16 | var currentStateWanted, useSvcMgr bool 17 | 18 | // Execute runs the root command 19 | func Execute() { 20 | var name string 21 | 22 | var rootCmd = &cobra.Command{ 23 | Use: "check_service", 24 | Short: "Determine the status of a service.", 25 | Long: `Perform various checks for a service. These checks depend on the options 26 | given and the --name (-n) option is always required.` + getHelpOsConstrained(), 27 | Run: func(cmd *cobra.Command, args []string) { 28 | cmd.ParseFlags(os.Args) 29 | 30 | msg, retcode := nagiosfoundation.CheckService(name, state, user, currentStateWanted, useSvcMgr, metricName) 31 | 32 | fmt.Println(msg) 33 | os.Exit(retcode) 34 | }, 35 | } 36 | 37 | initcmd.AddVersionCommand(rootCmd) 38 | 39 | const nameFlag = "name" 40 | rootCmd.Flags().StringVarP(&name, nameFlag, "n", "", "service name") 41 | rootCmd.MarkFlagRequired(nameFlag) 42 | 43 | rootCmd.Flags().StringVarP(&metricName, "metric_name", "m", "service_state", "The name of the metric generated by this check") 44 | 45 | addFlagsOsConstrained(rootCmd) 46 | 47 | if err := rootCmd.Execute(); err != nil { 48 | fmt.Println(err) 49 | os.Exit(1) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /cmd/check_service/cmd/root_linux.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package cmd 4 | 5 | import "github.com/spf13/cobra" 6 | 7 | func getHelpOsConstrained() string { 8 | return ` 9 | 10 | For Linux, the only check done is for a running state. The --name (-n) 11 | option must be specified and the service is only checked 12 | to see if it is running. 13 | ` 14 | } 15 | 16 | func addFlagsOsConstrained(cmd *cobra.Command) { 17 | cmd.Flags().BoolVarP(¤tStateWanted, currentStateWantedFlag, "c", false, "output the service state in nagios output") 18 | } 19 | -------------------------------------------------------------------------------- /cmd/check_service/cmd/root_win.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package cmd 4 | 5 | import "github.com/spf13/cobra" 6 | 7 | func getHelpOsConstrained() string { 8 | return ` 9 | 10 | Some examples: 11 | check_service.exe --name audiosrv 12 | Checks for the service to exist and shows the service state and user. 13 | check_service.exe --name audiosrv --state running 14 | Checks for the service in the running state. 15 | check_service.exe --name audiosrv --state running --user "NT AUTHORITY\LocalService" 16 | Checks for the service in the running state and running as user. 17 | check_service.exe --name audiosrv --user "NT AUTHORITY\LocalService" 18 | Checks for the service to exist and would be run as user. 19 | ` 20 | } 21 | 22 | func addFlagsOsConstrained(cmd *cobra.Command) { 23 | cmd.Flags().StringVarP(&state, "state", "s", "", "the desired state of the service") 24 | cmd.Flags().StringVarP(&user, "user", "u", "", "the user the service should run as") 25 | cmd.Flags().BoolVarP(¤tStateWanted, currentStateWantedFlag, "c", false, "output the Windows service state in nagios output") 26 | cmd.Flags().BoolVarP(&useSvcMgr, serviceManagerFlag, "w", false, "Decides which Windows Service Manager to use. Default is false / \"Windows Management Instrumentation (wmi)\". Set to true to use \"Windows Control Manager\"") 27 | } 28 | -------------------------------------------------------------------------------- /cmd/check_service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/ncr-devops-platform/nagiosfoundation/cmd/check_service/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /cmd/check_uptime/README.md: -------------------------------------------------------------------------------- 1 | # Uptime Check 2 | The uptime check (`check_uptime`) system uptime. The uptime value is then compared against the `--warning` and `--critical` thresholds and an appropriate check result is output. 3 | 4 | ## Flags 5 | * `--warning`: The desired time (in seconds(s), minutes(m), or hours(h)) of uptime required to trigger a warning condition. Default is 72h. 6 | * `--critical`: The desired time (in seconds(s), minutes(m), or hours(h)) of uptime required to trigger a critical condition. Default is 1 week (168h). 7 | * `--metric_name`: The name used in the Nagios portion of the message output. Default `current_system_uptime`. 8 | 9 | ## Examples 10 | Issue a warning if uptime is over 72 hours and critical if uptime is over the default of 1 week. 11 | ``` 12 | check_uptime --warning 72h --critical 168h 13 | ``` -------------------------------------------------------------------------------- /cmd/check_uptime/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "time" 7 | 8 | "github.com/ncr-devops-platform/nagiosfoundation/cmd/initcmd" 9 | "github.com/ncr-devops-platform/nagiosfoundation/lib/app/nagiosfoundation" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | // Execute runs the root command 14 | func Execute() { 15 | var warning, critical time.Duration 16 | var metricName string 17 | 18 | var rootCmd = &cobra.Command{ 19 | Use: "check_uptime", 20 | Short: "Determine if system uptime used exceeds time threshold.", 21 | Long: `Determines the system uptime in seconds and if over the --critical 22 | threshold issue a CRITICAL response, then check if over the --warning threshold, 23 | issue a WARNING response. Otherwise, an OK response is issued.`, 24 | Run: func(cmd *cobra.Command, args []string) { 25 | cmd.ParseFlags(os.Args) 26 | msg, retval := nagiosfoundation.CheckUptime("", warning, critical, metricName) 27 | 28 | fmt.Println(msg) 29 | os.Exit(retval) 30 | }, 31 | } 32 | 33 | initcmd.AddVersionCommand(rootCmd) 34 | 35 | rootCmd.Flags().DurationVarP(&warning, "warning", "w", time.Duration(72*time.Hour), "The uptime threshold to issue a warning alert, default is 72h") 36 | rootCmd.Flags().DurationVarP(&critical, "critical", "c", time.Duration(168*time.Hour), "The uptime threshold to issue a critical alert, default is 1 week (168h)") 37 | rootCmd.Flags().StringVarP(&metricName, "metric_name", "m", "current_sytem_uptime", "the name of the metric generated by this check") 38 | 39 | if err := rootCmd.Execute(); err != nil { 40 | fmt.Println(err) 41 | os.Exit(1) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /cmd/check_uptime/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | 5 | "github.com/ncr-devops-platform/nagiosfoundation/cmd/check_uptime/cmd" 6 | ) 7 | 8 | func main() { 9 | cmd.Execute() 10 | } 11 | -------------------------------------------------------------------------------- /cmd/check_user_group/README.md: -------------------------------------------------------------------------------- 1 | # User and Group Check 2 | 3 | The user and group check (`check_user_group`) will check for the existence of a user, of a group, or of a user being in a group, depending on the command line flags given. 4 | 5 | All features of this check are applicable to both Linux and Windows. 6 | 7 | ## Check for a User 8 | Use the `--user` flag to check for the existence of a user. It is the only command like flag required for this check. 9 | 10 | ``` 11 | check_user_group --user nobody 12 | ``` 13 | 14 | ## Check for a Group 15 | 16 | Use the `--group` flag to check for the existence of a group. It is the only command line flag required for this check. 17 | 18 | ``` 19 | check_user_group --group sudo 20 | ``` 21 | 22 | ## Check for a User Belonging to a Group 23 | Use both the `--user` and `--group` flags to check if a user exists and verify the user is in a group. 24 | 25 | ``` 26 | check_user_group --user adm --group syslog 27 | ``` 28 | -------------------------------------------------------------------------------- /cmd/check_user_group/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/ncr-devops-platform/nagiosfoundation/cmd/initcmd" 8 | "github.com/ncr-devops-platform/nagiosfoundation/lib/app/nagiosfoundation" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // Execute runs the root command 13 | func Execute() { 14 | var user, group string 15 | 16 | var rootCmd = &cobra.Command{ 17 | Use: "check_user_group", 18 | Short: "Determine if a user and/or group is on a system.", 19 | Long: `Checks for the existence of a user, a group, or if a user exists and 20 | belongs to a group. At least one flag must be provided. 21 | 22 | - The --user (-u) flag, checks for the existence of the user. 23 | - The --group (-g) flag checks for the existence of a group. 24 | - The --user (-u) and --group (-g) flag together check for the 25 | existence of the user, then checks that the user belongs in 26 | the group.`, 27 | Run: func(cmd *cobra.Command, args []string) { 28 | cmd.ParseFlags(os.Args) 29 | 30 | if user == "" && group == "" { 31 | cmd.Help() 32 | } else { 33 | msg, retval := nagiosfoundation.CheckUserGroup(user, group) 34 | 35 | fmt.Println(msg) 36 | os.Exit(retval) 37 | } 38 | }, 39 | } 40 | 41 | initcmd.AddVersionCommand(rootCmd) 42 | 43 | rootCmd.Flags().StringVarP(&user, "user", "u", "", "user name") 44 | rootCmd.Flags().StringVarP(&group, "group", "g", "", "group name") 45 | 46 | if err := rootCmd.Execute(); err != nil { 47 | fmt.Println(err) 48 | os.Exit(1) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /cmd/check_user_group/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/ncr-devops-platform/nagiosfoundation/cmd/check_user_group/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /cmd/initcmd/initcmd.go: -------------------------------------------------------------------------------- 1 | package initcmd 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io" 7 | "os" 8 | "runtime" 9 | "strings" 10 | 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | // The command name and version are injected into 15 | // these variables at build time. 16 | // See godel/config/dist-plugin.yml 17 | var cmdName string 18 | var cmdVersion string 19 | 20 | // SetFlagIfNotProvided sets a command line flag if it wasn't 21 | // provided. This overcomes a command line flag in library 22 | // being set to a different default value than desired. 23 | // 24 | // Returns true if the command line flag was not provided 25 | // and therefore was set to the value provided. 26 | func SetFlagIfNotProvided(flagName string, flagValue string) bool { 27 | flagSet := false 28 | flag.Parse() 29 | 30 | flag.Visit(func(f *flag.Flag) { 31 | if (*f).Name == flagName { 32 | flagSet = true 33 | } 34 | }) 35 | 36 | if !flagSet { 37 | flag.Set(flagName, flagValue) 38 | } 39 | 40 | return !flagSet 41 | } 42 | 43 | // SetDefaultGlogStderr will prevent the glog package from 44 | // defaulting to creating new log files on every execution, 45 | // set the logtostderr option for glog to true if it wasn't 46 | // specified on the command line. 47 | func SetDefaultGlogStderr() { 48 | SetFlagIfNotProvided("logtostderr", "true") 49 | } 50 | 51 | // GetVersion returns the executable version as a string 52 | func GetVersion() string { 53 | const unknown = "" 54 | 55 | if cmdName == "" { 56 | cmdName = unknown 57 | } 58 | 59 | if cmdVersion == "" { 60 | cmdVersion = unknown 61 | } 62 | 63 | version := cmdName + " version " + cmdVersion + " " + runtime.GOOS + "/" + runtime.GOARCH 64 | 65 | return version 66 | } 67 | 68 | // ShowVersion checks for "version" to be the only argument 69 | // and if true, writes the version to the io.Writer passed 70 | // in. 71 | // 72 | // Returns true if the version was output, else false 73 | func ShowVersion(w io.Writer) bool { 74 | retval := false 75 | 76 | if len(os.Args) == 2 && strings.EqualFold(os.Args[1], "version") { 77 | if w == nil { 78 | w = os.Stdout 79 | } 80 | 81 | fmt.Fprintln(w, GetVersion()) 82 | retval = true 83 | } 84 | 85 | return retval 86 | } 87 | 88 | // CheckExecutableVersion will attempt to show the version of 89 | // the executable and if shown, will immediately exit. 90 | func CheckExecutableVersion() { 91 | if ShowVersion(os.Stdout) { 92 | os.Exit(0) 93 | } 94 | } 95 | 96 | // AddVersionCommand adds the version command via Cobra 97 | func AddVersionCommand(cmd *cobra.Command) { 98 | cmd.AddCommand(&cobra.Command{ 99 | Use: "version", 100 | Short: "Print the version", 101 | Long: "Print the version and OS/arch.", 102 | Run: func(cmd *cobra.Command, args []string) { 103 | ShowVersion(os.Stdout) 104 | }, 105 | }) 106 | } 107 | -------------------------------------------------------------------------------- /cmd/initcmd/initcmd_test.go: -------------------------------------------------------------------------------- 1 | package initcmd 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "runtime" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func TestSetFlagIfNotProvided(t *testing.T) { 14 | // Save args and flagset for restoration 15 | savedArgs := os.Args 16 | savedFlagCommandLine := flag.CommandLine 17 | 18 | // Reset the default flag set 19 | flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) 20 | 21 | pgmName := "TestSetFlagIfNotProvided" 22 | flagName := "testflagname" 23 | flagDefault := "defaultValue" 24 | flagOverride := "overrideValue" 25 | flagNotProvided := "notProvidedValue" 26 | flagPtr := flag.String(flagName, flagDefault, "flag for testing") 27 | 28 | // Flag not provided on the command line therefore it is 29 | // set internally. 30 | os.Args = []string{pgmName} 31 | SetFlagIfNotProvided(flagName, flagNotProvided) 32 | flag.Parse() 33 | if *flagPtr != flagNotProvided { 34 | t.Error("Flag not provided and was not set") 35 | } 36 | 37 | // Flag provided on the command line therefore do not 38 | // set it internally. 39 | os.Args = []string{pgmName, "-" + flagName, flagOverride} 40 | SetFlagIfNotProvided(flagName, flagNotProvided) 41 | flag.Parse() 42 | if *flagPtr != flagOverride { 43 | t.Error("Flag was provided but was also set") 44 | } 45 | 46 | os.Args = savedArgs 47 | flag.CommandLine = savedFlagCommandLine 48 | } 49 | 50 | func TestVersion(t *testing.T) { 51 | pgmName := "TestCmd" 52 | savedArgs := os.Args 53 | 54 | if ShowVersion(nil) != false { 55 | t.Error("Version should not have been shown since \"version\" was not an argument.") 56 | } 57 | 58 | os.Args = []string{pgmName, "version"} 59 | cmdName = pgmName 60 | cmdVersion = "TestVersion" 61 | 62 | if ShowVersion(nil) == false { 63 | t.Error("Version should have been shown since \"version\" was an argument.") 64 | } 65 | 66 | var s strings.Builder 67 | if ShowVersion(&s) == false { 68 | t.Error("Version should have been shown since \"version\" was an argument.") 69 | } 70 | 71 | expectedResult := cmdName + " version " + cmdVersion + " " + runtime.GOOS + "/" + runtime.GOARCH + "\n" 72 | if s.String() != expectedResult { 73 | t.Errorf("Version string returned is not correct. Expected result: %s Actual Result: %s", 74 | expectedResult, 75 | s.String()) 76 | } 77 | 78 | cmdName = "" 79 | cmdVersion = "" 80 | s.Reset() 81 | ShowVersion(&s) 82 | expectedResult = " version " + runtime.GOOS + "/" + runtime.GOARCH + "\n" 83 | if s.String() != expectedResult { 84 | t.Errorf("Version string returned is not correct. Expected result: %s Actual Result: %s", 85 | expectedResult, 86 | s.String()) 87 | } 88 | 89 | testCmd := &cobra.Command{} 90 | AddVersionCommand(testCmd) 91 | cmdList := testCmd.Commands() 92 | cmdList[0].Execute() 93 | if cmdList[0].Use != "version" { 94 | t.Error("version command did not load into Cobra") 95 | } 96 | 97 | os.Args = savedArgs 98 | } 99 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ncr-devops-platform/nagiosfoundation 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/PaesslerAG/gval v1.0.1 7 | github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 8 | github.com/pbnjay/memory v0.0.0-20190104145345-974d429e7ae4 9 | github.com/shirou/gopsutil v2.17.13-0.20180801053943-8048a2e9c577+incompatible 10 | github.com/spf13/cobra v0.0.4-0.20190311125509-ba1052d4cbce 11 | github.com/thedevsaddam/gojsonq v2.2.2+incompatible 12 | golang.org/x/sys v0.0.0-20190219203350-90b0e4468f99 13 | ) 14 | 15 | require ( 16 | github.com/go-ole/go-ole v1.2.4 // indirect 17 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 18 | github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect 19 | github.com/spf13/pflag v1.0.3 // indirect 20 | github.com/stretchr/testify v1.7.0 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/PaesslerAG/gval v1.0.1 h1:QnCvok0w0Y3uZNxmNmC6GZ0cuBl+jH0tu/rBMT8pso4= 2 | github.com/PaesslerAG/gval v1.0.1/go.mod h1:y/nm5yEyTeX6av0OfKJNp9rBNj2XrGhAf5+v24IBN1I= 3 | github.com/PaesslerAG/jsonpath v0.1.0 h1:gADYeifvlqK3R3i2cR5B4DGgxLXIPb3TRTH1mGi0jPI= 4 | github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8= 5 | github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= 6 | github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= 7 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= 10 | github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= 11 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 12 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 13 | github.com/pbnjay/memory v0.0.0-20190104145345-974d429e7ae4 h1:MfIUBZ1bz7TgvQLVa/yPJZOGeKEgs6eTKUjz3zB4B+U= 14 | github.com/pbnjay/memory v0.0.0-20190104145345-974d429e7ae4/go.mod h1:RMU2gJXhratVxBDTFeOdNhd540tG57lt9FIUV0YLvIQ= 15 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 16 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 17 | github.com/shirou/gopsutil v2.17.13-0.20180801053943-8048a2e9c577+incompatible h1:G5sSEVYPmF5brwNsv/EcbzzlmlQQCf+jvgccZkNgT7c= 18 | github.com/shirou/gopsutil v2.17.13-0.20180801053943-8048a2e9c577+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 19 | github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U= 20 | github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= 21 | github.com/spf13/cobra v0.0.4-0.20190311125509-ba1052d4cbce h1:KcZdp6ZYVGTJDBmQhlYYTj8LydCxXzOGDvDy1Bk2CVU= 22 | github.com/spf13/cobra v0.0.4-0.20190311125509-ba1052d4cbce/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 23 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 24 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 25 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 26 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 27 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 28 | github.com/thedevsaddam/gojsonq v2.2.2+incompatible h1:IDBN1FNzhv9p83n8JEnuu0rrlXIVFlf8K9lWuXPJHfI= 29 | github.com/thedevsaddam/gojsonq v2.2.2+incompatible/go.mod h1:RBcQaITThgJAAYKH7FNp2onYodRz8URfsuEGpAch0NA= 30 | golang.org/x/sys v0.0.0-20190219203350-90b0e4468f99 h1:mlL4HvR5ojTCLdWRydhoj7jto5SXLsxLc0b1r/3DNlE= 31 | golang.org/x/sys v0.0.0-20190219203350-90b0e4468f99/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 32 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 33 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 34 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 35 | -------------------------------------------------------------------------------- /godel/config/check-plugin.yml: -------------------------------------------------------------------------------- 1 | checks: 2 | golint: 3 | filters: 4 | - value: "should have comment or be unexported" 5 | - value: "or a comment on this block" 6 | -------------------------------------------------------------------------------- /godel/config/format-plugin.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ncr-devops-platform/nagiosfoundation/7d76b79aafc481546a74b959a66ac1fd91bbdc3e/godel/config/format-plugin.yml -------------------------------------------------------------------------------- /godel/config/godel.properties: -------------------------------------------------------------------------------- 1 | distributionURL=https://github.com/palantir/godel/releases/download/v2.84.0/godel-2.84.0.tgz 2 | distributionSHA256=8f8935ca249cdf0176053a715df159e1ebe4da42e753e6d43b75d5b4dae3ab31 3 | -------------------------------------------------------------------------------- /godel/config/godel.yml: -------------------------------------------------------------------------------- 1 | exclude: 2 | names: 3 | - "\\..+" 4 | - "vendor" 5 | paths: 6 | - "godel" 7 | -------------------------------------------------------------------------------- /godel/config/license-plugin.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ncr-devops-platform/nagiosfoundation/7d76b79aafc481546a74b959a66ac1fd91bbdc3e/godel/config/license-plugin.yml -------------------------------------------------------------------------------- /godel/config/test-plugin.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ncr-devops-platform/nagiosfoundation/7d76b79aafc481546a74b959a66ac1fd91bbdc3e/godel/config/test-plugin.yml -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_cpu.go: -------------------------------------------------------------------------------- 1 | // +build windows linux darwin 2 | 3 | package nagiosfoundation 4 | 5 | import ( 6 | "errors" 7 | 8 | "github.com/ncr-devops-platform/nagiosfoundation/lib/pkg/cpu" 9 | "github.com/ncr-devops-platform/nagiosfoundation/lib/pkg/nagiosformatters" 10 | ) 11 | 12 | // CheckCPUWithHandler gets the CPU load then emits a critical response 13 | // if it's above the critical argument, a warning if it's above 14 | // warning argument and good response for everything else. 15 | // 16 | // Returns are a response message and response code. 17 | func CheckCPUWithHandler(warning, critical int, metricName string, cpuHandler func() (float64, error)) (string, int) { 18 | const checkName = "CheckAVGCPULoad" 19 | 20 | var msg string 21 | var retcode int 22 | var value float64 23 | var err error 24 | 25 | if cpuHandler == nil { 26 | err = errors.New("No GetCPULoad() service") 27 | } else { 28 | value, err = cpuHandler() 29 | } 30 | 31 | if err == nil { 32 | msg, retcode = nagiosformatters.GreaterFormatNagiosCheck(checkName, value, float64(warning), float64(critical), metricName) 33 | } else { 34 | msg, _ = resultMessage(checkName, statusTextCritical, err.Error()) 35 | retcode = 2 36 | } 37 | 38 | return msg, retcode 39 | } 40 | 41 | // CheckCPU executes CheckCPUWithHandler(), passing it the OS 42 | // constrained GetCPULoad() function, prints the returned message 43 | // and exits with the returned exit code. 44 | func CheckCPU(warning, critical int, metricName string) (string, int) { 45 | return CheckCPUWithHandler(warning, critical, metricName, cpu.GetCPULoad) 46 | } 47 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_cpu_test.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | ) 7 | 8 | func TestCheckCpu(t *testing.T) { 9 | testReturnValid := func() (float64, error) { return 0.5, nil } 10 | testReturnError := func() (float64, error) { return 0.5, errors.New("GetCPULoad() failure") } 11 | 12 | // No "get memory" service passed 13 | msg, retcode := CheckCPUWithHandler(85, 95, "", nil) 14 | 15 | if retcode != 2 || msg == "" { 16 | t.Error("CheckCPUWithHandler() failed to handle nil service") 17 | } 18 | 19 | msg, retcode = CheckCPUWithHandler(85, 95, "pct_processor_time", testReturnValid) 20 | 21 | if retcode != 0 || msg == "" { 22 | t.Error("CheckCPUWithHandler() failed with valid returns from service") 23 | } 24 | 25 | msg, retcode = CheckCPUWithHandler(85, 95, "pct_processor_time", testReturnError) 26 | 27 | if retcode != 2 || msg == "" { 28 | t.Error("CheckCPUWithHandler() failed with error returned from service") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_file_exists.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "strconv" 7 | ) 8 | 9 | // CheckFileExists tests the assertion that one or more files matching specified pattern should or should not exist. 10 | func CheckFileExists(pattern string, negate bool) (string, int) { 11 | var msg string 12 | var retCode int 13 | var checkStateText, msgString string 14 | 15 | matches, err := filepath.Glob(pattern) 16 | 17 | switch { 18 | case err != nil: 19 | checkStateText = statusTextUnknown 20 | msgString = fmt.Sprintf("Error matching pattern %s: %s", pattern, err) 21 | retCode = 3 22 | default: 23 | matchCount := len(matches) 24 | 25 | switch { 26 | case (matchCount == 0 && negate == false) || 27 | (matchCount > 0 && negate == true): 28 | checkStateText = statusTextCritical 29 | retCode = 2 30 | case (matchCount == 0 && negate == true) || 31 | (matchCount > 0 && negate == false): 32 | checkStateText = statusTextOK 33 | retCode = 0 34 | } 35 | 36 | msgString = fmt.Sprintf("%s files matched pattern %s", strconv.Itoa(len(matches)), pattern) 37 | } 38 | 39 | msg, _ = resultMessage("CheckFileExists", checkStateText, msgString) 40 | return msg, retCode 41 | } 42 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_file_exists_test.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func validateTestCheckFileExistsResponse(t *testing.T, description string, expectedCode int, expectedMsg string, actualCode int, actualMsg string) { 10 | t.Helper() 11 | 12 | if expectedCode != actualCode { 13 | t.Errorf("%s: Expected Code: %d, Actual Code: %d", description, expectedCode, actualCode) 14 | } 15 | 16 | if strings.Contains(actualMsg, expectedMsg) == false { 17 | t.Errorf("%s: Expected Message: %s, Actual Message: %s", description, expectedMsg, actualMsg) 18 | } 19 | } 20 | 21 | func TestCheckFileExists(t *testing.T) { 22 | var msg string 23 | var code int 24 | const validTestFile = "validtestfile" 25 | const invalidTestFile = "invalidtestfile" 26 | 27 | type testItem struct { 28 | description string 29 | file string 30 | inverted bool 31 | expectedCode int 32 | expectedMsg string 33 | } 34 | 35 | testList := []testItem{ 36 | { 37 | description: "Invalid Glob Pattern", 38 | file: "[]a", 39 | inverted: false, 40 | expectedCode: 3, 41 | expectedMsg: statusTextUnknown, 42 | }, 43 | { 44 | description: "File does not exist, not inverted", 45 | file: invalidTestFile, 46 | inverted: false, 47 | expectedCode: 2, 48 | expectedMsg: statusTextCritical, 49 | }, 50 | { 51 | description: "File does not exist, inverted", 52 | file: invalidTestFile, 53 | inverted: true, 54 | expectedCode: 0, 55 | expectedMsg: statusTextOK, 56 | }, 57 | { 58 | description: "File exists, not inverted", 59 | file: validTestFile, 60 | inverted: false, 61 | expectedCode: 0, 62 | expectedMsg: statusTextOK, 63 | }, 64 | { 65 | description: "File exists, inverted", 66 | file: validTestFile, 67 | inverted: true, 68 | expectedCode: 2, 69 | expectedMsg: statusTextCritical, 70 | }, 71 | } 72 | 73 | // Create a valid file 74 | if fp, err := os.OpenFile(validTestFile, os.O_RDONLY|os.O_CREATE, 0666); err != nil { 75 | t.Errorf("Error creating test file: %s. Error: %s", validTestFile, err) 76 | } else { 77 | defer os.Remove(validTestFile) 78 | fp.Close() 79 | } 80 | 81 | for _, i := range testList { 82 | msg, code = CheckFileExists(i.file, i.inverted) 83 | validateTestCheckFileExistsResponse(t, i.description, i.expectedCode, i.expectedMsg, code, msg) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_http.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "crypto/tls" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strconv" 10 | "time" 11 | 12 | "github.com/PaesslerAG/gval" 13 | "github.com/thedevsaddam/gojsonq" 14 | ) 15 | 16 | func getAcceptText(format string) (string, error) { 17 | var accept string 18 | var err error 19 | 20 | switch format { 21 | case "json": 22 | accept = "application/json" 23 | case "": 24 | accept = "" 25 | default: 26 | accept = "" 27 | err = errors.New("Invalid accept type") 28 | } 29 | 30 | return accept, err 31 | } 32 | 33 | func evaluateStatusCode(status int, redirect bool) (int, string) { 34 | var retCode int 35 | var responseStateText string 36 | 37 | switch { 38 | case status >= http.StatusBadRequest: 39 | retCode = 2 40 | responseStateText = statusTextCritical 41 | case status >= http.StatusMultipleChoices && redirect: 42 | retCode = 0 43 | responseStateText = statusTextOK 44 | case status >= http.StatusMultipleChoices: 45 | retCode = 1 46 | responseStateText = statusTextWarning 47 | case status == -1: 48 | retCode = 2 49 | responseStateText = statusTextUnknown 50 | default: 51 | retCode = 0 52 | responseStateText = statusTextOK 53 | } 54 | 55 | return retCode, responseStateText 56 | } 57 | 58 | func evaluateExpectedValue(actualValue, expectedValue, path string) (int, string, string) { 59 | var retCode int 60 | var responseStateText, checkMsg string 61 | 62 | if actualValue == expectedValue { 63 | retCode = 0 64 | responseStateText = statusTextOK 65 | checkMsg = fmt.Sprintf(". The value found at %s has expected value %s", path, expectedValue) 66 | } else { 67 | retCode = 2 68 | responseStateText = statusTextCritical 69 | checkMsg = fmt.Sprintf(". The value found at %s has unexpected value %s", path, actualValue) 70 | } 71 | 72 | return retCode, responseStateText, checkMsg 73 | } 74 | 75 | func evaluateExpression(actualValue interface{}, expression, path string) (int, string, string) { 76 | var retCode int 77 | var responseStateText, checkMsg string 78 | 79 | evalResult, err := gval.Evaluate("value "+expression, map[string]interface{}{"value": actualValue}) 80 | if err == nil { 81 | if evalResult == true { 82 | retCode = 0 83 | responseStateText = statusTextOK 84 | checkMsg = fmt.Sprintf(". The value found at %s with value %v and expression \"%s\" yields true", path, actualValue, expression) 85 | } else { 86 | retCode = 2 87 | responseStateText = statusTextCritical 88 | checkMsg = fmt.Sprintf(". The value found at %s with value %v does not match expression \"%s\"", path, actualValue, expression) 89 | } 90 | } else { 91 | retCode = 2 92 | responseStateText = statusTextCritical 93 | checkMsg = fmt.Sprintf(". Error processing value found at %s with value %v using expression \"%s\": %s", path, actualValue, expression, err) 94 | } 95 | 96 | return retCode, responseStateText, checkMsg 97 | } 98 | 99 | // CheckHTTP attempts an HTTP request against the provided url, reporting the HTTP response code and overall request state. 100 | func CheckHTTP(url string, redirect, insecure bool, host string, timeout int, format, path, expectedValue, expression string) (string, int) { 101 | const checkName = "CheckHttp" 102 | var retCode int 103 | var msg string 104 | 105 | acceptText, err := getAcceptText(format) 106 | if err != nil { 107 | msg, _ = resultMessage(checkName, statusTextCritical, fmt.Sprintf("The format (--format) \"%s\" is not valid. The only valid value is \"json\".", format)) 108 | 109 | return msg, 2 110 | } 111 | 112 | status, body, _ := statusCode(url, insecure, timeout, acceptText, host) 113 | 114 | retCode, responseStateText := evaluateStatusCode(status, redirect) 115 | responseCode := strconv.Itoa(status) 116 | 117 | var checkMsg = "" 118 | if retCode == 0 && len(format) > 0 && len(path) > 0 { 119 | var queryValue string 120 | 121 | switch { 122 | case format == "json": 123 | expectedValueLen := len(expectedValue) 124 | expressionLen := len(expression) 125 | 126 | value := gojsonq.New().JSONString(body).Find(path) 127 | 128 | if value == nil { 129 | retCode = 2 130 | responseStateText = statusTextCritical 131 | checkMsg = fmt.Sprintf(". No entry at path %s", path) 132 | } else if expectedValueLen > 0 && expressionLen > 0 { 133 | retCode = 2 134 | responseStateText = statusTextCritical 135 | checkMsg = fmt.Sprintf(". Both --expectedValue and --expression given but only one is used") 136 | } else if expectedValueLen > 0 { 137 | queryValue = fmt.Sprintf("%v", value) 138 | retCode, responseStateText, checkMsg = evaluateExpectedValue(queryValue, expectedValue, path) 139 | } else if expressionLen > 0 { 140 | retCode, responseStateText, checkMsg = evaluateExpression(value, expression, path) 141 | } else { 142 | retCode = 2 143 | responseStateText = statusTextCritical 144 | checkMsg = fmt.Sprintf(". --expectedValue or --expression not given") 145 | } 146 | } 147 | } 148 | 149 | msg, _ = resultMessage(checkName, responseStateText, fmt.Sprintf("Url %s responded with %s%s", url, responseCode, checkMsg)) 150 | 151 | return msg, retCode 152 | } 153 | 154 | func statusCode(url string, insecure bool, timeout int, accept string, host string) (int, string, error) { 155 | http.DefaultClient.Timeout = time.Duration(timeout) * time.Second 156 | 157 | request, err := http.NewRequest("GET", url, nil) 158 | if err != nil { 159 | return -1, "", err 160 | } 161 | 162 | request.Header.Set("accept", accept) 163 | 164 | if host != "" { 165 | request.Host = host 166 | } 167 | 168 | if insecure { 169 | http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} 170 | } 171 | 172 | response, err := http.DefaultTransport.RoundTrip(request) 173 | if err != nil { 174 | return -1, "", err 175 | } 176 | defer response.Body.Close() 177 | 178 | body, readErr := ioutil.ReadAll(response.Body) 179 | if readErr != nil { 180 | return -1, "", readErr 181 | } 182 | 183 | return response.StatusCode, string(body), nil 184 | } 185 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_memory.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/ncr-devops-platform/nagiosfoundation/lib/pkg/memory" 7 | "github.com/ncr-devops-platform/nagiosfoundation/lib/pkg/nagiosformatters" 8 | ) 9 | 10 | // CheckMemoryWithHandler determines the percentage of 11 | // memory used and emits a critical response if it's over 12 | // the critical argument, a warning response if it's over the 13 | // warning argument, and good response otherwise. 14 | func CheckMemoryWithHandler(checkType string, warning, critical int, metricName string, memoryHandler func() uint64) (string, int) { 15 | const checkName = "CheckMemory" 16 | 17 | var msg string 18 | var retcode int 19 | var usedMemoryPercentage uint64 20 | var err error 21 | 22 | if memoryHandler == nil { 23 | err = errors.New("No used memory percentage service") 24 | } else { 25 | usedMemoryPercentage = memoryHandler() 26 | 27 | if usedMemoryPercentage == 0 { 28 | err = errors.New("Failed to determine used memory percentage") 29 | } 30 | } 31 | 32 | if err != nil { 33 | msg, _ = resultMessage(checkName, statusTextCritical, err.Error()) 34 | retcode = 2 35 | } else { 36 | msg, retcode = nagiosformatters.GreaterFormatNagiosCheck(checkName, float64(usedMemoryPercentage), float64(warning), float64(critical), metricName) 37 | } 38 | 39 | return msg, retcode 40 | } 41 | 42 | // CheckMemory executes CheckMemoryWithHandler(), 43 | // passing it the OS constranted GetFreeMemory() function, prints 44 | // the returned message and exits with the returned exit code. 45 | // 46 | // Returns are those of CheckMemoryWithHandler() 47 | func CheckMemory(checkType string, warning, critical int, metricName string) (string, int) { 48 | return CheckMemoryWithHandler(checkType, warning, critical, metricName, memory.GetUsedMemoryPercentage) 49 | } 50 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_memory_test.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCheckMemory(t *testing.T) { 8 | testReturnValid := func() uint64 { return uint64(50) } 9 | testReturnZero := func() uint64 { return uint64(0) } 10 | 11 | // No "get memory" service passed 12 | msg, retcode := CheckMemoryWithHandler("", 85, 95, "available_memory_percent", nil) 13 | 14 | if retcode != 2 || msg == "" { 15 | t.Error("CheckMemoryWithHandler() failed to handle nil service") 16 | } 17 | 18 | // Valid memory service with flag defaults 19 | msg, retcode = CheckMemoryWithHandler("", 85, 95, "available_memory_percent", testReturnValid) 20 | 21 | if retcode != 0 || msg == "" { 22 | t.Error("CheckMemoryWithHandler() failed with valid GetFreeMemory() call") 23 | } 24 | 25 | // Valid memory service but service returns error 26 | msg, retcode = CheckMemoryWithHandler("", 85, 95, "available_memory_percent", testReturnZero) 27 | 28 | if retcode != 2 || msg == "" { 29 | t.Error("CheckMemoryWithHandler() failed with valid GetFreeMemory() call") 30 | } 31 | 32 | msg, retcode = CheckMemoryWithHandler("", 40, 95, "available_memory_percent", testReturnValid) 33 | 34 | if retcode != 1 || msg == "" { 35 | t.Error("CheckMemoryWithHandler() should have emitted WARNING") 36 | } 37 | 38 | msg, retcode = CheckMemoryWithHandler("", 85, 45, "available_memory_percent", testReturnValid) 39 | 40 | if retcode != 2 || msg == "" { 41 | t.Error("CheckMemoryWithHandler() should have emitted CRITICAL") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_performance_counter.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/ncr-devops-platform/nagiosfoundation/lib/pkg/nagiosformatters" 8 | "github.com/ncr-devops-platform/nagiosfoundation/lib/pkg/perfcounters" 9 | ) 10 | 11 | // CheckPerformanceCounterWithHandler fetches a performance counter 12 | // specified with the counterName parameter. It then performs checks 13 | // against the value based on the threshold test specified along with 14 | // the warning and critical thresholds. 15 | // 16 | // Returns are a message stating the results of the check and a return 17 | // value from the check. 18 | func CheckPerformanceCounterWithHandler(warning, critical float64, greaterThan bool, pollingAttempts, pollingDelay int, metricName, counterName string, perfCounterHandler func(string, int, int) (perfcounters.PerformanceCounter, error)) (string, int) { 19 | var msg string 20 | var retcode int 21 | var counter perfcounters.PerformanceCounter 22 | var err error 23 | 24 | if perfCounterHandler == nil { 25 | err = errors.New("No ReadPerformanceCounter() service") 26 | } else { 27 | counter, err = perfCounterHandler(counterName, pollingAttempts, pollingDelay) 28 | } 29 | 30 | if err == nil { 31 | if greaterThan { 32 | msg, retcode = nagiosformatters.GreaterFormatNagiosCheck(counterName, counter.Value, warning, critical, metricName) 33 | } else { 34 | msg, retcode = nagiosformatters.LesserFormatNagiosCheck(counterName, counter.Value, warning, critical, metricName) 35 | } 36 | } else { 37 | msg = fmt.Sprintf("%s CRITICAL - %s", counterName, err) 38 | retcode = 2 39 | } 40 | 41 | return msg, retcode 42 | } 43 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_performance_counter_test.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/ncr-devops-platform/nagiosfoundation/lib/pkg/perfcounters" 8 | ) 9 | 10 | func TestCheckPerformanceCounter(t *testing.T) { 11 | testReturnValid := func(string, int, int) (perfcounters.PerformanceCounter, error) { 12 | return perfcounters.PerformanceCounter{Value: 5.0}, nil 13 | } 14 | testReturnError := func(string, int, int) (perfcounters.PerformanceCounter, error) { 15 | return perfcounters.PerformanceCounter{Value: 5.0}, errors.New("GetPerformanceCounter() failure") 16 | } 17 | 18 | // No "ReadPerformanceCounter" service passed 19 | msg, retcode := CheckPerformanceCounterWithHandler(0, 0, false, 2, 1, "test metric name", "test counter name", nil) 20 | 21 | if retcode != 2 || msg == "" { 22 | t.Error("CheckPerformanceCounterWithHandler() failed to handle nil service") 23 | } 24 | 25 | // Valid ReadPerformanceCounter service with valid returns from service 26 | msg, retcode = CheckPerformanceCounterWithHandler(0, 0, false, 2, 1, "test metric name", "test counter name", testReturnValid) 27 | 28 | if retcode != 0 || msg == "" { 29 | t.Error("CheckPerformanceCounterWithHandler() failed with valid returns from service") 30 | } 31 | 32 | // Valid memory service with flag defaults 33 | msg, retcode = CheckPerformanceCounterWithHandler(10, 10, true, 2, 1, "test metric name", "test counter name", testReturnValid) 34 | 35 | if retcode != 0 || msg == "" { 36 | t.Error("CheckPerformanceCounterWithHandler() failed with with -greater_than set") 37 | } 38 | 39 | // Valid ReadPerformanceCounter service with error returned from service 40 | msg, retcode = CheckPerformanceCounterWithHandler(10, 10, true, 2, 1, "test metric name", "test counter name", testReturnError) 41 | 42 | if retcode != 2 || msg == "" { 43 | t.Error("CheckPerformanceCounterWithHandler() failed with error return from service") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_performance_counter_win.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "github.com/ncr-devops-platform/nagiosfoundation/lib/pkg/perfcounters" 5 | ) 6 | 7 | // CheckPerformanceCounter executes CheckPerformanceCounterWitHandler(), 8 | // passing it the OS constrained ReadPerformanceCounter() function, prints 9 | // the returned message and exits with the returned exit code. 10 | func CheckPerformanceCounter(warning, critical float64, greaterThan bool, pollingAttempts, pollingDelay int, metricName, counterName string) (string, int) { 11 | return CheckPerformanceCounterWithHandler(warning, 12 | critical, greaterThan, pollingAttempts, pollingDelay, 13 | metricName, counterName, perfcounters.ReadPerformanceCounter) 14 | } 15 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_port.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "time" 7 | ) 8 | 9 | // CheckPortProtocol is the type of protocol to use for checking the port 10 | type CheckPortProtocol int 11 | 12 | const ( 13 | // CheckPortProtocolTCP specifies using TCP as the network protocol for 14 | // checking theport 15 | CheckPortProtocolTCP CheckPortProtocol = 0 16 | ) 17 | 18 | func (cpp CheckPortProtocol) String() string { 19 | s := "unknown" 20 | 21 | switch cpp { 22 | case CheckPortProtocolTCP: 23 | s = "tcp" 24 | } 25 | 26 | return s 27 | } 28 | 29 | func checkPort(protocol CheckPortProtocol, address string, port, timeout int, invert bool) (string, int, string) { 30 | resultText := statusTextCritical 31 | resultCode := statusCodeCritical 32 | resultDesc := "" 33 | 34 | d := net.Dialer{Timeout: time.Duration(timeout) * time.Second} 35 | conn, err := d.Dial(protocol.String(), fmt.Sprintf("%s:%d", address, port)) 36 | 37 | if err != nil { 38 | resultDesc = err.Error() 39 | 40 | if invert == true { 41 | resultText = statusTextOK 42 | resultCode = statusCodeOK 43 | } 44 | } else { 45 | conn.Close() 46 | 47 | if invert == false { 48 | resultText = statusTextOK 49 | resultCode = statusCodeOK 50 | } 51 | } 52 | 53 | return resultText, resultCode, resultDesc 54 | } 55 | 56 | // CheckPort checks for a listening port at an address 57 | func CheckPort(protocol CheckPortProtocol, address string, port, timeout int, invert bool, metricName string) (string, int) { 58 | const checkName = "CheckPort" 59 | var retCode int 60 | var msg, desc, nagiosOutput, retText, retDesc string 61 | 62 | if protocol.String() == "unknown" { 63 | desc = fmt.Sprintf("Unknown protocol value of %d", protocol) 64 | retText = statusTextCritical 65 | retCode = statusCodeCritical 66 | } else if metricName == "" { 67 | desc = "Empty metric name" 68 | retText = statusTextCritical 69 | retCode = statusCodeCritical 70 | } else if address == "" { 71 | desc = "Empty address" 72 | retText = statusTextCritical 73 | retCode = statusCodeCritical 74 | } else { 75 | retText, retCode, retDesc = checkPort(protocol, address, port, timeout, invert) 76 | desc = fmt.Sprintf("port %d on %s using %s", port, address, protocol) 77 | if retDesc != "" { 78 | desc = desc + fmt.Sprintf(" (%s)", retDesc) 79 | } 80 | } 81 | 82 | nagiosOutput = fmt.Sprintf("%s=%d", metricName, retCode) 83 | msg, _ = resultMessage(checkName, retText, desc, nagiosOutput) 84 | 85 | return msg, retCode 86 | } 87 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_port_test.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "strconv" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func validateCheckPortMsg(t *testing.T, msg string) { 12 | t.Helper() 13 | 14 | if msg == "" { 15 | t.Error("CheckPort() returned empty message") 16 | } 17 | } 18 | 19 | func TestCheckPort(t *testing.T) { 20 | var msg string 21 | var ret int 22 | 23 | httpServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 24 | })) 25 | defer httpServer.Close() 26 | url := strings.Split(strings.TrimPrefix(httpServer.URL, "http://"), ":") 27 | address := url[0] 28 | port, _ := strconv.Atoi(url[1]) 29 | 30 | // happy path 31 | msg, ret = CheckPort(CheckPortProtocolTCP, address, port, 30, false, "metric") 32 | if ret != statusCodeOK { 33 | t.Error("CheckPort() should return OK code when given valid machine and port") 34 | } 35 | 36 | validateCheckPortMsg(t, msg) 37 | 38 | // inverted happy path 39 | msg, ret = CheckPort(CheckPortProtocolTCP, address, port, 30, true, "metric") 40 | if ret != statusCodeCritical { 41 | t.Error("CheckPort() should return critical code when given valid machine and port and is inverted") 42 | } 43 | 44 | validateCheckPortMsg(t, msg) 45 | 46 | // invalid protocol 47 | msg, ret = CheckPort(42, address, port, 30, false, "metric") 48 | if ret != statusCodeCritical { 49 | t.Error("CheckPort() should return critical code when given invalid protocol") 50 | } 51 | 52 | validateCheckPortMsg(t, msg) 53 | 54 | // invalid metric name 55 | msg, ret = CheckPort(CheckPortProtocolTCP, address, port, 30, false, "") 56 | if ret != statusCodeCritical { 57 | t.Error("CheckPort() should return critical code when given invalid metric name") 58 | } 59 | 60 | validateCheckPortMsg(t, msg) 61 | 62 | // invalid machine name 63 | msg, ret = CheckPort(CheckPortProtocolTCP, "", port, 30, false, "metric") 64 | if ret != statusCodeCritical { 65 | t.Error("CheckPort() should return critical code when given invalid machine name") 66 | } 67 | 68 | validateCheckPortMsg(t, msg) 69 | 70 | // no listener 71 | httpServer.Close() 72 | msg, ret = CheckPort(CheckPortProtocolTCP, address, port, 30, false, "metric") 73 | if ret != statusCodeCritical { 74 | t.Error("CheckPort() should return critical code when there is no listener") 75 | } 76 | 77 | validateCheckPortMsg(t, msg) 78 | 79 | // no listener with invert on 80 | httpServer.Close() 81 | msg, ret = CheckPort(CheckPortProtocolTCP, address, port, 30, true, "metric") 82 | if ret != statusCodeOK { 83 | t.Error("CheckPort() should return OK code when there is no listener and is inverted") 84 | } 85 | 86 | validateCheckPortMsg(t, msg) 87 | } 88 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_process.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | const checkProcessName = "CheckProcess" 10 | 11 | // ProcessService is an interface required by ProcessCheck. 12 | // 13 | // The given a process name, the method IsProcessRunning() 14 | // must return true if the named process is running, otherwise 15 | // false. Note the code will be different for each OS. 16 | type ProcessService interface { 17 | IsProcessRunning(string) bool 18 | } 19 | 20 | type processHandler struct{} 21 | 22 | func (p processHandler) IsProcessRunning(name string) bool { 23 | return isProcessRunningOsConstrained(name) 24 | } 25 | 26 | // ProcessCheck is used to encapsulate a named process 27 | // along with the methods used to get information about 28 | // that process. Currently the only check is for the named 29 | // process running. 30 | type ProcessCheck struct { 31 | ProcessName string 32 | 33 | ProcessCheckHandler ProcessService 34 | } 35 | 36 | // IsProcessRunning interrogates the OS for the named 37 | // process to check if it's running. Note this function 38 | // calls IsProcessRunning in the injected service and 39 | // in this implementation will ultimately call an OS 40 | // constrained function. 41 | func (p ProcessCheck) IsProcessRunning() bool { 42 | return p.ProcessCheckHandler.IsProcessRunning(p.ProcessName) 43 | } 44 | 45 | func checkRunning(processCheck ProcessCheck, metricName string, invert bool) (string, int) { 46 | var msg string 47 | var retcode int 48 | var responseStateText string 49 | var checkInfo string 50 | 51 | result := processCheck.IsProcessRunning() 52 | if result != invert { 53 | retcode = statusCodeOK 54 | responseStateText = statusTextOK 55 | } else { 56 | retcode = statusCodeCritical 57 | responseStateText = statusTextCritical 58 | } 59 | 60 | nagiosOutput := metricName + "=" 61 | if result == true { 62 | checkInfo = "" 63 | nagiosOutput = nagiosOutput + strconv.Itoa(statusCodeOK) 64 | } else { 65 | checkInfo = "not " 66 | nagiosOutput = nagiosOutput + strconv.Itoa(statusCodeCritical) 67 | } 68 | 69 | msg, _ = resultMessage(checkProcessName, responseStateText, 70 | fmt.Sprintf("Process %s is %srunning", processCheck.ProcessName, checkInfo), 71 | nagiosOutput) 72 | 73 | return msg, retcode 74 | } 75 | 76 | // checkProcessWithService provides a way to inject a custom 77 | // service for interrogating the OS for the named process. 78 | // This is mainly used for testing but can also be used for any 79 | // application wishing to override the normal interrogations. 80 | func checkProcessWithService(name, checkType, metricName string, processService ProcessService) (string, int) { 81 | pc := ProcessCheck{ 82 | ProcessName: name, 83 | ProcessCheckHandler: processService, 84 | } 85 | 86 | var msg string 87 | var retcode int 88 | 89 | switch checkType { 90 | case "running": 91 | msg, retcode = checkRunning(pc, metricName, false) 92 | case "notrunning": 93 | msg, retcode = checkRunning(pc, metricName, true) 94 | default: 95 | msg = fmt.Sprintf("Invalid check type: %s", checkType) 96 | retcode = statusCodeCritical 97 | } 98 | 99 | return msg, retcode 100 | } 101 | 102 | // checkProcessCmd will interrogate the OS for details on 103 | // a named process. The details of the interrogation 104 | // depend on the check type. 105 | func checkProcessCmd(name, checkType, metricName string, checkProcess func(string, string, string, ProcessService) (string, int), processService ProcessService) (string, int) { 106 | var invalidParametersMsg string 107 | var msg string 108 | var retcode int 109 | 110 | checkType = strings.ToLower(checkType) 111 | 112 | if name == "" { 113 | invalidParametersMsg = invalidParametersMsg + 114 | "A process name must be specified." 115 | } else if checkType != "running" && checkType != "notrunning" { 116 | invalidParametersMsg = invalidParametersMsg + 117 | fmt.Sprintf("Invalid check type (%s). Only \"running\" and \"notrunning\" are supported.", 118 | checkType) 119 | } 120 | 121 | if invalidParametersMsg != "" { 122 | msg, _ = resultMessage(checkProcessName, statusTextCritical, invalidParametersMsg) 123 | retcode = statusCodeCritical 124 | } else { 125 | msg, retcode = checkProcess(name, checkType, metricName, processService) 126 | } 127 | 128 | return msg, retcode 129 | } 130 | 131 | // CheckProcess finds a process by name to determine 132 | // if it is running or not running. 133 | func CheckProcess(name, checkType, metricName string) (string, int) { 134 | return checkProcessCmd(name, checkType, metricName, checkProcessWithService, new(processHandler)) 135 | } 136 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_process_cpu.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/ncr-devops-platform/nagiosfoundation/lib/pkg/cpu" 7 | 8 | "github.com/ncr-devops-platform/nagiosfoundation/lib/pkg/nagiosformatters" 9 | ) 10 | 11 | // CheckProcessCPUWithHandler gets the CPU load of a process then emits a critical response 12 | // if it's above the critical argument, a warning if it's above 13 | // warning argument and good response for everything else. 14 | // 15 | // Returns are a response message and response code. 16 | func CheckProcessCPUWithHandler(warning, critical int, processName, metricName string, perCoreCalculation bool, 17 | processCPUCoreHandler func(string, bool) (float64, error)) (string, int) { 18 | const checkName = "CheckProcessCPULoad" 19 | 20 | var msg string 21 | var retcode int 22 | var value float64 23 | var err error 24 | 25 | if processCPUCoreHandler == nil { 26 | err = errors.New("No GetProcessCPULoad() service") 27 | } else { 28 | value, err = processCPUCoreHandler(processName, perCoreCalculation) 29 | } 30 | 31 | if err == nil { 32 | msg, retcode = nagiosformatters.GreaterFormatNagiosCheck(checkName, value, float64(warning), float64(critical), metricName) 33 | } else { 34 | msg, _ = resultMessage(checkName, statusTextCritical, err.Error()) 35 | retcode = 2 36 | } 37 | 38 | return msg, retcode 39 | } 40 | 41 | // CheckProcessCPU executes CheckProcessCPUWithHandler(), passing it the OS 42 | // constrained GetProcessCPULoad() function, prints the returned message 43 | // and exits with the returned exit code. 44 | func CheckProcessCPU(warning, critical int, processName, metricName string, perCoreCalculation bool) (string, int) { 45 | return CheckProcessCPUWithHandler(warning, critical, processName, metricName, perCoreCalculation, cpu.GetProcessCPULoad) 46 | } 47 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_process_cpu_test.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | ) 7 | 8 | func TestCheckProcessCPUWithHandler(t *testing.T) { 9 | type args struct { 10 | warning int 11 | critical int 12 | processName string 13 | metricName string 14 | perCoreCalculation bool 15 | processCPUCoreHandler func(string, bool) (float64, error) 16 | } 17 | tests := []struct { 18 | name string 19 | args args 20 | want int 21 | }{ 22 | { 23 | name: "WarningResponse", 24 | args: args{ 25 | warning: 50, 26 | critical: 99, 27 | processName: "test", 28 | metricName: "test", 29 | processCPUCoreHandler: func(_ string, _ bool) (float64, error) { 30 | return 55, nil 31 | }, 32 | }, 33 | want: 1, 34 | }, 35 | { 36 | name: "CriticalResponse", 37 | args: args{ 38 | warning: 50, 39 | critical: 99, 40 | processName: "test", 41 | metricName: "test", 42 | processCPUCoreHandler: func(_ string, _ bool) (float64, error) { 43 | return 100, nil 44 | }, 45 | }, 46 | want: 2, 47 | }, 48 | { 49 | name: "CriticalResponse2", 50 | args: args{ 51 | warning: 50, 52 | critical: 99, 53 | processName: "test", 54 | metricName: "test", 55 | processCPUCoreHandler: func(_ string, _ bool) (float64, error) { 56 | return 0.0, errors.New("TEST") 57 | }, 58 | }, 59 | want: 2, 60 | }, 61 | { 62 | name: "OkResponse", 63 | args: args{ 64 | warning: 50, 65 | critical: 99, 66 | processName: "test", 67 | metricName: "test", 68 | processCPUCoreHandler: func(_ string, _ bool) (float64, error) { 69 | return 50, nil 70 | }, 71 | }, 72 | want: 0, 73 | }, 74 | { 75 | name: "NoCPUHandler", 76 | args: args{ 77 | warning: 50, 78 | critical: 99, 79 | processName: "test", 80 | metricName: "test", 81 | processCPUCoreHandler: nil, 82 | }, 83 | want: 2, 84 | }, 85 | } 86 | for _, tt := range tests { 87 | t.Run(tt.name, func(t *testing.T) { 88 | _, retcode := CheckProcessCPUWithHandler(tt.args.warning, tt.args.critical, tt.args.processName, tt.args.metricName, tt.args.perCoreCalculation, tt.args.processCPUCoreHandler) 89 | if retcode != tt.want { 90 | t.Errorf("CheckProcessCPUWithHandler() got = %v, want %v", retcode, tt.want) 91 | } 92 | }) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_process_linux.go: -------------------------------------------------------------------------------- 1 | //go:build !windows || darwin 2 | // +build !windows darwin 3 | 4 | package nagiosfoundation 5 | 6 | import "github.com/ncr-devops-platform/nagiosfoundation/lib/pkg/process" 7 | 8 | func isProcessRunningOsConstrained(name string) bool { 9 | retVal := false 10 | 11 | if processEntries, _ := process.GetProcessesByName(name); len(processEntries) > 0 { 12 | retVal = true 13 | } 14 | 15 | return retVal 16 | } 17 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_process_memory.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/ncr-devops-platform/nagiosfoundation/lib/pkg/memory" 7 | "github.com/ncr-devops-platform/nagiosfoundation/lib/pkg/nagiosformatters" 8 | ) 9 | 10 | // CheckProcessMemoryWithHandler determines the percentage of 11 | // memory used by a process and emits a critical response if it's over 12 | // the critical argument, a warning response if it's over the 13 | // warning argument, and good response otherwise. 14 | func CheckProcessMemoryWithHandler(warning, critical int, processName, metricName string, memoryHandler func(string) (float64, error)) (string, int) { 15 | const checkName = "CheckProcessMemory" 16 | 17 | var msg string 18 | var retcode int 19 | var usedMemoryPercentage float64 20 | var err error 21 | 22 | if memoryHandler == nil { 23 | err = errors.New("No GetProcessMemoryPercentage service") 24 | } else { 25 | usedMemoryPercentage, err = memoryHandler(processName) 26 | } 27 | 28 | if err != nil { 29 | msg, _ = resultMessage(checkName, statusTextCritical, err.Error()) 30 | retcode = 2 31 | } else { 32 | msg, retcode = nagiosformatters.GreaterFormatNagiosCheck(checkName, usedMemoryPercentage, float64(warning), float64(critical), metricName) 33 | } 34 | 35 | return msg, retcode 36 | } 37 | 38 | // CheckProcessMemory executes CheckProcessMemoryWithHandler(), 39 | // passing it the OS constrained GetProcessMemoryPercentage() function, prints 40 | // the returned message and exits with the returned exit code. 41 | // 42 | // Returns are those of CheckProcessMemoryWithHandler() 43 | func CheckProcessMemory(warning, critical int, processName, metricName string) (string, int) { 44 | return CheckProcessMemoryWithHandler(warning, critical, processName, metricName, memory.GetProcessMemoryPercentage) 45 | } 46 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_process_memory_test.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | ) 7 | 8 | func TestCheckProcessMemoryWithHandler(t *testing.T) { 9 | type args struct { 10 | warning int 11 | critical int 12 | processName string 13 | metricName string 14 | memoryHandler func(string) (float64, error) 15 | } 16 | tests := []struct { 17 | name string 18 | args args 19 | want int 20 | }{ 21 | { 22 | name: "WarningResponse", 23 | args: args{ 24 | warning: 50, 25 | critical: 99, 26 | processName: "test", 27 | metricName: "test", 28 | memoryHandler: func(_ string) (float64, error) { 29 | return 55, nil 30 | }, 31 | }, 32 | want: 1, 33 | }, 34 | { 35 | name: "CriticalResponse", 36 | args: args{ 37 | warning: 50, 38 | critical: 99, 39 | processName: "test", 40 | metricName: "test", 41 | memoryHandler: func(_ string) (float64, error) { 42 | return 100, nil 43 | }, 44 | }, 45 | want: 2, 46 | }, 47 | { 48 | name: "CriticalResponse2", 49 | args: args{ 50 | warning: 50, 51 | critical: 99, 52 | processName: "test", 53 | metricName: "test", 54 | memoryHandler: func(_ string) (float64, error) { 55 | return 0.0, errors.New("TEST") 56 | }, 57 | }, 58 | want: 2, 59 | }, 60 | { 61 | name: "OkResponse", 62 | args: args{ 63 | warning: 50, 64 | critical: 99, 65 | processName: "test", 66 | metricName: "test", 67 | memoryHandler: func(_ string) (float64, error) { 68 | return 50, nil 69 | }, 70 | }, 71 | want: 0, 72 | }, 73 | { 74 | name: "NoMemoryHandler", 75 | args: args{ 76 | warning: 50, 77 | critical: 99, 78 | processName: "test", 79 | metricName: "test", 80 | memoryHandler: nil, 81 | }, 82 | want: 2, 83 | }, 84 | } 85 | for _, tt := range tests { 86 | t.Run(tt.name, func(t *testing.T) { 87 | _, retcode := CheckProcessMemoryWithHandler(tt.args.warning, tt.args.critical, tt.args.processName, tt.args.metricName, tt.args.memoryHandler) 88 | if retcode != tt.want { 89 | t.Errorf("CheckProcessMemoryWithHandler() got = %v, want %v", retcode, tt.want) 90 | } 91 | }) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_process_test.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | const testProcessGoodName = "goodName" 9 | const testProcessBadName = "badName" 10 | 11 | type testProcessHandler struct{} 12 | 13 | func (p testProcessHandler) IsProcessRunning(name string) bool { 14 | retval := false 15 | 16 | if name == testProcessGoodName { 17 | retval = true 18 | } 19 | 20 | return retval 21 | } 22 | func TestCheckProcess(t *testing.T) { 23 | fmt.Println("TestCheckProcess()") 24 | 25 | pc := ProcessCheck{ 26 | ProcessName: testProcessGoodName, 27 | ProcessCheckHandler: new(testProcessHandler), 28 | } 29 | 30 | if pc.IsProcessRunning() != true { 31 | t.Error("isProcessRunning() failed") 32 | } 33 | 34 | pc.ProcessName = testProcessBadName 35 | 36 | if pc.IsProcessRunning() != false { 37 | t.Error("isProcessRunning() failed") 38 | } 39 | 40 | var retcode int 41 | // Running check with running process 42 | _, retcode = checkProcessWithService(testProcessGoodName, "running", "metric", new(testProcessHandler)) 43 | if retcode != statusCodeOK { 44 | t.Errorf("Running check with running process failed with retcode %d", retcode) 45 | } 46 | 47 | // Not running check with running process 48 | _, retcode = checkProcessWithService(testProcessGoodName, "notrunning", "metric", new(testProcessHandler)) 49 | if retcode != statusCodeCritical { 50 | t.Errorf("Not running check with running process failed with retcode %d", retcode) 51 | } 52 | 53 | // Running check with not running process 54 | _, retcode = checkProcessWithService(testProcessBadName, "running", "metric", new(testProcessHandler)) 55 | if retcode != statusCodeCritical { 56 | t.Errorf("Running check with not running process failed with retcode %d", retcode) 57 | } 58 | 59 | // Not running check with not running process 60 | _, retcode = checkProcessWithService(testProcessBadName, "notrunning", "metric", new(testProcessHandler)) 61 | if retcode != statusCodeOK { 62 | t.Errorf("Not running check with not running process failed with retcode %d", retcode) 63 | } 64 | 65 | // Invalid check type 66 | _, retcode = checkProcessWithService(testProcessGoodName, "", "metric", new(testProcessHandler)) 67 | if retcode != statusCodeCritical { 68 | t.Errorf("Invalid check type not detected with retcode %d", retcode) 69 | } 70 | 71 | testMsg := "Test Message" 72 | testCheckProcess := func(name, checkType, metricName string, processService ProcessService) (string, int) { 73 | return testMsg, statusCodeOK 74 | } 75 | 76 | _, retcode = checkProcessCmd("dummyprocess", "running", "metric", testCheckProcess, new(testProcessHandler)) 77 | 78 | if retcode != statusCodeOK { 79 | t.Error("valid check process test should have returned OK") 80 | } 81 | 82 | _, retcode = checkProcessCmd("", "dummytype", "metric", testCheckProcess, new(testProcessHandler)) 83 | 84 | if retcode != statusCodeCritical { 85 | t.Error("check process with no -name should return CRITICAL") 86 | } 87 | 88 | _, retcode = checkProcessCmd("", "", "metric", testCheckProcess, new(testProcessHandler)) 89 | 90 | if retcode != statusCodeCritical { 91 | t.Error("check process test with no parameters should have returned CRITICAL") 92 | } 93 | 94 | _, retcode = checkProcessCmd("dummyprocess", "badtype", "metric", testCheckProcess, new(testProcessHandler)) 95 | 96 | if retcode != statusCodeCritical { 97 | t.Error("check process test with invalid type should have returned CRITICAL") 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_process_win.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package nagiosfoundation 4 | 5 | import ( 6 | "strings" 7 | "syscall" 8 | "unsafe" 9 | 10 | "golang.org/x/sys/windows" 11 | ) 12 | 13 | func uint16SliceToString(array []uint16) string { 14 | var end int 15 | 16 | for end = 0; array[end] != 0; end++ { 17 | } 18 | 19 | return syscall.UTF16ToString(array[:end]) 20 | } 21 | 22 | func isProcessRunningOsConstrained(name string) bool { 23 | retval := false 24 | 25 | handle, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0) 26 | if err == nil { 27 | defer windows.CloseHandle(handle) 28 | 29 | var entry windows.ProcessEntry32 30 | entry.Size = uint32(unsafe.Sizeof(entry)) 31 | 32 | err = windows.Process32First(handle, &entry) 33 | 34 | for err == nil && retval == false { 35 | exeName := uint16SliceToString(entry.ExeFile[0:len(entry.ExeFile)]) 36 | //fmt.Println("Entry:", exeName, "| Match:", name) 37 | retval = strings.EqualFold(name, exeName) 38 | 39 | err = windows.Process32Next(handle, &entry) 40 | } 41 | } 42 | 43 | return retval 44 | } 45 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_uptime.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/shirou/gopsutil/host" 8 | "github.com/ncr-devops-platform/nagiosfoundation/lib/pkg/nagiosformatters" 9 | ) 10 | 11 | // CheckUptime gathers information about the host uptime. 12 | func CheckUptime(checkType string, warning, critical time.Duration, metricName string) (string, int) { 13 | 14 | const checkName = "CheckUptime" 15 | 16 | var msg string 17 | var retcode int 18 | 19 | uptime, err := host.Uptime() 20 | 21 | if err != nil { 22 | msg, _ = resultMessage(checkName, statusTextCritical, fmt.Sprintf("Failed to determine uptime %s", err.Error())) 23 | retcode = 2 24 | } else { 25 | msg, retcode = nagiosformatters.GreaterFormatNagiosCheck(checkName, float64(uptime), float64(warning.Seconds()), float64(critical.Seconds()), metricName) 26 | } 27 | 28 | return msg, retcode 29 | } -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_uptime_test.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestCheckUptime(t *testing.T) { 9 | forever := time.Duration(24000 * time.Hour) 10 | msg, retcode := CheckUptime("", forever, forever, "uptime_name") 11 | if len(msg) == 0 { 12 | t.Error("Message not populated for uptime OK") 13 | } 14 | 15 | if retcode != 0 { 16 | t.Error("Incorrect return code for uptime OK") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/check_user_group_test.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "errors" 5 | "os/user" 6 | "testing" 7 | ) 8 | 9 | const ( 10 | goodUserGroupString = "good" 11 | badUserGroupString = "bad" 12 | errorUserGroupString = "error" 13 | ) 14 | 15 | type userGroupTestHandler struct{} 16 | 17 | func (u userGroupTestHandler) testString(value string) error { 18 | var retval error 19 | 20 | if value != badUserGroupString { 21 | retval = nil 22 | } else { 23 | retval = errors.New("testing error") 24 | } 25 | 26 | return retval 27 | } 28 | 29 | func (u userGroupTestHandler) Lookup(userName string) (*user.User, error) { 30 | user := user.User{ 31 | Username: userName, 32 | } 33 | 34 | return &user, u.testString(userName) 35 | } 36 | 37 | func (u userGroupTestHandler) LookupGroup(groupName string) (*user.Group, error) { 38 | return nil, u.testString(groupName) 39 | } 40 | 41 | func (u userGroupTestHandler) LookupGroupID(groupID string) (*user.Group, error) { 42 | return &user.Group{Name: goodUserGroupString}, nil 43 | } 44 | 45 | func (u userGroupTestHandler) GroupIds(userInfo *user.User) ([]string, error) { 46 | groupIDList := []string{"1"} 47 | var err error 48 | 49 | if userInfo != nil && userInfo.Username == errorUserGroupString { 50 | groupIDList = nil 51 | err = errors.New("Error fetching Group IDs") 52 | } 53 | return groupIDList, err 54 | } 55 | 56 | func TestCheckUser(t *testing.T) { 57 | var retval int 58 | 59 | if _, retval = (UserGroupCheck{ 60 | UserName: goodUserGroupString, 61 | GroupName: "", 62 | Service: new(userGroupTestHandler), 63 | }).CheckUser(); retval != statusCodeOK { 64 | t.Error("CheckUser() with good user failed") 65 | } 66 | 67 | if _, retval = (UserGroupCheck{ 68 | UserName: badUserGroupString, 69 | GroupName: "", 70 | Service: new(userGroupTestHandler), 71 | }).CheckUser(); retval != statusCodeCritical { 72 | t.Error("CheckUser() with bad user failed") 73 | } 74 | } 75 | 76 | func TestCheckGroup(t *testing.T) { 77 | var retval int 78 | handler := new(userGroupTestHandler) 79 | 80 | if _, retval = (UserGroupCheck{ 81 | UserName: "", 82 | GroupName: goodUserGroupString, 83 | Service: handler, 84 | }).CheckGroup(); retval != statusCodeOK { 85 | t.Error("CheckGroup() with good group failed") 86 | } 87 | 88 | if _, retval = (UserGroupCheck{ 89 | UserName: "", 90 | GroupName: badUserGroupString, 91 | Service: handler, 92 | }).CheckGroup(); retval != statusCodeCritical { 93 | t.Error("CheckGroup() with bad group failed") 94 | } 95 | } 96 | 97 | func TestCheckUserGroup(t *testing.T) { 98 | var retval int 99 | handler := new(userGroupTestHandler) 100 | 101 | if _, retval = (UserGroupCheck{ 102 | UserName: goodUserGroupString, 103 | GroupName: goodUserGroupString, 104 | Service: handler, 105 | }).CheckUserGroup(); retval != statusCodeOK { 106 | t.Error("CheckGroup() with good user and good group failed") 107 | } 108 | 109 | if _, retval = (UserGroupCheck{ 110 | UserName: errorUserGroupString, 111 | GroupName: goodUserGroupString, 112 | Service: handler, 113 | }).CheckUserGroup(); retval != statusCodeCritical { 114 | t.Error("CheckGroup() with GroupIds() returning error failed") 115 | } 116 | 117 | if _, retval = (UserGroupCheck{ 118 | UserName: goodUserGroupString, 119 | GroupName: badUserGroupString, 120 | Service: handler, 121 | }).CheckUserGroup(); retval != statusCodeCritical { 122 | t.Error("CheckGroup() with good user and bad group failed") 123 | } 124 | 125 | if _, retval = (UserGroupCheck{ 126 | UserName: badUserGroupString, 127 | GroupName: badUserGroupString, 128 | Service: handler, 129 | }).CheckUserGroup(); retval != statusCodeCritical { 130 | t.Error("CheckGroup() with bad user and bad group failed") 131 | } 132 | 133 | if _, retval = (UserGroupCheck{ 134 | UserName: badUserGroupString, 135 | GroupName: goodUserGroupString, 136 | Service: handler, 137 | }).CheckUserGroup(); retval != statusCodeCritical { 138 | t.Error("CheckGroup() with bad user and good group failed") 139 | } 140 | } 141 | 142 | func TestCheckUserGroupWithFlags(t *testing.T) { 143 | handler := new(userGroupTestHandler) 144 | 145 | _, err := CheckUserGroupWithHandler(goodUserGroupString, "", handler) 146 | if err != statusCodeOK { 147 | t.Error("CheckUserGroup with --user flag failed") 148 | } 149 | 150 | _, err = CheckUserGroupWithHandler("", goodUserGroupString, handler) 151 | if err != statusCodeOK { 152 | t.Error("CheckUserGroup with --group flag failed") 153 | } 154 | 155 | _, err = CheckUserGroupWithHandler(goodUserGroupString, goodUserGroupString, handler) 156 | if err != statusCodeOK { 157 | t.Error("CheckUserGroup with --user and --group flags failed") 158 | } 159 | } 160 | 161 | func TestOsUserCalls(t *testing.T) { 162 | ugh := UserGroupHandler{} 163 | 164 | currentUser, err := user.Current() 165 | if err != nil { 166 | t.Error("Lookingup current user") 167 | } 168 | 169 | // Lookup Group by ID 170 | groupByID, err := ugh.LookupGroupID(currentUser.Gid) 171 | if err != nil { 172 | t.Error("Lookup current user primary group ID") 173 | } 174 | 175 | // Lookup Group by Name 176 | groupByName, err := ugh.LookupGroup(groupByID.Name) 177 | if err != nil { 178 | t.Error("Lookup primary group ID by name") 179 | } 180 | 181 | if groupByID.Gid != groupByName.Gid { 182 | t.Error("Lookup group by ID does not match lookup group by name") 183 | } 184 | 185 | _, result := CheckUserGroup(currentUser.Username, groupByID.Name) 186 | if result != 0 { 187 | t.Error("Check for current user name and group ID") 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/resultmessage.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | const ( 9 | statusTextOK = "OK" 10 | statusTextWarning = "WARNING" 11 | statusTextCritical = "CRITICAL" 12 | statusTextUnknown = "UNKNOWN" 13 | ) 14 | 15 | const ( 16 | statusCodeOK = iota 17 | statusCodeWarning 18 | statusCodeCritical 19 | statusCodeUnknown 20 | ) 21 | 22 | var errResultMsgNotEnoughArgs = errors.New("Not enough arguments") 23 | var errResultMsgTooManyArgs = errors.New("Too many arguments") 24 | var errResultMsgInvalidStatus = errors.New("Invalid status text") 25 | 26 | func resultMessage(s ...string) (string, error) { 27 | // s[0] - check name 28 | // s[1] - status text 29 | // s[2] - result description 30 | // s[3] - nagios output 31 | 32 | const ( 33 | checkNameOffset = iota 34 | statusTextOffset 35 | resultDescOffset 36 | nagiosOutputOffset 37 | ) 38 | 39 | var msg string 40 | var err error 41 | 42 | argCount := len(s) 43 | 44 | if argCount < 2 { 45 | err = errResultMsgNotEnoughArgs 46 | } else if argCount > 4 { 47 | err = errResultMsgTooManyArgs 48 | } else if s[statusTextOffset] != statusTextOK && s[statusTextOffset] != statusTextWarning && 49 | s[statusTextOffset] != statusTextCritical && s[statusTextOffset] != statusTextUnknown { 50 | err = errResultMsgInvalidStatus 51 | } else { 52 | // "CheckName OK" 53 | msg = fmt.Sprintf("%s %s", s[checkNameOffset], s[statusTextOffset]) 54 | 55 | // "CheckName OK - Description of result" 56 | if argCount > resultDescOffset && len(s[resultDescOffset]) > 0 { 57 | msg += fmt.Sprintf(" - %s", s[resultDescOffset]) 58 | } 59 | 60 | // "CheckName OK - Description of result | nagios output" 61 | if argCount > nagiosOutputOffset && len(s[nagiosOutputOffset]) > 0 { 62 | msg += fmt.Sprintf(" | %s", s[nagiosOutputOffset]) 63 | } 64 | } 65 | 66 | return msg, err 67 | } 68 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/resutmessage_test.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestResultMessage(t *testing.T) { 9 | checkName := "TestName" 10 | statusText := "OK" 11 | description := "Description" 12 | nagiosOutput := "nagios output" 13 | 14 | // All parameters populated, valid result 15 | expectedValue := fmt.Sprintf("%s %s - %s | %s", checkName, statusText, description, nagiosOutput) 16 | actualValue, err := resultMessage(checkName, statusText, description, nagiosOutput) 17 | if err != nil { 18 | t.Error("resultMessage() with all parameters should have returned valid result. Err:", err) 19 | } 20 | if actualValue != expectedValue { 21 | t.Errorf("resultMessage() with all parameters did not return expected message. Expected: %s, Actual: %s", expectedValue, actualValue) 22 | } 23 | 24 | // Nagios output not populated, valid result 25 | expectedValue = fmt.Sprintf("%s %s - %s", checkName, statusText, description) 26 | actualValue, err = resultMessage(checkName, statusText, description) 27 | if err != nil { 28 | t.Error("resultMessage() with 3 parameters should have returned valid result. Err:", err) 29 | } 30 | if actualValue != expectedValue { 31 | t.Errorf("resultMessage() with 3 parameters did not return expected message. Expected: %s, Actual: %s", expectedValue, actualValue) 32 | } 33 | 34 | // Only required parameters populated, valid result 35 | expectedValue = fmt.Sprintf("%s %s", checkName, statusText) 36 | actualValue, err = resultMessage(checkName, statusText) 37 | if err != nil { 38 | t.Error("resultMessage() with all parameters should have returned valid result. Err:", err) 39 | } 40 | if actualValue != expectedValue { 41 | t.Errorf("resultMessage() with all parameters did not return expected message. Expected: %s, Actual: %s", expectedValue, actualValue) 42 | } 43 | 44 | // Invalid status text 45 | _, err = resultMessage("TestName", "INVALIDTEXTRESULT") 46 | if err != errResultMsgInvalidStatus { 47 | t.Error("resultMessage() with invalid status text should have returned errResultMsgInvalidStatus") 48 | } 49 | 50 | // Only one parameter 51 | _, err = resultMessage("TestName") 52 | if err != errResultMsgNotEnoughArgs { 53 | t.Error("resultMessage() with one parameter should have returned errResultMsgNotEnoughArgs") 54 | } 55 | 56 | // No parameters 57 | _, err = resultMessage() 58 | if err != errResultMsgNotEnoughArgs { 59 | t.Error("resultMessage() with no parameters should have returned errResultMsgNotEnoughArgs") 60 | } 61 | 62 | // Too many parameters 63 | _, err = resultMessage("", "", "", "", "") 64 | if err != errResultMsgTooManyArgs { 65 | t.Error("resultMessage() with five parameters should have returned errResultMsgTooManyArgs") 66 | } 67 | 68 | // Test remaining status text values 69 | for _, statusText := range []string{statusTextCritical, statusTextWarning, statusTextUnknown} { 70 | _, err = resultMessage("TestName", statusText) 71 | if err != nil { 72 | t.Errorf("resultMessage() with valid status text of %s failed", statusText) 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/servicestatus.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | const serviceCheckName = "CheckService" 10 | 11 | type getServiceInfoFunc func(string) (string, string, string, int, error) 12 | 13 | type serviceInfo struct { 14 | // The name of the service to process. 15 | desiredName string 16 | 17 | // The state of the service to match. 18 | desiredState string 19 | 20 | // The user of the service to match. 21 | desiredUser string 22 | 23 | // User only wants current state 24 | currentStateWanted bool 25 | 26 | // The name of the outputted metric 27 | metricName string 28 | 29 | actualName string 30 | actualStateText string 31 | actualStateNbr int 32 | actualUser string 33 | 34 | getServiceInfo getServiceInfoFunc 35 | } 36 | 37 | // Returns the actual name of the service resulting from the service query. 38 | func (i *serviceInfo) ActualName() string { 39 | return i.actualName 40 | } 41 | 42 | // Returns the actual state text of the service resulting from the service query. 43 | func (i *serviceInfo) ActualStateText() string { 44 | return i.actualStateText 45 | } 46 | 47 | // Returns the actual state number of the service resulting from the service query. 48 | func (i *serviceInfo) ActualStateNbr() int { 49 | return i.actualStateNbr 50 | } 51 | 52 | // Returns the actual user of the service resulting from the service query. 53 | func (i *serviceInfo) ActualUser() string { 54 | return i.actualUser 55 | } 56 | 57 | // Checks for a match against the actual name of the service. The comparison 58 | // is case insensitive. 59 | func (i *serviceInfo) IsName(name string) bool { 60 | return strings.EqualFold(i.ActualName(), name) 61 | } 62 | 63 | // Checks for a match against the actual state of the service. The comparison 64 | // is case insensitive. 65 | func (i *serviceInfo) IsState(state string) bool { 66 | return strings.EqualFold(i.ActualStateText(), state) 67 | } 68 | 69 | // Checks for a match against the actual user of the service. The comparison 70 | // is case insensitive. 71 | func (i *serviceInfo) IsUser(user string) bool { 72 | return strings.EqualFold(i.ActualUser(), user) 73 | } 74 | 75 | // Executes the OS constrained function to retrieve information about a service. 76 | // This information is derived differently in Windows and Linux and must execute 77 | // an OS constrained method named getInfoOsConstrained(). 78 | func (i *serviceInfo) GetInfo() error { 79 | var err error 80 | 81 | if i.getServiceInfo == nil { 82 | return errors.New("No get service info handler declared") 83 | } 84 | 85 | i.actualName, i.actualUser, i.actualStateText, i.actualStateNbr, err = i.getServiceInfo(i.desiredName) 86 | 87 | return err 88 | } 89 | 90 | // Process the desired service info against the actual service info and return 91 | // check text and a return code. 92 | func (i *serviceInfo) ProcessInfo() (string, int) { 93 | var ( 94 | checkInfo, nagiosInfo string 95 | retcode int 96 | ) 97 | 98 | if !i.IsName(i.desiredName) { 99 | if i.currentStateWanted { 100 | nagiosInfo = fmt.Sprintf("%s=255 service_name=%s", i.metricName, i.desiredName) 101 | if i.desiredState == "" { 102 | retcode = 0 103 | } else { 104 | retcode = 2 105 | } 106 | } else { 107 | retcode = 2 108 | } 109 | checkInfo = fmt.Sprintf("%s does not exist", i.desiredName) 110 | } else if i.currentStateWanted { 111 | checkInfo = fmt.Sprintf("%s is in a %s state", i.desiredName, i.ActualStateText()) 112 | nagiosInfo = fmt.Sprintf("%s=%d service_name=%s", i.metricName, i.ActualStateNbr(), i.desiredName) 113 | if i.desiredState != "" && !i.IsState(i.desiredState) { 114 | retcode = 2 115 | } else { 116 | retcode = 0 117 | } 118 | } else if i.desiredState != "" && i.desiredUser != "" { 119 | if i.IsState(i.desiredState) && i.IsUser(i.desiredUser) { 120 | checkInfo = fmt.Sprintf("%s in a %s state and started by user %s", 121 | i.ActualName(), i.ActualStateText(), i.ActualUser()) 122 | retcode = 0 123 | } else { 124 | checkInfo = fmt.Sprintf("%s either not in a %s state or not started by user %s", 125 | i.ActualName(), i.desiredState, i.desiredUser) 126 | retcode = 2 127 | } 128 | } else if i.desiredState != "" { 129 | if i.IsState(i.desiredState) { 130 | checkInfo = fmt.Sprintf("%s in a %s state", 131 | i.ActualName(), i.ActualStateText()) 132 | retcode = 0 133 | } else { 134 | checkInfo = fmt.Sprintf("%s not in a %s state", 135 | i.ActualName(), i.desiredState) 136 | retcode = 2 137 | } 138 | } else if i.desiredUser != "" { 139 | if i.IsUser(i.desiredUser) { 140 | checkInfo = fmt.Sprintf("%s started by user %s", 141 | i.ActualName(), i.ActualUser()) 142 | retcode = 0 143 | } else { 144 | checkInfo = fmt.Sprintf("%s not started by user %s", 145 | i.ActualName(), i.desiredUser) 146 | retcode = 2 147 | } 148 | } else { 149 | checkInfo = fmt.Sprintf("%s in a %s state and started by user %s", 150 | i.ActualName(), i.ActualStateText(), i.ActualUser()) 151 | retcode = 0 152 | } 153 | 154 | var responseStateText, actualInfo string 155 | 156 | if retcode == 0 { 157 | responseStateText = statusTextOK 158 | actualInfo = "" 159 | } else { 160 | responseStateText = statusTextCritical 161 | actualInfo = fmt.Sprintf(" (Name: %s, State: %s, User: %s)", 162 | i.ActualName(), i.ActualStateText(), i.ActualUser()) 163 | } 164 | 165 | msg, _ := resultMessage(serviceCheckName, responseStateText, checkInfo+actualInfo, nagiosInfo) 166 | 167 | return msg, retcode 168 | } 169 | 170 | // CheckService checks a service based on name, state, 171 | // user, and manager 172 | func CheckService(name, state, user string, currentStateWanted bool, useSvcMgr bool, metricName string) (string, int) { 173 | return checkServiceOsConstrained(name, state, user, currentStateWanted, useSvcMgr, metricName) 174 | } 175 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/servicestatus_test.go: -------------------------------------------------------------------------------- 1 | package nagiosfoundation 2 | 3 | import "testing" 4 | 5 | func TestActualIs(t *testing.T) { 6 | var goodName = "goodName" 7 | var goodState = "goodState" 8 | var goodUser = "goodUser" 9 | 10 | var badName = "badName" 11 | var badState = "badState" 12 | var badUser = "badUser" 13 | 14 | var matchName = "GOODNAME" 15 | var matchStateText = "GOODSTATE" 16 | var matchStateNbr = 0 17 | var matchUser = "GOODUSER" 18 | 19 | si := serviceInfo{ 20 | getServiceInfo: func(n string) (string, string, string, int, error) { 21 | return goodName, goodUser, goodState, 0, nil 22 | }, 23 | } 24 | 25 | var err error 26 | err = si.GetInfo() 27 | if err != nil { 28 | t.Errorf("GetInfo() returned error but was fed good data") 29 | } 30 | 31 | var actualResult string 32 | 33 | actualResult = si.ActualName() 34 | if actualResult != goodName { 35 | t.Errorf("ActualName (%s) does not match desiredName (%s)", actualResult, goodName) 36 | } 37 | 38 | actualResult = si.ActualStateText() 39 | if actualResult != goodState { 40 | t.Errorf("ActualState (%s) does not match desiredState (%s)", actualResult, goodState) 41 | } 42 | 43 | actualResult = si.ActualUser() 44 | if actualResult != goodUser { 45 | t.Errorf("ActualUser (%s) does not match desiredUser (%s)", actualResult, goodUser) 46 | } 47 | 48 | actualResult = si.ActualName() 49 | if actualResult != goodName { 50 | t.Errorf("ActualName (%s) does not match desiredName (%s)", actualResult, goodName) 51 | } 52 | 53 | var isResult bool 54 | isResult = si.IsName(matchName) 55 | if !isResult { 56 | t.Errorf("IsName(%s) does not match actualName (%s)", matchName, si.ActualName()) 57 | } 58 | 59 | isResult = si.IsState(matchStateText) 60 | if !isResult { 61 | t.Errorf("IsState(%s) does not match actualState (%s)", matchStateText, si.ActualStateText()) 62 | } 63 | 64 | if si.ActualStateNbr() != matchStateNbr { 65 | isResult = false 66 | } else { 67 | isResult = true 68 | } 69 | if !isResult { 70 | t.Errorf("IsState(%d) does not match actualState (%d)", matchStateNbr, si.ActualStateNbr()) 71 | } 72 | 73 | isResult = si.IsUser(matchUser) 74 | if !isResult { 75 | t.Errorf("IsUser(%s) does not match actualUser (%s)", matchUser, si.ActualUser()) 76 | } 77 | 78 | si.desiredName = goodName 79 | si.desiredState = goodState 80 | si.desiredUser = goodUser 81 | 82 | var msg string 83 | var retcode int 84 | 85 | // All good check 86 | msg, retcode = si.ProcessInfo() 87 | if retcode != 0 { 88 | t.Errorf("ProcessInfo() failed on good data with retcode %d, msg %s", retcode, msg) 89 | } 90 | 91 | // Check all with bad name 92 | si.desiredName = badName 93 | msg, retcode = si.ProcessInfo() 94 | if retcode != 2 { 95 | t.Errorf("ProcessInfo() failed on bad name with retcode %d, msg %s", retcode, msg) 96 | } 97 | 98 | // Check all with bad state 99 | si.desiredName = goodName 100 | si.desiredState = badState 101 | msg, retcode = si.ProcessInfo() 102 | if retcode != 2 { 103 | t.Errorf("ProcessInfo() failed on bad state with retcode %d, msg %s", retcode, msg) 104 | } 105 | 106 | // Check all with bad user 107 | si.desiredState = goodState 108 | si.desiredUser = badUser 109 | msg, retcode = si.ProcessInfo() 110 | if retcode != 2 { 111 | t.Errorf("ProcessInfo() failed on bad user with retcode %d, msg %s", retcode, msg) 112 | } 113 | 114 | // Check good state only 115 | si.desiredUser = "" 116 | msg, retcode = si.ProcessInfo() 117 | if retcode != 0 { 118 | t.Errorf("ProcessInfo() failed on blank user with retcode %d, msg %s", retcode, msg) 119 | } 120 | 121 | // Check bad state only 122 | si.desiredState = badState 123 | msg, retcode = si.ProcessInfo() 124 | if retcode != 2 { 125 | t.Errorf("ProcessInfo() failed on bad state with retcode %d, msg %s", retcode, msg) 126 | } 127 | 128 | // Check good user only 129 | si.desiredUser = goodUser 130 | si.desiredState = "" 131 | msg, retcode = si.ProcessInfo() 132 | if retcode != 0 { 133 | t.Errorf("ProcessInfo() failed on blank state with retcode %d, msg %s", retcode, msg) 134 | } 135 | 136 | // Check bad user only 137 | si.desiredUser = badUser 138 | msg, retcode = si.ProcessInfo() 139 | if retcode != 2 { 140 | t.Errorf("ProcessInfo() failed on bad user with retcode %d, msg %s", retcode, msg) 141 | } 142 | 143 | // Get service info only 144 | si.desiredState = "" 145 | si.desiredUser = "" 146 | msg, retcode = si.ProcessInfo() 147 | if retcode != 0 { 148 | t.Errorf("ProcessInfo() failed returning service info only retcode %d, msg %s", retcode, msg) 149 | } 150 | 151 | si.currentStateWanted = true 152 | msg, retcode = si.ProcessInfo() 153 | if retcode != 0 { 154 | t.Errorf("ProcessInfo() failed when fetching current state") 155 | } 156 | 157 | si.desiredName = badName 158 | msg, retcode = si.ProcessInfo() 159 | if retcode != 0 { 160 | t.Errorf("ProcessInfo() failed when fetching current state for unknown service") 161 | } 162 | 163 | si.desiredState = goodState 164 | msg, retcode = si.ProcessInfo() 165 | if retcode != 2 { 166 | t.Errorf("ProcessInfo() failed when fetching current state for unknown service with good service provided") 167 | } 168 | 169 | si.getServiceInfo = nil 170 | err = si.GetInfo() 171 | if err == nil { 172 | t.Errorf("GetInfo() returned no error but had a nil handler") 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/servicestatuslinux.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package nagiosfoundation 4 | 5 | import ( 6 | "fmt" 7 | "os/exec" 8 | "strings" 9 | ) 10 | 11 | // Not Used 12 | // 13 | // To be implemented in the future to make this Linux module 14 | // compliant with the serviceInfo structure like the Windows 15 | // version is. 16 | // 17 | // When implemented, the name parameter will contain the name of the 18 | // service for which to get info and returns will be the actual name, 19 | // state, and user of the service being checked. Note the service 20 | // manager is not passed in. When implemented, this will probably 21 | // need to be added as a parameter but ignored in the Windows build 22 | // or assigned to a local global in CheckServiceOsConstrained() then 23 | // accessed here. 24 | // 25 | // See the Windows version of GetInfoOsConstrained() for an example. 26 | func getInfoOsConstrained(name string) (string, string, string, error) { 27 | return "", "", "", nil 28 | } 29 | 30 | func systemdServiceTest(serviceName string, currentStateWanted bool, metricName string) (string, int) { 31 | cmd := exec.Command("systemctl", "check", serviceName) 32 | out, err := cmd.CombinedOutput() 33 | state := strings.TrimSpace(string(out)) 34 | 35 | var retcode int 36 | var info string 37 | var serviceState int 38 | 39 | if err != nil { 40 | if _, ok := err.(*exec.ExitError); ok { 41 | info = fmt.Sprintf("%s not in a running state", serviceName) 42 | retcode = 2 43 | } else { 44 | info = fmt.Sprintf("Failed to execute systemctl. %s Status unknown: %v", serviceName, err) 45 | retcode = 2 46 | } 47 | 48 | serviceState = 0 49 | } else { 50 | info = fmt.Sprintf("%s in a running state", serviceName) 51 | serviceState = 1 52 | retcode = 0 53 | } 54 | 55 | var responseStateText string 56 | var actualInfo string 57 | 58 | if retcode == 0 { 59 | responseStateText = "OK" 60 | } else { 61 | responseStateText = "CRITICAL" 62 | actualInfo = fmt.Sprintf(" (State: %s)", state) 63 | } 64 | 65 | msg := fmt.Sprintf("%s %s - %s%s", serviceCheckName, responseStateText, info, actualInfo) 66 | 67 | if currentStateWanted { 68 | msg = msg + fmt.Sprintf(" | %s=%d service_name=%s", 69 | metricName, serviceState, serviceName) 70 | retcode = 0 71 | } 72 | 73 | return msg, retcode 74 | } 75 | 76 | func checkServiceOsConstrained(name string, state string, user string, currentStateWanted bool, useSvcMgr bool, metricName string) (string, int) { 77 | return systemdServiceTest(name, currentStateWanted, metricName) 78 | } 79 | -------------------------------------------------------------------------------- /lib/app/nagiosfoundation/servicestatuswin.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package nagiosfoundation 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | 9 | "golang.org/x/sys/windows" 10 | "golang.org/x/sys/windows/svc" 11 | "golang.org/x/sys/windows/svc/mgr" 12 | 13 | "github.com/StackExchange/wmi" 14 | ) 15 | 16 | func getStateNbrFromText(state string) int { 17 | var nbrState int 18 | 19 | switch state { 20 | case "Stopped": 21 | nbrState = 6 22 | case "Start Pending": 23 | nbrState = 2 24 | case "Stop Pending": 25 | nbrState = 5 26 | case "Running": 27 | nbrState = 0 28 | case "Continue Pending": 29 | nbrState = 4 30 | case "Pause Pending": 31 | nbrState = 3 32 | case "Paused": 33 | nbrState = 1 34 | default: 35 | nbrState = 7 36 | } 37 | 38 | return nbrState 39 | } 40 | 41 | func getInfoWmi(name string) (string, string, string, int, error) { 42 | type win32_Service struct { 43 | Name string 44 | State string 45 | StartName string 46 | } 47 | 48 | var dst []win32_Service 49 | var actualName string 50 | var actualUser string 51 | var actualStateText string 52 | var actualStateNbr int 53 | 54 | w := fmt.Sprintf("where name = '%v'", name) 55 | 56 | query := wmi.CreateQuery(&dst, w) 57 | 58 | err := wmi.Query(query, &dst) 59 | 60 | if err == nil && len(dst) >= 1 { 61 | actualName = dst[0].Name 62 | actualUser = dst[0].StartName 63 | actualStateText = dst[0].State 64 | actualStateNbr = getStateNbrFromText(actualStateText) 65 | } 66 | 67 | return actualName, actualUser, actualStateText, actualStateNbr, err 68 | } 69 | 70 | func getStateText(state svc.State) string { 71 | var txtState string 72 | 73 | switch state { 74 | case windows.SERVICE_STOPPED: 75 | txtState = "Stopped" 76 | case windows.SERVICE_START_PENDING: 77 | txtState = "Start Pending" 78 | case windows.SERVICE_STOP_PENDING: 79 | txtState = "Stop Pending" 80 | case windows.SERVICE_RUNNING: 81 | txtState = "Running" 82 | case windows.SERVICE_CONTINUE_PENDING: 83 | txtState = "Continue Pending" 84 | case windows.SERVICE_PAUSE_PENDING: 85 | txtState = "Pause Pending" 86 | case windows.SERVICE_PAUSED: 87 | txtState = "Paused" 88 | default: 89 | txtState = "Unknown" 90 | } 91 | 92 | return txtState 93 | } 94 | 95 | func getStateNbr(state svc.State) int { 96 | var nbrState int 97 | 98 | switch state { 99 | case windows.SERVICE_STOPPED: 100 | nbrState = 6 101 | case windows.SERVICE_START_PENDING: 102 | nbrState = 2 103 | case windows.SERVICE_STOP_PENDING: 104 | nbrState = 5 105 | case windows.SERVICE_RUNNING: 106 | nbrState = 0 107 | case windows.SERVICE_CONTINUE_PENDING: 108 | nbrState = 4 109 | case windows.SERVICE_PAUSE_PENDING: 110 | nbrState = 3 111 | case windows.SERVICE_PAUSED: 112 | nbrState = 1 113 | default: 114 | nbrState = 7 115 | } 116 | 117 | return nbrState 118 | } 119 | 120 | func getInfoSvcMgr(name string) (string, string, string, int, error) { 121 | var serviceName string 122 | var serviceStartName string 123 | var serviceStateText string 124 | var serviceStateNbr int 125 | 126 | mgrPtr, err := mgr.Connect() 127 | if err != nil { 128 | err = errors.New("Connect to Service Manager failed: " + err.Error()) 129 | } 130 | 131 | var service *mgr.Service 132 | if err == nil { 133 | service, err = mgrPtr.OpenService(name) 134 | if err == nil { 135 | serviceName = service.Name 136 | } else { 137 | // Error is valid - service doesn't exist which is 138 | // what is being checked. 139 | err = nil 140 | } 141 | } 142 | 143 | if service != nil { 144 | var config mgr.Config 145 | if err == nil { 146 | config, err = service.Config() 147 | 148 | if err == nil { 149 | serviceStartName = config.ServiceStartName 150 | } else { 151 | err = errors.New("Getting service configuration failed: " + err.Error()) 152 | } 153 | } 154 | 155 | var status svc.Status 156 | if err == nil { 157 | status, err = service.Query() 158 | 159 | if err == nil { 160 | serviceStateText = getStateText(status.State) 161 | serviceStateNbr = getStateNbr(status.State) 162 | } else { 163 | err = errors.New("Query service failed: " + err.Error()) 164 | } 165 | } 166 | } 167 | 168 | return serviceName, serviceStartName, serviceStateText, serviceStateNbr, err 169 | } 170 | 171 | func checkServiceOsConstrained(name string, state string, user string, currentStateWanted bool, useSvcMgr bool, metricName string) (string, int) { 172 | managers := make(map[bool]getServiceInfoFunc) 173 | managers[false] = getInfoWmi 174 | managers[true] = getInfoSvcMgr 175 | 176 | var msg string 177 | var retcode int 178 | 179 | i := serviceInfo{ 180 | desiredName: name, 181 | desiredState: state, 182 | desiredUser: user, 183 | currentStateWanted: currentStateWanted, 184 | getServiceInfo: managers[useSvcMgr], 185 | metricName: metricName, 186 | } 187 | 188 | err := i.GetInfo() 189 | 190 | if err != nil { 191 | msg = fmt.Sprintf("%s CRITICAL - %s", serviceCheckName, err) 192 | retcode = 2 193 | } else { 194 | msg, retcode = i.ProcessInfo() 195 | } 196 | 197 | return msg, retcode 198 | } 199 | -------------------------------------------------------------------------------- /lib/pkg/cpu/cpu.go: -------------------------------------------------------------------------------- 1 | package cpu 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "strconv" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | func average(input []float64) float64 { 12 | count := len(input) 13 | if count <= 0.0 { 14 | return 0.0 15 | } 16 | 17 | sum := float64(0.0) 18 | for _, value := range input { 19 | sum += value 20 | } 21 | 22 | return sum / float64(count) 23 | } 24 | 25 | func getStats(getStatsData func() (string, error)) ([]float64, error) { 26 | if getStatsData == nil { 27 | return []float64{}, errors.New("No stats data handler given") 28 | } 29 | 30 | statsData, err := getStatsData() 31 | 32 | if err != nil { 33 | return []float64{}, err 34 | } 35 | 36 | line := strings.Split(statsData, "\n")[0] 37 | fields := strings.Fields(line) 38 | 39 | if len(fields) < 3 { 40 | return []float64{}, errors.New("CPU data not found") 41 | } 42 | 43 | stats := fields[1:] 44 | result := make([]float64, len(stats)) 45 | 46 | for i := range stats { 47 | result[i], err = strconv.ParseFloat(stats[i], 64) 48 | if err != nil { 49 | return result, err 50 | } 51 | } 52 | 53 | return result, nil 54 | } 55 | 56 | func getCPULoadLinuxWithHandler(getStatsData func() (string, error)) (float64, error) { 57 | var sleep = 1 58 | var usage, totalDiff float64 59 | 60 | beforeStats, err := getStats(getStatsData) 61 | if err != nil { 62 | return usage, err 63 | } 64 | 65 | time.Sleep(time.Duration(sleep) * time.Second) 66 | 67 | afterStats, err := getStats(getStatsData) 68 | if err != nil { 69 | return usage, err 70 | } 71 | 72 | diffStats := make([]float64, len(beforeStats)) 73 | for i := range beforeStats { 74 | diffStats[i] = afterStats[i] - beforeStats[i] 75 | totalDiff += diffStats[i] 76 | } 77 | 78 | usage = 100.0 * (totalDiff - diffStats[3]) / totalDiff 79 | return usage, nil 80 | } 81 | 82 | func getStatsDataService() (string, error) { 83 | var statsData string 84 | 85 | contents, err := ioutil.ReadFile("/proc/stat") 86 | 87 | if err == nil { 88 | statsData = string(contents) 89 | } 90 | 91 | return statsData, err 92 | } 93 | 94 | func getCPULoadLinux() (float64, error) { 95 | return getCPULoadLinuxWithHandler(getStatsDataService) 96 | } 97 | 98 | // GetCPULoad returns the current CPU load as a percentage. 99 | func GetCPULoad() (float64, error) { 100 | return getCPULoadOsConstrained() 101 | } 102 | 103 | // GetProcessCPULoad returns the current CPU load of a process as a percentage (normalized for core count). 104 | // If perCoreCalculation is true, then reports highest used core utilization instead (Linux only). 105 | func GetProcessCPULoad(processName string, perCoreCalculation bool) (float64, error) { 106 | return getProcessCPULoadOsConstrained(processName, perCoreCalculation) 107 | } 108 | -------------------------------------------------------------------------------- /lib/pkg/cpu/cpu_test.go: -------------------------------------------------------------------------------- 1 | package cpu 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | ) 7 | 8 | func TestCpu(t *testing.T) { 9 | var cpuData [2]string 10 | var testError error 11 | 12 | goodData := [2]string{ 13 | "cpu 10 10 10 00 10 10 10 10 10 10", 14 | "cpu 20 20 20 100 30 20 20 20 20 20", 15 | } 16 | 17 | badBeforeData := [2]string{ 18 | "cpu 235445 1470 abc 15137277 201523 0 3938 0 0 0", 19 | "cpu 235445 1470 74483 15137277 201523 0 3938 0 0 0", 20 | } 21 | 22 | badAfterData := [2]string{ 23 | "cpu 235445 1470 74481 15137277 201523 0 3938 0 0 0", 24 | "cpu 235445 1470 abc 15137277 201523 0 3938 0 0 0", 25 | } 26 | 27 | emptyData := [2]string{"", ""} 28 | 29 | shortData := [2]string{"cpu 10", "cpu 20"} 30 | 31 | var counter int 32 | 33 | testCPUData := func() (string, error) { 34 | data := cpuData[counter] 35 | counter++ 36 | 37 | return data, testError 38 | } 39 | 40 | cpuData = goodData 41 | testError = nil 42 | counter = 0 43 | usage, err := getCPULoadLinuxWithHandler(testCPUData) 44 | 45 | if err != nil { 46 | t.Error("Good CPU data should not return an error") 47 | } 48 | 49 | if usage != 50 { 50 | t.Error("Good CPU data should give usage of 50") 51 | } 52 | 53 | cpuData = goodData 54 | testError = errors.New("Test Error") 55 | counter = 0 56 | usage, err = getCPULoadLinuxWithHandler(testCPUData) 57 | 58 | if err == nil { 59 | t.Error("Error getting CPU data should return an error") 60 | } 61 | 62 | if usage != 0 { 63 | t.Error("Error getting CPU data should give usage of 0") 64 | } 65 | 66 | cpuData = badAfterData 67 | testError = nil 68 | counter = 0 69 | usage, err = getCPULoadLinuxWithHandler(testCPUData) 70 | 71 | if err == nil { 72 | t.Error("Getting error on second CPU check should return an error") 73 | } 74 | 75 | if usage != 0 { 76 | t.Error("Bad CPU data should give usage of 0") 77 | } 78 | 79 | cpuData = badBeforeData 80 | testError = nil 81 | counter = 0 82 | usage, err = getCPULoadLinuxWithHandler(testCPUData) 83 | 84 | if err == nil { 85 | t.Error("Unparseable CPU data should return an error") 86 | } 87 | 88 | if usage != 0 { 89 | t.Error("Bad CPU data should give usage of 0") 90 | } 91 | 92 | cpuData = emptyData 93 | testError = nil 94 | counter = 0 95 | usage, err = getCPULoadLinuxWithHandler(testCPUData) 96 | 97 | if err == nil { 98 | t.Error("Empty CPU data should return an error") 99 | } 100 | 101 | if usage != 0 { 102 | t.Error("Empty CPU data should give usage of 0") 103 | } 104 | 105 | cpuData = shortData 106 | testError = nil 107 | counter = 0 108 | usage, err = getCPULoadLinuxWithHandler(testCPUData) 109 | 110 | if err == nil { 111 | t.Error("Short line of CPU data should return an error") 112 | } 113 | 114 | if usage != 0 { 115 | t.Error("Short line of CPU data should give usage of 0") 116 | } 117 | 118 | if _, err := getStats(nil); err == nil { 119 | t.Error("No stats data handler should return an error") 120 | } 121 | 122 | if _, err := getCPULoadLinuxWithHandler(nil); err == nil { 123 | t.Error("No stats data handler should return an error") 124 | } 125 | // Execute to at least make sure there's no panic 126 | getStatsDataService() 127 | } 128 | 129 | func Test_average(t *testing.T) { 130 | type args struct { 131 | input []float64 132 | } 133 | tests := []struct { 134 | name string 135 | args args 136 | want float64 137 | }{ 138 | { 139 | name: "Average", 140 | args: args{ 141 | input: []float64{ 142 | 5, 143 | 10, 144 | 14.8, 145 | 30, 146 | }, 147 | }, 148 | want: 14.95, 149 | }, 150 | { 151 | name: "Zero", 152 | args: args{ 153 | input: []float64{}, 154 | }, 155 | want: 0.0, 156 | }, 157 | } 158 | for _, tt := range tests { 159 | t.Run(tt.name, func(t *testing.T) { 160 | if got := average(tt.args.input); got != tt.want { 161 | t.Errorf("average() = %v, want %v", got, tt.want) 162 | } 163 | }) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /lib/pkg/cpu/cpulinux.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package cpu 4 | 5 | import ( 6 | "fmt" 7 | "os/exec" 8 | "runtime" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/ncr-devops-platform/nagiosfoundation/lib/pkg/process" 13 | ) 14 | 15 | func getCPULoadOsConstrained() (float64, error) { 16 | return getCPULoadLinux() 17 | } 18 | 19 | var getProcessesByName = process.GetProcessesByName 20 | var getCPUCount = runtime.NumCPU 21 | 22 | var execBash = func(command string) ([]byte, error) { 23 | return exec.Command("bash", "-c", command).CombinedOutput() 24 | } 25 | 26 | func parseTopSamples(lines []string) (float64, error) { 27 | var result float64 = 0 28 | // Fields: PID, USER, PR, NI, VIRT, RES, SHR, S, %CPU, %MEM, TIME+, COMMAND 29 | for _, line := range lines { 30 | lineSplit := strings.Fields(line) 31 | if len(lineSplit) != 12 { 32 | return 0.0, fmt.Errorf("Unexpected Top stat line. Expected 12 elements, actual %v", len(lineSplit)) 33 | } 34 | 35 | cpuValue, err := strconv.ParseFloat(lineSplit[8], 64) 36 | if err != nil { 37 | return 0.0, err 38 | } 39 | 40 | result += cpuValue 41 | } 42 | 43 | return result, nil 44 | } 45 | func handleTopOutput(output string, err error) (float64, error) { 46 | if err != nil { 47 | return 0.0, fmt.Errorf("%v. Full shell output: %v", err, output) 48 | } 49 | 50 | split := strings.Split(output, "\n") 51 | totalLines := len(split) 52 | 53 | cpuSamples := []float64{} 54 | ignoredFirstSample := false 55 | for i := 0; i < totalLines; i++ { 56 | line := split[i] 57 | 58 | // read till next PID command 59 | if !strings.Contains(line, "PID") { 60 | continue 61 | } 62 | 63 | // ignore first sample since on older machines it will report PID lifetime stats 64 | if !ignoredFirstSample { 65 | ignoredFirstSample = true 66 | continue 67 | } 68 | 69 | sampleLines := []string{} 70 | for idx := i + 1; idx < totalLines; idx++ { 71 | line := split[idx] 72 | if len(line) <= 0 { 73 | // reached end of >sample< output 74 | i = idx 75 | break 76 | } 77 | sampleLines = append(sampleLines, split[idx]) 78 | } 79 | 80 | // either reached end of sample output or end of output 81 | cpuSum, err := parseTopSamples(sampleLines) 82 | if err != nil { 83 | return 0.0, err 84 | } 85 | cpuSamples = append(cpuSamples, cpuSum) 86 | } 87 | 88 | result := average(cpuSamples) / float64(getCPUCount()) 89 | return result, nil 90 | } 91 | 92 | func getProcessCPULoad(processInfo []process.GeneralInfo) (float64, error) { 93 | if len(processInfo) < 1 { 94 | return 0.0, nil 95 | } 96 | 97 | pidsString := "" 98 | for _, entry := range processInfo { 99 | pidsString = fmt.Sprintf("%v,%v", pidsString, entry.PID) 100 | } 101 | pidsString = pidsString[1:] 102 | 103 | command := fmt.Sprintf("top -b -n 4 -d 1 -p %v", pidsString) 104 | out, err := execBash(command) 105 | max, err := handleTopOutput(string(out), err) 106 | return max, err 107 | } 108 | 109 | // returns highest single core CPU load of a given process based on multiple samples from pidstat 110 | // tasks of a process will be considered as well 111 | func getProcessCPULoadOsConstrained(processName string, perCoreCalculation bool) (float64, error) { 112 | processInfo, err := getProcessesByName(processName) 113 | if err != nil { 114 | return 0, err 115 | } 116 | 117 | if perCoreCalculation { 118 | return getProcessCoreCPULoad(processInfo) 119 | } 120 | 121 | return getProcessCPULoad(processInfo) 122 | } 123 | -------------------------------------------------------------------------------- /lib/pkg/cpu/cpuwin.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package cpu 4 | 5 | import ( 6 | "fmt" 7 | "regexp" 8 | "runtime" 9 | "time" 10 | 11 | "github.com/StackExchange/wmi" 12 | ) 13 | 14 | type Win32_PerfFormattedData_PerfOS_Processor struct { 15 | PercentProcessorTime uint64 16 | } 17 | 18 | func getCPULoadOsConstrained() (float64, error) { 19 | var dst []Win32_PerfFormattedData_PerfOS_Processor 20 | q := wmi.CreateQuery(&dst, "WHERE name='_Total'") 21 | err := wmi.Query(q, &dst) 22 | if err != nil { 23 | return 0.0, err 24 | } 25 | return (float64)(dst[0].PercentProcessorTime), nil 26 | } 27 | 28 | // See WMI class: Win32_PerfFormattedData_PerfProc_Process 29 | // Must match the original class name (case insensitive) 30 | type win32_PerfFormattedData_PerfProc_Process struct { 31 | Name string 32 | PercentProcessorTime uint64 33 | } 34 | 35 | var getWin32Processes = func(query string, dst interface{}) error { 36 | return wmi.Query(query, dst) 37 | } 38 | 39 | var getCPUCount = runtime.NumCPU 40 | 41 | func getProcessCPULoadOsConstrained(processName string, _ bool) (float64, error) { 42 | cpuValues := []float64{} 43 | for i := 0; i < 3; i++ { 44 | 45 | var dst []win32_PerfFormattedData_PerfProc_Process 46 | q := wmi.CreateQuery(&dst, "") 47 | err := getWin32Processes(q, &dst) 48 | if err != nil { 49 | return 0.0, err 50 | } 51 | 52 | var percentage uint64 53 | for _, entry := range dst { 54 | pattern := fmt.Sprintf("^%v#[0-9]+$", processName) 55 | match, err := regexp.MatchString(pattern, entry.Name) 56 | if err != nil { 57 | return 0.0, err 58 | } 59 | 60 | if entry.Name == processName || match { 61 | // PercentProcessorTime is reported as a value in range [0, CORES * 100] 62 | percentage += entry.PercentProcessorTime 63 | } 64 | } 65 | cpuValues = append(cpuValues, float64(percentage)) 66 | time.Sleep(1 * time.Second) 67 | } 68 | 69 | result := average(cpuValues) / float64(getCPUCount()) 70 | return result, nil 71 | } 72 | -------------------------------------------------------------------------------- /lib/pkg/cpu/cpuwin_test.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package cpu 4 | 5 | import ( 6 | "errors" 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | func Test_getProcessCPULoadOsConstrained(t *testing.T) { 12 | type args struct { 13 | processName string 14 | } 15 | tests := []struct { 16 | name string 17 | args args 18 | getWin32ProcessesOverride func(string, interface{}) error 19 | getCPUCountOverride func() int 20 | want float64 21 | wantErr bool 22 | }{ 23 | { 24 | name: "Win32Error", 25 | args: args{ 26 | processName: "test", 27 | }, 28 | getWin32ProcessesOverride: func(_ string, _ interface{}) error { 29 | return errors.New("TEST") 30 | }, 31 | getCPUCountOverride: func() int { 32 | return 1 33 | }, 34 | want: 0.0, 35 | wantErr: true, 36 | }, 37 | { 38 | name: "NoProcesses", 39 | args: args{ 40 | processName: "test", 41 | }, 42 | getWin32ProcessesOverride: func(_ string, _ interface{}) error { 43 | return nil 44 | }, 45 | getCPUCountOverride: func() int { 46 | return 1 47 | }, 48 | want: 0.0, 49 | wantErr: false, 50 | }, 51 | { 52 | name: "Match", 53 | args: args{ 54 | processName: "test", 55 | }, 56 | getWin32ProcessesOverride: func(_ string, dst interface{}) error { 57 | ptrValue := reflect.ValueOf(dst).Elem() 58 | process := win32_PerfFormattedData_PerfProc_Process{ 59 | Name: "test", 60 | PercentProcessorTime: 15, 61 | } 62 | process2 := win32_PerfFormattedData_PerfProc_Process{ 63 | Name: "testio", 64 | PercentProcessorTime: 15, 65 | } 66 | process3 := win32_PerfFormattedData_PerfProc_Process{ 67 | Name: "test#17", 68 | PercentProcessorTime: 30, 69 | } 70 | ptrValue.Set(reflect.Append(ptrValue, reflect.ValueOf(process))) 71 | ptrValue.Set(reflect.Append(ptrValue, reflect.ValueOf(process2))) 72 | ptrValue.Set(reflect.Append(ptrValue, reflect.ValueOf(process3))) 73 | return nil 74 | }, 75 | getCPUCountOverride: func() int { 76 | return 1 77 | }, 78 | want: 45.0, 79 | wantErr: false, 80 | }, 81 | { 82 | name: "CpuCount", 83 | args: args{ 84 | processName: "test", 85 | }, 86 | getWin32ProcessesOverride: func(_ string, dst interface{}) error { 87 | ptrValue := reflect.ValueOf(dst).Elem() 88 | process := win32_PerfFormattedData_PerfProc_Process{ 89 | Name: "test", 90 | PercentProcessorTime: 20, 91 | } 92 | ptrValue.Set(reflect.Append(ptrValue, reflect.ValueOf(process))) 93 | return nil 94 | }, 95 | getCPUCountOverride: func() int { 96 | return 4 97 | }, 98 | want: 5.0, 99 | wantErr: false, 100 | }, 101 | } 102 | for _, tt := range tests { 103 | oldGetWin32Processes := getWin32Processes 104 | if tt.getWin32ProcessesOverride != nil { 105 | getWin32Processes = tt.getWin32ProcessesOverride 106 | defer func() { 107 | getWin32Processes = oldGetWin32Processes 108 | }() 109 | } 110 | 111 | oldGetCPUCount := getCPUCount 112 | if tt.getCPUCountOverride != nil { 113 | getCPUCount = tt.getCPUCountOverride 114 | defer func() { 115 | getCPUCount = oldGetCPUCount 116 | }() 117 | } 118 | 119 | t.Run(tt.name, func(t *testing.T) { 120 | got, err := getProcessCPULoadOsConstrained(tt.args.processName, false) 121 | if (err != nil) != tt.wantErr { 122 | t.Errorf("getProcessCPULoadOsConstrained() error = %v, wantErr %v", err, tt.wantErr) 123 | return 124 | } 125 | if got != tt.want { 126 | t.Errorf("getProcessCPULoadOsConstrained() = %v, want %v", got, tt.want) 127 | } 128 | }) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /lib/pkg/memory/memory.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "os" 7 | "regexp" 8 | "strconv" 9 | "strings" 10 | 11 | m "github.com/pbnjay/memory" 12 | ) 13 | 14 | func getMemInfoEntryFromFile(filename string, memInfoEntry string) uint64 { 15 | var memoryInfo uint64 16 | 17 | reader, err := os.Open(filename) 18 | 19 | if err == nil { 20 | memoryInfo = getMemInfoEntryFromReader(reader, memInfoEntry) 21 | } 22 | 23 | return memoryInfo 24 | } 25 | 26 | func getMemInfoEntryFromReader(reader io.Reader, memInfoEntry string) uint64 { 27 | var memoryInfo uint64 28 | 29 | memData, err := ioutil.ReadAll(reader) 30 | 31 | if err == nil { 32 | memDataSplit := strings.Split(string(memData), "\n") 33 | exp := regexp.MustCompile("^" + memInfoEntry + ":.*\\s(\\d*).*\\skB$") 34 | 35 | for _, entry := range memDataSplit { 36 | if strings.HasPrefix(entry, memInfoEntry+":") { 37 | err = nil 38 | m := exp.FindStringSubmatch(entry) 39 | 40 | if m != nil && len(m) == 2 { 41 | if memoryInfo, err = strconv.ParseUint(m[1], 10, 64); err != nil { 42 | memoryInfo = 0 43 | } 44 | } 45 | 46 | break 47 | } 48 | } 49 | } 50 | 51 | return memoryInfo * 1024 52 | } 53 | 54 | func getFreeMemoryWithHandler(freeMemory func() uint64) uint64 { 55 | return freeMemory() 56 | } 57 | 58 | // GetFreeMemory returns the amount of available memory. 59 | func GetFreeMemory() uint64 { 60 | return getFreeMemoryWithHandler(getFreeMemoryOsConstrained) 61 | } 62 | 63 | func getTotalMemoryWithHandler(totalMemory func() uint64) uint64 { 64 | return totalMemory() 65 | } 66 | 67 | // GetTotalMemory returns the total amount of memory. 68 | func GetTotalMemory() uint64 { 69 | return getTotalMemoryWithHandler(m.TotalMemory) 70 | } 71 | 72 | func getUsedMemoryWithHandlers(totalMemory func() uint64, freeMemory func() uint64) uint64 { 73 | var used uint64 74 | 75 | total := getTotalMemoryWithHandler(totalMemory) 76 | free := getFreeMemoryWithHandler(freeMemory) 77 | 78 | if total != 0 && free != 0 { 79 | used = total - free 80 | } 81 | 82 | return used 83 | } 84 | 85 | // GetUsedMemory returns the amount of used memory. 86 | func GetUsedMemory() uint64 { 87 | return getUsedMemoryWithHandlers(m.TotalMemory, getFreeMemoryOsConstrained) 88 | } 89 | 90 | func getUsedMemoryPercentageWithHandlers(totalMemory func() uint64, freeMemory func() uint64) uint64 { 91 | var percentageUsed uint64 92 | 93 | used := getUsedMemoryWithHandlers(totalMemory, freeMemory) 94 | total := getTotalMemoryWithHandler(totalMemory) 95 | 96 | if used != 0 && total != 0 { 97 | percentageUsed = uint64(float64(used) / float64(total) * 100) 98 | } 99 | 100 | return percentageUsed 101 | } 102 | 103 | // GetUsedMemoryPercentage returns the amount of used memory 104 | // as a percentage. 105 | func GetUsedMemoryPercentage() uint64 { 106 | return getUsedMemoryPercentageWithHandlers(m.TotalMemory, getFreeMemoryOsConstrained) 107 | } 108 | 109 | // GetProcessMemoryPercentage returns the percentage of used memory of a process against total system memory. 110 | func GetProcessMemoryPercentage(processName string) (float64, error) { 111 | return getProcessMemoryPercentageOsContrained(processName) 112 | } 113 | -------------------------------------------------------------------------------- /lib/pkg/memory/memory_test.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func availableMemoryErrorText(description string, expected, actual uint64) string { 11 | return fmt.Sprintf("%s. Expected Result: %d Actual Result %d", 12 | description, expected, actual) 13 | } 14 | 15 | func TestAvailableMemory(t *testing.T) { 16 | totalMemoryReturned := uint64(10000) 17 | freeMemoryReturned := uint64(6000) 18 | getTotalMemory := func() uint64 { return totalMemoryReturned } 19 | getTotalMemoryZero := func() uint64 { return uint64(0) } 20 | getFreeMemory := func() uint64 { return freeMemoryReturned } 21 | getFreeMemoryZero := func() uint64 { return uint64(0) } 22 | 23 | actualResult := getTotalMemoryWithHandler(getTotalMemory) 24 | if actualResult != totalMemoryReturned { 25 | t.Error(availableMemoryErrorText("getTotalMemoryWithHandler returned memory not correct", 26 | totalMemoryReturned, actualResult)) 27 | } 28 | 29 | actualResult = getFreeMemoryWithHandler(getFreeMemory) 30 | if actualResult != freeMemoryReturned { 31 | t.Error(availableMemoryErrorText("getFreeMemoryWithHandler returned memory not correct", 32 | freeMemoryReturned, actualResult)) 33 | } 34 | 35 | actualResult = getUsedMemoryWithHandlers(getTotalMemory, getFreeMemory) 36 | expectedResult := totalMemoryReturned - freeMemoryReturned 37 | if actualResult != expectedResult { 38 | t.Error(availableMemoryErrorText("getUsedMemoryWithHandlers did not calculate used memory properly", 39 | expectedResult, actualResult)) 40 | } 41 | 42 | actualResult = getUsedMemoryWithHandlers(getTotalMemoryZero, getFreeMemory) 43 | expectedResult = 0 44 | if actualResult != expectedResult { 45 | t.Error(availableMemoryErrorText("getUsedMemoryWithHandlers did not properly handle a total memory of 0", 46 | expectedResult, actualResult)) 47 | } 48 | 49 | actualResult = getUsedMemoryWithHandlers(getTotalMemory, getFreeMemoryZero) 50 | expectedResult = 0 51 | if actualResult != expectedResult { 52 | t.Error(availableMemoryErrorText("getUsedMemoryWithHandlers did not properly handle free memory of 0", 53 | expectedResult, actualResult)) 54 | } 55 | 56 | actualResult = getUsedMemoryPercentageWithHandlers(getTotalMemory, getFreeMemory) 57 | expectedResult = uint64(float64((totalMemoryReturned - freeMemoryReturned)) / float64(totalMemoryReturned) * 100) 58 | if actualResult != expectedResult { 59 | t.Error(availableMemoryErrorText("getUsedMemoryPercentageWithHandlers did not calculated used memory percentage properly", 60 | expectedResult, actualResult)) 61 | } 62 | 63 | actualResult = getUsedMemoryPercentageWithHandlers(getTotalMemoryZero, getFreeMemory) 64 | expectedResult = 0 65 | if actualResult != expectedResult { 66 | t.Error(availableMemoryErrorText("getUsedMemoryPercentageWithHandlers did not properly handle a total memory of 0", 67 | expectedResult, actualResult)) 68 | } 69 | 70 | actualResult = getUsedMemoryPercentageWithHandlers(getTotalMemory, getFreeMemoryZero) 71 | expectedResult = 0 72 | if actualResult != expectedResult { 73 | t.Error(availableMemoryErrorText("getUsedMemoryPercentageWithHandlers did not properly handle free memory of 0", 74 | expectedResult, actualResult)) 75 | } 76 | 77 | // Non-DI calls should at least return some numbers 78 | actualResult = GetTotalMemory() 79 | if actualResult == 0 { 80 | t.Error("GetTotalMemory did return > 0") 81 | } 82 | 83 | actualResult = GetFreeMemory() 84 | if actualResult == 0 { 85 | t.Error("GetFreeMemory did return > 0") 86 | } 87 | 88 | actualResult = GetUsedMemory() 89 | if actualResult == 0 { 90 | t.Error("GetUsedMemory did return > 0") 91 | } 92 | 93 | actualResult = GetUsedMemoryPercentage() 94 | if actualResult == 0 { 95 | t.Error("GetUsedMemoryPercentage did return > 0") 96 | } 97 | } 98 | 99 | func TestLinuxGetAvailableMemory(t *testing.T) { 100 | type parameters struct { 101 | filename string 102 | match string 103 | } 104 | 105 | memInfoContents := `This string 106 | has a valid 107 | MemAvailable: 123456 kB 108 | line` 109 | 110 | availableMemory := getMemInfoEntryFromReader(strings.NewReader(memInfoContents), "MemAvailable") 111 | if availableMemory != 123456*1024 { 112 | t.Error("Failed to parse memory amount from valid line") 113 | } 114 | 115 | memInfoContents = `This string has 116 | no valid entry 117 | to parse 118 | ` 119 | availableMemory = getMemInfoEntryFromReader(strings.NewReader(memInfoContents), "MemAvailable") 120 | if availableMemory != 0 { 121 | t.Error("Invalid memory info string failed to generate error") 122 | } 123 | 124 | memInfoContents = `This string has 125 | MemAvailable: breakme kB 126 | to parse 127 | ` 128 | availableMemory = getMemInfoEntryFromReader(strings.NewReader(memInfoContents), "MemAvailable") 129 | if availableMemory != 0 { 130 | t.Error("Invalid memory info string failed to generate error") 131 | } 132 | 133 | memInfoContents = `This string has 134 | MemAvailable: 28446744073709551615 kB 135 | to parse 136 | ` 137 | availableMemory = getMemInfoEntryFromReader(strings.NewReader(memInfoContents), "MemAvailable") 138 | if availableMemory != 0 { 139 | t.Error("Invalid memory info string failed to generate error") 140 | } 141 | 142 | availableMemory = getMemInfoEntryFromFile(os.Args[0], "") 143 | if availableMemory != 0 { 144 | t.Error("Invalid memory info file and match string failed to generate error") 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /lib/pkg/memory/memorylinux.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package memory 4 | 5 | import ( 6 | "fmt" 7 | "os/exec" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/ncr-devops-platform/nagiosfoundation/lib/pkg/process" 12 | ) 13 | 14 | // getFreeMemoryOsConstrained returns the amount of available memory. 15 | func getFreeMemoryOsConstrained() uint64 { 16 | return getMemInfoEntryFromFile("/proc/meminfo", "MemAvailable") 17 | } 18 | 19 | var getProcessesByName = process.GetProcessesByName 20 | var execBash = func(command string) ([]byte, error) { 21 | return exec.Command("bash", "-c", command).CombinedOutput() 22 | } 23 | 24 | func handlePSOutput(output string, err error) (float64, error) { 25 | if err != nil { 26 | return 0.0, fmt.Errorf("%v. Full shell output: %v", err, output) 27 | } 28 | split := strings.Split(output, "\n") 29 | if len(split) < 2 { 30 | return 0.0, fmt.Errorf("Invalid output from 'ps' command. Expected 1 header and 1 value line") 31 | } 32 | // second line should contain memory consumption 33 | value, err := strconv.ParseFloat(strings.TrimSpace(split[1]), 64) 34 | if err != nil { 35 | return 0.0, err 36 | } 37 | 38 | return value, nil 39 | } 40 | 41 | func getProcessMemoryPercentageOsContrained(processName string) (float64, error) { 42 | processInfo, err := getProcessesByName(processName) 43 | if err != nil { 44 | return 0.0, err 45 | } 46 | 47 | var memoryUsedTotal float64 = 0 48 | for _, entry := range processInfo { 49 | command := fmt.Sprintf("ps -p %v -o %%mem", entry.PID) 50 | out, err := execBash(command) 51 | memoryUsed, err := handlePSOutput(string(out), err) 52 | if err != nil { 53 | return 0.0, err 54 | } 55 | 56 | memoryUsedTotal += memoryUsed 57 | } 58 | 59 | return memoryUsedTotal, nil 60 | } 61 | -------------------------------------------------------------------------------- /lib/pkg/memory/memorylinux_test.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package memory 4 | 5 | import ( 6 | "errors" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/ncr-devops-platform/nagiosfoundation/lib/pkg/process" 11 | ) 12 | 13 | func Test_handlePSOutput(t *testing.T) { 14 | type args struct { 15 | output string 16 | err error 17 | } 18 | tests := []struct { 19 | name string 20 | args args 21 | want float64 22 | wantErr bool 23 | }{ 24 | { 25 | name: "EmptyOutput", 26 | args: args{ 27 | output: "", 28 | }, 29 | want: 0.0, 30 | wantErr: true, 31 | }, 32 | { 33 | name: "ShellError", 34 | args: args{ 35 | output: "abc\n 2.23343", 36 | err: errors.New("TEST"), 37 | }, 38 | want: 0.0, 39 | wantErr: true, 40 | }, 41 | { 42 | name: "InvalidFormat", 43 | args: args{ 44 | output: "abc\nqwe", 45 | }, 46 | want: 0.0, 47 | wantErr: true, 48 | }, 49 | { 50 | name: "ValidCase", 51 | args: args{ 52 | output: "abc\n 1.234567", 53 | }, 54 | want: 1.234567, 55 | wantErr: false, 56 | }, 57 | } 58 | for _, tt := range tests { 59 | t.Run(tt.name, func(t *testing.T) { 60 | got, err := handlePSOutput(tt.args.output, tt.args.err) 61 | if (err != nil) != tt.wantErr { 62 | t.Errorf("handlePSOutput() error = %v, wantErr %v", err, tt.wantErr) 63 | return 64 | } 65 | if got != tt.want { 66 | t.Errorf("handlePSOutput() = %v, want %v", got, tt.want) 67 | } 68 | }) 69 | } 70 | } 71 | 72 | func Test_getProcessMemoryPercentageOsContrained(t *testing.T) { 73 | tests := []struct { 74 | name string 75 | getProcessesByNameOverride func(string) ([]process.GeneralInfo, error) 76 | execBashOverride func(string) ([]byte, error) 77 | want float64 78 | wantErr bool 79 | }{ 80 | { 81 | name: "GetProcessesError", 82 | getProcessesByNameOverride: func(_ string) ([]process.GeneralInfo, error) { 83 | return nil, errors.New("TEST") 84 | }, 85 | want: 0.0, 86 | wantErr: true, 87 | }, 88 | { 89 | name: "NoProcesses", 90 | getProcessesByNameOverride: func(_ string) ([]process.GeneralInfo, error) { 91 | return []process.GeneralInfo{}, nil 92 | }, 93 | want: 0.0, 94 | wantErr: false, 95 | }, 96 | { 97 | name: "CommandError", 98 | getProcessesByNameOverride: func(_ string) ([]process.GeneralInfo, error) { 99 | return []process.GeneralInfo{ 100 | process.GeneralInfo{ 101 | PID: 123, 102 | }, 103 | }, nil 104 | }, 105 | execBashOverride: func(cmd string) ([]byte, error) { 106 | return nil, nil 107 | }, 108 | want: 0.0, 109 | wantErr: true, 110 | }, 111 | { 112 | name: "SingleProcess", 113 | getProcessesByNameOverride: func(_ string) ([]process.GeneralInfo, error) { 114 | return []process.GeneralInfo{ 115 | process.GeneralInfo{ 116 | PID: 123, 117 | }, 118 | }, nil 119 | }, 120 | execBashOverride: func(cmd string) ([]byte, error) { 121 | return []byte("%mem\n 1.23456"), nil 122 | }, 123 | want: 1.23456, 124 | wantErr: false, 125 | }, 126 | { 127 | name: "MultipleProcesses", 128 | getProcessesByNameOverride: func(_ string) ([]process.GeneralInfo, error) { 129 | return []process.GeneralInfo{ 130 | process.GeneralInfo{ 131 | PID: 123, 132 | }, 133 | process.GeneralInfo{ 134 | PID: 456, 135 | }, 136 | }, nil 137 | }, 138 | execBashOverride: func(cmd string) ([]byte, error) { 139 | if strings.Contains(cmd, "123") { 140 | return []byte("%mem\n 1.25"), nil 141 | } 142 | return []byte("%mem\n 1.5"), nil 143 | }, 144 | want: 2.75, 145 | wantErr: false, 146 | }, 147 | } 148 | for _, tt := range tests { 149 | t.Run(tt.name, func(t *testing.T) { 150 | oldGetProcessesByName := getProcessesByName 151 | if tt.getProcessesByNameOverride != nil { 152 | getProcessesByName = tt.getProcessesByNameOverride 153 | defer func() { 154 | getProcessesByName = oldGetProcessesByName 155 | }() 156 | } 157 | 158 | oldExecBash := execBash 159 | if tt.execBashOverride != nil { 160 | execBash = tt.execBashOverride 161 | defer func() { 162 | execBash = oldExecBash 163 | }() 164 | } 165 | 166 | got, err := getProcessMemoryPercentageOsContrained("test") 167 | if (err != nil) != tt.wantErr { 168 | t.Errorf("getProcessMemoryPercentageOsContrained() error = %v, wantErr %v", err, tt.wantErr) 169 | return 170 | } 171 | if got != tt.want { 172 | t.Errorf("getProcessMemoryPercentageOsContrained() = %v, want %v", got, tt.want) 173 | } 174 | }) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /lib/pkg/memory/memorywin.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package memory 4 | 5 | import ( 6 | "github.com/StackExchange/wmi" 7 | "github.com/ncr-devops-platform/nagiosfoundation/lib/pkg/perfcounters" 8 | memlib "github.com/pbnjay/memory" 9 | ) 10 | 11 | func getFreeMemoryOsConstrained() uint64 { 12 | counter, err := perfcounters.ReadPerformanceCounter("\\Memory\\Available Bytes", 2, 1) 13 | 14 | var memoryAvailable uint64 15 | 16 | if err != nil { 17 | memoryAvailable = uint64(0) 18 | } else { 19 | memoryAvailable = uint64(counter.Value) 20 | } 21 | 22 | return uint64(memoryAvailable) 23 | } 24 | 25 | // See WMI class: Win32_Process 26 | // Must match the original class name (case insensitive) 27 | type win32_process struct { 28 | Name string 29 | ProcessID uint32 30 | 31 | // ParentProcessId may refer to a process reusing process identifier (in case parent has already terminated) 32 | ParentProcessID uint32 33 | 34 | // WorkingSetSize field seems to be the most accurate representation of currently used RAM 35 | WorkingSetSize uint64 36 | } 37 | 38 | type processInfo struct { 39 | Process win32_process 40 | UsedInCalculation bool 41 | } 42 | 43 | var getTotalMemory = memlib.TotalMemory 44 | var getWin32Processes = wmi.Query 45 | 46 | func getMemoryUsedByPIDAndItsChildren(data map[uint32]*processInfo, pid uint32) uint64 { 47 | rootProcess, isRootFound := data[pid] 48 | if !isRootFound || rootProcess.UsedInCalculation { 49 | return 0 50 | } 51 | 52 | result := rootProcess.Process.WorkingSetSize 53 | rootProcess.UsedInCalculation = true 54 | 55 | for processPID, process := range data { 56 | if process.Process.ParentProcessID == pid { 57 | result += getMemoryUsedByPIDAndItsChildren(data, processPID) 58 | } 59 | } 60 | 61 | return result 62 | } 63 | 64 | func getMemoryUsedByProcessNameAndItsChildren(data map[uint32]*processInfo, name string) uint64 { 65 | var result uint64 = 0 66 | for pid, process := range data { 67 | if process.Process.Name == name { 68 | result += getMemoryUsedByPIDAndItsChildren(data, pid) 69 | } 70 | } 71 | 72 | return result 73 | } 74 | 75 | func getProcessMemoryPercentageOsContrained(processName string) (float64, error) { 76 | memoryTotal := getTotalMemory() 77 | 78 | // query all processes 79 | var dst []win32_process 80 | q := wmi.CreateQuery(&dst, "") 81 | err := getWin32Processes(q, &dst) 82 | if err != nil { 83 | return 0.0, err 84 | } 85 | 86 | pidDataMap := map[uint32]*processInfo{} 87 | for _, entry := range dst { 88 | pidDataMap[entry.ProcessID] = &processInfo{ 89 | Process: entry, 90 | UsedInCalculation: false, 91 | } 92 | } 93 | 94 | // calculate consumed memory, including children of matched processes 95 | nameToMatch := processName + ".exe" 96 | memoryUsedTotal := getMemoryUsedByProcessNameAndItsChildren(pidDataMap, nameToMatch) 97 | percentage := float64(memoryUsedTotal) / float64(memoryTotal) * 100 98 | return percentage, nil 99 | } 100 | -------------------------------------------------------------------------------- /lib/pkg/nagiosformatters/nagiosformatters.go: -------------------------------------------------------------------------------- 1 | package nagiosformatters 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // GreaterFormatNagiosCheck compares a value against thresholds and returns nagios output 8 | func GreaterFormatNagiosCheck(name string, value float64, warning float64, critical float64, metricName string) (string, int) { 9 | if value > critical { 10 | return fmt.Sprintf("%s CRITICAL - value = %f | %s=%f", name, value, metricName, value), 2 11 | } 12 | if value > warning { 13 | return fmt.Sprintf("%s WARNING - value = %f | %s=%f", name, value, metricName, value), 1 14 | } 15 | return fmt.Sprintf("%s OK - value = %f | %s=%f", name, value, metricName, value), 0 16 | } 17 | 18 | // LesserFormatNagiosCheck compares a value against thresholds and returns nagios output 19 | func LesserFormatNagiosCheck(name string, value float64, warning float64, critical float64, metricName string) (string, int) { 20 | if value < critical { 21 | return fmt.Sprintf("%s CRITICAL - value = %f | %s=%f", name, value, metricName, value), 2 22 | } 23 | if value < warning { 24 | return fmt.Sprintf("%s WARNING - value = %f | %s=%f", name, value, metricName, value), 1 25 | } 26 | return fmt.Sprintf("%s OK - value = %f | %s=%f", name, value, metricName, value), 0 27 | } 28 | -------------------------------------------------------------------------------- /lib/pkg/nagiosformatters/nagiosformatters_test.go: -------------------------------------------------------------------------------- 1 | package nagiosformatters 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestNagiosFormatters(t *testing.T) { 9 | const testCheck = "TestCheck" 10 | const testMetric = "Test Metric" 11 | const testOk = "OK" 12 | const testWarning = "WARNING" 13 | const testCritical = "CRITICAL" 14 | 15 | type formatTests struct { 16 | name string 17 | method func(string, float64, float64, float64, string) (string, int) 18 | expectedValue int 19 | expectedString string 20 | value float64 21 | warning float64 22 | critical float64 23 | } 24 | 25 | testList := []formatTests{ 26 | { 27 | name: "Greater", 28 | method: GreaterFormatNagiosCheck, 29 | expectedValue: 0, 30 | expectedString: testOk, 31 | value: 50, 32 | warning: 80, 33 | critical: 90, 34 | }, 35 | { 36 | name: "Greater", 37 | method: GreaterFormatNagiosCheck, 38 | expectedValue: 1, 39 | expectedString: testWarning, 40 | value: 85, 41 | warning: 80, 42 | critical: 90, 43 | }, 44 | { 45 | name: "Greater", 46 | method: GreaterFormatNagiosCheck, 47 | expectedValue: 2, 48 | expectedString: testCritical, 49 | value: 95, 50 | warning: 80, 51 | critical: 90, 52 | }, 53 | { 54 | name: "Lesser", 55 | method: LesserFormatNagiosCheck, 56 | expectedValue: 0, 57 | expectedString: testOk, 58 | value: 95, 59 | warning: 90, 60 | critical: 80, 61 | }, 62 | { 63 | name: "Lesser", 64 | method: LesserFormatNagiosCheck, 65 | expectedValue: 1, 66 | expectedString: testWarning, 67 | value: 85, 68 | warning: 90, 69 | critical: 80, 70 | }, 71 | { 72 | name: "Lesser", 73 | method: LesserFormatNagiosCheck, 74 | expectedValue: 2, 75 | expectedString: testCritical, 76 | value: 50, 77 | warning: 90, 78 | critical: 80, 79 | }, 80 | } 81 | 82 | var msg string 83 | var retval int 84 | 85 | for _, test := range testList { 86 | msg, retval = test.method(testCheck, test.value, test.warning, test.critical, testMetric) 87 | 88 | if retval != test.expectedValue { 89 | t.Errorf("%s check %s failed with retval %d", test.name, test.expectedString, retval) 90 | } 91 | 92 | if !strings.HasPrefix(msg, testCheck+" "+test.expectedString) { 93 | t.Errorf("%s check %s failed with msg %s", test.name, test.expectedString, msg) 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/pkg/perfcounters/perfcounters.go: -------------------------------------------------------------------------------- 1 | package perfcounters 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "os/exec" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | // PerformanceCounter is a struct that contains the name 13 | // of the performance counter and the value retrieved 14 | // from the performance counter. 15 | type PerformanceCounter struct { 16 | Name string 17 | Value float64 18 | } 19 | 20 | type powerShellService interface { 21 | Execute(...string) (string, string, error) 22 | } 23 | 24 | type powerShell struct { 25 | powerShell string 26 | 27 | command func(string, ...string) *exec.Cmd 28 | } 29 | 30 | func newPowerShell(lookPath func(string) (string, error), command func(string, ...string) *exec.Cmd) *powerShell { 31 | ps, _ := lookPath("powershell.exe") 32 | 33 | return &powerShell{ 34 | powerShell: ps, 35 | command: command, 36 | } 37 | } 38 | 39 | func (p *powerShell) Execute(args ...string) (stdOut string, stdErr string, err error) { 40 | args = append([]string{"-NoProfile", "-NonInteractive"}, args...) 41 | 42 | cmd := p.command(p.powerShell, args...) 43 | 44 | var stdout bytes.Buffer 45 | var stderr bytes.Buffer 46 | cmd.Stdout = &stdout 47 | cmd.Stderr = &stderr 48 | 49 | err = cmd.Run() 50 | stdOut, stdErr = stdout.String(), stderr.String() 51 | return 52 | } 53 | 54 | // ReadPerformanceCounterWithHandler reads a performance counter 55 | func ReadPerformanceCounterWithHandler(poshService powerShellService, counter string, pollingAttempts int, pollingDelay int) (PerformanceCounter, error) { 56 | // in amd64 the pdh.dll usage isn't playing nice. We're going to use powershell directly and text parsing 57 | var perfcounter PerformanceCounter 58 | 59 | perfcounter.Name = counter 60 | perfcounter.Value = 0 61 | 62 | if poshService == nil { 63 | return PerformanceCounter{}, errors.New("No Powershell Execute service") 64 | } 65 | 66 | var command string 67 | command = fmt.Sprintf("Write-Output (Get-Counter -Counter \"%s\" -SampleInterval %d -MaxSamples %d |\n", counter, pollingDelay, pollingAttempts) + 68 | "Select-Object -ExpandProperty CounterSamples |\n" + 69 | "Select-Object -ExpandProperty CookedValue |\n" + 70 | "Measure-Object -Average).Average" 71 | 72 | stdout, _, err := poshService.Execute(command) 73 | 74 | if err != nil { 75 | return perfcounter, err 76 | } 77 | 78 | trimmedStdout := strings.TrimSpace(stdout) 79 | 80 | if trimmedStdout == "" { 81 | return perfcounter, fmt.Errorf("No data returned for %s so this counter probably doesn't exist", counter) 82 | } 83 | 84 | avgValue, err := strconv.ParseFloat(trimmedStdout, 64) 85 | 86 | if err != nil { 87 | return perfcounter, fmt.Errorf("Error processing for counter (%s): %s", counter, err) 88 | } 89 | 90 | perfcounter.Value = avgValue 91 | 92 | return perfcounter, nil 93 | } 94 | 95 | // ReadPerformanceCounter reads a performance counter 96 | func ReadPerformanceCounter(counter string, pollingAttempts int, pollingDelay int) (PerformanceCounter, error) { 97 | return ReadPerformanceCounterWithHandler(newPowerShell(exec.LookPath, exec.Command), counter, pollingAttempts, pollingDelay) 98 | } 99 | -------------------------------------------------------------------------------- /lib/pkg/perfcounters/perfcounters_test.go: -------------------------------------------------------------------------------- 1 | package perfcounters 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "strconv" 9 | "testing" 10 | ) 11 | 12 | const testPerfCounterValue = "4.2" 13 | 14 | type testPowerShellService struct { 15 | testType int 16 | } 17 | 18 | func (pss testPowerShellService) Execute(args ...string) (string, string, error) { 19 | var stdOut string 20 | var stdErr string 21 | var err error 22 | 23 | switch pss.testType { 24 | case 1: 25 | stdOut = "" 26 | stdErr = "" 27 | err = nil 28 | case 2: 29 | stdOut = testPerfCounterValue 30 | stdErr = "" 31 | err = nil 32 | case 3: 33 | stdOut = testPerfCounterValue 34 | stdErr = "" 35 | err = errors.New("testPowerShellService Error") 36 | case 4: 37 | stdOut = "unparseable float data" 38 | stdErr = "" 39 | err = nil 40 | } 41 | 42 | return stdOut, stdErr, err 43 | } 44 | 45 | func TestReadPerformanceCounter(t *testing.T) { 46 | counterName := "TestCounter" 47 | type pss struct { 48 | } 49 | 50 | svc := new(testPowerShellService) 51 | 52 | var pcResult PerformanceCounter 53 | var err error 54 | 55 | // Passing no handler should yield an error 56 | _, err = ReadPerformanceCounterWithHandler(nil, counterName, 0, 0) 57 | 58 | if err == nil { 59 | t.Error("nil Powershell service failed to yield an error") 60 | } 61 | 62 | // Not returning a number should yield an error 63 | svc.testType = 1 64 | _, err = ReadPerformanceCounterWithHandler(svc, counterName, 0, 0) 65 | if err == nil { 66 | t.Error("Powershell service returning empty string did not yield an error") 67 | } 68 | 69 | // Returning valid data should return good Powershell struct 70 | svc.testType = 2 71 | pcResult, err = ReadPerformanceCounterWithHandler(svc, counterName, 0, 0) 72 | 73 | if err != nil { 74 | t.Error("Powershell service returned valid data but yielded an error") 75 | } 76 | 77 | value, _ := strconv.ParseFloat(testPerfCounterValue, 64) 78 | if pcResult.Value != value { 79 | t.Error("PerformanceCounter value not correct on valid data from Powershell service") 80 | } 81 | 82 | if pcResult.Name != counterName { 83 | t.Error("PerformanceCounter name was not properly populated") 84 | } 85 | 86 | // Returning an error should bubble back up 87 | svc.testType = 3 88 | _, err = ReadPerformanceCounterWithHandler(svc, counterName, 0, 0) 89 | if err == nil { 90 | t.Error("Powershell service returned an error but ReadPerformanceCounterWithHandler did not pass it up") 91 | } 92 | 93 | // Returning unparseable data should return an error 94 | svc.testType = 4 95 | _, err = ReadPerformanceCounterWithHandler(svc, counterName, 0, 0) 96 | if err == nil { 97 | t.Error("Powershell service returning empty string did not yield an error") 98 | } 99 | } 100 | 101 | // Lifted from exec_test.go 102 | func fakeExecCommand(command string, args ...string) *exec.Cmd { 103 | cs := []string{"-test.run=TestHelperProcess", "--", command} 104 | cs = append(cs, args...) 105 | cmd := exec.Command(os.Args[0], cs...) 106 | cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} 107 | return cmd 108 | } 109 | 110 | const powerShellRunResult = "great success" 111 | 112 | func TestHelperProcess(t *testing.T) { 113 | if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { 114 | return 115 | } 116 | 117 | fmt.Fprintf(os.Stdout, powerShellRunResult) 118 | fmt.Fprintf(os.Stderr, powerShellRunResult) 119 | os.Exit(0) 120 | } 121 | 122 | func TestPowerShell(t *testing.T) { 123 | path := "/path/to/" 124 | 125 | ps := newPowerShell(func(file string) (string, error) { return path + file, nil }, 126 | func(string, ...string) *exec.Cmd { return fakeExecCommand("") }) 127 | 128 | if ps.powerShell != path+"powershell.exe" { 129 | t.Error("PowerShell did not properly populate lookup string") 130 | } 131 | 132 | if ps.command == nil { 133 | t.Error("PowerShell command was not initialized") 134 | } 135 | 136 | stdout, stderr, err := ps.Execute("") 137 | 138 | if stdout != powerShellRunResult { 139 | t.Error("Stdout wasn't populated with the proper run result") 140 | } 141 | 142 | if stderr != powerShellRunResult { 143 | t.Error("Stderr wasn't populated with the proper run result") 144 | } 145 | 146 | if err != nil { 147 | t.Error("PowerShell execute should have not returned an error") 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /lib/pkg/process/process.go: -------------------------------------------------------------------------------- 1 | package process 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | // GetProcessesByName returns FileInfo for stat files and pids for a given process name 13 | func GetProcessesByName(name string) ([]GeneralInfo, error) { 14 | svc := processByNameHandlers{ 15 | open: os.Open, 16 | close: func(f *os.File) error { 17 | return f.Close() 18 | }, 19 | readDir: func(f *os.File, entries int) ([]os.FileInfo, error) { 20 | return f.Readdir(entries) 21 | }, 22 | getPidName: getPidNameWithHandler, 23 | readFile: ioutil.ReadFile, 24 | } 25 | 26 | return getProcessesByNameWithHandlers(svc, name) 27 | } 28 | 29 | func getProcessesByNameWithHandlers(svc processByNameHandlers, name string) ([]GeneralInfo, error) { 30 | var errorReturn error 31 | matchingEntries := make([]GeneralInfo, 0) 32 | 33 | dir, err := svc.open("/proc") 34 | if err != nil { 35 | matchingEntries = nil 36 | errorReturn = err 37 | } 38 | 39 | defer svc.close(dir) 40 | 41 | var procEntries []os.FileInfo 42 | if errorReturn == nil { 43 | procEntries, err = svc.readDir(dir, 0) 44 | 45 | if err != nil { 46 | matchingEntries = nil 47 | errorReturn = err 48 | } 49 | } 50 | 51 | if errorReturn == nil { 52 | for _, procEntry := range procEntries { 53 | // Skip entries that aren't directories 54 | if !procEntry.IsDir() { 55 | continue 56 | } 57 | 58 | // Skip entries that aren't numbers 59 | pid, err := strconv.Atoi(procEntry.Name()) 60 | if err != nil { 61 | continue 62 | } 63 | 64 | if procName, _ := svc.getPidName(svc.readFile, pid); procName == name { 65 | matchingEntries = append(matchingEntries, GeneralInfo{ 66 | PID: pid, 67 | StatFile: procEntry, 68 | }) 69 | } 70 | } 71 | } 72 | 73 | return matchingEntries, errorReturn 74 | } 75 | 76 | func getPidNameWithHandler(readFile func(string) ([]byte, error), pid int) (string, error) { 77 | procFile := fmt.Sprintf("/proc/%d/stat", pid) 78 | procDataBytes, err := readFile(procFile) 79 | if err != nil { 80 | return "", err 81 | } 82 | 83 | procData := string(procDataBytes) 84 | 85 | procNameStart := strings.IndexRune(procData, '(') + 1 86 | procNameEnd := strings.IndexRune(procData, ')') 87 | 88 | if procNameStart >= procNameEnd { 89 | return "", errors.New("Could not parse process name") 90 | } 91 | 92 | procName := procData[procNameStart:procNameEnd] 93 | 94 | return procName, nil 95 | } 96 | 97 | type processByNameHandlers struct { 98 | open func(string) (*os.File, error) 99 | close func(*os.File) error 100 | readDir func(*os.File, int) ([]os.FileInfo, error) 101 | getPidName func(readFile func(string) ([]byte, error), pid int) (string, error) 102 | readFile func(string) ([]byte, error) 103 | } 104 | 105 | // GeneralInfo represent process information, such as PID and stat file 106 | type GeneralInfo struct { 107 | PID int 108 | StatFile os.FileInfo 109 | } 110 | -------------------------------------------------------------------------------- /lib/pkg/process/process_test.go: -------------------------------------------------------------------------------- 1 | package process 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | // 0: Not started 11 | // 1: Not directory, filename not a number 12 | // 2: Is directory, filename is a number 13 | // 3: Is directory, filename not a number 14 | var stateTestCheckProcessLinux int 15 | 16 | type testFileInfo struct { 17 | } 18 | 19 | func (fi testFileInfo) Name() string { 20 | var name string 21 | 22 | switch stateTestCheckProcessLinux { 23 | case 1: 24 | name = "testFilename" 25 | case 2: 26 | name = "123" 27 | case 3: 28 | name = "notanumber" 29 | } 30 | 31 | return name 32 | } 33 | 34 | func (fi testFileInfo) Size() int64 { 35 | return 0 36 | } 37 | 38 | func (fi testFileInfo) Mode() os.FileMode { 39 | return os.ModePerm 40 | } 41 | 42 | func (fi testFileInfo) ModTime() time.Time { 43 | return time.Now() 44 | } 45 | 46 | func (fi testFileInfo) IsDir() bool { 47 | var isDir bool 48 | 49 | stateTestCheckProcessLinux = stateTestCheckProcessLinux + 1 50 | 51 | switch stateTestCheckProcessLinux { 52 | case 2, 3: 53 | isDir = true 54 | default: 55 | isDir = false 56 | } 57 | 58 | return isDir 59 | } 60 | 61 | func (fi testFileInfo) Sys() interface{} { 62 | return nil 63 | } 64 | 65 | func TestCheckProcessLinux(t *testing.T) { 66 | goodOutput := func(string) ([]byte, error) { 67 | return []byte("123 (bash) 1 1 1"), nil 68 | } 69 | 70 | errorReturn := func(string) ([]byte, error) { 71 | return []byte("123 (bash) 1 1 1"), errors.New("read data error") 72 | } 73 | 74 | badOutput := func(string) ([]byte, error) { 75 | return []byte("123 bash 1 1 1"), nil 76 | } 77 | 78 | procName, err := getPidNameWithHandler(goodOutput, 123) 79 | 80 | if err != nil { 81 | t.Error("getPidNameWithHandler returned an error on valid data") 82 | } 83 | 84 | if procName == "" { 85 | t.Error("getPidNameWithHandler did not return a valid name on valid data") 86 | } 87 | 88 | procName, err = getPidNameWithHandler(errorReturn, 123) 89 | 90 | if err == nil { 91 | t.Error("getPidNameWithHandler did not return an error on a read data error") 92 | } 93 | 94 | if procName != "" { 95 | t.Error("getPidNameWithHandler returned a name on a read data error") 96 | } 97 | 98 | procName, err = getPidNameWithHandler(badOutput, 123) 99 | 100 | if err == nil { 101 | t.Error("getPidNameWithHandler did not return an error when data parse should fail") 102 | } 103 | 104 | if procName != "" { 105 | t.Error("getPidNameWithHandler returned a name when data parse should fail") 106 | } 107 | 108 | svc := processByNameHandlers{ 109 | open: func(n string) (*os.File, error) { 110 | 111 | return nil, nil 112 | }, 113 | close: func(f *os.File) error { 114 | return nil 115 | }, 116 | readDir: func(f *os.File, entries int) ([]os.FileInfo, error) { 117 | fi := testFileInfo{} 118 | fiSlice := []os.FileInfo{fi, fi, fi} 119 | 120 | return fiSlice, nil 121 | }, 122 | getPidName: getPidNameWithHandler, 123 | readFile: func(string) ([]byte, error) { 124 | return []byte("123 (bash) 1 1 1"), nil 125 | }, 126 | } 127 | 128 | fileList, err := getProcessesByNameWithHandlers(svc, "bash") 129 | 130 | if err != nil { 131 | t.Error("getProcessesByNameWithHandlers returned an error when given valid input") 132 | } 133 | if fileList == nil && len(fileList) != 1 { 134 | t.Error("getProcessesByNameWithHandlers file list not correct when given valid input") 135 | } 136 | 137 | errString := "read directory error" 138 | svc.readDir = func(f *os.File, entries int) ([]os.FileInfo, error) { 139 | return nil, errors.New(errString) 140 | } 141 | 142 | fileList, err = getProcessesByNameWithHandlers(svc, "bash") 143 | 144 | if err == nil || err.Error() != errString { 145 | t.Error("getProcessesByNameWithHandlers should have returned a read directory error") 146 | } 147 | 148 | if fileList != nil { 149 | t.Error("getProcessesByNameWithHandlers returned a file list but should have returned an error") 150 | } 151 | 152 | errString = "file open error" 153 | svc.open = func(n string) (*os.File, error) { 154 | return nil, errors.New(errString) 155 | } 156 | 157 | fileList, err = getProcessesByNameWithHandlers(svc, "bash") 158 | 159 | if err == nil || err.Error() != errString { 160 | t.Error("getProcessesByNameWithHandlers should have returned a file open error") 161 | } 162 | 163 | if fileList != nil { 164 | t.Error("getProcessesByNameWithHandlers returned a file list but should have returned an error") 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /scripts/inject-name-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PACKAGE="github.com/ncr-devops-platform/nagiosfoundation/cmd/initcmd" 4 | 5 | echo "-ldflags" 6 | echo -n "-X $PACKAGE.cmdName=$PRODUCT " 7 | echo "-X $PACKAGE.cmdVersion=$VERSION" 8 | 9 | -------------------------------------------------------------------------------- /scripts/validate-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | tar zxf out/package/nagiosfoundation-linux-amd64-*.tgz 4 | CHECK_VERSION=$(./godelw project-version) 5 | EXIT=0 6 | 7 | for CHECK_NAME in $(ls bin); do 8 | OUTPUT=$(bin/$CHECK_NAME version) 9 | 10 | echo $OUTPUT 11 | 12 | OUT_NAME=$(echo $OUTPUT | cut -d \ -f 1) 13 | OUT_VERSION=$(echo $OUTPUT | cut -d \ -f 3) 14 | 15 | if [ "$OUT_NAME" != "$CHECK_NAME" ]; then 16 | echo "Check name is $CHECK_NAME but output name is $OUT_NAME" 17 | EXIT=1 18 | elif [ "$OUT_VERSION" != "$CHECK_VERSION" ]; then 19 | echo "Version is $CHECK_VERSION but output version is $OUT_VERSION" 20 | EXIT=1 21 | fi 22 | done 23 | 24 | exit $EXIT 25 | --------------------------------------------------------------------------------