drone 1.0.2 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ *.gem
2
+ .bundle
3
+ .yardoc
4
+ Gemfile.lock
5
+ pkg/*
6
+ coverage/
7
+ gems/
8
+ doc/
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.2
@@ -0,0 +1 @@
1
+ --no-private lib/**/*.rb - README.md LICENSE
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in drone.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011-2011 Julien Ammous
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,162 @@
1
+
2
+ # What is this ?
3
+
4
+ Drone is a monitoring library designed to collect data from your application and export them
5
+ to virtually any monitoring tool.
6
+ Its core is heavily based on the impressive works of Coda Hale on the metrics java library.
7
+
8
+ A fully working example is included in examples/simple, to run it
9
+ (I suppose you already cloned the repository and have a prompt at the root of it):
10
+
11
+ gem install bundler
12
+ bundle
13
+ ruby examples/simple.rb
14
+
15
+ The example will output the collected statistics directly on the console every second.
16
+
17
+ # Supported Runtimes
18
+
19
+ - MRI 1.8.7+
20
+ - Rubinius 1.2.2+
21
+
22
+
23
+ # Status
24
+ - Most of the features I wanted in are (see below for usage examples):
25
+ - timing method calls
26
+ - method calls rate
27
+ - counters
28
+ - gauges
29
+
30
+ - Good test coverage
31
+
32
+ The gem was created for a specific need and is currently used in preproduction environment,
33
+ no major bugs until now.
34
+
35
+
36
+ # How is it done
37
+
38
+ The library is split in different parts
39
+
40
+ - the core:<br/>
41
+ it contains all the API used to declare which data to collect and how as well as the storage for them
42
+
43
+ - the metrics:<br/>
44
+ that is all the metrics type the library know.
45
+
46
+ - the interfaces:<br/>
47
+ those are the parts which will decides how the stored data are made available.
48
+
49
+ - the schedulers:</br>
50
+ this is where the timers are scheduled, currently there is only one scheduler: eventmachine
51
+
52
+ - the storage:<br/>
53
+ this part decides where the actual data for the metrics are stored, the default is to store them
54
+ in memory but other possible options are: redis, memcached, etc...
55
+ The goal for external storage is to allow concurrent applications to share the same metrics, an
56
+ immediate example of such application is a rails application ran under passenger or any other spawner
57
+
58
+ ## Constraints
59
+
60
+ - the name of each metric can be formatted how it pleases you (note that output interfaces may expect some format)
61
+ but the name is expected to be unique or you could end up reusing the same metric without wanting it.
62
+ (this only applies to monitor_time and monitor_rate helpers but could apply anywhere else as needed)
63
+
64
+ # Usage
65
+
66
+ I try to keep things as simple as possible, there is currently two ways to use
67
+ this library:
68
+
69
+ - the first one is to just instantiate metrics by hand and use them directly
70
+
71
+ ``` ruby
72
+ require 'drone'
73
+ Drone::init_drone()
74
+ @counter = Drone::Metris::Counter.new('my_counter')
75
+
76
+ def some_method
77
+ @counter.inc()
78
+ end
79
+ ```
80
+
81
+ - the other way is to instrument a class:
82
+
83
+ ``` ruby
84
+ require 'drone'
85
+ Drone::init_drone()
86
+
87
+ class User
88
+ include Drone::Monitoring
89
+
90
+ monitor_rate("users/new")
91
+ def initialize(login, pass); end
92
+
93
+ monitor_time("users/rename")
94
+ def rename(new_login); end
95
+
96
+ end
97
+ ```
98
+
99
+ This code will create three metrics:
100
+ - "users/new" : how many users are created each second
101
+ - "users/rename" : how much time renaming a user takes and how many users are renamed
102
+ each second
103
+
104
+
105
+ Once you have your data you need to add a way to serve them, each lives in a separate
106
+ gem to limit the core's dependencies so the only one in core is:
107
+
108
+ - console output (puts), mainly for debug:
109
+
110
+ ``` ruby
111
+ require 'drone'
112
+ Drone::init_drone()
113
+ Drone::add_output(:console, 1)
114
+ ```
115
+
116
+ The values will be printed on the console at the inter
117
+
118
+ The others output are available in their own gems:
119
+
120
+ - drone_json:<br/>
121
+ The stats are served by a thin server in json
122
+
123
+ - drone_collectd:<br/>
124
+ The stats are send to a collectd daemon.
125
+
126
+ # Goals
127
+
128
+ My goal is to be able to serve stats efficiently from any ruby 1.9 application built
129
+ on top of eventmachine and fibers but I built the library to allow non eventmachine uses too, for
130
+ now the only part where eventmachine is required is the scheduler.
131
+
132
+ Implementing a scheduler based on a background Thread is possible but before that work
133
+ needs to be done to ensure thread safety, Actually the library is not thread safe.
134
+
135
+ if someone wants to implements it I am not against it but I prefer it to be added as an
136
+ optional part instead of in the core. There should not be any problem to implements it
137
+ in an includable module not included as default (it may requires some modifications in the core):
138
+
139
+ ``` ruby
140
+ require 'drone'
141
+ require 'drone/threadsafe'
142
+
143
+ # [...]
144
+ ```
145
+
146
+ # Development
147
+
148
+ Installing the development environment is pretty simple thanks to bundler:
149
+
150
+ gem install bundler
151
+ bundle
152
+
153
+ ## Running specs
154
+
155
+ The specs are written with bacon, mocha and em-spec, they can be ran with:
156
+
157
+ rake spec
158
+
159
+ ## Build the doc
160
+ You will need the gems: yard and bluecloth and then run:
161
+
162
+ rake doc
@@ -0,0 +1,49 @@
1
+ require 'bundler'
2
+
3
+ # Bundler::GemHelper.install_tasks
4
+
5
+ GEM_FOLDER = File.expand_path('../pkg', __FILE__)
6
+
7
+
8
+ def build_gem(path)
9
+ dir = File.dirname(path)
10
+ f = File.basename(path)
11
+
12
+ sh "cd #{dir} && gem build #{f} && mv *.gem #{GEM_FOLDER}/"
13
+ end
14
+
15
+ task :build do
16
+ # drone
17
+ build_gem(File.expand_path('../drone.gemspec', __FILE__))
18
+
19
+ # extensions
20
+ Dir["extensions/**/*.gemspec"].each do |path|
21
+ build_gem(path)
22
+ end
23
+
24
+ end
25
+
26
+
27
+ # task :release do
28
+ # Dir.chdir(File.expand_path('../pkg', __FILE__)) do
29
+ # %()
30
+ #
31
+ # end
32
+ # end
33
+
34
+ task :spec do
35
+ ENV['COVERAGE'] = "1"
36
+ Dir.chdir( File.dirname(__FILE__) ) do
37
+ Dir["specs/**/*_spec.rb"].each do |path|
38
+ load(path)
39
+ end
40
+ end
41
+ end
42
+
43
+ begin
44
+ require 'yard'
45
+ require 'bluecloth'
46
+ YARD::Rake::YardocTask.new(:doc)
47
+ rescue LoadError
48
+
49
+ end
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "drone/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "drone"
7
+ s.version = Drone::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Julien Ammous"]
10
+ s.email = []
11
+ s.homepage = ""
12
+ s.summary = %q{Drone is a monitoring library}
13
+ s.description = %q{Drone is a monitoring library based on the metrics java library}
14
+
15
+ s.rubyforge_project = "drone"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency("eventmachine", "~> 0.12.10")
23
+
24
+ s.add_development_dependency("mocha")
25
+ s.add_development_dependency("bacon")
26
+ s.add_development_dependency("schmurfy-em-spec")
27
+ s.add_development_dependency("delorean")
28
+ s.add_development_dependency("simplecov")
29
+ end
@@ -0,0 +1,50 @@
1
+ require File.expand_path('../common', __FILE__)
2
+ init_environment()
3
+
4
+ require 'fiber'
5
+
6
+ Drone::init_drone()
7
+ Drone::register_gauge("cpu:0/user"){ rand(200) }
8
+
9
+ class User
10
+ include Drone::Monitoring
11
+
12
+ def initialize(name)
13
+ @name = name
14
+ end
15
+
16
+ monitor_rate("users:rename:rate")
17
+ def rename(new_name)
18
+ @name = new_name
19
+ end
20
+
21
+ monitor_time("users:do_something:time")
22
+ monitor_rate("users:do_something:rate")
23
+ def do_something
24
+ # fb = Fiber.current
25
+ # EM::add_timer(1){ fb.resume() }
26
+ # Fiber.yield
27
+ end
28
+ end
29
+
30
+ Drone::add_output(:console, 1)
31
+
32
+ EM::run do
33
+ Drone::start_monitoring()
34
+
35
+ counter1 = Drone::register_counter("something_counted")
36
+ counter1.increment()
37
+
38
+ a = User.new("bob")
39
+
40
+ EM::add_periodic_timer(2) do
41
+ rand(100).times{|n| a.rename("user#{n}") }
42
+ counter1.increment()
43
+ end
44
+
45
+ EM::add_periodic_timer(2) do
46
+ Fiber.new do
47
+ a.do_something()
48
+ end.resume
49
+ end
50
+ end
@@ -0,0 +1,23 @@
1
+
2
+ module Drone
3
+ def self.require_lib(path)
4
+ require File.expand_path("../#{path}", __FILE__)
5
+ end
6
+
7
+ require_lib("drone/version")
8
+
9
+ require_lib("drone/monitoring")
10
+
11
+ # Schedulers
12
+ require_lib("drone/schedulers/eventmachine")
13
+
14
+ # Metrics
15
+ require_lib("drone/metrics/counter")
16
+ require_lib("drone/metrics/gauge")
17
+ require_lib("drone/metrics/histogram")
18
+ require_lib("drone/metrics/meter")
19
+ require_lib("drone/metrics/timer")
20
+
21
+ # Output
22
+ require_lib("drone/interfaces/console")
23
+ end
@@ -0,0 +1,141 @@
1
+ require 'forwardable'
2
+
3
+
4
+ require File.expand_path('../schedulers/eventmachine', __FILE__)
5
+
6
+ require File.expand_path('../storage/memory', __FILE__)
7
+
8
+ module Drone
9
+ ##
10
+ # This module contains all the metrics you can use to collect data
11
+ #
12
+ module Metrics; end
13
+
14
+
15
+ ##
16
+ # This module contains all the interfaces to the outside world,
17
+ # they are the only way to communicate with external applications
18
+ #
19
+ module Interface; end
20
+
21
+
22
+ ##
23
+ # This module contains the class used for scheduling timers
24
+ #
25
+ module Schedulers; end
26
+
27
+
28
+ ##
29
+ # This module contains the class used for storage,
30
+ # they determine where the metric's data are stored
31
+ #
32
+ module Storage; end
33
+
34
+ class <<self
35
+ extend Forwardable
36
+
37
+ def init_drone(scheduler = Schedulers::EMScheduler, storage = Storage::Memory.new)
38
+ @metrics = []
39
+ @scheduler = scheduler
40
+ @storage = storage
41
+ @monitored_classes = []
42
+ @output_modules = []
43
+ end
44
+
45
+ ##
46
+ # Start monitoring.
47
+ # This method needs to be called when the timers can be started
48
+ # In the case of eventmachine scheduler it needs to be called
49
+ # in the EM::run block
50
+ #
51
+ def start_monitoring
52
+ @scheduler.start()
53
+ end
54
+
55
+ def each_metric
56
+ raise "Block expected" unless block_given?
57
+ @metrics.each{|m| yield(m) }
58
+ end
59
+
60
+
61
+ ##
62
+ # Fetch a metric by its name
63
+ #
64
+ # @param [String] name The mtric's name
65
+ #
66
+ def find_metric(name)
67
+ @metrics.detect{|m| m.name == name }
68
+ end
69
+
70
+ ##
71
+ # Instantiate an output module.
72
+ #
73
+ # @param [String,Symbol] type Class name in lowercase
74
+ # @param [Array] args additional parameters will be sent to thh
75
+ # class constructor
76
+ #
77
+ def add_output(type, *args)
78
+ class_name = type.to_s.capitalize
79
+ klass = Drone::Interfaces.const_get(class_name)
80
+ @output_modules << klass.new(*args)
81
+ end
82
+
83
+ ##
84
+ # Register a new counter
85
+ # @see Drone::Metrics::Counter
86
+ # @param [String] type Name of this metric
87
+ # @api public
88
+ #
89
+ def register_counter(type)
90
+ register_metric( Drone::Metrics::Counter.new(type) )
91
+ end
92
+
93
+
94
+ ##
95
+ # Register an Histogram
96
+ # @see Drone::Metrics::Histogram
97
+ # @param [String] name Name of this metric
98
+ # @param [optional,Enum] type one of Drone::Metrics::Histogram::TYPE_UNIFORM or Drone::Metrics::Histogram::TYPE_BIASED
99
+ #
100
+ def register_histogram(name, type = :uniform)
101
+ register_metric( Drone::Metrics::Histogram.new(name, type) )
102
+ end
103
+
104
+ ##
105
+ # Register a new gauge
106
+ # @see Drone::Metrics::Gauge
107
+ # @param [String] type Name of this metric
108
+ # @api public
109
+ #
110
+ def register_gauge(type, &block)
111
+ register_metric( Drone::Metrics::Gauge.new(type, &block) )
112
+ end
113
+
114
+ ##
115
+ # Register a new metric
116
+ # This method can be used bu the user but the prefered
117
+ # way is to use the register_counter / register_gauge methods
118
+ #
119
+ # @param [Metric] metric The Metric to register
120
+ # @private
121
+ #
122
+ def register_metric(metric)
123
+ @metrics << metric
124
+ metric
125
+ end
126
+
127
+
128
+ def_delegators :@storage, :request_fixed_size_array, :request_number, :request_hash
129
+ def_delegators :@scheduler, :schedule_periodic, :schedule_once
130
+
131
+
132
+
133
+ ##
134
+ # Register a monitored class.
135
+ # @private
136
+ #
137
+ def register_monitored_class(klass)
138
+ @monitored_classes << klass
139
+ end
140
+ end
141
+ end