pulse_meter_core 0.4.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/.gitignore +19 -0
  2. data/.rbenv-version +1 -0
  3. data/.rspec +1 -0
  4. data/.rvmrc +1 -0
  5. data/.travis.yml +8 -0
  6. data/Gemfile +2 -0
  7. data/LICENSE +22 -0
  8. data/README.md +40 -0
  9. data/Rakefile +20 -0
  10. data/lib/pulse_meter/command_aggregator/async.rb +83 -0
  11. data/lib/pulse_meter/command_aggregator/sync.rb +18 -0
  12. data/lib/pulse_meter/command_aggregator/udp.rb +48 -0
  13. data/lib/pulse_meter/mixins/dumper.rb +87 -0
  14. data/lib/pulse_meter/mixins/utils.rb +155 -0
  15. data/lib/pulse_meter/observer.rb +118 -0
  16. data/lib/pulse_meter/observer/extended.rb +32 -0
  17. data/lib/pulse_meter/sensor.rb +61 -0
  18. data/lib/pulse_meter/sensor/base.rb +88 -0
  19. data/lib/pulse_meter/sensor/configuration.rb +106 -0
  20. data/lib/pulse_meter/sensor/counter.rb +39 -0
  21. data/lib/pulse_meter/sensor/hashed_counter.rb +36 -0
  22. data/lib/pulse_meter/sensor/hashed_indicator.rb +24 -0
  23. data/lib/pulse_meter/sensor/indicator.rb +35 -0
  24. data/lib/pulse_meter/sensor/multi.rb +97 -0
  25. data/lib/pulse_meter/sensor/timeline.rb +236 -0
  26. data/lib/pulse_meter/sensor/timeline_reduce.rb +68 -0
  27. data/lib/pulse_meter/sensor/timelined/average.rb +32 -0
  28. data/lib/pulse_meter/sensor/timelined/counter.rb +23 -0
  29. data/lib/pulse_meter/sensor/timelined/hashed_counter.rb +31 -0
  30. data/lib/pulse_meter/sensor/timelined/hashed_indicator.rb +30 -0
  31. data/lib/pulse_meter/sensor/timelined/indicator.rb +23 -0
  32. data/lib/pulse_meter/sensor/timelined/max.rb +19 -0
  33. data/lib/pulse_meter/sensor/timelined/median.rb +14 -0
  34. data/lib/pulse_meter/sensor/timelined/min.rb +19 -0
  35. data/lib/pulse_meter/sensor/timelined/multi_percentile.rb +34 -0
  36. data/lib/pulse_meter/sensor/timelined/percentile.rb +22 -0
  37. data/lib/pulse_meter/sensor/timelined/uniq_counter.rb +22 -0
  38. data/lib/pulse_meter/sensor/timelined/zset_based.rb +37 -0
  39. data/lib/pulse_meter/sensor/uniq_counter.rb +24 -0
  40. data/lib/pulse_meter/server.rb +0 -0
  41. data/lib/pulse_meter/server/command_line_options.rb +0 -0
  42. data/lib/pulse_meter/server/config_options.rb +0 -0
  43. data/lib/pulse_meter/server/sensors.rb +0 -0
  44. data/lib/pulse_meter/udp_server.rb +45 -0
  45. data/lib/pulse_meter_core.rb +66 -0
  46. data/pulse_meter_core.gemspec +33 -0
  47. data/spec/pulse_meter/command_aggregator/async_spec.rb +53 -0
  48. data/spec/pulse_meter/command_aggregator/sync_spec.rb +25 -0
  49. data/spec/pulse_meter/command_aggregator/udp_spec.rb +45 -0
  50. data/spec/pulse_meter/mixins/dumper_spec.rb +162 -0
  51. data/spec/pulse_meter/mixins/utils_spec.rb +212 -0
  52. data/spec/pulse_meter/observer/extended_spec.rb +92 -0
  53. data/spec/pulse_meter/observer_spec.rb +207 -0
  54. data/spec/pulse_meter/sensor/base_spec.rb +106 -0
  55. data/spec/pulse_meter/sensor/configuration_spec.rb +103 -0
  56. data/spec/pulse_meter/sensor/counter_spec.rb +54 -0
  57. data/spec/pulse_meter/sensor/hashed_counter_spec.rb +43 -0
  58. data/spec/pulse_meter/sensor/hashed_indicator_spec.rb +39 -0
  59. data/spec/pulse_meter/sensor/indicator_spec.rb +43 -0
  60. data/spec/pulse_meter/sensor/multi_spec.rb +137 -0
  61. data/spec/pulse_meter/sensor/timeline_spec.rb +88 -0
  62. data/spec/pulse_meter/sensor/timelined/average_spec.rb +6 -0
  63. data/spec/pulse_meter/sensor/timelined/counter_spec.rb +6 -0
  64. data/spec/pulse_meter/sensor/timelined/hashed_counter_spec.rb +8 -0
  65. data/spec/pulse_meter/sensor/timelined/hashed_indicator_spec.rb +8 -0
  66. data/spec/pulse_meter/sensor/timelined/indicator_spec.rb +6 -0
  67. data/spec/pulse_meter/sensor/timelined/max_spec.rb +7 -0
  68. data/spec/pulse_meter/sensor/timelined/median_spec.rb +7 -0
  69. data/spec/pulse_meter/sensor/timelined/min_spec.rb +7 -0
  70. data/spec/pulse_meter/sensor/timelined/multi_percentile_spec.rb +21 -0
  71. data/spec/pulse_meter/sensor/timelined/percentile_spec.rb +17 -0
  72. data/spec/pulse_meter/sensor/timelined/uniq_counter_spec.rb +9 -0
  73. data/spec/pulse_meter/sensor/uniq_counter_spec.rb +28 -0
  74. data/spec/pulse_meter/udp_server_spec.rb +36 -0
  75. data/spec/pulse_meter_spec.rb +73 -0
  76. data/spec/shared_examples/timeline_sensor.rb +439 -0
  77. data/spec/shared_examples/timelined_subclass.rb +23 -0
  78. data/spec/spec_helper.rb +37 -0
  79. data/spec/support/matchers.rb +34 -0
  80. data/spec/support/observered.rb +40 -0
  81. metadata +342 -0
