check 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +19 -0
- data/LICENSE +22 -0
- data/README.md +75 -0
- data/Rakefile +11 -0
- data/benchmarks/benchmark.rb +9 -0
- data/benchmarks/metric_check.rb +51 -0
- data/benchmarks/metric_crud.rb +43 -0
- data/check.gemspec +29 -0
- data/examples/config.ru +4 -0
- data/examples/example.rb +14 -0
- data/examples/metric_check.rb +36 -0
- data/examples/metric_crud.rb +21 -0
- data/examples/unicorn.conf.rb +41 -0
- data/lib/check.rb +17 -0
- data/lib/check/api.rb +50 -0
- data/lib/check/api/metrics.rb +64 -0
- data/lib/check/metric.rb +225 -0
- data/lib/check/notifications.rb +65 -0
- data/test/check/api/metrics_test.rb +109 -0
- data/test/check/metric_test.rb +255 -0
- data/test/check_test.rb +12 -0
- data/test/test_helper.rb +24 -0
- metadata +190 -0
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
ruby "1.9.3"
|
2
|
+
source 'https://rubygems.org'
|
3
|
+
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
group :development do
|
7
|
+
gem 'awesome_print', '~> 1.1.0'
|
8
|
+
gem 'benchmark-ips', '~> 1.2.0'
|
9
|
+
gem 'pry', '~> 0.9.10'
|
10
|
+
gem 'rake', '~> 0.9.2.2'
|
11
|
+
end
|
12
|
+
|
13
|
+
group :test do
|
14
|
+
gem 'guard-minitest', '~> 0.5.0'
|
15
|
+
gem 'minitest', '~> 4.2.0'
|
16
|
+
gem 'rack-test', '~> 0.6.2'
|
17
|
+
gem 'redis_gun', '~> 0.1.0'
|
18
|
+
gem 'turn', '~> 0.9.6'
|
19
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) Gerhard Lazu
|
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,75 @@
|
|
1
|
+
# Check
|
2
|
+
|
3
|
+
[![travis][1]][2]
|
4
|
+
|
5
|
+
Redis backed service for monitoring metric data streams against
|
6
|
+
pre-defined thresholds.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
gem 'check'
|
13
|
+
|
14
|
+
And then run:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install check
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
Check the examples directory. To run a specific example:
|
25
|
+
|
26
|
+
$ ruby examples/metric_check.rb
|
27
|
+
|
28
|
+
NB: you will need to have all gems in the `development` group installed.
|
29
|
+
|
30
|
+
## Benchmarks
|
31
|
+
|
32
|
+
Check the benchmarks directory. To run a specific benchmark:
|
33
|
+
|
34
|
+
$ ruby benchmarks/metric_check.rb
|
35
|
+
|
36
|
+
NB: you will need to have all gems in `development` group installed.
|
37
|
+
|
38
|
+
## API
|
39
|
+
|
40
|
+
The gem comes with a self-contained API. It's powered by grape and
|
41
|
+
it includes a config.ru and unicorn.conf (check the examples folder).
|
42
|
+
|
43
|
+
Unicorn is **not** declared as a dependency, feel free to choose
|
44
|
+
whichever ruby web server you prefer in the service implementing this
|
45
|
+
gem.
|
46
|
+
|
47
|
+
If you have the gem repository cloned and want to test out the API:
|
48
|
+
|
49
|
+
unicorn -c examples/unicorn.conf.rb examples/config.ru
|
50
|
+
|
51
|
+
## Notifications
|
52
|
+
|
53
|
+
Check uses redis pub/sub to notify of new positives. There is a
|
54
|
+
`Check::Notifications` class which should make it easy to set up and
|
55
|
+
configure your own subscriber. It the simplest form, it looks like this:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
require 'check/notifications'
|
59
|
+
|
60
|
+
Check::Notifications.new
|
61
|
+
```
|
62
|
+
|
63
|
+
The default notifications subscriber is highly customizable, but if this
|
64
|
+
isn't enough, feel free to use it as an example for rolling your own.
|
65
|
+
|
66
|
+
## Contributing
|
67
|
+
|
68
|
+
1. Fork it
|
69
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
70
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
71
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
72
|
+
5. Create new Pull Request
|
73
|
+
|
74
|
+
[1]: https://secure.travis-ci.org/gosquared/check.png
|
75
|
+
[2]: http://travis-ci.org/gosquared/check
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative 'benchmark'
|
4
|
+
require_relative '../lib/check/metric'
|
5
|
+
|
6
|
+
metric1 = Check::Metric.new(
|
7
|
+
name: "metric1",
|
8
|
+
lower: 5,
|
9
|
+
matches_for_positive: 1000,
|
10
|
+
suspend_after_positives: 10
|
11
|
+
).save
|
12
|
+
|
13
|
+
metric2 = Check::Metric.new(
|
14
|
+
name: "metric2",
|
15
|
+
lower: 5,
|
16
|
+
matches_for_positive: 10,
|
17
|
+
suspend_after_positives: 2
|
18
|
+
).save
|
19
|
+
|
20
|
+
metric3 = Check::Metric.new(
|
21
|
+
name: "metric3"
|
22
|
+
).save
|
23
|
+
metric3.disable!
|
24
|
+
|
25
|
+
below_lower = (0..4).to_a
|
26
|
+
|
27
|
+
Benchmark.ips(1) do |x|
|
28
|
+
x.report("Check always") do
|
29
|
+
Check::Metric.find(name: "metric1").check(
|
30
|
+
value: below_lower.sample
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
x.report("Check suspended") do
|
35
|
+
Check::Metric.find(name: "metric2").check(
|
36
|
+
value: below_lower.sample
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
x.report("Check disabled") do
|
41
|
+
Check::Metric.find(name: "metric3").check(
|
42
|
+
value: below_lower.sample
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
x.report("Check inexistent") do
|
47
|
+
Check::Metric.find(name: "metric4").check(
|
48
|
+
value: below_lower.sample
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative 'benchmark'
|
4
|
+
require_relative '../lib/check/metric'
|
5
|
+
|
6
|
+
range = (1..10000).to_a
|
7
|
+
10000.times do |i|
|
8
|
+
Check::Metric.new(name: "metric-#{i}", lower: i).save
|
9
|
+
Check::Metric.new(name: "metric-delete", lower: range.sample).save
|
10
|
+
end
|
11
|
+
|
12
|
+
Benchmark.ips(1) do |x|
|
13
|
+
x.report("Create unique") do
|
14
|
+
i = 0
|
15
|
+
Check::Metric.new(
|
16
|
+
name: "metric#{i}",
|
17
|
+
lower: 10,
|
18
|
+
upper: 100,
|
19
|
+
over_seconds: 120
|
20
|
+
).save
|
21
|
+
i += 1
|
22
|
+
end
|
23
|
+
|
24
|
+
x.report("Create similar") do
|
25
|
+
i = 0
|
26
|
+
Check::Metric.new(
|
27
|
+
name: "metric",
|
28
|
+
lower: 10,
|
29
|
+
upper: 100,
|
30
|
+
over_seconds: 120
|
31
|
+
).save
|
32
|
+
i += 1
|
33
|
+
end
|
34
|
+
|
35
|
+
x.report("Delete unique") do
|
36
|
+
Check::Metric.new(name: "metric-delete", lower: range.sample).delete
|
37
|
+
end
|
38
|
+
|
39
|
+
x.report("Delete similar") do
|
40
|
+
sample = range.sample
|
41
|
+
Check::Metric.new(name: "metric-#{sample}", lower: sample).delete
|
42
|
+
end
|
43
|
+
end
|
data/check.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env gem build
|
2
|
+
# -*- encoding: utf-8 -*-
|
3
|
+
lib = File.expand_path('../lib/', __FILE__)
|
4
|
+
$:.unshift lib unless $:.include?(lib)
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'check'
|
8
|
+
gem.version = '0.2.0'
|
9
|
+
gem.authors = ['Gerhard Lazu']
|
10
|
+
gem.email = ['gerhard@lazu.co.uk']
|
11
|
+
gem.description = 'Redis backed service for monitoring metric data streams against pre-defined thresholds'
|
12
|
+
gem.summary = 'Data stream monitor'
|
13
|
+
gem.homepage = 'https://github.com/gosquared/osprey'
|
14
|
+
|
15
|
+
gem.files = Dir['lib/**/*', 'examples/**/*', 'benchmarks/**/*', 'Gemfile', 'check.gemspec', 'Rakefile', 'README.md', 'LICENSE']
|
16
|
+
gem.executables = Dir['bin/*']
|
17
|
+
gem.test_files = Dir['test/**/*']
|
18
|
+
gem.require_paths = ['lib']
|
19
|
+
|
20
|
+
gem.add_runtime_dependency 'hashr', '~> 0.0.21'
|
21
|
+
gem.add_runtime_dependency 'hiredis', '~> 0.4.5'
|
22
|
+
gem.add_runtime_dependency 'msgpack', '~> 0.4.7'
|
23
|
+
gem.add_runtime_dependency 'redis', '~> 3.0.2'
|
24
|
+
gem.add_runtime_dependency 'redis-objects', '~> 0.6.1'
|
25
|
+
|
26
|
+
## API
|
27
|
+
gem.add_runtime_dependency 'grape', '~> 0.2.2'
|
28
|
+
gem.add_runtime_dependency 'grape-swagger', '~> 0.3.0'
|
29
|
+
end
|
data/examples/config.ru
ADDED
data/examples/example.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'awesome_print'
|
5
|
+
require 'pry'
|
6
|
+
|
7
|
+
require_relative '../test/support/redis'
|
8
|
+
|
9
|
+
def exemplify(description, object)
|
10
|
+
puts "\n::: #{description} ".ljust(70, ":::")
|
11
|
+
ap(object, indent: -2)
|
12
|
+
end
|
13
|
+
|
14
|
+
at_exit { stop_test_redis_server }
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative 'example'
|
4
|
+
require_relative '../lib/check/metric'
|
5
|
+
|
6
|
+
metric = Check::Metric.new(
|
7
|
+
name: "metric1",
|
8
|
+
lower: 5,
|
9
|
+
upper: 10,
|
10
|
+
matches_for_positive: 2,
|
11
|
+
suspend_after_positives: 1,
|
12
|
+
suspend_for_seconds: 60
|
13
|
+
).save
|
14
|
+
|
15
|
+
exemplify("Default metric check matches", metric.matches.values)
|
16
|
+
|
17
|
+
metric.check(value: 4)
|
18
|
+
exemplify("Metric check matches after first match", metric.matches.values)
|
19
|
+
exemplify("Is this metric checking suspended?", metric.suspended?)
|
20
|
+
|
21
|
+
metric.check(value: 3)
|
22
|
+
exemplify("Metric check matches after second match", metric.matches.values)
|
23
|
+
exemplify("Metric check positives after second match", metric.positives.values)
|
24
|
+
exemplify("Is this metric checking suspended?", metric.suspended?)
|
25
|
+
|
26
|
+
metric.check(value: 2)
|
27
|
+
exemplify("As the metric check is suspended, new matches will be ignored", metric.matches.values)
|
28
|
+
exemplify("Deleting all positives will unsuspend it", metric.delete_positives)
|
29
|
+
|
30
|
+
exemplify("Metric checking manually disabled", metric.disable!)
|
31
|
+
metric.check(value: 1)
|
32
|
+
exemplify("As the metric check is disabled, new matches will be ignored", metric.matches.values)
|
33
|
+
|
34
|
+
exemplify("Metric checking manually enabled", metric.enable!)
|
35
|
+
metric.check(value: 0)
|
36
|
+
exemplify("New matches no longer ignored", metric.matches.values)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'example'
|
2
|
+
require_relative '../lib/check/metric'
|
3
|
+
|
4
|
+
metric_check = Check::Metric.new(name: "metric1", lower: 10, upper: 100, over_seconds: 120).save
|
5
|
+
exemplify("Check::Metric from hash", metric_check)
|
6
|
+
|
7
|
+
metric_check = Check::Metric.new(name: metric_check[:name])
|
8
|
+
metric_check.lower = 100
|
9
|
+
metric_check.upper = 1000
|
10
|
+
metric_check.over_seconds = 3600
|
11
|
+
metric_check.matches_for_positive = 1
|
12
|
+
metric_check.save
|
13
|
+
exemplify("Check::Metric from attributes", metric_check)
|
14
|
+
|
15
|
+
exemplify("Creating a new metric with the same name as the existing one will group them together", metric_check.similar)
|
16
|
+
|
17
|
+
metric_check.delete
|
18
|
+
exemplify("Similar metrics after metric.delete", metric_check.similar)
|
19
|
+
|
20
|
+
Check::Metric.delete_all(metric_check.name)
|
21
|
+
exemplify("Similar metrics after Metric.delete_all(name)", metric_check.similar)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
|
2
|
+
# documentation.
|
3
|
+
|
4
|
+
# Use at least one worker per core if you're on a dedicated server,
|
5
|
+
# more will usually help for _short_ waits on databases/caches.
|
6
|
+
worker_processes ENV.fetch('API_INSTANCES') { 1 }
|
7
|
+
|
8
|
+
# listen on both a Unix domain socket and a TCP port,
|
9
|
+
#
|
10
|
+
# The backlog is for the listen() syscall.
|
11
|
+
# Some operating systems allow negative values here to specify the
|
12
|
+
# maximum allowable value. In most cases, this number is only
|
13
|
+
# recommendation and there are other OS-specific tunables and variables
|
14
|
+
# that can affect this number. See the listen(2) syscall documentation
|
15
|
+
# of your OS for the exact semantics of this.
|
16
|
+
# The shorter backlog ensures quicker failover when busy, and helps the
|
17
|
+
# load balancer spread requests evenly.
|
18
|
+
listen ENV.fetch('PORT') { 9000 }, backlog: ENV.fetch('TCP_BACKLOG') { 128 }.to_i
|
19
|
+
listen ENV.fetch('SOCKET') { '/tmp/check_api.sock' }, backlog: ENV.fetch('UNIX_BACKLOG') { 128 }.to_i
|
20
|
+
|
21
|
+
# Sets the timeout of worker processes to seconds. Workers handling the
|
22
|
+
# request/app.call/response cycle taking longer than this time period
|
23
|
+
# will be forcibly killed (via SIGKILL). This timeout is enforced by the
|
24
|
+
# master process itself and not subject to the scheduling limitations by
|
25
|
+
# the worker process. Due the low-complexity, low-overhead
|
26
|
+
# implementation, timeouts of less than 3.0 seconds can be considered
|
27
|
+
# inaccurate and unsafe.
|
28
|
+
# For running Unicorn behind nginx, it is recommended to set
|
29
|
+
# "fail_timeout=0" for in your nginx configuration like this to have
|
30
|
+
# nginx always retry backends that may have had workers SIGKILL-ed due
|
31
|
+
# to timeouts.
|
32
|
+
timeout ENV.fetch('API_TIMEOUT') { 30 }
|
33
|
+
|
34
|
+
# PID of the unicorn master process
|
35
|
+
pid ENV.fetch('API_PID') { '/tmp/check_api.pid' }
|
36
|
+
|
37
|
+
# By default, the Unicorn logger will write to stderr.
|
38
|
+
# Additionally, some applications/frameworks log to stderr or stdout,
|
39
|
+
# so prevent them from going to /dev/null when daemonized here:
|
40
|
+
stderr_path ENV.fetch('API_STDERR') { $STDERR }
|
41
|
+
stdout_path ENV.fetch('API_STDOUT') { $STDOUT }
|
data/lib/check.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'redis'
|
3
|
+
|
4
|
+
module Check
|
5
|
+
extend self
|
6
|
+
|
7
|
+
# Can be either redis:// or unix://
|
8
|
+
REDIS_URI = ENV.fetch('REDIS_URI', "redis://localhost:6379")
|
9
|
+
REDIS_DB = ENV.fetch('REDIS_DB', 0).to_i
|
10
|
+
REDIS_NOTIFICATIONS = ENV.fetch('REDIS_NOTIFICATIONS', "check_notifications")
|
11
|
+
|
12
|
+
Redis.current = Redis.new(
|
13
|
+
url: Check::REDIS_URI,
|
14
|
+
db: Check::REDIS_DB,
|
15
|
+
driver: :hiredis
|
16
|
+
)
|
17
|
+
end
|
data/lib/check/api.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'grape'
|
2
|
+
#require 'grape-swagger'
|
3
|
+
|
4
|
+
require_relative 'api/metrics'
|
5
|
+
|
6
|
+
module Check
|
7
|
+
class API < Grape::API
|
8
|
+
default_format :json
|
9
|
+
error_format :json
|
10
|
+
format :json
|
11
|
+
|
12
|
+
KEY = ENV.fetch('API_KEY') { false }
|
13
|
+
|
14
|
+
helpers do
|
15
|
+
def authorize!
|
16
|
+
if API::KEY
|
17
|
+
unless params[:key] == API::KEY
|
18
|
+
throw :error,
|
19
|
+
:message => { :errors => { :key => ['invalid'] } },
|
20
|
+
:status => 401
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
before { authorize! }
|
27
|
+
|
28
|
+
mount Metrics
|
29
|
+
# add_swagger_documentation(mount_path: '/swagger')
|
30
|
+
#
|
31
|
+
# TODO: would have been very nice to get this working, but grape-swagger
|
32
|
+
# looks broken and I can't look into a fix for the initial release.
|
33
|
+
#
|
34
|
+
# 1. All REST methods get .json format hardcoded
|
35
|
+
#
|
36
|
+
# 2. When testing, all requests get sent with the OPTIONS method, even
|
37
|
+
# though they are clearly defined as POST, DELETE etc.
|
38
|
+
#
|
39
|
+
# In the meantime, let's expose all routes
|
40
|
+
resources :routes do
|
41
|
+
# This should have been the default route, but grape requires a fix for this:
|
42
|
+
# https://github.com/intridea/grape/issues/86
|
43
|
+
desc 'The current page, showing details about all available routes'
|
44
|
+
get do
|
45
|
+
Metrics.routes.map { |route| route.instance_variable_get(:@options) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|