├── .gitignore ├── .travis.yml ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── bin └── check_graphite ├── check_graphite.gemspec ├── lib ├── check_graphite.rb └── check_graphite │ └── version.rb └── spec └── check_graphite_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | .*.s?? 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | script: "rake spec" 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Specify your gem's dependencies in check_graphite.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013-2018 Pierre-Yves Ritschard 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | check_graphite is a nagios module to query graphite 2 | 3 | [![Build 4 | Status](https://secure.travis-ci.org/pyr/check-graphite.png)](http://travis-ci.org/pyr/check-graphite) 5 | 6 | 7 | ## Example 8 | 9 | check_graphite -H 'http://my.graphite.host 10 | 11 | check_graphite -H "http://your.graphite.host/render" -M collectd.somebox.load.load.midterm -w 1 -c 2 -N load 12 | WARNING|load=1.4400000000000002;;;; 13 | 14 | check_graphite accepts the following options: 15 | 16 | * `-H` or `--endpoint`: the graphite HTTP endpoint which can be queried 17 | * `-M` or `--metric`: the metric expression which will be queried, it can be an expression 18 | * `-F` or `--from`: time frame for which to query metrics, defaults to "30seconds" 19 | * `-N` or `--name`: name to give to the metric, defaults to "value" 20 | * `-U` or `--username`: username used for basic authentication 21 | * `-P` or `--password`: password used for basic authentication 22 | * `-w`: warning threshold for the metric 23 | * `-c`: critical threshold for the metric 24 | * `-t`: timeout after which the metric should be considered unknown 25 | * `--ignore-missing`: return `OK` when the metric doesn't exist yet e.g. errors have not occurred 26 | 27 | ## How it works 28 | 29 | check_graphite, asks for a small window of metrics, and computes an average over the last valid 30 | points collected, it then checks the value against supplied thresholds. Thresholds are expressed 31 | in the format given in [The Nagios Developer Guidelines](https://nagios-plugins.org/doc/guidelines.html#THRESHOLDFORMAT). 32 | 33 | NaN values are not taken into account in the average 34 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | desc "Default: run specs." 5 | task :default => :spec 6 | 7 | desc "Run specs" 8 | RSpec::Core::RakeTask.new do |t| 9 | t.rspec_opts = "--color" 10 | end 11 | -------------------------------------------------------------------------------- /bin/check_graphite: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'check_graphite' 4 | CheckGraphite::Command.new.run 5 | -------------------------------------------------------------------------------- /check_graphite.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "check_graphite/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "check_graphite" 7 | s.version = CheckGraphite::VERSION 8 | s.authors = ["Pierre-Yves Ritschard"] 9 | s.email = ["pyr@spootnik.org"] 10 | s.homepage = "https://github.com/pyr/check-graphite" 11 | s.summary = %q{check_graphite} 12 | s.description = %q{check values from a graphite server} 13 | 14 | s.rubyforge_project = "check_graphite" 15 | 16 | s.files = `git ls-files`.split("\n") 17 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 18 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 19 | s.require_paths = ["lib"] 20 | 21 | # specify any dependencies here; for example: 22 | # s.add_development_dependency "rspec" 23 | s.add_runtime_dependency "nagios_check" 24 | 25 | s.add_development_dependency "rake" 26 | s.add_development_dependency "rspec" 27 | s.add_development_dependency "fakeweb" 28 | end 29 | -------------------------------------------------------------------------------- /lib/check_graphite.rb: -------------------------------------------------------------------------------- 1 | require "nagios_check" 2 | require "json" 3 | require "net/https" 4 | require "check_graphite/version" 5 | 6 | module CheckGraphite 7 | 8 | class Command 9 | include NagiosCheck 10 | 11 | on "--endpoint ENDPOINT", "-H ENDPOINT", :mandatory 12 | on "--metric METRIC", "-M METRIC", :mandatory 13 | on "--from TIMEFRAME", "-F TIMEFRAME", :default => "30seconds" 14 | on "--name NAME", "-N NAME", :default => :value 15 | on "--username USERNAME", "-U USERNAME" 16 | on "--password PASSWORD", "-P PASSWORD" 17 | on "--dropfirst N", "-A N", Integer, :default => 0 18 | on "--droplast N", "-Z N", Integer, :default => 0 19 | on "-I", "--ignore-missing", :default => false do 20 | options.send("ignore-missing=", true) 21 | end 22 | 23 | enable_warning 24 | enable_critical 25 | enable_timeout 26 | 27 | def check 28 | uri = URI(URI.encode("#{options.endpoint}?target=#{options.metric}&from=-#{options.from}&format=json")) 29 | req = Net::HTTP::Get.new(uri.request_uri) 30 | 31 | # use basic auth if username is set 32 | if options.username 33 | req.basic_auth options.username, options.password 34 | end 35 | 36 | res = Net::HTTP.start(uri.host, uri.port, :use_ssl => 'https' == uri.scheme) { |http| 37 | http.request(req) 38 | } 39 | 40 | raise "HTTP error code #{res.code}" unless res.code == "200" 41 | if res.body == "[]" 42 | if options.send("ignore-missing") 43 | store_value options.name, 0 44 | store_message "#{options.name} missing - ignoring" 45 | return 46 | else 47 | raise "no data returned for target" 48 | end 49 | end 50 | 51 | datapoints = JSON(res.body).map { |e| e["datapoints"] }.reduce { |a, b| a + b } 52 | datapoints = datapoints.slice( 53 | options.dropfirst, 54 | (datapoints.size - options.dropfirst - options.droplast) 55 | ) 56 | 57 | # Remove NULL values. Return UNKNOWN if there's nothing left. 58 | datapoints.reject! { |v| v.first.nil? } 59 | raise "no valid datapoints" if datapoints.size == 0 60 | 61 | sum = datapoints.reduce(0.0) {|acc, v| acc + v.first } 62 | value = sum / datapoints.size 63 | store_value options.name, value 64 | store_message "#{options.name}=#{value}" 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/check_graphite/version.rb: -------------------------------------------------------------------------------- 1 | module CheckGraphite 2 | VERSION = "0.2.5" 3 | end 4 | -------------------------------------------------------------------------------- /spec/check_graphite_spec.rb: -------------------------------------------------------------------------------- 1 | require "rspec" 2 | require "fake_web" 3 | 4 | require "check_graphite" 5 | 6 | describe CheckGraphite::Command do 7 | before do 8 | FakeWeb.allow_net_connect = false 9 | end 10 | 11 | describe "it should make http requests and return data" do 12 | before do 13 | FakeWeb.register_uri(:get, "http://your.graphite.host/render?target=collectd.somebox.load.load.midterm&from=-30seconds&format=json", 14 | :body => '[{"target": "default.test.boottime", "datapoints": [[1.0, 1339512060], [2.0, 1339512120], [6.0, 1339512180], [7.0, 1339512240]]}]', 15 | :content_type => "application/json") 16 | end 17 | 18 | it "should return OK" do 19 | stub_const("ARGV", %w{ -H http://your.graphite.host/render -M collectd.somebox.load.load.midterm }) 20 | c = CheckGraphite::Command.new 21 | STDOUT.should_receive(:puts).with("OK: value=4.0|value=4.0;;;;") 22 | lambda { c.run }.should raise_error SystemExit 23 | end 24 | 25 | it "should return WARNING" do 26 | stub_const("ARGV", %w{ -H http://your.graphite.host/render -M collectd.somebox.load.load.midterm -w 0 }) 27 | c = CheckGraphite::Command.new 28 | STDOUT.should_receive(:puts).with("WARNING: value=4.0|value=4.0;;;;") 29 | lambda { c.run }.should raise_error SystemExit 30 | end 31 | 32 | it "should return CRITICAL" do 33 | stub_const("ARGV", %w{ -H http://your.graphite.host/render -M collectd.somebox.load.load.midterm -c 0 }) 34 | c = CheckGraphite::Command.new 35 | STDOUT.should_receive(:puts).with("CRITICAL: value=4.0|value=4.0;;;;") 36 | lambda { c.run }.should raise_error SystemExit 37 | end 38 | 39 | it "should honour dropfirst" do 40 | stub_const("ARGV", %w{ -H http://your.graphite.host/render -M collectd.somebox.load.load.midterm --dropfirst 1 }) 41 | c = CheckGraphite::Command.new 42 | STDOUT.should_receive(:puts).with("OK: value=5.0|value=5.0;;;;") 43 | lambda { c.run }.should raise_error SystemExit 44 | end 45 | 46 | it "should honour droplast" do 47 | stub_const("ARGV", %w{ -H http://your.graphite.host/render -M collectd.somebox.load.load.midterm --droplast 1 }) 48 | c = CheckGraphite::Command.new 49 | STDOUT.should_receive(:puts).with("OK: value=3.0|value=3.0;;;;") 50 | lambda { c.run }.should raise_error SystemExit 51 | end 52 | 53 | it "should honour dropfirst and droplast together" do 54 | stub_const("ARGV", %w{ -H http://your.graphite.host/render -M collectd.somebox.load.load.midterm --dropfirst 1 --droplast 1 }) 55 | c = CheckGraphite::Command.new 56 | STDOUT.should_receive(:puts).with("OK: value=4.0|value=4.0;;;;") 57 | lambda { c.run }.should raise_error SystemExit 58 | end 59 | end 60 | 61 | describe "when data contains null values" do 62 | before do 63 | FakeWeb.register_uri(:get, "http://your.graphite.host/render?target=collectd.somebox.load.load.midterm&from=-30seconds&format=json", 64 | :body => '[{"target": "default.test.boottime", "datapoints": [[1.0, 1339512060], [null, 1339512120], [null, 1339512180], [3.0, 1339512240]]}]', 65 | :content_type => "application/json") 66 | end 67 | 68 | it "should discard them" do 69 | stub_const("ARGV", %w{ -H http://your.graphite.host/render -M collectd.somebox.load.load.midterm }) 70 | c = CheckGraphite::Command.new 71 | STDOUT.should_receive(:puts).with("OK: value=2.0|value=2.0;;;;") 72 | lambda { c.run }.should raise_error SystemExit 73 | end 74 | end 75 | 76 | describe "when Graphite returns no data at all" do 77 | before do 78 | FakeWeb.register_uri(:get, "http://your.graphite.host/render?target=value.does.not.exist&from=-30seconds&format=json", 79 | :body => '[]', 80 | :content_type => "application/json") 81 | end 82 | 83 | it "should be unknown" do 84 | stub_const("ARGV", %w{ -H http://your.graphite.host/render -M value.does.not.exist }) 85 | c = CheckGraphite::Command.new 86 | STDOUT.should_receive(:puts).with(/UNKNOWN: INTERNAL ERROR: (RuntimeError: )?no data returned for target/) 87 | lambda { c.run }.should raise_error SystemExit 88 | end 89 | 90 | it "should be ok when ignoring missing data" do 91 | stub_const("ARGV", %w{ -H http://your.graphite.host/render -M value.does.not.exist --ignore-missing -w 1 -c 3 }) 92 | c = CheckGraphite::Command.new 93 | STDOUT.should_receive(:puts).with(/OK: value missing - ignoring/) 94 | lambda { c.run }.should raise_error SystemExit 95 | end 96 | end 97 | 98 | describe "when Graphite returns only NULL values" do 99 | before do 100 | FakeWeb.register_uri(:get, "http://your.graphite.host/render?target=all.values.null&from=-30seconds&format=json", 101 | :body => '[{"target": "all.values.null", "datapoints": [[null, 1339512060], [null, 1339512120], [null, 1339512180], [null, 1339512240]]}]', 102 | :content_type => "application/json") 103 | end 104 | 105 | it "should be unknown" do 106 | stub_const("ARGV", %w{ -H http://your.graphite.host/render -M all.values.null }) 107 | c = CheckGraphite::Command.new 108 | STDOUT.should_receive(:puts).with(/UNKNOWN: INTERNAL ERROR: (RuntimeError: )?no valid datapoints/) 109 | lambda { c.run }.should raise_error SystemExit 110 | end 111 | end 112 | 113 | describe "when Graphite returns multiple targets" do 114 | before do 115 | FakeWeb.register_uri(:get, "http://your.graphite.host/render?target=collectd.somebox*.load.load.midterm&from=-30seconds&format=json", 116 | :body => '[{"target": "collectd.somebox2.load.load.midterm", "datapoints": [[null, 1339512060], [null, 1339512120], [null, 1339512180], [null, 1339512240]]},{"target": "collectd.somebox1.load.load.midterm", "datapoints": [[1.0, 1339512060], [2.0, 1339512120], [6.0, 1339512180], [7.0, 1339512240]]}]', 117 | :content_type => "application/json") 118 | end 119 | 120 | it "should return OK" do 121 | stub_const("ARGV", %w{ -H http://your.graphite.host/render -M collectd.somebox*.load.load.midterm }) 122 | c = CheckGraphite::Command.new 123 | STDOUT.should_receive(:puts).with("OK: value=4.0|value=4.0;;;;") 124 | lambda { c.run }.should raise_error SystemExit 125 | end 126 | end 127 | 128 | describe "it should make http requests with basic auth and return data" do 129 | before do 130 | FakeWeb.register_uri(:get, "http://baduser:badpass@your.graphite.host/render?target=collectd.somebox.load.load.midterm&from=-30seconds&format=json", 131 | :body => "Unauthorized", :status => ["401", "Unauthorized"]) 132 | 133 | FakeWeb.register_uri(:get, "http://testuser:testpass@your.graphite.host/render?target=collectd.somebox.load.load.midterm&from=-30seconds&format=json", 134 | :body => '[{"target": "default.test.boottime", "datapoints": [[1.0, 1339512060], [3.0, 1339512120]]}]', 135 | :content_type => "application/json") 136 | end 137 | 138 | it "should work with valid username and password" do 139 | stub_const("ARGV", %w{ -H http://your.graphite.host/render -M collectd.somebox.load.load.midterm -U testuser -P testpass}) 140 | c = CheckGraphite::Command.new 141 | STDOUT.should_receive(:puts).with("OK: value=2.0|value=2.0;;;;") 142 | lambda { c.run }.should raise_error SystemExit 143 | end 144 | 145 | it "should fail with bad username and password" do 146 | stub_const("ARGV", %w{ -H http://your.graphite.host/render -M collectd.somebox.load.load.midterm -U baduser -P badpass }) 147 | c = CheckGraphite::Command.new 148 | STDOUT.should_receive(:puts).with(/UNKNOWN: INTERNAL ERROR: (RuntimeError: )?HTTP error code 401/) 149 | lambda { c.run }.should raise_error SystemExit 150 | end 151 | end 152 | end 153 | --------------------------------------------------------------------------------