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,32 @@
1
+ module PulseMeter
2
+ module Sensor
3
+ module Timelined
4
+ # Average value over interval
5
+ class Average < Timeline
6
+
7
+ def aggregate_event(key, value)
8
+ command_aggregator.hincrby(key, :count, 1)
9
+ command_aggregator.hincrby(key, :sum, value)
10
+ end
11
+
12
+ def summarize(key)
13
+ count = redis.hget(key, :count)
14
+ sum = redis.hget(key, :sum)
15
+ if count && !count.empty?
16
+ sum.to_f / count.to_f
17
+ else
18
+ 0
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def deflate(value)
25
+ value.to_f
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,23 @@
1
+ module PulseMeter
2
+ module Sensor
3
+ module Timelined
4
+ # Counts events per interval
5
+ class Counter < Timeline
6
+ def aggregate_event(key, value)
7
+ command_aggregator.incrby(key, value.to_i)
8
+ end
9
+
10
+ def summarize(key)
11
+ redis.get(key)
12
+ end
13
+
14
+ private
15
+
16
+ def deflate(value)
17
+ value.to_i
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,31 @@
1
+ require 'json'
2
+
3
+ module PulseMeter
4
+ module Sensor
5
+ module Timelined
6
+ # Counts multiple types of events per interval.
7
+ # Good replacement for multiple counters to be visualized together
8
+ class HashedCounter < Timeline
9
+ def aggregate_event(key, data)
10
+ data.each_pair do |k, v|
11
+ command_aggregator.hincrby(key, k, v)
12
+ command_aggregator.hincrby(key, :total, v)
13
+ end
14
+ end
15
+
16
+ def summarize(key)
17
+ redis.
18
+ hgetall(key).
19
+ inject({}) {|h, (k, v)| h[k] = v.to_i; h}.
20
+ to_json
21
+ end
22
+
23
+ private
24
+
25
+ def deflate(value)
26
+ JSON.parse(value)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ require 'json'
2
+
3
+ module PulseMeter
4
+ module Sensor
5
+ module Timelined
6
+ # Saves last registered values for multiple flags per interval.
7
+ # Good replacement for multiple indicators to be visualized together
8
+ class HashedIndicator < Timeline
9
+ def aggregate_event(key, data)
10
+ data.each_pair do |k, v|
11
+ command_aggregator.hset(key, k, v) if v.respond_to?(:to_f)
12
+ end
13
+ end
14
+
15
+ def summarize(key)
16
+ redis.
17
+ hgetall(key).
18
+ inject({}) {|h, (k, v)| h[k] = v.to_f; h}.
19
+ to_json
20
+ end
21
+
22
+ private
23
+
24
+ def deflate(value)
25
+ JSON.parse(value)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,23 @@
1
+ module PulseMeter
2
+ module Sensor
3
+ module Timelined
4
+ # Saves last registered flag float value for each interval
5
+ class Indicator < Timeline
6
+ def aggregate_event(key, value)
7
+ command_aggregator.set(key, value.to_f)
8
+ end
9
+
10
+ def summarize(key)
11
+ redis.get(key)
12
+ end
13
+
14
+ private
15
+
16
+ def deflate(value)
17
+ value.to_f
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ module PulseMeter
2
+ module Sensor
3
+ module Timelined
4
+ # Calculates max value in interval
5
+ class Max < ZSetBased
6
+
7
+ def update(key)
8
+ command_aggregator.zremrangebyrank(key, 0, -2)
9
+ end
10
+
11
+ def calculate(key, _)
12
+ max_el = redis.zrange(key, -1, -1)[0]
13
+ redis.zscore(key, max_el)
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ module PulseMeter
2
+ module Sensor
3
+ module Timelined
4
+ # Calculates median of iterval values
5
+ class Median < Percentile
6
+
7
+ def initialize(name, options)
8
+ super(name, options.merge({:p => 0.5}))
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ module PulseMeter
2
+ module Sensor
3
+ module Timelined
4
+ # Calculates min value in interval
5
+ class Min < ZSetBased
6
+
7
+ def update(key)
8
+ command_aggregator.zremrangebyrank(key, 1, -1)
9
+ end
10
+
11
+ def calculate(key, _)
12
+ min_el = redis.zrange(key, 0, 0)[0]
13
+ redis.zscore(key, min_el)
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,34 @@
1
+ module PulseMeter
2
+ module Sensor
3
+ module Timelined
4
+ # Calculates n'th percentile in interval
5
+ class MultiPercentile < ZSetBased
6
+ attr_reader :p_value
7
+
8
+ def initialize(name, options)
9
+ @p_value = assert_array!(options, :p, [])
10
+ @p_value.each {|p| assert_ranged_float!({:percentile => p}, :percentile, 0, 1)}
11
+ super(name, options)
12
+ end
13
+
14
+ def calculate(key, count)
15
+ count =
16
+ @p_value.each_with_object({}) { |p, acc|
17
+ position = p > 0 ? (p * count).round - 1 : 0
18
+ el = redis.zrange(key, position, position)[0]
19
+ acc[p] = redis.zscore(key, el)
20
+ }.to_json
21
+ end
22
+
23
+ private
24
+
25
+ def deflate(value)
26
+ hash = JSON.parse(value)
27
+ hash.each {|p, v| hash[p] = v.to_f}
28
+ hash
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,22 @@
1
+ module PulseMeter
2
+ module Sensor
3
+ module Timelined
4
+ # Calculates n'th percentile in interval
5
+ class Percentile < ZSetBased
6
+ attr_reader :p_value
7
+
8
+ def initialize(name, options)
9
+ @p_value = assert_ranged_float!(options, :p, 0, 1)
10
+ super(name, options)
11
+ end
12
+
13
+ def calculate(key, count)
14
+ position = @p_value > 0 ? (@p_value * count).round - 1 : 0
15
+ el = redis.zrange(key, position, position)[0]
16
+ redis.zscore(key, el)
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module PulseMeter
2
+ module Sensor
3
+ module Timelined
4
+ # Counts unique events per interval
5
+ class UniqCounter < Timeline
6
+ def aggregate_event(key, value)
7
+ command_aggregator.sadd(key, value)
8
+ end
9
+
10
+ def summarize(key)
11
+ redis.scard(key)
12
+ end
13
+
14
+ private
15
+
16
+ def deflate(value)
17
+ value.to_i
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,37 @@
1
+ module PulseMeter
2
+ module Sensor
3
+ module Timelined
4
+ class ZSetBased < Timeline
5
+
6
+ def update(_)
7
+ end
8
+
9
+ def calculate(key)
10
+ 0
11
+ end
12
+
13
+ def aggregate_event(key, value)
14
+ command_aggregator.zadd(key, value, "#{value}::#{uniqid}")
15
+ update(key)
16
+ end
17
+
18
+ def summarize(key)
19
+ count = redis.zcard(key)
20
+ if count > 0
21
+ calculate(key, count)
22
+ else
23
+ nil
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def deflate(value)
30
+ value.to_f
31
+ end
32
+
33
+ end
34
+ end
35
+ end
36
+ end
37
+
@@ -0,0 +1,24 @@
1
+ require 'json'
2
+
3
+ # Static counter to count unique values
4
+ module PulseMeter
5
+ module Sensor
6
+ class UniqCounter < Counter
7
+
8
+ # Returs number of unique values ever sent to counter
9
+ # @return [Fixnum]
10
+ def value
11
+ redis.scard(value_key)
12
+ end
13
+
14
+ private
15
+
16
+ # Processes event
17
+ # @param name [String] value to be counted
18
+ def process_event(name)
19
+ command_aggregator.sadd(value_key, name)
20
+ end
21
+
22
+ end
23
+ end
24
+ end
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,45 @@
1
+ require 'socket'
2
+ require 'timeout'
3
+
4
+ module PulseMeter
5
+ class UDPServer
6
+ MAX_PACKET = 1024
7
+
8
+ def initialize(host, port)
9
+ @socket = UDPSocket.new
10
+ @socket.do_not_reverse_lookup = true
11
+ @socket.bind(host, port)
12
+ end
13
+
14
+ def start(max_packets = nil)
15
+ while true do
16
+ if max_packets
17
+ break if max_packets <= 0
18
+ max_packets -= 1
19
+ end
20
+ process_packet
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def process_packet
27
+ raw_data, _ = @socket.recvfrom(MAX_PACKET)
28
+ data = parse_data(raw_data)
29
+ PulseMeter.redis.multi do
30
+ data.each do |command|
31
+ PulseMeter.redis.send(*command)
32
+ end
33
+ end
34
+ rescue StandardError => e
35
+ PulseMeter.error "Error processing packet: #{e}"
36
+ end
37
+
38
+ def parse_data(data)
39
+ JSON.parse(data)
40
+ rescue
41
+ PulseMeter.error "Bad redis data: #{data.inspect}"
42
+ []
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,66 @@
1
+ require "redis"
2
+ require "logger"
3
+ require "pulse_meter/mixins/dumper"
4
+ require "pulse_meter/mixins/utils"
5
+ require "pulse_meter/observer"
6
+ require "pulse_meter/observer/extended"
7
+ require "pulse_meter/sensor"
8
+ require "pulse_meter/sensor/configuration"
9
+
10
+ require "pulse_meter/command_aggregator/async"
11
+ require "pulse_meter/command_aggregator/sync"
12
+ require "pulse_meter/command_aggregator/udp"
13
+
14
+ require "pulse_meter/udp_server"
15
+
16
+ module PulseMeter
17
+ @@redis = nil
18
+
19
+ # Returns global Redis client
20
+ def self.redis
21
+ @@redis
22
+ end
23
+
24
+ # Sets global Redis client
25
+ # @param redis [Redis] redis client
26
+ def self.redis=(redis)
27
+ @@redis = redis
28
+ end
29
+
30
+ # Returns global command aggegator (i.e. object that accumulates Redis commands emitted by events and sends them into client)
31
+ def self.command_aggregator
32
+ @@command_aggregator ||= PulseMeter::CommandAggregator::Async.instance
33
+ end
34
+
35
+ # Sets global command_aggregator
36
+ # @param type [Symbol] type of command aggegator (:async or :sync)
37
+ # @raise [ArgumentError] if type is none of :async, :sync
38
+ def self.command_aggregator=(command_aggregator)
39
+ @@command_aggregator = case command_aggregator
40
+ when :sync; PulseMeter::CommandAggregator::Sync.instance
41
+ when :async; PulseMeter::CommandAggregator::Async.instance
42
+ else command_aggregator
43
+ end
44
+ end
45
+
46
+ # Sets global logger for all PulseMeter error messages
47
+ # @param logger [Logger] logger to be used
48
+ def self.logger=(new_logger)
49
+ @@logger = new_logger
50
+ end
51
+
52
+ # Returns global PulseMeter logger
53
+ def self.logger
54
+ @@logger ||= begin
55
+ logger = Logger.new($stderr)
56
+ logger.datetime_format = '%Y-%m-%d %H:%M:%S.%3N'
57
+ logger
58
+ end
59
+ end
60
+
61
+ # Sends error message to PulseMeter logger
62
+ # @param message [String] error message
63
+ def self.error(msg)
64
+ logger.error(msg)
65
+ end
66
+ end