redis_alerting 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NDAxYmIwZDg4OWI1NzkxMWU3NjYwYTNhYjBhMDgyZWE4OGU4OTQ1Yg==
5
+ data.tar.gz: !binary |-
6
+ ODRlY2JlMTIyMzZiOWU5YTgwM2NlMjE4YjNlZGU2NGI3MDlkNmM2ZQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ODlkNjRiMTQ3MzE3MzUwYzU1ZTU2OWI5YzdkMjYyYzk0ZmI4OGQ3NWEyOGE1
10
+ NzRlNDM5YjA1NmU4MDkzNjQyN2E3ZWUzZDE4ZjIyYjI5Y2Q2OWIyMzQ0NTkx
11
+ NjE0ZmE3ZDZlYTVlMjZmMmI3MGI2YzIwYTE2NzE1ZTZiMTVhYTg=
12
+ data.tar.gz: !binary |-
13
+ MTg2YWNiODQyZTg4NzgwNTVkZGIzZTAzMDVjMDE2OGUyYTcwMWE3MjBiMjUy
14
+ MjlkM2M3Zjk5ZmEzOTkxOWRiYzY0ZmM3NDBkZmY1YTEyYWNkZjUwN2U5ZjZl
15
+ N2UyZjQ1ZTA2NTljMGZlNGI4MzQ0Y2Q5NGRhYmZkMWZmZDlhMTg=
data/.gitignore ADDED
@@ -0,0 +1,14 @@
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
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in redis-alerting.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Robert McLeod
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.
data/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # RedisAlerting
2
+
3
+ An alerting engine that uses keys from redis to determine if a reading is out of range. It then writes to a key in redis to indicate that the reading is out of range.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'redis_alerting'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install redis_alerting
20
+
21
+ ## Usage
22
+
23
+ To start the engine:
24
+
25
+ ```sh
26
+ redis-alerting start -- -c path/to/config.yml
27
+ ```
28
+
29
+ Check the status:
30
+
31
+ ```sh
32
+ redis-alerting status
33
+ ```
34
+
35
+ Stop it:
36
+
37
+ ```sh
38
+ redis-alerting stop
39
+ ```
40
+
41
+ ## How it works
42
+
43
+ The gem uses specific key patterns to get the min and the max limits for a reading.
44
+
45
+ Given the config file below we will describe how the gem would check some limits and write back to redis to inidicate an out of range state.
46
+
47
+ ```yaml
48
+ ---
49
+ alerting:
50
+ :interval: 1 # how often to check the readings
51
+ :namespace: alerts # where the min/max settings, and active alerts are located
52
+ :channel: alerts # publish alert messages to this channel
53
+ :sources: # keys to obtain values from that need to be checked
54
+ :ph: readings.ph
55
+ :ec: readings.ec
56
+ :flow: readings.flow
57
+ # :name: redis.key.with.live.value
58
+ ```
59
+
60
+ With the example of `ph` above, the alerting system would check the redis key `readings.ph` and determine if it was outside the limits set in `alerts.ph.max` and `alerts.ph.min`.
61
+
62
+ When it finds that the value in `readings.ph` is outside the range, it will add "ph" to the redis set `alerts.active`. When it comes back into range "ph" will be removed from the `alerts.active` set.
63
+
64
+ So to quickly summarize:
65
+
66
+ * `readings.ph` - the reading value used to check against the min and max settings
67
+ * `alerts.ph.min` - the minimum value for the reading (below which an alert is raised)
68
+ * `alerts.ph.max` - the maximum value for the reading (above which an alert is raised)
69
+ * `alerts.active` - the Redis SET that contains the names of the active alerts (e.g. "ph", "ec" or "flow")
70
+
71
+ ### Published messages
72
+
73
+ When an alert condition is added or removed, the following message will be published the channel specified in the config file in JSON format:
74
+
75
+ So when an alert is raised, this message will be published:
76
+
77
+ ```json
78
+ {
79
+ "action" : "add",
80
+ "name" : "ec",
81
+ "condition": "high",
82
+ "value" : 6.2,
83
+ "min" : 0.1,
84
+ "max" : 5.8
85
+ }
86
+ ```
87
+
88
+ When an alert is no longer active, this message will be published:
89
+
90
+ ```json
91
+ {
92
+ "action" : "remove",
93
+ "name" : "flow",
94
+ "value" : 2.4,
95
+ "min" : 0.1,
96
+ "max" : 5.8
97
+ }
98
+ ```
99
+
100
+ ### Simple example
101
+
102
+ With the engine started try this (still sticking with the keys in the config file above):
103
+
104
+ ```sh
105
+ $ redis-cli
106
+ 127.0.0.1:6379> set alerts.ph.min 4000
107
+ 127.0.0.1:6379> set alerts.ph.max 9000
108
+ 127.0.0.1:6379> set readings.ph 6000
109
+ 127.0.0.1:6379> smembers alerts.active
110
+ (empty list or set)
111
+ 127.0.0.1:6379> set readings.ph 9100
112
+ 127.0.0.1:6379> smembers alerts.active
113
+ 1) "ph"
114
+ 127.0.0.1:6379> set readings.ph 3900
115
+ 127.0.0.1:6379> smembers alerts.active
116
+ 1) "ph"
117
+ 127.0.0.1:6379> set readings.ph 6000
118
+ 127.0.0.1:6379> smembers alerts.active
119
+ (empty list or set)
120
+ ```
121
+
122
+ ## Todo
123
+
124
+ * more specs
125
+
126
+ ## Contributing
127
+
128
+ 1. Fork it ( https://github.com/AutogrowSystems/RedisAlerting/fork )
129
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
130
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
131
+ 4. Push to the branch (`git push origin my-new-feature`)
132
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ begin
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec) do |task|
7
+ task.rspec_opts = "--color"
8
+ end
9
+
10
+ task :test => :spec
11
+ rescue LoadError
12
+ # no rspec available
13
+ end
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'redis_alerting'
4
+ require 'slop'
5
+ require 'daemons'
6
+
7
+ daemon_opts = {
8
+ monitor: true,
9
+ multiple: false,
10
+ log_output: true,
11
+ log_dir: "/tmp"
12
+ }
13
+
14
+ slop_args = []
15
+
16
+ # build the slop args custom because daemons gem overrides
17
+ # ARGV and slop can't deal with it
18
+ if ARGV.index("--")
19
+ slop_args = ARGV[(ARGV.index("--")+1)..-1]
20
+
21
+ # set the PWD
22
+ if slop_args.index("-p").nil?
23
+ slop_args << "-p"
24
+ slop_args << Dir.pwd
25
+ end
26
+ end
27
+
28
+ Daemons.run_proc("redis_alerting", daemon_opts ) do
29
+ opts = Slop.parse slop_args do
30
+ on :c, :config=, 'Configuration File'
31
+ on :p, :pwd=, "Working directory"
32
+ end
33
+
34
+ begin
35
+ RedisAlerting.run(opts.to_hash)
36
+ rescue ArgumentError => e
37
+ abort "ERROR: #{e.message}"
38
+ end
39
+ end
data/etc/config.yml ADDED
@@ -0,0 +1,9 @@
1
+ ---
2
+ alerting:
3
+ :interval: 1
4
+ :namespace: alerts
5
+ :channel: alerts
6
+ :sources:
7
+ :ph: readings.ph
8
+ :ec: readings.ec
9
+ :flow: readings.flow
@@ -0,0 +1,35 @@
1
+ module RedisAlerting
2
+ class Config
3
+ def initialize(opts)
4
+ @config = opts
5
+ parse_config
6
+ end
7
+
8
+ def to_hash
9
+ @config
10
+ end
11
+
12
+ private
13
+
14
+ def parse_config
15
+ raise ArgumentError, "No config file specified" if @config[:config].nil?
16
+
17
+ # automatically use a relative config path
18
+ if @config[:config][0] != "/"
19
+ @config[:config] = File.expand_path(@config[:config], @config[:pwd])
20
+ end
21
+
22
+ raise ArgumentError, "Invalid config file: #{@config[:config]}" unless File.exists? @config[:config]
23
+
24
+ yaml = YAML.load_file(@config[:config])
25
+ @config.merge!(yaml["alerting"])
26
+
27
+ raise ArgumentError, "Incomplete configuration" unless valid_config?
28
+ end
29
+
30
+ # TODO: check we have all the needed options
31
+ def valid_config?
32
+ true
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,79 @@
1
+ module RedisAlerting
2
+ class Engine
3
+ def initialize(config, redis)
4
+ @config = config
5
+ @active_set = "#{@config[:namespace]}.active"
6
+ @redis = redis
7
+ check_redis
8
+ end
9
+
10
+ def run
11
+ ns = @config[:namespace]
12
+ @config[:sources].each do |name, source|
13
+
14
+ # get the readings and alert ranges
15
+ min = @redis.get("#{ns}.#{name}.min").to_i
16
+ max = @redis.get("#{ns}.#{name}.max").to_i
17
+ value = @redis.get(source).to_i
18
+
19
+ # silently ignore
20
+ next if max.nil? or min.nil? or value.nil?
21
+
22
+ # check for alert conditions
23
+ if condition = out_of_range?(value, min, max)
24
+ add_alert_for(name, condition, value, min, max)
25
+ next
26
+ end
27
+
28
+ # if we got to here the alert conditions are not present anymore
29
+ # so we should remove the alert if it exists
30
+ remove_if_alert_exists name, value, min, max
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def out_of_range?(value, min, max)
37
+ return :high if value > max
38
+ return :low if value < min
39
+ return false
40
+ end
41
+
42
+ def add_alert_for(name, condition, value, min, max)
43
+ return if @redis.sismember(@active_set, name)
44
+ @redis.sadd @active_set, name
45
+
46
+ publish({
47
+ action: :add,
48
+ name: name,
49
+ condition: condition,
50
+ value: value,
51
+ min: min,
52
+ max: max
53
+ })
54
+ end
55
+
56
+ def remove_if_alert_exists(name, value, min, max)
57
+ return unless @redis.sismember(@active_set, name)
58
+ @redis.srem @active_set, name
59
+
60
+ publish({
61
+ action: :remove,
62
+ name: name,
63
+ value: value,
64
+ min: min,
65
+ max: max
66
+ })
67
+ end
68
+
69
+ def publish(message)
70
+ @redis.publish @config[:channel], message.to_json
71
+ puts "pushed message: #{message.inspect}"
72
+ end
73
+
74
+ def check_redis
75
+ raise ArgumentError, "Invalid Redis instance given" unless @redis.is_a? Redis
76
+ raise ArgumentError, "Could not connect to Redis" unless @redis.ping == "PONG"
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,3 @@
1
+ module RedisAlerting
2
+ VERSION = "1.1.1"
3
+ end
@@ -0,0 +1,20 @@
1
+ require "redis_alerting/version"
2
+ require "redis_alerting/engine"
3
+ require "redis_alerting/config"
4
+ require 'redis'
5
+ require 'yaml'
6
+ require 'json'
7
+
8
+ module RedisAlerting
9
+ class << self
10
+ def run(opts)
11
+ config = RedisAlerting::Config.new(opts).to_hash
12
+ engine = RedisAlerting::Engine.new(config, ::Redis.new)
13
+
14
+ loop do
15
+ engine.run
16
+ sleep config[:interval]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'redis_alerting/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "redis_alerting"
8
+ spec.version = RedisAlerting::VERSION
9
+ spec.authors = ["Robert McLeod"]
10
+ spec.email = ["robert@autogrow.com"]
11
+ spec.summary = %q{Checks redis for alert conditions}
12
+ spec.description = %q{Checks redis for alert conditions and adds keys to a set when a value is round to be out of range}
13
+ spec.homepage = "https://github.com/AutogrowSystems/RedisAlerting"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "redis"
22
+ spec.add_dependency "slop"
23
+ spec.add_dependency "daemons"
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.7"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ spec.add_development_dependency "rspec"
28
+ spec.add_development_dependency "pry"
29
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ describe RedisAlerting::Engine do
4
+ subject(:engine) { RedisAlerting::Engine.new(config, redis) }
5
+ let(:config) { test_config }
6
+ let(:redis) { ::Redis.new }
7
+
8
+ before(:all) do
9
+ @r = Redis.new
10
+ @key = "ph"
11
+ @source = test_config[:sources][:ph]
12
+ @namespace = test_config[:namespace]
13
+ end
14
+
15
+ before(:each) do
16
+ @r.set "#{@namespace}.ph.min", 4000
17
+ @r.set "#{@namespace}.ph.max", 9000
18
+ end
19
+
20
+ after(:each) { @r.del @namespace }
21
+
22
+ context "the reading is above the max limit" do
23
+ before(:each) { @r.set @source, 9100 }
24
+
25
+ it "should add the key to the set" do
26
+ engine.run
27
+ expect(redis.sismember(config[:namespace], "ph")).to be_truthy
28
+ end
29
+
30
+ it "should add the number of members in the set" do
31
+ expect {
32
+ engine.run
33
+ }.to change { redis.scard config[:namespace] }.by(1)
34
+ end
35
+ end
36
+
37
+ context "the reading is below the max limit" do
38
+ before(:each) { @r.set @source, 3900 }
39
+
40
+ it "should add the number of members in the set" do
41
+ expect {
42
+ engine.run
43
+ }.to change { redis.scard config[:namespace] }.by(1)
44
+ end
45
+
46
+ it "should add the key to the set" do
47
+ engine.run
48
+ expect(redis.sismember(config[:namespace], "ph")).to be_truthy
49
+ end
50
+ end
51
+
52
+ context "an alert exists" do
53
+ before(:each) { @r.sadd @namespace, "ph" }
54
+
55
+ context "and the reading comes back into range" do
56
+ before(:each) { @r.set @source, 6800 }
57
+
58
+ it "should remove the key from the set" do
59
+ engine.run
60
+ expect(redis.sismember(config[:namespace], "ph")).to be_falsey
61
+ end
62
+ end
63
+ end
64
+
65
+ end
@@ -0,0 +1,12 @@
1
+ require 'redis'
2
+ require 'pry'
3
+ require 'redis_alerting'
4
+
5
+ def test_config
6
+ {
7
+ namespace: "test.alerts",
8
+ sources: {
9
+ ph: "test.readings.ph"
10
+ }
11
+ }
12
+ end
metadata ADDED
@@ -0,0 +1,160 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: redis_alerting
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Robert McLeod
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redis
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: slop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: daemons
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.7'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '1.7'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Checks redis for alert conditions and adds keys to a set when a value
112
+ is round to be out of range
113
+ email:
114
+ - robert@autogrow.com
115
+ executables:
116
+ - redis-alerting
117
+ extensions: []
118
+ extra_rdoc_files: []
119
+ files:
120
+ - .gitignore
121
+ - Gemfile
122
+ - LICENSE.txt
123
+ - README.md
124
+ - Rakefile
125
+ - bin/redis-alerting
126
+ - etc/config.yml
127
+ - lib/redis_alerting.rb
128
+ - lib/redis_alerting/config.rb
129
+ - lib/redis_alerting/engine.rb
130
+ - lib/redis_alerting/version.rb
131
+ - redis_alerting.gemspec
132
+ - spec/lib/redis_alerting/engine_spec.rb
133
+ - spec/spec_helper.rb
134
+ homepage: https://github.com/AutogrowSystems/RedisAlerting
135
+ licenses:
136
+ - MIT
137
+ metadata: {}
138
+ post_install_message:
139
+ rdoc_options: []
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ! '>='
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ! '>='
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ requirements: []
153
+ rubyforge_project:
154
+ rubygems_version: 2.2.2
155
+ signing_key:
156
+ specification_version: 4
157
+ summary: Checks redis for alert conditions
158
+ test_files:
159
+ - spec/lib/redis_alerting/engine_spec.rb
160
+ - spec/spec_helper.rb