overwatch-collection 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ log/*.log
2
+ pkg
3
+ .yardoc
4
+ doc
data/.watchr ADDED
@@ -0,0 +1,18 @@
1
+ def run_spec(file)
2
+ unless File.exist?(file)
3
+ puts "#{file} does not exist"
4
+ return
5
+ end
6
+
7
+ puts "Running #{file}"
8
+ system "bundle exec rspec #{file}"
9
+ puts
10
+ end
11
+
12
+ watch("spec/.*/*_spec\.rb") do |match|
13
+ run_spec match[0]
14
+ end
15
+
16
+ watch("lib/(.*/.*)\.rb") do |match|
17
+ run_spec %{spec/#{match[1]}_spec.rb}
18
+ end
data/Gemfile ADDED
@@ -0,0 +1,33 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'dm-core', '>= 1.1.0'
4
+ gem 'dm-active_model', '>= 1.1.0'
5
+ gem 'dm-redis-adapter', '>= 0.4.0'
6
+ gem 'dm-serializer', '>= 1.1.0'
7
+ gem 'dm-timestamps', '>= 1.1.0'
8
+ gem 'dm-validations', '>= 1.1.0'
9
+ gem 'dm-types', '>= 1.1.0'
10
+ gem 'yajl-ruby', '>= 0.8.2', :require => 'yajl'
11
+ gem 'hashie', '>= 1.0.0'
12
+ gem 'rest-client', '>= 1.6.3'
13
+ gem 'sinatra', '>= 1.2.6'
14
+ gem 'sinatra-logger', '>= 0.1.1', :require => 'sinatra/logger'
15
+ gem 'activesupport', '>= 3.0.9', :require => 'active_support/all'
16
+ gem 'gli', '>= 1.3.2'
17
+
18
+ group :development, :test do
19
+ gem 'rspec', '>= 2.6.0'
20
+ gem 'rack-test', '>= 0.6.0', :require => 'rack/test'
21
+ gem 'spork', '>= 0.9.0.rc8'
22
+ gem 'watchr', '>= 0.7'
23
+ gem 'factory_girl', '>= 1.3.3'
24
+ gem 'json_spec', '>= 0.5.0'
25
+ gem "timecop", ">= 0.3.5"
26
+ end
27
+
28
+
29
+ group :doc do
30
+ gem 'yard'
31
+ # gem 'rdiscount'
32
+ gem 'yard-dm', '>= 0'
33
+ end
@@ -0,0 +1,104 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activemodel (3.0.9)
5
+ activesupport (= 3.0.9)
6
+ builder (~> 2.1.2)
7
+ i18n (~> 0.5.0)
8
+ activesupport (3.0.9)
9
+ addressable (2.2.6)
10
+ bcrypt-ruby (2.1.4)
11
+ builder (2.1.2)
12
+ diff-lcs (1.1.2)
13
+ dm-active_model (1.1.0)
14
+ activemodel (~> 3.0.4)
15
+ dm-core (~> 1.1.0)
16
+ dm-core (1.1.0)
17
+ addressable (~> 2.2.4)
18
+ dm-redis-adapter (0.4.0)
19
+ dm-core (>= 1.1.0)
20
+ dm-types (>= 1.1.0)
21
+ hiredis (~> 0.3.0)
22
+ redis (~> 2.2)
23
+ dm-serializer (1.1.0)
24
+ dm-core (~> 1.1.0)
25
+ fastercsv (~> 1.5.4)
26
+ json (~> 1.4.6)
27
+ dm-timestamps (1.1.0)
28
+ dm-core (~> 1.1.0)
29
+ dm-types (1.1.0)
30
+ bcrypt-ruby (~> 2.1.4)
31
+ dm-core (~> 1.1.0)
32
+ fastercsv (~> 1.5.4)
33
+ json (~> 1.4.6)
34
+ stringex (~> 1.2.0)
35
+ uuidtools (~> 2.1.2)
36
+ dm-validations (1.1.0)
37
+ dm-core (~> 1.1.0)
38
+ factory_girl (1.3.3)
39
+ fastercsv (1.5.4)
40
+ gli (1.3.2)
41
+ hashie (1.0.0)
42
+ hiredis (0.3.2)
43
+ i18n (0.5.0)
44
+ json (1.4.6)
45
+ json_spec (0.5.0)
46
+ json (~> 1.0)
47
+ rspec (~> 2.0)
48
+ mime-types (1.16)
49
+ rack (1.3.1)
50
+ rack-test (0.6.0)
51
+ rack (>= 1.0)
52
+ redis (2.2.1)
53
+ rest-client (1.6.3)
54
+ mime-types (>= 1.16)
55
+ rspec (2.6.0)
56
+ rspec-core (~> 2.6.0)
57
+ rspec-expectations (~> 2.6.0)
58
+ rspec-mocks (~> 2.6.0)
59
+ rspec-core (2.6.4)
60
+ rspec-expectations (2.6.0)
61
+ diff-lcs (~> 1.1.2)
62
+ rspec-mocks (2.6.0)
63
+ sinatra (1.2.6)
64
+ rack (~> 1.1)
65
+ tilt (< 2.0, >= 1.2.2)
66
+ sinatra-logger (0.1.1)
67
+ sinatra (>= 1.0)
68
+ spork (0.9.0.rc9)
69
+ stringex (1.2.2)
70
+ tilt (1.3.2)
71
+ timecop (0.3.5)
72
+ uuidtools (2.1.2)
73
+ watchr (0.7)
74
+ yajl-ruby (0.8.2)
75
+ yard (0.7.2)
76
+ yard-dm (0.1.1)
77
+
78
+ PLATFORMS
79
+ ruby
80
+
81
+ DEPENDENCIES
82
+ activesupport (>= 3.0.9)
83
+ dm-active_model (>= 1.1.0)
84
+ dm-core (>= 1.1.0)
85
+ dm-redis-adapter (>= 0.4.0)
86
+ dm-serializer (>= 1.1.0)
87
+ dm-timestamps (>= 1.1.0)
88
+ dm-types (>= 1.1.0)
89
+ dm-validations (>= 1.1.0)
90
+ factory_girl (>= 1.3.3)
91
+ gli (>= 1.3.2)
92
+ hashie (>= 1.0.0)
93
+ json_spec (>= 0.5.0)
94
+ rack-test (>= 0.6.0)
95
+ rest-client (>= 1.6.3)
96
+ rspec (>= 2.6.0)
97
+ sinatra (>= 1.2.6)
98
+ sinatra-logger (>= 0.1.1)
99
+ spork (>= 0.9.0.rc8)
100
+ timecop (>= 0.3.5)
101
+ watchr (>= 0.7)
102
+ yajl-ruby (>= 0.8.2)
103
+ yard
104
+ yard-dm
@@ -0,0 +1,26 @@
1
+
2
+
3
+
4
+ ## Features
5
+
6
+ ### Resources
7
+
8
+ One of the biggest differences
9
+ ### Snapshots
10
+
11
+ When a snapshot is recorded, it's socked away in its raw form so you can come back at a later time and review the exact state of a given resource without having to piece individual metrics together.
12
+
13
+ ### Metrics
14
+
15
+ Snapshots are also broken up and saved as individual attribute/value pairs, which enables you to track a particular attribute over a given period of time.
16
+
17
+
18
+ ## Roadmap
19
+
20
+ * Documentation. Like, seriously.
21
+ * Log to STDOUT like a proper service.
22
+ * Callbacks! Decide what happens after data gets collected.
23
+ * Taggable resources and snapshots
24
+ ## TODO
25
+
26
+ * Let config file be, er, configurable
@@ -0,0 +1,57 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require 'rake'
5
+
6
+ # require 'resque/tasks'
7
+ # require 'resque_scheduler/tasks'
8
+
9
+ require 'bundler/gem_tasks'
10
+
11
+ require 'rspec/core/rake_task'
12
+
13
+ desc "Run specs"
14
+ RSpec::Core::RakeTask.new do |task|
15
+ task.pattern = "spec/**/*_spec.rb"
16
+ end
17
+
18
+ desc "Run watchr"
19
+ task :watchr do
20
+ sh %{bundle exec watchr .watchr}
21
+ end
22
+
23
+ desc "Run spork"
24
+ task :spork do
25
+ sh %{bundle exec spork}
26
+ end
27
+
28
+
29
+ Bundler.require(:doc)
30
+ desc "Generate documentation"
31
+ YARD::Rake::YardocTask.new do |t|
32
+ t.files = [ 'lib/**/*.rb' ]
33
+ end
34
+
35
+ # namespace :resque do
36
+ # task :setup do
37
+ # require 'resque'
38
+ # require 'resque_scheduler'
39
+ # require 'resque/scheduler'
40
+
41
+ # Resque.redis = 'localhost:6379'
42
+
43
+ # Resque.schedule = YAML.load_file(
44
+ # File.join(File.expand_path(File.dirname(__FILE__)), 'config/schedule.yml')
45
+ # )
46
+
47
+ # end
48
+ # end
49
+
50
+ namespace :overwatch do
51
+ namespace :test do
52
+ task :snapshot => :environment do
53
+ a = Asset.first
54
+ a.snapshots.create(:raw_data => {:one => rand(10), :two => { :three => rand(10) }})
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'gli'
4
+
5
+ $: << File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
6
+
7
+ require 'overwatch/collection'
8
+
9
+ include GLI
10
+
11
+ version Overwatch::Collection::VERSION
12
+
13
+ desc 'Start overwatch-collection server'
14
+ command :start do |c|
15
+ c.desc 'Port to which to bind'
16
+ c.arg_name 'PORT'
17
+ c.default_value '9001'
18
+ c.flag [:p, :port]
19
+
20
+ c.desc 'Host on which to run'
21
+ c.arg_name 'HOST'
22
+ c.default_value 'localhost'
23
+ c.flag [:h, :host]
24
+
25
+ c.desc 'Config file'
26
+ c.arg_name 'CONFIG'
27
+ c.default_value File.expand_path(File.join(File.dirname(__FILE__), "/../config/overwatch.yml"))
28
+ c.flag [:c, :config]
29
+
30
+ c.action do |global_options, options, args|
31
+ Overwatch.config_path = options[:c]
32
+ Overwatch::Collection::Application.run! :host => options[:h], :port => options[:p].to_i
33
+ end
34
+
35
+ end
36
+
37
+
38
+ exit run(ARGV)
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.require(:default)
4
+
5
+ $: << File.join(File.dirname(__FILE__), "lib")
6
+
7
+ require 'overwatch/collection'
8
+
9
+ run Overwatch::Collection::Application
@@ -0,0 +1,6 @@
1
+ collection:
2
+ storage:
3
+ adapter: 'redis'
4
+ db: '10'
5
+ host: 'localhost'
6
+ port: '6379'
@@ -0,0 +1,5 @@
1
+ require "./version"
2
+
3
+ module .
4
+ # Your code goes here...
5
+ end
@@ -0,0 +1,42 @@
1
+ require 'bundler'
2
+ Bundler.require(:default)
3
+
4
+ require File.expand_path(File.join(File.dirname(__FILE__), "collection/version"))
5
+
6
+ require File.expand_path(File.join(File.dirname(__FILE__), "collection/attributes"))
7
+ require File.expand_path(File.join(File.dirname(__FILE__), "collection/models/resource"))
8
+ require File.expand_path(File.join(File.dirname(__FILE__), "collection/models/snapshot"))
9
+ require File.expand_path(File.join(File.dirname(__FILE__), "collection/application"))
10
+
11
+ module Overwatch
12
+ module Collection
13
+ end
14
+ class << self
15
+ def config_path=(path)
16
+ @config_path = path
17
+ end
18
+
19
+ def config_path
20
+ @config_path ||= File.expand_path(File.dirname(__FILE__)) + "/../../config/overwatch.yml"
21
+ end
22
+
23
+ def config
24
+ @config ||= {}
25
+ @config.merge!(YAML.load_file(config_path))
26
+ end
27
+ end
28
+
29
+ end
30
+
31
+ $redis = Redis.new(
32
+ :host => Overwatch.config['collection']['storage']['host'],
33
+ :port => Overwatch.config['collection']['storage']['port'],
34
+ :db => Overwatch.config['collection']['storage']['db']
35
+ )
36
+
37
+ DataMapper.setup(:default, {
38
+ :adapter => "redis",
39
+ :host => Overwatch.config['collection']['storage']['host'],
40
+ :port => Overwatch.config['collection']['storage']['port'],
41
+ :db => Overwatch.config['collection']['storage']['db']
42
+ })
@@ -0,0 +1,46 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/logger'
3
+ # require 'sinatra/reloader' if development?
4
+
5
+ module Overwatch
6
+ module Collection
7
+ class Application < Sinatra::Base
8
+ register Sinatra::Logger
9
+ configure do
10
+ set :app_file, __FILE__
11
+ set :root, File.expand_path(File.join(File.dirname(__FILE__), "../../../"))
12
+ set :logging, true
13
+ set :run, false
14
+ set :show_exceptions, false
15
+ set :server, %w[ thin mongrel webrick ]
16
+ set :raise_errors, false
17
+ set :logger_level, :info
18
+ end
19
+
20
+ [ :development, :test].each do |env|
21
+ configure(env) do
22
+ set :raise_errors, true
23
+ set :show_exceptions, true
24
+
25
+ end
26
+ end
27
+
28
+ error DataMapper::ObjectNotFoundError do
29
+ halt 404
30
+ end
31
+
32
+ before do
33
+ content_type "application/json"
34
+ end
35
+
36
+ configure(:production) do
37
+ # TODO: allow this variable to be configured
38
+ set :redis_url, ENV['REDIS_URL'] || 'redis://localhost:6379/0'
39
+ end
40
+
41
+ end
42
+ end
43
+ end
44
+
45
+ require File.expand_path(File.join(File.dirname(__FILE__), "routes/resource"))
46
+ require File.expand_path(File.join(File.dirname(__FILE__), "routes/snapshot"))
@@ -0,0 +1,136 @@
1
+ module Overwatch
2
+ module Collection
3
+ module Attributes
4
+
5
+ def attribute_keys
6
+ $redis.smembers("overwatch::resource:#{self.id}:attribute_keys").sort
7
+ end
8
+
9
+ def average(attr, options={})
10
+ function(:average, attr, options)
11
+ end
12
+
13
+ def min(attr, options={})
14
+ function(:min, attr, options)
15
+ end
16
+
17
+ def max(attr, options={})
18
+ function(:max, attr, options)
19
+ end
20
+
21
+ def median(attr, options={})
22
+ function(:median, attr, options)
23
+ end
24
+
25
+ def first(attr, options={})
26
+ function(:first, attr, options)
27
+ end
28
+
29
+ def last(attr, options={})
30
+ function(:last, attr, options)
31
+ end
32
+
33
+ def function(func, attr, options={})
34
+ case func
35
+ when :max
36
+ values_for(attr, options)[:data].max
37
+ when :min
38
+ values_for(attr, options)[:data].min
39
+ when :average
40
+ values = values_for(attr, options)[:data]
41
+ if is_a_number?(values.first)
42
+ values.map!(&:to_f)
43
+ values.inject(:+) / values.size
44
+ else
45
+ values.first
46
+ end
47
+ when :median
48
+ values = values_for(attr, options)[:data].sort
49
+ mid = values.size / 2
50
+ values[mid]
51
+ when :first
52
+ value = $redis.zrangebyscore("resource:#{self.id}:#{attr}", options[:start_at], options[:end_at])[0]
53
+ value.split(":")[1] rescue nil
54
+ when :last
55
+ value = $redis.zrevrangebyscore("resource:#{self.id}:#{attr}", options[:end_at], options[:start_at])[0]
56
+ value.split(":")[1] rescue nil
57
+ end
58
+ end
59
+
60
+ def values_for(attr, options={})
61
+ raise ArgumentError, "attribute does not exist" unless attribute_keys.include?(attr)
62
+ start_at = options[:start_at] || "-inf" #(Time.now - 1.day).to_i.to_s
63
+ end_at = options[:end_at] || "+inf"
64
+ interval = options[:interval]
65
+ values = $redis.zrangebyscore("overwatch::resource:#{self.id}:#{attr}", start_at, end_at)
66
+ values.map! do |v|
67
+ val = v.split(":")[1]
68
+ is_a_number?(val) ? val.to_f : val
69
+ end
70
+ values.compact!
71
+ values = case interval
72
+ when 'hour'
73
+ values
74
+ when 'day'
75
+ values.each_slice(60).map { |s| s[0] }
76
+ when 'week'
77
+ values.each_slice(100).map { |s| s[0] }
78
+ when 'month'
79
+ values.each_slice(432).map { |s| s[0] }
80
+ else
81
+ values
82
+ end
83
+ { :name => attr, :data => values }#, :start_at => start_at, :end_at => end_at }
84
+ end
85
+
86
+ def is_a_number?(str)
87
+ str.to_s.match(/\A[+-]?\d+?(\.\d+)?\Z/) == nil ? false : true
88
+ end
89
+
90
+ def values_with_dates_for(attr, options={})
91
+ raise ArgumentError, "attribute does not exist" unless attribute_keys.include?(attr)
92
+ start_at = options[:start_at] || "-inf"
93
+ end_at = options[:end_at] || "+inf"
94
+ interval = options[:interval]
95
+ values = $redis.zrangebyscore("overwatch::resource:#{self.id}:#{attr}", start_at, end_at)
96
+ values.map! do |v|
97
+ val = v.split(":")
98
+ [ val[0].to_i * 1000, is_a_number?(val[1]) ? val[1].to_f : val[1] ]
99
+ end
100
+ values.compact!
101
+ values = case interval
102
+ when 'hour'
103
+ values
104
+ when 'day'
105
+ values.each_slice(60).map { |s| s[0] }
106
+ when 'week'
107
+ values.each_slice(100).map { |s| s[0] }
108
+ when 'month'
109
+ values.each_slice(432).map { |s| s[0] }
110
+ else
111
+ values
112
+ end
113
+ { :name => attr, :data => values } #, :start_at => start_at, :end_at => end_at }
114
+ end
115
+
116
+ def from_dotted_hash(source=self.attribute_keys)
117
+ source.map do |main_value|
118
+ main_value.to_s.split(".").reverse.inject(main_value) do |value, key|
119
+ {key.to_sym => value}
120
+ end
121
+ end
122
+ end
123
+
124
+
125
+ def top_level_attributes
126
+ self.attribute_keys.map do |key|
127
+ key.split(".")[0]
128
+ end.uniq
129
+ end
130
+
131
+ def sub_attributes(sub_attr)
132
+ self.attribute_keys.select {|k| k =~ /^#{sub_attr}/ }
133
+ end
134
+ end
135
+ end
136
+ end