pulse_meter_core 0.4.13

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.
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