├── .bonsai.yml ├── .github ├── PULL_REQUEST_TEMPLATE.md └── dependabot.yml ├── .gitignore ├── .rubocop.yml ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── bin ├── check-disk-usage.rb ├── check-fstab-mounts.rb ├── check-smart-status.rb ├── check-smart-tests.rb ├── check-smart.rb ├── metrics-disk-capacity.rb ├── metrics-disk-usage.rb └── metrics-disk.rb ├── lib ├── sensu-plugins-disk-checks.rb └── sensu-plugins-disk-checks │ └── version.rb ├── sensu-plugins-disk-checks.gemspec └── test └── spec_helper.rb /.bonsai.yml: -------------------------------------------------------------------------------- 1 | --- 2 | description: "#{repo}" 3 | builds: 4 | - platform: "alpine" 5 | arch: "amd64" 6 | asset_filename: "#{repo}_#{version}_alpine_linux_amd64.tar.gz" 7 | sha_filename: "#{repo}_#{version}_sha512-checksums.txt" 8 | filter: 9 | - "entity.system.os == 'linux'" 10 | - "entity.system.arch == 'amd64'" 11 | - "entity.system.platform == 'alpine'" 12 | - platform: "debian" 13 | arch: "amd64" 14 | asset_filename: "#{repo}_#{version}_debian_linux_amd64.tar.gz" 15 | sha_filename: "#{repo}_#{version}_sha512-checksums.txt" 16 | filter: 17 | - "entity.system.os == 'linux'" 18 | - "entity.system.arch == 'amd64'" 19 | - "entity.system.platform_family == 'debian'" 20 | - platform: "centos6" 21 | arch: "amd64" 22 | asset_filename: "#{repo}_#{version}_centos6_linux_amd64.tar.gz" 23 | sha_filename: "#{repo}_#{version}_sha512-checksums.txt" 24 | filter: 25 | - "entity.system.os == 'linux'" 26 | - "entity.system.arch == 'amd64'" 27 | - "entity.system.platform_family == 'rhel'" 28 | - "parseInt(entity.system.platform_version.split('.')[0]) == 6" 29 | - platform: "centos7" 30 | arch: "amd64" 31 | asset_filename: "#{repo}_#{version}_centos7_linux_amd64.tar.gz" 32 | sha_filename: "#{repo}_#{version}_sha512-checksums.txt" 33 | filter: 34 | - "entity.system.os == 'linux'" 35 | - "entity.system.arch == 'amd64'" 36 | - "entity.system.platform_family == 'rhel'" 37 | - "parseInt(entity.system.platform_version.split('.')[0]) == 7" 38 | - platform: "centos8" 39 | arch: "amd64" 40 | asset_filename: "#{repo}_#{version}_centos8_linux_amd64.tar.gz" 41 | sha_filename: "#{repo}_#{version}_sha512-checksums.txt" 42 | filter: 43 | - "entity.system.os == 'linux'" 44 | - "entity.system.arch == 'amd64'" 45 | - "entity.system.platform_family == 'rhel'" 46 | - "parseInt(entity.system.platform_version.split('.')[0]) == 8" 47 | 48 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Pull Request Checklist 2 | 3 | **Is this in reference to an existing issue?** 4 | 5 | #### General 6 | 7 | - [ ] Update Changelog following the conventions laid out [here](https://github.com/sensu-plugins/community/blob/master/HOW_WE_CHANGELOG.md) 8 | 9 | - [ ] Update README with any necessary configuration snippets 10 | 11 | - [ ] Binstubs are created if needed 12 | 13 | - [ ] RuboCop passes 14 | 15 | - [ ] Existing tests pass 16 | 17 | #### New Plugins 18 | 19 | - [ ] Tests 20 | 21 | - [ ] Add the plugin to the README 22 | 23 | - [ ] Does it have a complete header as outlined [here](http://sensu-plugins.io/docs/developer_guidelines.html#coding-style) 24 | 25 | #### Purpose 26 | 27 | #### Known Compatibility Issues 28 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | .vagrant/* 16 | .DS_Store 17 | .idea/* 18 | *.gem 19 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | TargetRubyVersion: 2.3 3 | 4 | MethodLength: 5 | Max: 200 6 | 7 | LineLength: 8 | Max: 175 9 | 10 | AbcSize: 11 | Max: 175 12 | 13 | FileName: 14 | Enabled: false 15 | 16 | PerceivedComplexity: 17 | Enabled: false 18 | 19 | CyclomaticComplexity: 20 | Enabled: false 21 | 22 | ClassLength: 23 | Enabled: false 24 | 25 | IfUnlessModifier: 26 | Enabled: false 27 | 28 | RegexpLiteral: 29 | Enabled: false 30 | 31 | Style/Documentation: 32 | Enabled: false 33 | 34 | Style/Next: 35 | Enabled: false 36 | 37 | Style/ZeroLengthPredicate: 38 | Enabled: false 39 | 40 | Lint/RaiseException: 41 | Enabled: true 42 | 43 | Lint/StructNewOverride: 44 | Enabled: true 45 | 46 | Style/HashEachMethods: 47 | Enabled: true 48 | 49 | Style/HashTransformKeys: 50 | Enabled: true 51 | 52 | Style/HashTransformValues: 53 | Enabled: true 54 | 55 | Style/NumericPredicate: 56 | Enabled: false 57 | 58 | # syntax is not common outside of the ruby community; try to reduce the opinionated back and forth as a more language neutral community 59 | Style/SafeNavigation: 60 | Enabled: false 61 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | services: 3 | - docker 4 | cache: 5 | - bundler 6 | before_install: 7 | - bash -c 'if [ command -v bundler ]; then bundle update --bundler; fi' 8 | install: 9 | - bundle install 10 | rvm: 11 | - 2.3.0 12 | - 2.4.1 13 | - 2.5.3 14 | notifications: 15 | email: 16 | recipients: 17 | - sensu-plugin@sensu-plugins.io 18 | on_success: change 19 | on_failure: always 20 | script: 21 | - bundle exec rake default 22 | - gem build sensu-plugins-disk-checks.gemspec 23 | - gem install sensu-plugins-disk-checks-*.gem 24 | before_deploy: 25 | - bash -c "[ ! -d bonsai/ ] && git clone https://github.com/sensu/sensu-go-bonsai-asset.git bonsai || echo 'bonsai/ exists, skipping git clone'" 26 | deploy: 27 | - provider: script 28 | script: bonsai/ruby-runtime/travis-build-ruby-plugin-assets.sh sensu-plugins-disk-checks 29 | skip_cleanup: true 30 | on: 31 | tags: true 32 | all_branches: true 33 | rvm: 2.4.1 34 | - provider: rubygems 35 | api_key: 36 | secure: MA9lRNkP7h07yWS10BZ6ZdlWNdgKB4UU5OyLiwM+IFB8uWMM/l5OsCThYkPPRTNCcGR6O2QaXukgKSz8TX+c3b/+ocMa2iJRnREIkH6/F0ujdmMmtVFFh8/lUdtlxdg3i7HqhjbhZgUNPbDUI5eVO72QAb4s20Idyw92kPEFBXU= 37 | gem: sensu-plugins-disk-checks 38 | on: 39 | tags: true 40 | all_branches: true 41 | rvm: 2.4.1 42 | repo: sensu-plugins/sensu-plugins-disk-checks 43 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | This project adheres to [Semantic Versioning](http://semver.org/). 3 | 4 | This CHANGELOG follows the format listed [here](https://github.com/sensu-plugins/community/blob/master/HOW_WE_CHANGELOG.md) 5 | 6 | ## [Unreleased] 7 | ### Fixed 8 | - check-smart-status.rb: - Check was ignoring all parameters, when ignore was not defined. @VeselaHouba 9 | - check-smart-status.rb: Now returns disk path instead of Disk Class instance reference. @VeselaHouba 10 | 11 | ## [5.1.4] - 2020-11-05 12 | ### Fixed 13 | - change Sensu asset build definition to match ruby-runtime build collection. No change to underlying plugin functionality 14 | 15 | ## [5.1.3] - 2020-06-02 16 | ### Fixed 17 | - fix for incorrect Ruby literal string freeze in binaries 18 | 19 | ## [5.1.2] - 2020-05-20 20 | ### Fixed 21 | - check-disk-usage.rb: More accurate disk percent usage ignoring privileged bytes (@SaviourSelf) 22 | 23 | ## [5.1.1] - 2020-04-22 24 | ### Changed 25 | - Removed centos from .bonsai.yml to make Bonsai happy again 26 | 27 | ## [5.1.0] - 2020-04-22 28 | ### Changed 29 | - Updated bundler dependancy to '~> 2.1' 30 | - Added option to ignore privileged bytes 31 | - Updated rubocop dependency to '~> 0.81.0' 32 | - Remediated rubocop issues 33 | - Updated sys-filesystem gem dependency from 1.3.2 to 1.3.4 34 | 35 | ## [5.0.1] - 2019-12-10 36 | ### Changed 37 | - Updated readme for compliance with proposed plugins style guide 38 | - Updated sys-filesystem gem dependency from 1.1.7 to 1.3.2 39 | 40 | ### Added 41 | - Updated asset build targets to support centos6 42 | 43 | ## [5.0.0] - 2019-04-10 44 | ### Breaking Changes 45 | - Update minimum required ruby version to 2.3. Drop unsupported ruby versions. 46 | - Bump `sensu-plugin` dependency from `~> 1.2` to `~> 4.0` you can read the changelog entries for [4.0](https://github.com/sensu-plugins/sensu-plugin/blob/master/CHANGELOG.md#400---2018-02-17), [3.0](https://github.com/sensu-plugins/sensu-plugin/blob/master/CHANGELOG.md#300---2018-12-04), and [2.0](https://github.com/sensu-plugins/sensu-plugin/blob/master/CHANGELOG.md#v200---2017-03-29) 47 | 48 | ### Added 49 | - Travis build automation to generate Sensu Asset tarballs that can be used n conjunction with Sensu provided ruby runtime assets and the Bonsai Asset Index 50 | - Require latest sensu-plugin for [Sensu Go support](https://github.com/sensu-plugins/sensu-plugin#sensu-go-enablement) 51 | 52 | ## [4.0.1] 53 | ### Fixed 54 | - check-smart-status.rb: Check for overrides when --device is used (@GwennG) 55 | 56 | ## [4.0.0] - 57 | ### Breaking Changes 58 | - check-smart.rb: fixing a `undefined` error by renaming `no-smart-capable-disks` to `--zero-smart-capable_disks` as the parser sees any `--no-` argument and attempts to negate it which a `(True|False)Class` can not be cast to a symbol (@bdeluca) 59 | 60 | ## [3.1.1] - 2018-01-07 61 | ### Fixed 62 | - metrics-disk-capacity.rb: incorrect translation of devicenames by switching to `sub`. This differs by replacing only the first occurrence of `/dev/` as opposed to all occurrences (@nmollerup) 63 | 64 | ### Added 65 | - check-disk-usage: adds documentation for `-x` and `-p` options 66 | 67 | ## [3.1.0] - 2018-05-02 68 | ### Added 69 | - metrics-disk-capacity.rb, added `solaris` compatibility. Does not support inodes on `solaris` (@makaveli0129). 70 | - metrics-disk-usage.rb: added `solaris` compatibility. Does not support block_size for config (@makaveli0129) 71 | 72 | ## [3.0.1] - 2018-03-27 73 | ### Security 74 | - updated yard dependency to `~> 0.9.11` per: https://nvd.nist.gov/vuln/detail/CVE-2017-17042 (@majormoses) 75 | 76 | ## [3.0.0] - 2018-03-07 77 | ### Security 78 | - updated rubocop dependency to `~> 0.51.0` per: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-8418. (@majormoses) 79 | 80 | ### Breaking Changes 81 | - removed ruby `< 2.1` support (@majormoses) 82 | 83 | ### Changed 84 | - updated the changelog location guidelines (@majormoses) 85 | - appeased the cops and created TODOs for refactoring (@majormoses) 86 | 87 | ## [2.5.1] - 2017-12-05 88 | ### Fixed 89 | - check-disk-usage.rb: now exits unknown instead of critical when retrieving mountpoints fail (@exeral) 90 | 91 | ## [2.5.0] - 2017-08-28 92 | ### Changed 93 | - check-smart.rb: The JSON file is now does not need to exist (@ChrisPortman) 94 | 95 | ### Added 96 | - check-smart.rb: Add support for legacy smartctl output relating to `Available` and `Enabled` (@ChrisPortman) 97 | - check-smart.rb: Add option to specify the exit code to use when there are no smart capable devices (@ChrisPortman) 98 | 99 | ### Removed 100 | - check-smart.rb: Removed duplicated smartctl runs 101 | 102 | ## [2.4.2] - 2017-08-17 103 | ### Fixed 104 | - check-smart-status.rb: fixed regex for `Available` and `Enabled` (@nagyt234) 105 | 106 | ## [2.4.1] - 2017-08-08 107 | ### Fixed 108 | - check-smart.rb: Fixed 'override' being ignored (@sovaa) 109 | 110 | ## [2.4.0] - 2017-07-20 111 | - check-smart.rb: Add path overrides via smart.json (@ArakniD) 112 | - check-smart-status.rb: Add path overrides via smart.json (@ArakniD) 113 | 114 | ## [2.3.0] - 2017-07-03 115 | ### Added 116 | - travis testing on ruby 2.4.1 (@majormoses) 117 | 118 | ### Fixed 119 | - fixed spelling of "Compatibility" in PR template (@majormoses) 120 | - check-smart.rb: deal with long keys in the info output (@robx) 121 | - check-smart.rb: accept "OK" disk health status (@robx) 122 | 123 | ## [2.2.0] - 2017-06-24 124 | ### Added 125 | - check-smart-tests.rb: a plugin to check S.M.A.R.T. self tests status (@sndl) 126 | 127 | ## [2.1.0] - 2017-05-04 128 | ### Changed 129 | - check-disk-usage.rb: show the decimals for disk usage figures 130 | - check-disk-usage.rb: dont do utilization check of devfs filesystems 131 | - bump sys-filesystem to 1.1.7 132 | 133 | ### Fixed 134 | - check-fstab-mounts.rb: support swap mounts defined using UUID or LABEL 135 | - check-fstab-mounts.rb: support swap mounts defined using LVM /dev/mapper/* 136 | 137 | ## [2.0.1] - 2016-06-15 138 | ### Fixed 139 | - metrics-disk-capacity.rb: fixed invalid string matching with =~ operator 140 | 141 | ## [2.0.0] - 2016-06-14 142 | ### Changed 143 | - Updated Rubocop to 0.40, applied auto-correct. 144 | - metrics-disk.rb: Now using sysfs instead of lsblk for translating device mapper numbers to names 145 | - check-disk-usage.rb: values =< 1024 will not be modified for display in output 146 | 147 | ### Removed 148 | - Remove Ruby 1.9.3 support; add Ruby 2.3.0 support 149 | 150 | ## [1.1.3] - 2016-01-15 151 | ### Added 152 | - Add --json option to check-smart-status.rb 153 | 154 | ### Changed 155 | - Let check-smart-status.rb skip SMART incapable disks 156 | 157 | ### Fixed 158 | - metrics-disk-usage.rb: fix regular expression in unless statement 159 | - check-disk-usage.rb: fix wrong TiB formatting in to_human function 160 | 161 | ## [1.1.2] - 2015-12-14 162 | ### Added 163 | - check-disk-usage.rb: Add option to include only certain mount points 164 | 165 | ## [1.1.1] - 2015-12-11 166 | ### Fixed 167 | - check-disk-usage.rb fails on Windows with sys-filesystem (1.1.4) 168 | - bump sys-filesystem to 1.1.5 169 | 170 | ## [1.1.0] - 2015-12-08 171 | ### Added 172 | - check-disk-usage.rb: Add ignore option to exclude mount point(s) based on regular expression 173 | - metrics-disk.rb: Add ignore and include options to exclude or include devices 174 | 175 | ### Fixed 176 | - check-disk-usage.rb: Don't blow up when unable to read a filesystem 177 | 178 | ### Removed 179 | - Remove dependency on `filesystem` gem 180 | 181 | ## [1.0.3] - 2015-10-25 182 | ### Changed 183 | - Adds Ignore option(s) to check-disk-usage.rb 184 | - Adaptive thresholds 185 | 186 | ### Adaptive Thresholds 187 | 188 | This borrows from check_mk's "df" check, which has the ability to 189 | adaptively adjust thresholds for filesystems based on their sizes. 190 | 191 | For example, a hard threshold of "warn at 85%" is quite different 192 | between small filesystems and very large filesystems - 85% utilization 193 | of a 20GB filesystem is arguably more serious than 85% utilization of a 194 | 10TB filesystem. 195 | 196 | This uses check_mk's original alorithm to optionally adjust the 197 | percentage thresholds based on size, using a "magic factor", a 198 | "normalized size", and a "minium size to adjust". 199 | 200 | The magic factor defaults to '1.0', which will not adjust thresholds. 201 | The minimum size defaults to '100' (GB). Filesystems smaller than this 202 | will not be adapted. 203 | 204 | check_mk's documentation has a lot more information on this, including 205 | examples of adjustments based on the magic factor: 206 | https://mathias-kettner.de/checkmk_filesystems.html 207 | 208 | ## [1.0.2] - 2015-08-04 209 | ### Changed 210 | - general cleanup 211 | 212 | ## [1.0.1] - 2015-07-14 213 | ### Changed 214 | - updated sensu-plugin gem to 1.2.0 215 | 216 | ##[1.0.0] - 2015-07-05 217 | ### Changed 218 | - changed metrics filenames to conform to std 219 | - updated Rakefile to remove cruft 220 | - updated documentation links in README and CONTRIBUTING 221 | - updated gemspec to put deps in alpha order 222 | 223 | ## [0.0.4] - 2015-06-22 224 | ### Fixed 225 | - Correct check of inodes method on object fs_info which was always returning false 226 | 227 | ## [0.0.3] - 2015-06-02 228 | ### Fixed 229 | - added binstubs 230 | 231 | ### Changed 232 | - removed cruft from /lib 233 | 234 | ## [0.0.2] - 2015-04-21 235 | ### Fixed 236 | - deployment issue 237 | 238 | ## 0.0.1 - 2015-04-21 239 | ### Added 240 | - initial release 241 | 242 | [Unreleased]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/5.1.3...HEAD 243 | [5.1.3]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/5.1.2...5.1.3 244 | [5.1.2]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/5.1.1...5.1.2 245 | [5.1.1]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/5.1.0...5.1.1 246 | [5.1.0]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/5.0.1...5.1.0 247 | [5.0.1]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/5.0.0...5.0.1 248 | [5.0.0]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/4.0.1...5.0.0 249 | [4.0.1]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/4.0.0...4.0.1 250 | [4.0.0]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/3.1.1...4.0.0 251 | [3.1.1]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/3.1.0...3.1.1 252 | [3.1.0]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/3.0.1...3.1.0 253 | [3.0.1]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/3.0.0...3.0.1 254 | [3.0.0]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/2.5.1...3.0.0 255 | [2.5.1]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/2.5.0...2.5.1 256 | [2.5.0]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/2.4.2...2.5.0 257 | [2.4.2]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/2.4.1...2.4.2 258 | [2.4.1]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/2.4.0...2.4.1 259 | [2.4.0]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/2.3.0...2.4.0 260 | [2.3.0]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/2.2.0...2.3.0 261 | [2.2.0]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/2.1.0...2.2.0 262 | [2.1.0]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/2.0.1...2.1.0 263 | [2.0.1]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/2.0.0...2.0.1 264 | [2.0.0]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/1.1.3...2.0.0 265 | [1.1.3]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/1.1.2...1.1.3 266 | [1.1.2]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/1.1.1...1.1.2 267 | [1.1.1]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/1.1.1...1.1.1 268 | [1.1.0]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/1.0.3...1.1.0 269 | [1.0.3]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/1.0.2...1.0.3 270 | [1.0.2]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/1.0.1...1.0.2 271 | [1.0.1]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/1.0.0...1.0.1 272 | [1.0.0]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/0.0.4...1.0.0 273 | [0.0.4]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/0.0.3...0.0.4 274 | [0.0.3]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/0.0.2...0.0.3 275 | [0.0.2]: https://github.com/sensu-plugins/sensu-plugins-disk-checks/compare/0.0.1...0.0.2 276 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | [Development Documentation](http://sensu-plugins.io/docs/developer_guidelines.html) 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | # Specify your gem's dependencies in sensu-plugins-disk-checks.gemspec 6 | gemspec 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Sensu-Plugins 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Sensu Bonsai Asset](https://img.shields.io/badge/Bonsai-Download%20Me-brightgreen.svg?colorB=89C967&logo=sensu)](https://bonsai.sensu.io/assets/sensu-plugins/sensu-plugins-disk-checks) 2 | [![Build Status](https://travis-ci.org/sensu-plugins/sensu-plugins-disk-checks.svg?branch=master)](https://travis-ci.org/sensu-plugins/sensu-plugins-disk-checks) 3 | [![Gem Version](https://badge.fury.io/rb/sensu-plugins-disk-checks.svg)](http://badge.fury.io/rb/sensu-plugins-disk-checks) 4 | [![Code Climate](https://codeclimate.com/github/sensu-plugins/sensu-plugins-disk-checks/badges/gpa.svg)](https://codeclimate.com/github/sensu-plugins/sensu-plugins-disk-checks) 5 | [![Test Coverage](https://codeclimate.com/github/sensu-plugins/sensu-plugins-disk-checks/badges/coverage.svg)](https://codeclimate.com/github/sensu-plugins/sensu-plugins-disk-checks) 6 | [![Dependency Status](https://gemnasium.com/sensu-plugins/sensu-plugins-disk-checks.svg)](https://gemnasium.com/sensu-plugins/sensu-plugins-disk-checks) 7 | 8 | ## Sensu Disk Checks Plugin 9 | - [Overview](#overview) 10 | - [Usage examples](#usage-examples) 11 | - [Configuration](#configuration) 12 | - [Sensu Go](#sensu-go) 13 | - [Asset definition](#asset-definition) 14 | - [Check definition](#check-definition) 15 | - [Sensu Core](#sensu-core) 16 | - [Check definition](#check-definition) 17 | - [Functionality](#functionality) 18 | - [Additional information](#additional-information) 19 | - [Installation from source and contributing](#installation-from-source-and-contributing) 20 | 21 | ### Overview 22 | 23 | This plugin provides native disk instrumentation for monitoring and metrics collection, including: health, usage, and various metrics. 24 | 25 | The Sensu assets packaged from this repository are built against the Sensu ruby runtime environment. When using these assets as part of a Sensu Go resource (check, mutator or handler), make sure you include the corresponding Sensu ruby runtime asset in the list of assets needed by the resource. The current ruby-runtime assets can be found [here](https://bonsai.sensu.io/assets/sensu/sensu-ruby-runtime) in the [Bonsai Asset Index](bonsai.sensu.io) 26 | 27 | #### Files 28 | * bin/check-disk-usage.rb 29 | * bin/check-fstab-mounts.rb 30 | * bin/check-smart-status.rb 31 | * bin/check-smart.rb 32 | * bin/check-smart-tests.rb 33 | * bin/metrics-disk.rb 34 | * bin/metrics-disk-capacity.rb 35 | * bin/metrics-disk-usage.rb 36 | 37 | ### Usage examples 38 | 39 | #### Help 40 | 41 | **check-disk-usage.rb** 42 | ``` 43 | Usage: check-disk-usage.rb (options) 44 | -c PERCENT Critical if PERCENT or more of disk full 45 | -w PERCENT Warn if PERCENT or more of disk full 46 | -t TYPE[,TYPE] Only check fs type(s) 47 | -K PERCENT Critical if PERCENT or more of inodes used 48 | -i MNT[,MNT] Ignore mount point(s) 49 | -o TYPE[.TYPE] Ignore option(s) 50 | -p PATHRE Ignore mount point(s) matching regular expression 51 | -x TYPE[,TYPE] Ignore fs type(s) 52 | -I MNT[,MNT] Include only mount point(s) 53 | -r Ignore bytes reserved for privileged processes only 54 | -W PERCENT Warn if PERCENT or more of inodes used 55 | -m MAGIC Magic factor to adjust warn/crit thresholds. Example: .9 56 | -l MINIMUM Minimum size to adjust (in GB) 57 | -n NORMAL Levels are not adapted for filesystems of exactly this size, where levels are reduced for smaller filesystems and raised for larger filesystems. 58 | ``` 59 | 60 | **metrics-disk-usage.rb** 61 | ``` 62 | Usage: metrics-disk-usage.rb (options) 63 | -B, --block-size BLOCK_SIZE Set block size for sizes printed 64 | -f, --flatten Output mounts with underscore rather than dot 65 | -i, --ignore-mount MNT[,MNT] Ignore mounts matching pattern(s) 66 | -I, --include-mount MNT[,MNT] Include only mounts matching pattern(s) 67 | -l, --local Only check local filesystems (df -l option) 68 | --scheme SCHEME Metric naming scheme, text to prepend to .$parent.$child 69 | ``` 70 | 71 | 72 | ### Configuration 73 | #### Sensu Go 74 | ##### Asset registration 75 | 76 | Assets are the best way to make use of this plugin. If you're not using an asset, please consider doing so! If you're using sensuctl 5.13 or later, you can use the following command to add the asset: 77 | 78 | `sensuctl asset add sensu-plugins/sensu-plugins-disk-checks` 79 | 80 | If you're using an earlier version of sensuctl, you can download the asset definition from [this project's Bonsai Asset Index page](https://bonsai.sensu.io/assets/sensu-plugins/sensu-plugins-disk-checks). 81 | 82 | ##### Asset definition 83 | 84 | ```yaml 85 | --- 86 | type: Asset 87 | api_version: core/v2 88 | metadata: 89 | name: sensu-plugins-disk-checks 90 | spec: 91 | url: https://assets.bonsai.sensu.io/73a6f8b6f56672630d83ec21676f9a6251094475/sensu-plugins-disk-checks_5.0.0_centos_linux_amd64.tar.gz 92 | sha512: 0ce9d52b270b77f4cab754e55732ae002228201d0bd01a89b954a0655b88c1ee6546e2f82cfd1eec04689af90ad940cde128e8867912d9e415f4a58d7fdcdadf 93 | ``` 94 | 95 | ##### Check definition 96 | 97 | ```yaml 98 | --- 99 | type: CheckConfig 100 | spec: 101 | command: "metrics-disk-usage.rb" 102 | handlers: [] 103 | high_flap_threshold: 0 104 | interval: 10 105 | low_flap_threshold: 0 106 | publish: true 107 | runtime_assets: 108 | - sensu-plugins/sensu-plugins-disk-checks 109 | - sensu/sensu-ruby-runtime 110 | subscriptions: 111 | - linux 112 | output_metric_format: graphite_plaintext 113 | output_metric_handlers: 114 | - influx-db 115 | ``` 116 | #### Sensu Core 117 | ##### Check definition 118 | ```json 119 | { 120 | "checks": { 121 | "metrics-disk-usage": { 122 | "command": "metric-disk-usage.rb", 123 | "subscribers": ["linux"], 124 | "interval": 10, 125 | "refresh": 10, 126 | "handlers": ["influxdb"] 127 | } 128 | } 129 | } 130 | 131 | ``` 132 | 133 | ### Functionality 134 | 135 | **check-disk-usage** 136 | 137 | Check disk capacity and inodes based upon the gem sys-filesystem. 138 | 139 | Can adjust thresholds for larger filesystems by providing a 'magic factor' 140 | (`-m`). The default, `1.0`, will not adapt threshold percentages for volumes. 141 | 142 | The `-l` option can be used in combination with the 'magic factor' to specify 143 | the minimum size volume to adjust the thresholds for. 144 | 145 | By default all mounted filesystems are checked. 146 | 147 | The `-x` option can be used to exclude one or more filesystem types. e.g. 148 | 149 | check-disk-usage.rb -x debugfs,tracefs 150 | 151 | The `-p` option can be used to exlucde specific mount points. e.g. 152 | 153 | check-disk-usage.rb -p /run/lxcfs 154 | 155 | It's also possible to use regular expressions with the `-x` or `-p` option 156 | 157 | check-disk-usage.rb -p '(\/var|\/run|\/sys|\/snap)' 158 | 159 | Refer to [check_mk's](https://mathias-kettner.de/checkmk_filesystems.html) 160 | documentation on adaptive thresholds. 161 | 162 | You can also visualize the adjustment using 163 | [WolframAlpha]([https://www.wolframalpha.com/input/) with the following: 164 | 165 | y = 100 - (100-P)*(N^(1-m))/(x^(1-m)), y = P for x in 0 to 1024 166 | 167 | Where P = base percentage, N = normalize factor, and m = magic factor 168 | 169 | **check-fstab-mounts** 170 | 171 | Check the mount points in */etc/fstab* to ensure they are all accounted for. 172 | 173 | **metrics-disk-capacity** 174 | 175 | Acquire disk capacity metrics from `df` and convert them to a form usable by graphite 176 | 177 | **metrics-disk** 178 | 179 | Read */proc/iostats* for disk metrics and put them in a form usable by Graphite. See [iostats.txt](http://www.kernel.org/doc/Documentation/iostats.txt) for more details. 180 | 181 | **metrics-disk-usage** 182 | 183 | Based on disk-capacity-metrics.rb by bhenerey and nstielau. The difference here being how the key is defined in graphite and the size we emit to graphite(now using megabytes), inode info has also been dropped. 184 | 185 | **check-smart-status** 186 | 187 | Check the SMART status of hardrives and alert based upon a given set of thresholds 188 | 189 | **check-smart** 190 | 191 | Check the health of a disk using `smartctl` 192 | 193 | **check-smart-tests** 194 | 195 | Check the status of SMART offline tests and optionally check if tests were executed in a specified interval 196 | 197 | ### Additional information 198 | The `check-smart.rb` and `check-smart-status.rb` scripts can make use of a json file living on disk in `/etc/sensu/conf.d`with the name `smart.json`. You can see a sample input file used by these scripts below. Please refer to the individual scripts for further details. 199 | ```json 200 | { 201 | "smart": { 202 | "attributes": [ 203 | { "id": 1, "name": "Raw_read_Error_Rate", "read": "left16bit" }, 204 | { "id": 5, "name": "Reallocated_Sector_Ct" }, 205 | { "id": 9, "name": "Power_On_Hours", "read": "right16bit", "warn_max": 10000, "crit_max": 15000 }, 206 | { "id": 10 , "name": "Spin_Retry_Count" }, 207 | { "id": 184, "name": "End-to-End_Error" }, 208 | { "id": 187, "name": "Reported_Uncorrect" }, 209 | { "id": 188, "name": "Command_Timeout" }, 210 | { "id": 193, "name": "Load_Cycle_Count", "warn_max": 300000, "crit_max": 600000 }, 211 | { "id": 194, "name": "Temperature_Celsius", "read": "right16bit", "crit_min": 20, "warn_min": 10, "warn_max": 40, "crit_max": 50 }, 212 | { "id": 196, "name": "Reallocated_Event_Count" }, 213 | { "id": 197, "name": "Current_Pending_Sector" }, 214 | { "id": 198, "name": "Offline_Uncorrectable" }, 215 | { "id": 199, "name": "UDMA_CRC_Error_Count" }, 216 | { "id": 201, "name": "Unc_Soft_read_Err_Rate", "read": "left16bit" }, 217 | { "id": 230, "name": "Life_Curve_Status", "crit_min": 100, "warn_min": 100, "warn_max": 100, "crit_max": 100 } 218 | ] 219 | }, 220 | "hardware": { 221 | "devices": [ 222 | { "path": "sda", "ignore" : [ 187 ] }, 223 | { "path": "sdb", "override": "/dev/twa0 -d 3ware,0" } 224 | ] 225 | } 226 | } 227 | ``` 228 | 229 | ### Installation 230 | 231 | ### Sensu Go 232 | 233 | See the instructions above for [asset registration](#asset-registration) 234 | 235 | ### Sensu Core 236 | Install and setup plugins on [Sensu Core](https://docs.sensu.io/sensu-core/latest/installation/installing-plugins/) 237 | 238 | 239 | ### Certification Verification 240 | 241 | If you are verifying certificates in the gem install you will need the certificate for the `sys-filesystem` gem loaded in the gem certificate store. That cert can be found [here](https://raw.githubusercontent.com/djberg96/sys-filesystem/ffi/certs/djberg96_pub.pem). 242 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | require 'github/markup' 5 | require 'redcarpet' 6 | require 'rspec/core/rake_task' 7 | require 'rubocop/rake_task' 8 | require 'yard' 9 | require 'yard/rake/yardoc_task' 10 | 11 | desc 'Don\'t run Rubocop for unsupported versions' 12 | 13 | args = %i[spec make_bin_executable yard rubocop check_binstubs] 14 | 15 | YARD::Rake::YardocTask.new do |t| 16 | OTHER_PATHS = %w[].freeze 17 | t.files = ['lib/**/*.rb', 'bin/**/*.rb', OTHER_PATHS] 18 | t.options = %w[--markup-provider=redcarpet --markup=markdown --main=README.md --files CHANGELOG.md] 19 | end 20 | 21 | RuboCop::RakeTask.new 22 | 23 | RSpec::Core::RakeTask.new(:spec) do |r| 24 | r.pattern = FileList['**/**/*_spec.rb'] 25 | end 26 | 27 | desc 'Make all plugins executable' 28 | task :make_bin_executable do 29 | `chmod -R +x bin/*` 30 | end 31 | 32 | desc 'Test for binstubs' 33 | task :check_binstubs do 34 | bin_list = Gem::Specification.load('sensu-plugins-disk-checks.gemspec').executables 35 | bin_list.each do |b| 36 | `which #{b}` 37 | unless $CHILD_STATUS.success? 38 | puts "#{b} was not a binstub" 39 | exit 40 | end 41 | end 42 | end 43 | 44 | task default: args 45 | -------------------------------------------------------------------------------- /bin/check-disk-usage.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # frozen_string_literal: false 3 | 4 | # 5 | # check-disk 6 | # 7 | # DESCRIPTION: 8 | # Uses the sys-filesystem gem to get filesystem mount points and metrics 9 | # 10 | # OUTPUT: 11 | # plain text 12 | # 13 | # PLATFORMS: 14 | # Linux, BSD, Windows 15 | # 16 | # DEPENDENCIES: 17 | # gem: sensu-plugin 18 | # gem: sys-filesystem 19 | # 20 | # USAGE: 21 | # 22 | # NOTES: 23 | # 24 | # LICENSE: 25 | # Copyright 2015 Yieldbot Inc 26 | # Released under the same terms as Sensu (the MIT license); see LICENSE 27 | # for details. 28 | # 29 | 30 | require 'sensu-plugin/check/cli' 31 | require 'sys/filesystem' 32 | 33 | # 34 | # Check Disk 35 | # 36 | class CheckDisk < Sensu::Plugin::Check::CLI 37 | include Sys 38 | option :fstype, 39 | short: '-t TYPE[,TYPE]', 40 | description: 'Only check fs type(s)', 41 | proc: proc { |a| a.split(',') } 42 | 43 | option :ignoretype, 44 | short: '-x TYPE[,TYPE]', 45 | description: 'Ignore fs type(s)', 46 | proc: proc { |a| a.split(',') } 47 | 48 | option :ignoremnt, 49 | short: '-i MNT[,MNT]', 50 | description: 'Ignore mount point(s)', 51 | proc: proc { |a| a.split(',') } 52 | 53 | option :includemnt, 54 | description: 'Include only mount point(s)', 55 | short: '-I MNT[,MNT]', 56 | proc: proc { |a| a.split(',') } 57 | 58 | option :ignorepathre, 59 | short: '-p PATHRE', 60 | description: 'Ignore mount point(s) matching regular expression', 61 | proc: proc { |a| Regexp.new(a) } 62 | 63 | option :ignoreopt, 64 | short: '-o TYPE[.TYPE]', 65 | description: 'Ignore option(s)', 66 | proc: proc { |a| a.split('.') } 67 | 68 | option :ignorereadonly, 69 | long: '--ignore-readonly', 70 | description: 'Ignore read-only filesystems', 71 | boolean: true, 72 | default: false 73 | 74 | option :ignore_reserved, 75 | description: 'Ignore bytes reserved for privileged processes', 76 | short: '-r', 77 | long: '--ignore-reserved', 78 | boolean: true, 79 | default: false 80 | 81 | option :bwarn, 82 | short: '-w PERCENT', 83 | description: 'Warn if PERCENT or more of disk full', 84 | proc: proc(&:to_i), 85 | default: 85 86 | 87 | option :bcrit, 88 | short: '-c PERCENT', 89 | description: 'Critical if PERCENT or more of disk full', 90 | proc: proc(&:to_i), 91 | default: 95 92 | 93 | option :iwarn, 94 | short: '-W PERCENT', 95 | description: 'Warn if PERCENT or more of inodes used', 96 | proc: proc(&:to_i), 97 | default: 85 98 | 99 | option :icrit, 100 | short: '-K PERCENT', 101 | description: 'Critical if PERCENT or more of inodes used', 102 | proc: proc(&:to_i), 103 | default: 95 104 | 105 | option :magic, 106 | short: '-m MAGIC', 107 | description: 'Magic factor to adjust warn/crit thresholds. Example: .9', 108 | proc: proc(&:to_f), 109 | default: 1.0 110 | 111 | option :normal, 112 | short: '-n NORMAL', 113 | description: 'Levels are not adapted for filesystems of exactly this '\ 114 | 'size, where levels are reduced for smaller filesystems and raised '\ 115 | 'for larger filesystems.', 116 | proc: proc(&:to_f), 117 | default: 20 118 | 119 | option :minimum, 120 | short: '-l MINIMUM', 121 | description: 'Minimum size to adjust (in GB)', 122 | proc: proc(&:to_f), 123 | default: 100 124 | 125 | # Setup variables 126 | # 127 | def initialize 128 | super 129 | @crit_fs = [] 130 | @warn_fs = [] 131 | end 132 | 133 | # Get mount data 134 | # 135 | def fs_mounts 136 | begin 137 | mounts = Filesystem.mounts 138 | rescue StandardError 139 | unknown 'An error occured getting the mount info' 140 | end 141 | mounts.each do |line| 142 | begin 143 | next if config[:fstype] && !config[:fstype].include?(line.mount_type) 144 | next if config[:ignoretype]&.include?(line.mount_type) 145 | next if config[:ignoremnt]&.include?(line.mount_point) 146 | next if config[:ignorepathre]&.match(line.mount_point) 147 | next if config[:ignoreopt]&.include?(line.options) 148 | next if config[:ignorereadonly] && line.options.split(',').include?('ro') 149 | next if config[:includemnt] && !config[:includemnt].include?(line.mount_point) 150 | rescue StandardError 151 | unknown 'An error occured getting the mount info' 152 | end 153 | check_mount(line) 154 | end 155 | end 156 | 157 | # Adjust the percentages based on volume size 158 | # 159 | def adj_percent(size, percent) 160 | hsize = (size / (1024.0 * 1024.0)) / config[:normal].to_f 161 | felt = hsize**config[:magic] 162 | scale = felt / hsize 163 | 100 - ((100 - percent) * scale) 164 | end 165 | 166 | def check_mount(line) 167 | begin 168 | fs_info = Filesystem.stat(line.mount_point) 169 | rescue StandardError 170 | @warn_fs << "#{line.mount_point} Unable to read." 171 | return 172 | end 173 | if line.mount_type == 'devfs' 174 | return 175 | end 176 | 177 | if fs_info.respond_to?(:inodes) && !fs_info.inodes.nil? # needed for windows 178 | percent_i = percent_inodes(fs_info) 179 | if percent_i >= config[:icrit] 180 | @crit_fs << "#{line.mount_point} #{percent_i}% inode usage" 181 | elsif percent_i >= config[:iwarn] 182 | @warn_fs << "#{line.mount_point} #{percent_i}% inode usage" 183 | end 184 | end 185 | percent_b = percent_bytes(fs_info) 186 | 187 | if fs_info.bytes_total < (config[:minimum] * 1_000_000_000) 188 | bcrit = config[:bcrit] 189 | bwarn = config[:bwarn] 190 | else 191 | bcrit = adj_percent(fs_info.bytes_total, config[:bcrit]) 192 | bwarn = adj_percent(fs_info.bytes_total, config[:bwarn]) 193 | end 194 | 195 | used = to_human(fs_info.bytes_used) 196 | total = to_human(fs_info.bytes_total) 197 | 198 | if percent_b >= bcrit 199 | @crit_fs << "#{line.mount_point} #{percent_b}% bytes usage (#{used}/#{total})" 200 | elsif percent_b >= bwarn 201 | @warn_fs << "#{line.mount_point} #{percent_b}% bytes usage (#{used}/#{total})" 202 | end 203 | end 204 | 205 | def to_human(size) 206 | unit = [ 207 | [1_099_511_627_776, 'TiB'], 208 | [1_073_741_824, 'GiB'], 209 | [1_048_576, 'MiB'], 210 | [1024, 'KiB'], 211 | [0, 'B'] 212 | ].detect { |u| size >= u[0] } 213 | format( 214 | "%.2f #{unit[1]}", 215 | (size >= 1024 ? size.to_f / unit[0] : size) 216 | ) 217 | end 218 | 219 | # Determine the percent inode usage 220 | # 221 | def percent_inodes(fs_info) 222 | (100.0 - (100.0 * fs_info.inodes_free / fs_info.inodes)).round(2) 223 | end 224 | 225 | # Determine the percent byte usage 226 | # 227 | def percent_bytes(fs_info) 228 | if config[:ignore_reserved] 229 | u100 = fs_info.bytes_used * 100.0 230 | nonroot_total = fs_info.bytes_used + fs_info.bytes_available 231 | if nonroot_total.zero? 232 | 0 233 | else 234 | (u100 / nonroot_total + (u100 % nonroot_total != 0 ? 1 : 0)).round(2) 235 | end 236 | else 237 | (100.0 - (100.0 * fs_info.bytes_free / fs_info.bytes_total)).round(2) 238 | end 239 | end 240 | 241 | # Generate output 242 | # 243 | def usage_summary 244 | (@crit_fs + @warn_fs).join(', ') 245 | end 246 | 247 | # Main function 248 | # 249 | def run 250 | fs_mounts 251 | critical usage_summary unless @crit_fs.empty? 252 | warning usage_summary unless @warn_fs.empty? 253 | ok "All disk usage under #{config[:bwarn]}% and inode usage under #{config[:iwarn]}%" 254 | end 255 | end 256 | -------------------------------------------------------------------------------- /bin/check-fstab-mounts.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # frozen_string_literal: false 3 | 4 | # 5 | # check-fstab-mounts 6 | # 7 | # DESCRIPTION: 8 | # Check /etc/mtab to ensure all filesystems of the requested type(s) from 9 | # fstab are currently mounted. If no fstypes are specified, will check all 10 | # entries in fstab. 11 | # 12 | # OUTPUT: 13 | # plain text 14 | # 15 | # PLATFORMS: 16 | # Linux 17 | # 18 | # DEPENDENCIES: 19 | # gem: sensu-plugin 20 | # gem: pathname 21 | # 22 | # USAGE: 23 | # 24 | # NOTES: 25 | # 26 | # LICENSE: 27 | # Peter Fern 28 | # Released under the same terms as Sensu (the MIT license); see LICENSE 29 | # for details. 30 | # 31 | 32 | require 'sensu-plugin/check/cli' 33 | require 'pathname' 34 | 35 | # 36 | # Check Fstab Mounts 37 | # 38 | class CheckFstabMounts < Sensu::Plugin::Check::CLI 39 | option :fstypes, 40 | description: 'Filesystem types to check, comma-separated', 41 | short: '-t TYPES', 42 | long: '--types TYPES', 43 | proc: proc { |a| a.split(',') }, 44 | required: false 45 | 46 | # Setup variables 47 | # 48 | def initialize 49 | super 50 | @fstab = IO.readlines '/etc/fstab' 51 | @mtab = IO.readlines '/etc/mtab' 52 | @swap_mounts = IO.readlines '/proc/swaps' 53 | @missing_mounts = [] 54 | end 55 | 56 | def resolve_device(device) 57 | if device.start_with?('UUID=') 58 | uuid = device.split('=')[1] 59 | path = File.join('/', 'dev', 'disk', 'by-uuid', uuid) 60 | if File.exist?(path) && File.symlink?(path) 61 | return File.realpath(path) 62 | end 63 | end 64 | 65 | if device.start_with?('LABEL=') 66 | label = device.split('=')[1] 67 | path = File.join('/', 'dev', 'disk', 'by-label', label) 68 | if File.exist?(path) && File.symlink?(path) 69 | return File.realpath(path) 70 | end 71 | end 72 | 73 | if device.start_with?('/dev/mapper') 74 | if File.symlink?(device) 75 | device = File.realpath(device, '/') 76 | end 77 | end 78 | 79 | device 80 | end 81 | 82 | # Check by mount destination (col 2 in fstab and proc/mounts) 83 | # 84 | def check_mounts 85 | @fstab.each do |line| 86 | next if line =~ /^\s*#/ 87 | next if line =~ /^\s*$/ 88 | 89 | fields = line.split(/\s+/) 90 | next if fields[1] == 'none' || (fields[3].include? 'noauto') 91 | next if config[:fstypes] && !config[:fstypes].include?(fields[2]) 92 | 93 | if fields[2] != 'swap' 94 | @missing_mounts << fields[1] if @mtab.select { |m| m.split(/\s+/)[1] == fields[1] }.empty? 95 | else 96 | @missing_mounts << fields[1] if @swap_mounts.select { |m| m.split(/\s+/)[0] == resolve_device(fields[0]) }.empty? # rubocop:disable Style/IfInsideElse 97 | end 98 | end 99 | end 100 | 101 | # Main function 102 | # 103 | def run 104 | check_mounts 105 | if @missing_mounts.any? 106 | critical "Mountpoint(s) #{@missing_mounts.join(',')} not mounted!" 107 | else 108 | ok 'All mountpoints accounted for' 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /bin/check-smart-status.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # frozen_string_literal: false 3 | 4 | # 5 | # check-smart 6 | # 7 | # DESCRIPTION: 8 | # S.M.A.R.T. - Self-Monitoring, Analysis and Reporting Technology 9 | # 10 | # Check hdd and ssd SMART attributes defined in smart.json file. Default is 11 | # to check all attributes defined in this file if attribute is presented by hdd. 12 | # If attribute not presented script will skip it. 13 | # 14 | # I defined smart.json file based on this two specification 15 | # http://en.wikipedia.org/wiki/S.M.A.R.T.#cite_note-kingston1-32 16 | # http://media.kingston.com/support/downloads/MKP_306_SMART_attribute.pdf 17 | # 18 | # I tested on several Seagate, Western Digital hdd and Cosair force Gt SSD 19 | # 20 | # It is possible some hdd give strange attribute values and warnings based on it 21 | # but in this case simply define attribute list with '-a' parameter 22 | # and ignore wrong parameters. Maybe attribute 1 and 201 will be wrong because 23 | # format of this attributes specified by hdd vendors. 24 | # 25 | # You can test the script just make a copy of your smartctl output and change some 26 | # value. I put a hdd attribute file into 'test_hdd.txt' and a failed hdd file into 27 | # 'test_hdd_failed.txt'. 28 | # 29 | # OUTPUT: 30 | # plain text 31 | # 32 | # PLATFORMS: 33 | # Linux 34 | # 35 | # DEPENDENCIES: 36 | # gem: sensu-plugin 37 | # gem: json 38 | # smartmontools 39 | # smart.json 40 | # 41 | # USAGE: 42 | # You need to add 'sensu' user to suduers or you can't use 'smartctl' 43 | # sensu ALL=(ALL) NOPASSWD:/usr/sbin/smartctl 44 | # 45 | # PARAMETERS: 46 | # -b: smartctl binary to use, in case you hide yours (default: /usr/sbin/smartctl) 47 | # -d: default threshold for crit_min,warn_min,warn_max,crit_max (default: 0,0,0,0) 48 | # -a: SMART attributes to check (default: all) 49 | # -t: Custom threshold for SMART attributes. (id,crit_min,warn_min,warn_max,crit_max) 50 | # -o: Overall SMART health check (default: on) 51 | # -d: Devices to check (default: all) 52 | # --debug: turn debug output on (default: off) 53 | # --debug_file: process this file instead of smartctl output for testing 54 | # 55 | # NOTES: 56 | # 57 | # LICENSE: 58 | # Copyright 2013 Peter Kepes 59 | # Released under the same terms as Sensu (the MIT license); see LICENSE 60 | # for details. 61 | # 62 | 63 | require 'sensu-plugin/check/cli' 64 | require 'json' 65 | 66 | class Disk 67 | # Setup variables 68 | # 69 | def initialize(name, override, ignore) 70 | @device_path = "/dev/#{name}" 71 | @override_path = override 72 | @att_ignore = ignore 73 | end 74 | 75 | # Is the device SMART capable and enabled 76 | # 77 | def device_path 78 | if @override_path.nil? 79 | @device_path 80 | else 81 | @override_path 82 | end 83 | end 84 | 85 | def smart_ignore?(num) 86 | return false if @att_ignore.nil? 87 | 88 | @att_ignore.include? num 89 | end 90 | 91 | public :device_path, :smart_ignore? 92 | end 93 | 94 | # 95 | # Smart Check Status 96 | # 97 | class SmartCheckStatus < Sensu::Plugin::Check::CLI 98 | option :binary, 99 | short: '-b path/to/smartctl', 100 | long: '--binary /usr/sbin/smartctl', 101 | description: 'smartctl binary to use, in case you hide yours', 102 | required: false, 103 | default: 'smartctl' 104 | 105 | option :json, 106 | short: '-j path/to/smart.json', 107 | long: '--json path/to/smart.json', 108 | description: 'Path to SMART attributes JSON file', 109 | required: false, 110 | default: File.dirname(__FILE__) + '/smart.json' 111 | 112 | option :defaults, 113 | short: '-d 0,0,0,0', 114 | long: '--defaults 0,0,0,0', 115 | description: 'default threshold for crit_min,warn_min,warn_max,crit_max', 116 | required: false, 117 | default: '0,0,0,0' 118 | 119 | option :attributes, 120 | short: '-a 1,5,9,230', 121 | long: '--attributes 1,5,9,230', 122 | description: 'SMART attributes to check', 123 | required: false, 124 | default: 'all' 125 | 126 | option :threshold, 127 | short: '-t 194,5,10,50,60', 128 | long: '--threshold 194,5,10,50,60', 129 | description: 'Custom threshold for SMART attributes. (id,crit_min,warn_min,warn_max,crit_max)', 130 | required: false 131 | 132 | option :overall, 133 | short: '-o off', 134 | long: '--overall off', 135 | description: 'Overall SMART health check', 136 | required: false, 137 | default: 'on' 138 | 139 | option :devices, 140 | short: '-d sda,sdb,sdc', 141 | long: '--device sda,sdb,sdc', 142 | description: 'Devices to check', 143 | required: false, 144 | default: 'all' 145 | 146 | option :debug, 147 | long: '--debug on', 148 | description: 'Turn debug output on', 149 | required: false, 150 | default: 'off' 151 | 152 | option :debug_file, 153 | long: '--debugfile test_hdd.txt', 154 | description: 'Process a debug file for testing', 155 | required: false 156 | 157 | # Main function 158 | # 159 | def run 160 | @smart_attributes = JSON.parse(IO.read(config[:json]), symbolize_names: true)[:smart][:attributes] 161 | @smart_debug = config[:debug] == 'on' 162 | 163 | # Load in the device configuration 164 | @hardware = JSON.parse(IO.read(config[:json]), symbolize_names: true)[:hardware][:devices] 165 | 166 | # Set default threshold 167 | default_threshold = config[:defaults].split(',') 168 | raise 'Invalid default threshold parameter count' unless default_threshold.size == 4 169 | 170 | @smart_attributes.each do |att| 171 | att[:crit_min] = default_threshold[0].to_i if att[:crit_min].nil? 172 | att[:warn_min] = default_threshold[1].to_i if att[:warn_min].nil? 173 | att[:warn_max] = default_threshold[2].to_i if att[:warn_max].nil? 174 | att[:crit_max] = default_threshold[3].to_i if att[:crit_max].nil? 175 | end 176 | 177 | # Check threshold parameter if present 178 | unless config[:threshold].nil? 179 | thresholds = config[:threshold].split(',') 180 | # Check threshold parameter length 181 | raise 'Invalid threshold parameter count' unless (thresholds.size % 5).zero? 182 | 183 | (0..(thresholds.size / 5 - 1)).each do |i| 184 | att_id = @smart_attributes.index { |att| att[:id] == thresholds[i + 0].to_i } 185 | thash = { crit_min: thresholds[i + 1].to_i, 186 | warn_min: thresholds[i + 2].to_i, 187 | warn_max: thresholds[i + 3].to_i, 188 | crit_max: thresholds[i + 4].to_i } 189 | @smart_attributes[att_id].merge! thash 190 | end 191 | end 192 | 193 | # Attributes to check 194 | att_check_list = find_attributes 195 | 196 | # Devices to check 197 | devices = config[:debug_file].nil? ? find_devices : [Disk.new('sda', nil, nil)] 198 | 199 | # Overall health and attributes parameter 200 | parameters = '-H -A' 201 | 202 | # Get attributes in raw48 format 203 | att_check_list.each do |att| 204 | parameters += " -v #{att},raw48" 205 | end 206 | 207 | output = {} 208 | warnings = [] 209 | criticals = [] 210 | # TODO: refactor me 211 | devices.each do |dev| # rubocop:disable Metrics/BlockLength 212 | puts "#{config[:binary]} #{parameters} #{dev.device_path}" if @smart_debug 213 | # check if debug file specified 214 | if config[:debug_file].nil? 215 | output[dev] = `sudo #{config[:binary]} #{parameters} #{dev.device_path}` 216 | else 217 | test_file = File.open(config[:debug_file], 'rb') 218 | output[dev] = test_file.read 219 | test_file.close 220 | end 221 | 222 | # check overall helath status 223 | if config[:overall] == 'on' && !output[dev].include?('SMART overall-health self-assessment test result: PASSED') 224 | criticals << "Overall health check failed on #{dev.name}" 225 | end 226 | 227 | # #YELLOW 228 | output[dev].split("\n").each do |line| 229 | fields = line.split 230 | if fields.size == 10 && fields[0].to_i != 0 && att_check_list.include?(fields[0].to_i) && (dev.smart_ignore?(fields[0].to_i) == false) 231 | smart_att = @smart_attributes.find { |att| att[:id] == fields[0].to_i } 232 | att_value = fields[9].to_i 233 | att_value = send(smart_att[:read], att_value) unless smart_att[:read].nil? 234 | if att_value < smart_att[:crit_min] || att_value > smart_att[:crit_max] 235 | criticals << "#{dev.device_path} critical #{fields[0]} #{smart_att[:name]}: #{att_value}" 236 | puts "#{fields[0]} #{smart_att[:name]}: #{att_value} (critical)" if @smart_debug 237 | elsif att_value < smart_att[:warn_min] || att_value > smart_att[:warn_max] 238 | warnings << "#{dev.device_path} warning #{fields[0]} #{smart_att[:name]}: #{att_value}" 239 | puts "#{fields[0]} #{smart_att[:name]}: #{att_value} (warning)" if @smart_debug 240 | else 241 | puts "#{fields[0]} #{smart_att[:name]}: #{att_value} (ok)" if @smart_debug # rubocop:disable Style/IfInsideElse 242 | end 243 | end 244 | end 245 | puts "\n\n" if @smart_debug 246 | end 247 | 248 | # check the result 249 | if criticals.size != 0 250 | critical criticals.concat(warnings).join("\n") 251 | elsif warnings.size != 0 252 | warning warnings.join("\n") 253 | else 254 | ok 'All device operating properly' 255 | end 256 | end 257 | 258 | # Get right 16 bit from raw48 259 | # 260 | def right16bit(value) 261 | value & 0xffff 262 | end 263 | 264 | # Get left 16 bit from raw48 265 | # 266 | def left16bit(value) 267 | value >> 32 268 | end 269 | 270 | # find all devices from /proc/partitions or from parameter 271 | # 272 | def find_devices 273 | # Search for devices without number 274 | devices = [] 275 | 276 | # Return parameter value if it's defined 277 | if config[:devices] != 'all' 278 | config[:devices].split(',').each do |dev| 279 | jconfig = @hardware.find { |d| d[:path] == dev } 280 | 281 | if jconfig.nil? 282 | override = nil 283 | ignore = nil 284 | else 285 | override = jconfig[:override] 286 | ignore = jconfig[:ignore] 287 | end 288 | devices << Disk.new(dev.to_s, override, ignore) 289 | end 290 | return devices 291 | end 292 | 293 | `lsblk -nro NAME,TYPE`.each_line do |line| 294 | name, type = line.split 295 | 296 | if type == 'disk' 297 | jconfig = @hardware.find { |h1| h1[:path] == name } 298 | 299 | if jconfig.nil? 300 | override = nil 301 | ignore = nil 302 | else 303 | override = jconfig[:override] 304 | ignore = jconfig[:ignore] 305 | end 306 | 307 | device = Disk.new(name, override, ignore) 308 | 309 | output = `sudo #{config[:binary]} -i #{device.device_path}` 310 | 311 | # Check if we can use this device or not 312 | available = !output.scan(/SMART support is:\s+Available/).empty? 313 | enabled = !output.scan(/SMART support is:\s+Enabled/).empty? 314 | devices << device if available && enabled 315 | end 316 | end 317 | 318 | devices 319 | end 320 | 321 | # find all attribute id from parameter or json file 322 | # 323 | def find_attributes 324 | return config[:attributes].split(',') unless config[:attributes] == 'all' 325 | 326 | attributes = [] 327 | @smart_attributes.each do |att| 328 | attributes << att[:id] 329 | end 330 | 331 | attributes 332 | end 333 | end 334 | -------------------------------------------------------------------------------- /bin/check-smart-tests.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # frozen_string_literal: false 3 | 4 | # 5 | # check-smart-tests.rb 6 | # 7 | # DESCRIPTION: 8 | # This script checks S.M.A.R.T. self-tests status and optionally time of last 9 | # test run 10 | # 11 | # OUTPUT: 12 | # plain text 13 | # 14 | # PLATFORMS: 15 | # Linux 16 | # 17 | # DEPENDENCIES: 18 | # gem: sensu-plugin 19 | # 20 | # USAGE: 21 | # check-smart-tests.rb # Use default options 22 | # check-smart-tests.rb -d /dev/sda,/dev/sdb -l 24 -t 336 # Check smart tests status for 23 | # /dev/sda and /dev/sdb devices, also check if short tests were run in last 24 hours and 24 | # extended tests were run in last 14 days(336 hours) 25 | # 26 | # NOTES: 27 | # The plugin requires smartmontools to be installed and smartctl utility in particular. 28 | # 29 | # smartctl requires root rights to run, so you should allow sensu to execute 30 | # this command as root without password by adding following line to /etc/sudoers: 31 | # 32 | # sensu ALL=(ALL) NOPASSWD: /usr/sbin/smartctl 33 | # 34 | # Tested only on Debian. 35 | # 36 | # LICENSE: 37 | # Stanislav Sandalnikov 38 | # Released under the same terms as Sensu (the MIT license); see LICENSE 39 | # for details. 40 | 41 | require 'sensu-plugin/check/cli' 42 | 43 | class Device 44 | attr_accessor :name, :pwh, :str 45 | 46 | def initialize(name, smartctl_executable) 47 | @name = name 48 | @exec = smartctl_executable 49 | @pwh = poweron_hours 50 | @str = selftest_results 51 | end 52 | 53 | def poweron_hours 54 | `sudo #{@exec} -A #{@name}`.split("\n").each do |line| 55 | columns = line.split 56 | if columns[1] == 'Power_On_Hours' 57 | return columns[9] 58 | end 59 | end 60 | end 61 | 62 | def selftest_results 63 | results = [] 64 | headers = %w[num test_description status remaining lifetime lba_of_first_error] 65 | 66 | `sudo #{@exec} -l selftest #{@name}`.split("\n").grep(/^#/).each do |test| 67 | test = test.gsub!(/\s\s+/m, "\t").split("\t") 68 | res = {} 69 | 70 | headers.each_with_index do |v, k| 71 | res[v] = test[k] 72 | end 73 | 74 | results << res 75 | end 76 | 77 | results 78 | end 79 | end 80 | 81 | class CheckSMARTTests < Sensu::Plugin::Check::CLI 82 | option :executable, 83 | long: '--executable EXECUTABLE', 84 | short: '-e EXECUTABLE', 85 | default: '/usr/sbin/smartctl', 86 | description: 'Path to smartctl executable' 87 | option :devices, 88 | long: '--devices *DEVICES', 89 | short: '-d *DEVICES', 90 | default: 'all', 91 | description: 'Comma-separated list of devices to check, i.e. "/dev/sda,/dev/sdb"' 92 | option :short_test_interval, 93 | long: '--short_test_interval INTERVAL', 94 | short: '-s INTERVAL', 95 | description: 'If more time then this value passed since last short test run, then warning will be raised' 96 | option :long_test_interval, 97 | long: '--long_test_interval INTERVAL', 98 | short: '-l INTERVAL', 99 | description: 'If more time then this value passed since last extedned test run, then warning will be raised' 100 | 101 | def initialize 102 | super 103 | @devices = [] 104 | @warnings = [] 105 | @criticals = [] 106 | set_devices 107 | end 108 | 109 | def set_devices 110 | if config[:devices] == 'all' 111 | `lsblk -plnd -o NAME`.split.each do |name| 112 | unless name =~ /\/dev\/loop.*/ 113 | dev = Device.new(name, config[:executable]) 114 | @devices.push(dev) 115 | end 116 | end 117 | else 118 | config[:devices].split(',').each do |name| 119 | dev = Device.new(name, config[:executable]) 120 | @devices.push(dev) 121 | end 122 | end 123 | end 124 | 125 | def check_tests(dev) 126 | if dev.str.empty? 127 | @warnings << "#{dev.name}: No self-tests have been logged." 128 | return 129 | end 130 | 131 | unless dev.str[0]['status'] == 'Completed without error' || dev.str[0]['status'] =~ /Self-test routine in progress/ 132 | @criticals << "#{dev.name}: Last test failed - #{dev.str[0]['status']}" 133 | end 134 | 135 | unless config[:short_test_interval].nil? 136 | dev.str.each_with_index do |t, i| 137 | if t['test_description'] != 'Short offline' 138 | if i == dev.str.length - 1 139 | @warnings << "#{dev.name}: No short tests were run for this device in last #{dev.str.length} executions" 140 | end 141 | next 142 | else 143 | if dev.pwh.to_i - t['lifetime'].to_i > config[:short_test_interval].to_i 144 | @warnings << "#{dev.name}: More than #{config[:short_test_interval]} hours passed since the last short test" 145 | end 146 | break 147 | end 148 | end 149 | end 150 | 151 | # TODO: refactor me 152 | unless config[:long_test_interval].nil? # rubocop:disable Style/GuardClause 153 | dev.str.each_with_index do |t, i| 154 | if t['test_description'] != 'Extended offline' 155 | if i == dev.str.length - 1 156 | @warnings << "#{dev.name}: No extended tests were run for this device in last #{dev.str.length} executions" 157 | end 158 | next 159 | else 160 | if dev.pwh.to_i - t['lifetime'].to_i > config[:long_test_interval].to_i 161 | @warnings << "#{dev.name}: More than #{config[:long_test_interval]} hours passed since the last extended test" 162 | end 163 | break 164 | end 165 | end 166 | end 167 | end 168 | 169 | def run 170 | @devices.each do |device| 171 | check_tests(device) 172 | end 173 | 174 | if @criticals.any? 175 | critical @criticals.join(' ') 176 | elsif @warnings.any? 177 | warning @warnings.join(' ') 178 | else 179 | ok 'All devices are OK' 180 | end 181 | end 182 | end 183 | -------------------------------------------------------------------------------- /bin/check-smart.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # frozen_string_literal: false 3 | 4 | # 5 | # check-smart 6 | # 7 | # DESCRIPTION: 8 | # 9 | # OUTPUT: 10 | # plain text 11 | # 12 | # PLATFORMS: 13 | # Linux 14 | # 15 | # DEPENDENCIES: 16 | # gem: sensu-plugin 17 | # 18 | # USAGE: 19 | # 20 | # NOTES: 21 | # This is a drop-in replacement for check-disk-health.sh. 22 | # 23 | # smartctl requires root permissions. When running this script as a non-root 24 | # user such as sensu, ensure it is run with sudo. 25 | # 26 | # Create a file named /etc/sudoers.d/smartctl with this line inside : 27 | # sensu ALL=(ALL) NOPASSWD: /usr/sbin/smartctl 28 | # 29 | # Fedora has some additional restrictions : if requiretty is set, sudo will only 30 | # run when the user is logged in to a real tty. 31 | # Then add this in the sudoers file (/etc/sudoers), below the line Defaults requiretty : 32 | # Defaults sensu !requiretty 33 | # 34 | # LICENSE: 35 | # Copyright 2013 Mitsutoshi Aoe 36 | # Released under the same terms as Sensu (the MIT license); see LICENSE 37 | # for details. 38 | # 39 | 40 | require 'sensu-plugin/check/cli' 41 | require 'json' 42 | 43 | # 44 | # Disk 45 | # 46 | class Disk 47 | # Setup variables 48 | # 49 | def initialize(name, override, binary) 50 | @device_path = "/dev/#{name}" 51 | @smart_available = false 52 | @smart_enabled = false 53 | @smart_healty = nil 54 | @smart_binary = binary 55 | @override_path = override 56 | check_smart_capability! 57 | check_health! if smart_capable? 58 | end 59 | attr_reader :capability_output, :health_output, :smart_healthy 60 | alias healthy? smart_healthy 61 | 62 | # Is the device SMART capable and enabled 63 | # 64 | def smart_capable? 65 | @smart_available && @smart_enabled 66 | end 67 | 68 | # Is the device SMART capable and enabled 69 | # 70 | def device_path 71 | if @override_path.nil? 72 | @device_path 73 | else 74 | @override_path 75 | end 76 | end 77 | 78 | # Check for SMART cspability 79 | # 80 | def check_smart_capability! 81 | output = `sudo #{@smart_binary} -i #{device_path}` 82 | 83 | # Newer smartctl 84 | @smart_available = !output.scan(/SMART support is:\s+Available/).empty? 85 | @smart_enabled = !output.scan(/SMART support is:\s+Enabled/).empty? 86 | 87 | unless smart_capable? 88 | # Older smartctl 89 | @smart_available = !output.scan(/Device supports SMART/).empty? 90 | @smart_enabled = !output.scan(/and is Enabled/).empty? 91 | end 92 | 93 | @capability_output = output 94 | end 95 | 96 | # Check the SMART health 97 | # 98 | def check_health! 99 | output = `sudo #{@smart_binary} -H #{device_path}` 100 | @smart_healthy = !output.scan(/PASSED|OK$/).empty? 101 | @health_output = output 102 | end 103 | end 104 | 105 | # 106 | # Check SMART 107 | # 108 | class CheckSMART < Sensu::Plugin::Check::CLI 109 | option :smart_incapable_disks, 110 | long: '--smart-incapable-disks EXIT_CODE', 111 | description: 'Exit code when SMART is unavailable/disabled on a disk', 112 | proc: proc(&:to_sym), 113 | default: :unknown, 114 | in: %i[unknown ok warn critical] 115 | 116 | option :no_smart_capable_disks, 117 | long: '--zero-smart-capable-disks EXIT_CODE', 118 | description: 'Exit code when there are no SMART capable disks', 119 | proc: proc(&:to_sym), 120 | default: :unknown, 121 | in: %i[unknown ok warn critical] 122 | 123 | option :binary, 124 | short: '-b path/to/smartctl', 125 | long: '--binary /usr/sbin/smartctl', 126 | description: 'smartctl binary to use, in case you hide yours', 127 | required: false, 128 | default: 'smartctl' 129 | 130 | option :json, 131 | short: '-j path/to/smart.json', 132 | long: '--json path/to/smart.json', 133 | description: 'Path to SMART attributes JSON file', 134 | required: false, 135 | default: File.dirname(__FILE__) + '/smart.json' 136 | 137 | # Setup variables 138 | # 139 | def initialize 140 | super 141 | @devices = [] 142 | 143 | # Load in the device configuration 144 | @hardware = if File.readable?(config[:json]) 145 | JSON.parse(IO.read(config[:json]), symbolize_names: true)[:hardware][:devices] 146 | else 147 | {} 148 | end 149 | 150 | scan_disks! 151 | end 152 | 153 | # Generate a list of all block devices 154 | # 155 | def scan_disks! 156 | `lsblk -nro NAME,TYPE`.each_line do |line| 157 | name, type = line.split 158 | 159 | if type == 'disk' 160 | jconfig = @hardware.find { |h1| h1[:path] == name } 161 | 162 | override = !jconfig.nil? ? jconfig[:override] : nil 163 | 164 | device = Disk.new(name, override, config[:binary]) 165 | 166 | @devices << device if device.smart_capable? 167 | end 168 | end 169 | end 170 | 171 | # Main function 172 | # 173 | def run 174 | unless @devices.length > 0 175 | exit_with( 176 | config[:no_smart_capable_disks], 177 | 'No SMART capable devices found' 178 | ) 179 | end 180 | 181 | unhealthy_disks = @devices.select { |disk| disk.smart_capable? && !disk.healthy? } 182 | unknown_disks = @devices.reject(&:smart_capable?) 183 | 184 | if unhealthy_disks.length > 0 185 | output = unhealthy_disks.map(&:health_output) 186 | output.concat(unknown_disks.map(&:capability_output)) 187 | critical output.join("\n") 188 | end 189 | 190 | if unknown_disks.length > 0 191 | exit_with( 192 | config[:smart_incapable_disks], 193 | unknown_disks.map(&:capability_output).join("\n") 194 | ) 195 | end 196 | 197 | ok 'PASSED' 198 | end 199 | 200 | # Set exit status and message 201 | # 202 | def exit_with(sym, message) 203 | case sym 204 | when :ok 205 | ok message 206 | when :warn 207 | warn message 208 | when :critical 209 | critical message 210 | else 211 | unknown message 212 | end 213 | end 214 | end 215 | -------------------------------------------------------------------------------- /bin/metrics-disk-capacity.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # frozen_string_literal: false 3 | 4 | # 5 | # disk-capacity-metrics 6 | # 7 | # DESCRIPTION: 8 | # This plugin uses df to collect disk capacity metrics 9 | # disk-metrics.rb looks at /proc/stat which doesnt hold capacity metricss. 10 | # could have intetrated this into disk-metrics.rb, but thought I'd leave it up to 11 | # whomever implements the checks. 12 | # 13 | # OUTPUT: 14 | # metric data 15 | # 16 | # PLATFORMS: 17 | # Linux 18 | # 19 | # DEPENDENCIES: 20 | # gem: sensu-plugin 21 | # gem: socket 22 | # 23 | # USAGE: 24 | # 25 | # NOTES: 26 | # 27 | # LICENSE: 28 | # Copyright 2012 Sonian, Inc 29 | # Released under the same terms as Sensu (the MIT license); see LICENSE 30 | # for details. 31 | # 32 | 33 | require 'sensu-plugin/metric/cli' 34 | require 'socket' 35 | 36 | # 37 | # Disk Capacity 38 | # 39 | class DiskCapacity < Sensu::Plugin::Metric::CLI::Graphite 40 | option :scheme, 41 | description: 'Metric naming scheme, text to prepend to .$parent.$child', 42 | long: '--scheme SCHEME', 43 | default: Socket.gethostname.to_s 44 | 45 | # Unused ? 46 | # 47 | def convert_integers(values) 48 | values.each_with_index do |value, index| 49 | begin 50 | converted = Integer(value) 51 | values[index] = converted 52 | rescue ArgumentError # rubocop:disable Lint/SuppressedException 53 | end 54 | end 55 | values 56 | end 57 | 58 | # Main function 59 | # 60 | def run 61 | # Get capacity metrics from DF as they don't appear in /proc 62 | command = if Gem::Platform.local.os == 'solaris' 63 | 'df -k' 64 | else 65 | 'df -PT' 66 | end 67 | `#{command}`.split("\n").drop(1).each do |line| 68 | begin 69 | fs, _type, _blocks, used, avail, capacity, _mnt = line.split 70 | 71 | timestamp = Time.now.to_i 72 | if fs =~ /\/dev/ 73 | fs = fs.sub('/dev/', '') 74 | metrics = { 75 | disk: { 76 | "#{fs}.used" => used, 77 | "#{fs}.avail" => avail, 78 | "#{fs}.capacity" => capacity.delete('%') 79 | } 80 | } 81 | metrics.each do |parent, children| 82 | children.each do |child, value| 83 | output [config[:scheme], parent, child].join('.'), value, timestamp 84 | end 85 | end 86 | end 87 | rescue StandardError 88 | unknown "malformed line from df: #{line}" 89 | end 90 | end 91 | 92 | # Get inode capacity metrics 93 | if Gem::Platform.local.os != 'solaris' 94 | `df -Pi`.split("\n").drop(1).each do |line| 95 | begin 96 | fs, _inodes, used, avail, capacity, _mnt = line.split 97 | 98 | timestamp = Time.now.to_i 99 | if fs =~ /\/dev/ 100 | fs = fs.sub('/dev/', '') 101 | metrics = { 102 | disk: { 103 | "#{fs}.iused" => used, 104 | "#{fs}.iavail" => avail, 105 | "#{fs}.icapacity" => capacity.delete('%') 106 | } 107 | } 108 | metrics.each do |parent, children| 109 | children.each do |child, value| 110 | output [config[:scheme], parent, child].join('.'), value, timestamp 111 | end 112 | end 113 | end 114 | rescue StandardError 115 | unknown "malformed line from df: #{line}" 116 | end 117 | end 118 | end 119 | ok 120 | end 121 | end 122 | -------------------------------------------------------------------------------- /bin/metrics-disk-usage.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # frozen_string_literal: false 3 | 4 | # 5 | # disk-usage-metrics 6 | # 7 | # DESCRIPTION: 8 | # This plugin uses df to collect disk capacity metrics 9 | # disk-usage-metrics.rb looks at /proc/stat which doesnt hold capacity metricss. 10 | # 11 | # OUTPUT: 12 | # metric data 13 | # 14 | # PLATFORMS: 15 | # Linux 16 | # 17 | # DEPENDENCIES: 18 | # gem: sensu-plugin 19 | # gem: socket 20 | # 21 | # USAGE: 22 | # 23 | # NOTES: 24 | # Based on disk-capacity-metrics.rb by bhenerey and nstielau 25 | # The difference here being how the key is defined in graphite and the 26 | # size we emit to graphite(now using megabytes). Also i dropped inode info. 27 | # Using this as an example 28 | # Filesystem Size Used Avail Use% Mounted on 29 | # /dev/mapper/precise64-root 79G 3.5G 72G 5% / 30 | # /dev/sda1 228M 25M 192M 12% /boot 31 | # /dev/sdb1 99G 2G 97G 2% /media/sda1 32 | # The keys with this plugin will be 33 | # disk_usage.root, disk_usage.root.boot, and disk_usage.root.media.sda1 34 | # instead of disk.dev.mapper.precise64-root, disk.sda1, and disk.sda2 35 | # 36 | # Use --flatten option to reduce graphite "tree" by using underscores rather 37 | # then dots for subdirs. Also eliminates 'root' on mounts other than '/'. 38 | # Keys with --flatten option would be 39 | # disk_usage.root, disk_usage.boot, and disk_usage.media_sda1 40 | # 41 | # Mountpoints can be specifically included or ignored using -i or -I options: 42 | # e.g. disk-usage-metric.rb -i ^/boot,^/media 43 | # 44 | # LICENSE: 45 | # Copyright 2012 Sonian, Inc 46 | # Released under the same terms as Sensu (the MIT license); see LICENSE 47 | # for details. 48 | # 49 | 50 | require 'sensu-plugin/metric/cli' 51 | require 'socket' 52 | 53 | # 54 | # Disk Usage Metrics 55 | # 56 | class DiskUsageMetrics < Sensu::Plugin::Metric::CLI::Graphite 57 | option :scheme, 58 | description: 'Metric naming scheme, text to prepend to .$parent.$child', 59 | long: '--scheme SCHEME', 60 | default: "#{Socket.gethostname}.disk_usage" 61 | 62 | option :ignore_mnt, 63 | description: 'Ignore mounts matching pattern(s)', 64 | short: '-i MNT[,MNT]', 65 | long: '--ignore-mount', 66 | proc: proc { |a| a.split(',') } 67 | 68 | option :include_mnt, 69 | description: 'Include only mounts matching pattern(s)', 70 | short: '-I MNT[,MNT]', 71 | long: '--include-mount', 72 | proc: proc { |a| a.split(',') } 73 | 74 | option :flatten, 75 | description: 'Output mounts with underscore rather than dot', 76 | short: '-f', 77 | long: '--flatten', 78 | boolean: true, 79 | default: false 80 | 81 | option :local, 82 | description: 'Only check local filesystems (df -l option)', 83 | short: '-l', 84 | long: '--local', 85 | boolean: true, 86 | default: false 87 | 88 | option :block_size, 89 | description: 'Set block size for sizes printed', 90 | short: '-B BLOCK_SIZE', 91 | long: '--block-size BLOCK_SIZE', 92 | default: 'M' 93 | 94 | # Main function 95 | # 96 | def run 97 | delim = config[:flatten] == true ? '_' : '.' 98 | # Get disk usage from df with used and avail in megabytes 99 | # #YELLOW 100 | command = if Gem::Platform.local.os == 'solaris' 101 | "df -k #{config[:local] ? '-l' : ''}" 102 | else 103 | "df -PB#{config[:block_size]} #{config[:local] ? '-l' : ''}" 104 | end 105 | 106 | `#{command}`.split("\n").drop(1).each do |line| 107 | _, _, used, avail, used_p, mnt = line.split 108 | 109 | unless %r{/sys[/|$]|/dev[/|$]|/run[/|$]} =~ mnt 110 | next if config[:ignore_mnt]&.find { |x| mnt.match(x) } 111 | next if config[:include_mnt] && !config[:include_mnt].find { |x| mnt.match(x) } 112 | 113 | mnt = if config[:flatten] 114 | mnt.eql?('/') ? 'root' : mnt.gsub(/^\//, '') 115 | else 116 | # If mnt is only / replace that with root if its /tmp/foo 117 | # replace first occurance of / with root. 118 | mnt.length == 1 ? 'root' : mnt.gsub(/^\//, 'root.') 119 | end 120 | # Fix subsequent slashes 121 | mnt = mnt.gsub '/', delim 122 | output [config[:scheme], mnt, 'used'].join('.'), used.gsub(config[:block_size], '') 123 | output [config[:scheme], mnt, 'avail'].join('.'), avail.gsub(config[:block_size], '') 124 | output [config[:scheme], mnt, 'used_percentage'].join('.'), used_p.delete('%') 125 | end 126 | end 127 | ok 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /bin/metrics-disk.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # frozen_string_literal: false 3 | 4 | # 5 | # disk-metrics 6 | # 7 | # DESCRIPTION: 8 | # 9 | # OUTPUT: 10 | # metric data 11 | # 12 | # PLATFORMS: 13 | # Linux 14 | # 15 | # DEPENDENCIES: 16 | # gem: sensu-plugin 17 | # gem: socket 18 | # 19 | # USAGE: 20 | # 21 | # NOTES: 22 | # Devices can be specifically included or ignored using -i or -I options: 23 | # e.g. metrics-disk.rb -I [svx]d[a-z][0-9]* 24 | # 25 | # LICENSE: 26 | # Copyright 2012 Sonian, Inc 27 | # Released under the same terms as Sensu (the MIT license); see LICENSE 28 | # for details. 29 | # 30 | 31 | require 'sensu-plugin/metric/cli' 32 | require 'socket' 33 | 34 | # 35 | # Disk Graphite 36 | # 37 | class DiskGraphite < Sensu::Plugin::Metric::CLI::Graphite 38 | option :scheme, 39 | description: 'Metric naming scheme, text to prepend to metric', 40 | short: '-s SCHEME', 41 | long: '--scheme SCHEME', 42 | default: "#{Socket.gethostname}.disk" 43 | 44 | # this option uses lsblk to convert the dm- name to the LVM name. 45 | # sample metric scheme without this: 46 | # .disk.dm-0 47 | # sample metric scheme with this: 48 | # .disk.vg-root 49 | option :convert, 50 | description: 'Convert devicemapper to logical volume name', 51 | short: '-c', 52 | long: '--convert', 53 | default: false 54 | 55 | option :ignore_device, 56 | description: 'Ignore devices matching pattern(s)', 57 | short: '-i DEV[,DEV]', 58 | long: '--ignore-device', 59 | proc: proc { |a| a.split(',') } 60 | 61 | option :include_device, 62 | description: 'Include only devices matching pattern(s)', 63 | short: '-I DEV[,DEV]', 64 | long: '--include-device', 65 | proc: proc { |a| a.split(',') } 66 | 67 | # Main function 68 | def run 69 | # http://www.kernel.org/doc/Documentation/iostats.txt 70 | metrics = %w[reads readsMerged sectorsRead readTime writes writesMerged sectorsWritten writeTime ioInProgress ioTime ioTimeWeighted] 71 | 72 | File.open('/proc/diskstats', 'r').each_line do |line| 73 | stats = line.strip.split(/\s+/) 74 | _major, _minor, dev = stats.shift(3) 75 | if config[:convert] 76 | dev = File.read('/sys/block/' + dev + '/dm/name').chomp! if dev =~ /^dm-.*$/ 77 | end 78 | next if stats == ['0'].cycle.take(stats.size) 79 | 80 | next if config[:ignore_device]&.find { |x| dev.match(x) } 81 | next if config[:include_device] && !config[:include_device].find { |x| dev.match(x) } 82 | 83 | metrics.size.times { |i| output "#{config[:scheme]}.#{dev}.#{metrics[i]}", stats[i] } 84 | end 85 | 86 | ok 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/sensu-plugins-disk-checks.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'sensu-plugins-disk-checks/version' 4 | -------------------------------------------------------------------------------- /lib/sensu-plugins-disk-checks/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SensuPluginsDiskChecks 4 | module Version 5 | MAJOR = 5 6 | MINOR = 1 7 | PATCH = 4 8 | 9 | VER_STRING = [MAJOR, MINOR, PATCH].compact.join('.') 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /sensu-plugins-disk-checks.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path('lib', __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | 6 | require 'date' 7 | require_relative 'lib/sensu-plugins-disk-checks' 8 | 9 | Gem::Specification.new do |s| 10 | s.authors = ['Sensu-Plugins and contributors'] 11 | # s.cert_chain = ['certs/sensu-plugins.pem'] 12 | s.date = Date.today.to_s 13 | s.description = 'This plugin provides native disk instrumentation 14 | for monitoring and metrics collection, including: 15 | health, usage, and various metrics.' 16 | s.email = '' 17 | s.executables = Dir.glob('bin/**/*.rb').map { |file| File.basename(file) } 18 | s.files = Dir.glob('{bin,lib}/**/*') + %w[LICENSE README.md CHANGELOG.md] 19 | s.homepage = 'https://github.com/sensu-plugins/sensu-plugins-disk-checks' 20 | s.license = 'MIT' 21 | s.metadata = { 'maintainer' => '@mattyjones', 22 | 'development_status' => 'active', 23 | 'production_status' => 'unstable - testing recommended', 24 | 'release_draft' => 'false', 25 | 'release_prerelease' => 'false' } 26 | s.name = 'sensu-plugins-disk-checks' 27 | s.platform = Gem::Platform::RUBY 28 | s.post_install_message = 'You can use the embedded Ruby by setting EMBEDDED_RUBY=true in /etc/default/sensu' 29 | s.require_paths = ['lib'] 30 | s.required_ruby_version = '>= 2.3' 31 | # s.signing_key = File.expand_path(pvt_key) if $PROGRAM_NAME =~ /gem\z/ 32 | s.summary = 'Sensu plugins for disk checks' 33 | s.test_files = s.files.grep(%r{^(test|spec|features)/}) 34 | s.version = SensuPluginsDiskChecks::Version::VER_STRING 35 | 36 | s.add_runtime_dependency 'sensu-plugin', '~> 4.0' 37 | 38 | s.add_runtime_dependency 'sys-filesystem', '1.4.2' 39 | 40 | s.add_development_dependency 'bundler', '~> 2.1' 41 | s.add_development_dependency 'codeclimate-test-reporter', '~> 1.0' 42 | s.add_development_dependency 'github-markup', '~> 4.0' 43 | s.add_development_dependency 'pry', '~> 0.10' 44 | s.add_development_dependency 'rake', '~> 13.0' 45 | s.add_development_dependency 'redcarpet', '~> 3.2' 46 | s.add_development_dependency 'rspec', '~> 3.1' 47 | s.add_development_dependency 'rubocop', '~> 0.81.0' 48 | s.add_development_dependency 'yard', '~> 0.9.11' 49 | end 50 | -------------------------------------------------------------------------------- /test/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'codeclimate-test-reporter' 4 | CodeClimate::TestReporter.start 5 | --------------------------------------------------------------------------------