@@ -0,0 +1,118 @@
1
+ module PulseMeter
2
+ class Observer
3
+ extend PulseMeter::Mixins::Utils
4
+
5
+ class << self
6
+ # Removes observation from instance method
7
+ # @param klass [Class] class
8
+ # @param method [Symbol] instance method name
9
+ def unobserve_method(klass, method)
10
+ with_observer = method_with_observer(method)
11
+ if klass.method_defined?(with_observer)
12
+ block = unchain_block(method)
13
+ klass.class_eval &block
14
+ end
15
+ end
16
+
17
+ # Removes observation from class method
18
+ # @param klass [Class] class
19
+ # @param method [Symbol] class method name
20
+ def unobserve_class_method(klass, method)
21
+ with_observer = method_with_observer(method)
22
+ if klass.respond_to?(with_observer)
23
+ method_owner = metaclass(klass)
24
+ block = unchain_block(method)
25
+ method_owner.instance_eval &block
26
+ end
27
+ end
28
+
29
+ # Registeres an observer for instance method
30
+ # @param klass [Class] class
31
+ # @param method [Symbol] instance method
32
+ # @param sensor [Object] notifications receiver
33
+ # @param proc [Proc] proc to be called in context of receiver each time observed method called
34
+ def observe_method(klass, method, sensor, &proc)
35
+ with_observer = method_with_observer(method)
36
+ unless klass.method_defined?(with_observer)
37
+ block = chain_block(method, sensor, &proc)
38
+ klass.class_eval &block
39
+ end
40
+ end
41
+
42
+ # Registeres an observer for class method
43
+ # @param klass [Class] class
44
+ # @param method [Symbol] class method
45
+ # @param sensor [Object] notifications receiver
46
+ # @param proc [Proc] proc to be called in context of receiver each time observed method called
47
+ def observe_class_method(klass, method, sensor, &proc)
48
+ with_observer = method_with_observer(method)
49
+ unless klass.respond_to?(with_observer)
50
+ method_owner = metaclass(klass)
51
+ block = chain_block(method, sensor, &proc)
52
+ method_owner.instance_eval &block
53
+ end
54
+ end
55
+
56
+ protected
57
+
58
+ def define_instrumented_method(method_owner, method, receiver, &handler)
59
+ with_observer = method_with_observer(method)
60
+ without_observer = method_without_observer(method)
61
+ method_owner.send(:define_method, with_observer) do |*args, &block|
62
+ start_time = Time.now
63
+ begin
64
+ self.send(without_observer, *args, &block)
65
+ ensure
66
+ begin
67
+ delta = ((Time.now - start_time) * 1000).to_i
68
+ receiver.instance_exec(delta, *args, &handler)
69
+ rescue StandardError
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def unchain_block(method)
78
+ with_observer = method_with_observer(method)
79
+ without_observer = method_without_observer(method)
80
+
81
+ Proc.new do
82
+ alias_method(method, without_observer)
83
+ remove_method(with_observer)
84
+ remove_method(without_observer)
85
+ end
86
+ end
87
+
88
+ def chain_block(method, receiver, &handler)
89
+ with_observer = method_with_observer(method)
90
+ without_observer = method_without_observer(method)
91
+ me = self
92
+
93
+ Proc.new do
94
+ alias_method(without_observer, method)
95
+ method_owner = self
96
+ me.send(:define_instrumented_method, method_owner, method, receiver, &handler)
97
+ alias_method(method, with_observer)
98
+ end
99
+ end
100
+
101
+ def metaclass(klass)
102
+ klass.class_eval do
103
+ class << self
104
+ self
105
+ end
106
+ end
107
+ end
108
+
109
+ def method_with_observer(method)
110
+ "#{method}_with_#{underscore(self).tr('/', '_')}"
111
+ end
112
+
113
+ def method_without_observer(method)
114
+ "#{method}_without_#{underscore(self).tr('/', '_')}"
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,32 @@
1
+ module PulseMeter
2
+ class Observer::Extended < ::PulseMeter::Observer
3
+ class << self
4
+ protected
5
+
6
+ def define_instrumented_method(method_owner, method, receiver, &handler)
7
+ with_observer = method_with_observer(method)
8
+ without_observer = method_without_observer(method)
9
+ method_owner.send(:define_method, with_observer) do |*args, &block|
10
+ start_time = Time.now
11
+ begin
12
+ result = self.send(without_observer, *args, &block)
13
+ ensure
14
+ begin
15
+ delta = ((Time.now - start_time) * 1000).to_i
16
+ observe_parameters = {
17
+ self: self,
18
+ delta: delta,
19
+ result: result,
20
+ args: args,
21
+ exception: $!
22
+ }
23
+ receiver.instance_exec(observe_parameters, &handler)
24
+ rescue StandardError
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,61 @@
1
+ require 'pulse_meter/sensor/base'
2
+ require 'pulse_meter/sensor/configuration'
3
+ require 'pulse_meter/sensor/counter'
4
+ require 'pulse_meter/sensor/indicator'
5
+ require 'pulse_meter/sensor/hashed_counter'
6
+ require 'pulse_meter/sensor/hashed_indicator'
7
+ require 'pulse_meter/sensor/multi'
8
+ require 'pulse_meter/sensor/uniq_counter'
9
+ require 'pulse_meter/sensor/timeline_reduce'
10
+ require 'pulse_meter/sensor/timeline'
11
+ require 'pulse_meter/sensor/timelined/average'
12
+ require 'pulse_meter/sensor/timelined/counter'
13
+ require 'pulse_meter/sensor/timelined/indicator'
14
+ require 'pulse_meter/sensor/timelined/hashed_counter'
15
+ require 'pulse_meter/sensor/timelined/hashed_indicator'
16
+ require 'pulse_meter/sensor/timelined/zset_based'
17
+ require 'pulse_meter/sensor/timelined/min'
18
+ require 'pulse_meter/sensor/timelined/max'
19
+ require 'pulse_meter/sensor/timelined/percentile'
20
+ require 'pulse_meter/sensor/timelined/multi_percentile'
21
+ require 'pulse_meter/sensor/timelined/median'
22
+ require 'pulse_meter/sensor/timelined/uniq_counter'
23
+
24
+ # Top level sensor module
25
+ module PulseMeter
26
+
27
+ # Atomic sensor data
28
+ SensorData = Struct.new(:start_time, :value)
29
+
30
+ # General sensor exception
31
+ class SensorError < StandardError; end
32
+
33
+ # Exception to be raised when sensor name is malformed
34
+ class BadSensorName < SensorError
35
+ def initialize(name, options = {})
36
+ super("Bad sensor name: `#{name}', only a-z letters and _ are allowed")
37
+ end
38
+ end
39
+
40
+ # Exception to be raised when Redis is not initialized
41
+ class RedisNotInitialized < SensorError
42
+ def initialize
43
+ super("PulseMeter.redis is not set")
44
+ end
45
+ end
46
+
47
+ # Exception to be raised when sensor cannot be dumped
48
+ class DumpError < SensorError; end
49
+
50
+ # Exception to be raised on attempts of using the same key for different sensors
51
+ class DumpConflictError < DumpError; end
52
+
53
+ # Exception to be raised when sensor cannot be restored
54
+ class RestoreError < SensorError; end
55
+
56
+ module Remote
57
+ class MessageTooLarge < PulseMeter::SensorError; end
58
+ class ConnectionError < PulseMeter::SensorError; end
59
+ end
60
+ end
61
+
@@ -0,0 +1,88 @@
1
+ module PulseMeter
2
+ module Sensor
3
+ # @abstract Subclass and override {#event} to implement sensor
4
+ class Base
5
+ include PulseMeter::Mixins::Dumper
6
+
7
+ # @!attribute [rw] redis
8
+ # @return [Redis]
9
+ attr_accessor :redis
10
+ # @!attribute [r] name
11
+ # @return [String] sensor name
12
+ attr_reader :name
13
+
14
+ # Initializes sensor and dumps it to redis
15
+ # @param name [String] sensor name
16
+ # @option options [String] :annotation Sensor annotation
17
+ # @raise [BadSensorName] if sensor name is malformed
18
+ # @raise [RedisNotInitialized] unless Redis is initialized
19
+ def initialize(name, options={})
20
+ @name = name.to_s
21
+ if options[:annotation]
22
+ annotate(options[:annotation])
23
+ end
24
+ raise BadSensorName, @name unless @name =~ /\A\w+\z/
25
+ raise RedisNotInitialized unless PulseMeter.redis
26
+ dump!
27
+ end
28
+
29
+ # Returns Redis instance
30
+ def redis
31
+ PulseMeter.redis
32
+ end
33
+
34
+ def command_aggregator
35
+ PulseMeter.command_aggregator
36
+ end
37
+
38
+ # Saves annotation to Redis
39
+ # @param description [String] Sensor annotation
40
+ def annotate(description)
41
+ redis.set(desc_key, description)
42
+ end
43
+
44
+ # Retrieves annotation from Redis
45
+ # @return [String] Sensor annotation
46
+ def annotation
47
+ redis.get(desc_key)
48
+ end
49
+
50
+ # Cleans up all sensor metadata in Redis
51
+ def cleanup
52
+ redis.del(desc_key)
53
+ cleanup_dump
54
+ end
55
+
56
+ # Processes event
57
+ # @param value [Object] value produced by some kind of event
58
+ def event(value)
59
+ process_event(value)
60
+ true
61
+ rescue StandardError => e
62
+ false
63
+ end
64
+
65
+ protected
66
+
67
+ # Forms Redis key to store annotation
68
+ def desc_key
69
+ "pulse_meter:desc:#{name}"
70
+ end
71
+
72
+ # For a block
73
+ # @yield Executes it within Redis multi
74
+ def multi
75
+ redis.multi do
76
+ yield
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def process_event(value)
83
+ # do nothing here
84
+ end
85
+
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,106 @@
1
+ module PulseMeter
2
+ module Sensor
3
+ # Constructs multiple sensors from configuration passed
4
+ class Configuration
5
+ include PulseMeter::Mixins::Utils
6
+ include Enumerable
7
+
8
+ # Initializes sensors
9
+ # @param opts [Hash] sensors' configuration
10
+ def initialize(opts = {})
11
+ @opts = opts
12
+ end
13
+
14
+ # Returns previously initialized sensor by name
15
+ # @param name [Symbol] sensor name
16
+ # @yield [sensor] Gives sensor(if it is found) to the block
17
+ def sensor(name)
18
+ raise ArgumentError, "need a block" unless block_given?
19
+ with_resque do
20
+ s = sensors[name.to_s]
21
+ yield(s) if s
22
+ end
23
+ end
24
+
25
+ # Returns true value if sensor with specified name exists in configuration, false otherwise
26
+ # @param name [Symbol] sensor name
27
+ def has_sensor?(name)
28
+ has_sensor = false
29
+ with_resque do
30
+ has_sensor = sensors.has_key?(name)
31
+ end
32
+ has_sensor
33
+ end
34
+
35
+ # Adds sensor
36
+ # @param name [Symbol] sensor name
37
+ # @param opts [Hash] sensor options
38
+ def add_sensor(name, opts)
39
+ with_resque do
40
+ sensors[name.to_s] = create_sensor(name, opts)
41
+ end
42
+ end
43
+
44
+ # Iterates over each sensor
45
+ def each
46
+ with_resque do
47
+ sensors.each_value do |s|
48
+ yield(s)
49
+ end
50
+ end
51
+ end
52
+
53
+ # Invokes event(_at) for any sensor
54
+ # @raise [ArgumentError] unless sensor exists
55
+ def method_missing(name, *args)
56
+ with_resque do
57
+ name = name.to_s
58
+ if sensors.has_key?(name)
59
+ sensors[name].event(*args)
60
+ elsif name =~ /\A(.*)_at\z/
61
+ sensor_name = $1
62
+ sensors[sensor_name].event_at(*args) if sensors.has_key?(sensor_name)
63
+ end
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def with_resque
70
+ yield
71
+ rescue StandardError => e
72
+ PulseMeter.error "Configuration error: #{e}, #{e.backtrace.join("\n")}"
73
+ nil
74
+ end
75
+
76
+
77
+ # Tries to create a specific sensor
78
+ # @param name [Symbol] sensor name
79
+ # @param opts [Hash] sensor options
80
+ def create_sensor(name, opts)
81
+ sensor_type = opts.respond_to?(:sensor_type) ? opts.sensor_type : opts[:sensor_type]
82
+ klass_s = sensor_class(sensor_type)
83
+ klass = constantize(klass_s)
84
+ raise ArgumentError, "#{klass_s} is not a valid class for a sensor" unless klass
85
+ args = (opts.respond_to?(:args) ? opts.args : opts[:args]) || {}
86
+ klass.new(name, symbolize_keys(args.to_hash))
87
+ end
88
+
89
+ def sensor_class(sensor_type)
90
+ entries = sensor_type.to_s.split('/').map do |entry|
91
+ entry.split('_').map(&:capitalize).join
92
+ end
93
+ entries.unshift('PulseMeter::Sensor').join('::')
94
+ end
95
+
96
+ # Lazy collection of sensors, specified by opts
97
+ # @raise [ArgumentError] unless one of the sensors exists
98
+ def sensors
99
+ @sensors ||= @opts.each_with_object({}){ |(name, opts), sensor_acc|
100
+ sensor_acc[name.to_s] = create_sensor(name, opts)
101
+ }
102
+ end
103
+
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,39 @@
1
+ # Static counter
2
+ module PulseMeter
3
+ module Sensor
4
+ class Counter < Base
5
+
6
+ # Cleans up all sensor metadata in Redis
7
+ def cleanup
8
+ redis.del(value_key)
9
+ super
10
+ end
11
+
12
+ # Increments counter value by 1
13
+ def incr
14
+ event(1)
15
+ end
16
+
17
+ # Gets counter value
18
+ # @return [Fixnum]
19
+ def value
20
+ redis.get(value_key).to_i
21
+ end
22
+
23
+ # Gets redis key by which counter value is stored
24
+ # @return [String]
25
+ def value_key
26
+ @value_key ||= "pulse_meter:value:#{name}"
27
+ end
28
+
29
+ private
30
+
31
+ # Processes event by incremnting counter by given value
32
+ # @param value [Fixnum] increment
33
+ def process_event(value)
34
+ command_aggregator.incrby(value_key, value.to_i)
35
+ end
36
+
37
+ end
38
+ end
39
+ end