check 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|