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 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,11 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require 'bundler/setup'
4
+ require 'bundler/gem_tasks'
5
+ require 'rake/testtask'
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.pattern = 'test/**/*_test.rb'
9
+ end
10
+
11
+ task :default => [:test]
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'benchmark/ips'
5
+ require 'pry'
6
+
7
+ require_relative '../test/support/redis'
8
+
9
+ at_exit { stop_test_redis_server }
@@ -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
@@ -0,0 +1,4 @@
1
+ require'bundler/setup'
2
+ require 'check/api'
3
+
4
+ run Check::API
@@ -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