pulse-meter 0.0.1

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 (96) 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 +4 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE +22 -0
  8. data/Procfile +3 -0
  9. data/README.md +440 -0
  10. data/Rakefile +53 -0
  11. data/bin/pulse +6 -0
  12. data/examples/basic.ru +109 -0
  13. data/examples/basic_sensor_data.rb +38 -0
  14. data/examples/full/Procfile +2 -0
  15. data/examples/full/client.rb +82 -0
  16. data/examples/full/server.ru +114 -0
  17. data/examples/minimal/Procfile +2 -0
  18. data/examples/minimal/client.rb +16 -0
  19. data/examples/minimal/server.ru +20 -0
  20. data/examples/readme_client_example.rb +52 -0
  21. data/lib/cmd.rb +150 -0
  22. data/lib/pulse-meter.rb +17 -0
  23. data/lib/pulse-meter/mixins/dumper.rb +72 -0
  24. data/lib/pulse-meter/mixins/utils.rb +91 -0
  25. data/lib/pulse-meter/sensor.rb +44 -0
  26. data/lib/pulse-meter/sensor/base.rb +75 -0
  27. data/lib/pulse-meter/sensor/counter.rb +36 -0
  28. data/lib/pulse-meter/sensor/hashed_counter.rb +31 -0
  29. data/lib/pulse-meter/sensor/indicator.rb +33 -0
  30. data/lib/pulse-meter/sensor/timeline.rb +180 -0
  31. data/lib/pulse-meter/sensor/timelined/average.rb +26 -0
  32. data/lib/pulse-meter/sensor/timelined/counter.rb +16 -0
  33. data/lib/pulse-meter/sensor/timelined/hashed_counter.rb +22 -0
  34. data/lib/pulse-meter/sensor/timelined/max.rb +25 -0
  35. data/lib/pulse-meter/sensor/timelined/median.rb +14 -0
  36. data/lib/pulse-meter/sensor/timelined/min.rb +25 -0
  37. data/lib/pulse-meter/sensor/timelined/percentile.rb +31 -0
  38. data/lib/pulse-meter/version.rb +3 -0
  39. data/lib/pulse-meter/visualize/app.rb +43 -0
  40. data/lib/pulse-meter/visualize/dsl.rb +0 -0
  41. data/lib/pulse-meter/visualize/dsl/errors.rb +46 -0
  42. data/lib/pulse-meter/visualize/dsl/layout.rb +55 -0
  43. data/lib/pulse-meter/visualize/dsl/page.rb +50 -0
  44. data/lib/pulse-meter/visualize/dsl/sensor.rb +21 -0
  45. data/lib/pulse-meter/visualize/dsl/widget.rb +84 -0
  46. data/lib/pulse-meter/visualize/layout.rb +54 -0
  47. data/lib/pulse-meter/visualize/page.rb +30 -0
  48. data/lib/pulse-meter/visualize/public/css/application.css +19 -0
  49. data/lib/pulse-meter/visualize/public/css/bootstrap.css +4883 -0
  50. data/lib/pulse-meter/visualize/public/css/bootstrap.min.css +729 -0
  51. data/lib/pulse-meter/visualize/public/favicon.ico +0 -0
  52. data/lib/pulse-meter/visualize/public/img/glyphicons-halflings-white.png +0 -0
  53. data/lib/pulse-meter/visualize/public/img/glyphicons-halflings.png +0 -0
  54. data/lib/pulse-meter/visualize/public/js/application.coffee +262 -0
  55. data/lib/pulse-meter/visualize/public/js/application.js +279 -0
  56. data/lib/pulse-meter/visualize/public/js/backbone-min.js +38 -0
  57. data/lib/pulse-meter/visualize/public/js/bootstrap.js +1835 -0
  58. data/lib/pulse-meter/visualize/public/js/highcharts.js +203 -0
  59. data/lib/pulse-meter/visualize/public/js/jquery-1.7.2.min.js +4 -0
  60. data/lib/pulse-meter/visualize/public/js/json2.js +487 -0
  61. data/lib/pulse-meter/visualize/public/js/underscore-min.js +32 -0
  62. data/lib/pulse-meter/visualize/sensor.rb +60 -0
  63. data/lib/pulse-meter/visualize/views/main.haml +40 -0
  64. data/lib/pulse-meter/visualize/widget.rb +68 -0
  65. data/lib/pulse-meter/visualizer.rb +30 -0
  66. data/lib/test_helpers/matchers.rb +36 -0
  67. data/pulse-meter.gemspec +39 -0
  68. data/spec/pulse_meter/mixins/dumper_spec.rb +158 -0
  69. data/spec/pulse_meter/mixins/utils_spec.rb +134 -0
  70. data/spec/pulse_meter/sensor/base_spec.rb +97 -0
  71. data/spec/pulse_meter/sensor/counter_spec.rb +54 -0
  72. data/spec/pulse_meter/sensor/hashed_counter_spec.rb +39 -0
  73. data/spec/pulse_meter/sensor/indicator_spec.rb +43 -0
  74. data/spec/pulse_meter/sensor/timeline_spec.rb +58 -0
  75. data/spec/pulse_meter/sensor/timelined/average_spec.rb +6 -0
  76. data/spec/pulse_meter/sensor/timelined/counter_spec.rb +6 -0
  77. data/spec/pulse_meter/sensor/timelined/hashed_counter_spec.rb +8 -0
  78. data/spec/pulse_meter/sensor/timelined/max_spec.rb +7 -0
  79. data/spec/pulse_meter/sensor/timelined/median_spec.rb +7 -0
  80. data/spec/pulse_meter/sensor/timelined/min_spec.rb +7 -0
  81. data/spec/pulse_meter/sensor/timelined/percentile_spec.rb +17 -0
  82. data/spec/pulse_meter/visualize/app_spec.rb +27 -0
  83. data/spec/pulse_meter/visualize/dsl/layout_spec.rb +64 -0
  84. data/spec/pulse_meter/visualize/dsl/page_spec.rb +75 -0
  85. data/spec/pulse_meter/visualize/dsl/sensor_spec.rb +30 -0
  86. data/spec/pulse_meter/visualize/dsl/widget_spec.rb +127 -0
  87. data/spec/pulse_meter/visualize/layout_spec.rb +55 -0
  88. data/spec/pulse_meter/visualize/page_spec.rb +150 -0
  89. data/spec/pulse_meter/visualize/sensor_spec.rb +120 -0
  90. data/spec/pulse_meter/visualize/widget_spec.rb +113 -0
  91. data/spec/pulse_meter/visualizer_spec.rb +42 -0
  92. data/spec/pulse_meter_spec.rb +16 -0
  93. data/spec/shared_examples/timeline_sensor.rb +279 -0
  94. data/spec/shared_examples/timelined_subclass.rb +23 -0
  95. data/spec/spec_helper.rb +29 -0
  96. metadata +435 -0
@@ -0,0 +1,52 @@
1
+ $: << File.join(File.absolute_path(__FILE__), '..', 'lib')
2
+
3
+ require 'pulse-meter'
4
+ PulseMeter.redis = Redis.new
5
+
6
+ # static sensor examples
7
+
8
+ counter = PulseMeter::Sensor::Counter.new :my_counter
9
+ counter.event(1)
10
+ counter.event(2)
11
+ puts counter.value
12
+
13
+ indicator = PulseMeter::Sensor::Indicator.new :my_value
14
+ indicator.event(3.14)
15
+ indicator.event(2.71)
16
+ puts indicator.value
17
+
18
+ hashed_counter = PulseMeter::Sensor::HashedCounter.new :my_h_counter
19
+ hashed_counter.event(:x => 1)
20
+ hashed_counter.event(:y => 5)
21
+ hashed_counter.event(:y => 1)
22
+ p hashed_counter.value
23
+
24
+
25
+ # timeline sensor examples
26
+
27
+ requests_per_minute = PulseMeter::Sensor::Timelined::Counter.new(:my_t_counter,
28
+ :interval => 60, # count for each minute
29
+ :ttl => 24 * 60 * 60 # keep data one day
30
+ )
31
+ requests_per_minute.event(1)
32
+ requests_per_minute.event(1)
33
+ sleep(60)
34
+ requests_per_minute.event(1)
35
+ requests_per_minute.timeline(2 * 60).each do |v|
36
+ puts "#{v.start_time}: #{v.value}"
37
+ end
38
+
39
+ max_per_minute = PulseMeter::Sensor::Timelined::Max.new(:my_t_max,
40
+ :interval => 60, # max for each minute
41
+ :ttl => 24 * 60 * 60 # keep data one day
42
+ )
43
+ max_per_minute.event(3)
44
+ max_per_minute.event(1)
45
+ max_per_minute.event(2)
46
+ sleep(60)
47
+ max_per_minute.event(5)
48
+ max_per_minute.event(7)
49
+ max_per_minute.event(6)
50
+ max_per_minute.timeline(2 * 60).each do |v|
51
+ puts "#{v.start_time}: #{v.value}"
52
+ end
data/lib/cmd.rb ADDED
@@ -0,0 +1,150 @@
1
+ require 'thor'
2
+ require 'terminal-table'
3
+ require 'time'
4
+ require 'json'
5
+
6
+ module Cmd
7
+ class All < Thor
8
+ include PulseMeter::Mixins::Utils
9
+ no_tasks do
10
+ def with_redis
11
+ PulseMeter.redis = Redis.new :host => options[:host], :port => options[:port], :db => options[:db]
12
+ yield
13
+ end
14
+
15
+ def with_safe_restore_of(name, &block)
16
+ with_redis do
17
+ sensor = PulseMeter::Sensor::Base.restore(name)
18
+ block.call(sensor)
19
+ end
20
+ rescue PulseMeter::RestoreError
21
+ fail! "Sensor #{name} is unknown or cannot be restored"
22
+ end
23
+
24
+ def all_sensors
25
+ PulseMeter::Sensor::Timeline.list_objects
26
+ end
27
+
28
+ def all_sensors_table(title = nil)
29
+ table = Terminal::Table.new :title => title
30
+ table << ["Name", "Class", "ttl", "raw data ttl", "interval", "reduce delay"]
31
+ table << :separator
32
+ all_sensors.each do |s|
33
+ if s.kind_of? PulseMeter::Sensor::Timeline
34
+ table << [s.name, s.class, s.ttl, s.raw_data_ttl, s.interval, s.reduce_delay]
35
+ else
36
+ table << [s.name, s.class] + ['-'] * 4
37
+ end
38
+ end
39
+ table
40
+ end
41
+
42
+ def fail!(description = nil)
43
+ puts description if description
44
+ exit 1
45
+ end
46
+
47
+ def self.common_options
48
+ method_option :host, :default => '127.0.0.1', :desc => "Redis host"
49
+ method_option :port, :default => 6379, :desc => "Redis port"
50
+ method_option :db, :default => 0, :desc => "Redis db"
51
+ end
52
+ end
53
+
54
+ desc "sensors", "List all sensors available"
55
+ common_options
56
+ def sensors
57
+ with_redis {puts all_sensors_table('Registered sensors')}
58
+ end
59
+
60
+ desc "reduce", "Execute reduction for all sensors' raw data"
61
+ common_options
62
+ def reduce
63
+ with_redis do
64
+ puts all_sensors_table('Registered sensors to be reduced')
65
+ PulseMeter::Sensor::Timeline.reduce_all_raw
66
+ puts "DONE"
67
+ end
68
+ end
69
+
70
+ desc "event NAME VALUE", "Send event VALUE to sensor NAME"
71
+ common_options
72
+ method_option :format, :default => :plain, :desc => "Event format: plain or json"
73
+ def event(name, value)
74
+ if "json" == options[:format]
75
+ value = JSON.parse(value)
76
+ end
77
+ with_safe_restore_of(name) {|sensor| sensor.event(value)}
78
+ end
79
+
80
+ desc "timeline NAME SECONDS", "Get sensor's NAME timeline for last SECONDS"
81
+ common_options
82
+ def timeline(name, seconds)
83
+ with_safe_restore_of(name) do |sensor|
84
+ table = Terminal::Table.new
85
+ sensor.timeline(seconds).each {|data| table << [data.start_time, data.value || '-']}
86
+ puts table
87
+ end
88
+ end
89
+
90
+ desc "timeline_within NAME FROM TILL", "Get sensor's NAME timeline in interval. Time format: YYYY-MM-DD HH:MM:SS"
91
+ common_options
92
+ def timeline_within(name, from, till)
93
+ with_safe_restore_of(name) do |sensor|
94
+ table = Terminal::Table.new
95
+ sensor.timeline_within(
96
+ Time.parse(from),
97
+ Time.parse(till)
98
+ ).each {|data| table << [data.start_time, data.value || '-']}
99
+ puts table
100
+ end
101
+ end
102
+
103
+ desc "delete NAME", "Delete sensor by name"
104
+ common_options
105
+ def delete(name)
106
+ with_safe_restore_of(name) {|sensor| sensor.cleanup}
107
+ puts "Sensor #{name} deleted"
108
+ end
109
+
110
+ desc "create NAME TYPE", "Create sensor of given type"
111
+ common_options
112
+ method_option :interval, :required => true, :type => :numeric, :desc => "Rotation interval"
113
+ method_option :ttl, :required => true, :type => :numeric, :desc => "How long summarized data will be stored"
114
+ method_option :raw_data_ttl, :type => :numeric, :desc => "How long unsummarized raw data will be stored"
115
+ method_option :reduce_delay, :type => :numeric, :desc => "Delay between end of interval and summarization"
116
+ method_option :annotation, :type => :string, :desc => "Sensor annotation"
117
+ def create(name, type)
118
+ with_redis do
119
+ klass = constantize("PulseMeter::Sensor::Timelined::#{type}")
120
+ puts "PulseMeter::Sensor::Timelined::#{type}"
121
+ fail! "Unknown sensor type #{type}" unless klass
122
+ sensor = klass.new(name, options.dup)
123
+ puts "Sensor created"
124
+ puts all_sensors_table
125
+ end
126
+ end
127
+
128
+ desc "create_simple NAME TYPE", "Create simple non-timelined sensor of given type"
129
+ common_options
130
+ method_option :annotation, :type => :string, :desc => "Sensor annotation"
131
+ def create_simple(name, type)
132
+ with_redis do
133
+ klass = constantize("PulseMeter::Sensor::#{type}")
134
+ fail! "Unknown sensor type #{type}" unless klass
135
+ sensor = klass.new(name, options.dup)
136
+ puts "Sensor created"
137
+ puts all_sensors_table
138
+ end
139
+ end
140
+
141
+ desc "value NAME", "Get value of non-timelined sensor"
142
+ def value(name)
143
+ with_safe_restore_of(name) do |sensor|
144
+ fail! "Sensor #{name} has no value method" unless sensor.respond_to?(:value)
145
+ puts "Value: #{sensor.value}"
146
+ end
147
+ end
148
+
149
+ end
150
+ end
@@ -0,0 +1,17 @@
1
+ require "redis"
2
+ require "pulse-meter/version"
3
+ require "pulse-meter/mixins/dumper"
4
+ require "pulse-meter/mixins/utils"
5
+ require "pulse-meter/sensor"
6
+
7
+ module PulseMeter
8
+ @@redis = nil
9
+
10
+ def self.redis
11
+ @@redis
12
+ end
13
+
14
+ def self.redis=(redis)
15
+ @@redis = redis
16
+ end
17
+ end
@@ -0,0 +1,72 @@
1
+ module PulseMeter
2
+ module Mixins
3
+ # Mixin with dumping utilities
4
+ module Dumper
5
+ # Prefix for Redis keys with dumped sensors' metadata
6
+ DUMP_REDIS_KEY = "pulse_meter:dump"
7
+
8
+ module InstanceMethods
9
+ # Serializes object and saves it to Redis
10
+ # @raise [DumpError] if dumping fails for any reason
11
+ def dump!
12
+ ensure_storability!
13
+ serialized_obj = Marshal.dump(self)
14
+ redis.hset(DUMP_REDIS_KEY, self.name, serialized_obj)
15
+ rescue
16
+ raise DumpError, "object cannot be dumped"
17
+ end
18
+
19
+ # Ensures that object is dumpable
20
+ # @raise [DumpError] if object cannot be dumped
21
+ def ensure_storability!
22
+ raise DumpError, "#name attribute must be readable" unless self.respond_to?(:name)
23
+ raise DumpError, "#redis attribute must be available" unless self.respond_to?(:redis) && self.redis
24
+ end
25
+
26
+ # Cleans up object dump in Redis
27
+ def cleanup_dump
28
+ redis.hdel(DUMP_REDIS_KEY, self.name)
29
+ end
30
+ end
31
+
32
+ module ClassMethods
33
+ # Restores object from Redis
34
+ # @param name [String] object name
35
+ # @return [Object]
36
+ # @raise [RestoreError] if object cannot be restored for any reason
37
+ def restore(name)
38
+ serialized_obj = PulseMeter.redis.hget(DUMP_REDIS_KEY, name)
39
+ Marshal.load(serialized_obj)
40
+ rescue
41
+ raise RestoreError, "cannot restore #{name}"
42
+ end
43
+
44
+ # Lists all dumped objects' names
45
+ # @return [Array<String>]
46
+ # @raise [RestoreError] if list cannot be retrieved for any reason
47
+ def list_names
48
+ PulseMeter.redis.hkeys(DUMP_REDIS_KEY)
49
+ rescue
50
+ raise RestoreError, "cannot get data from redis"
51
+ end
52
+
53
+ # Safely restores all dumped objects
54
+ # @return [Array<Object>]
55
+ def list_objects
56
+ list_names.each_with_object([]) do |name, objects|
57
+ begin
58
+ objects << restore(name)
59
+ rescue
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ def self.included(base)
66
+ base.send :include, InstanceMethods
67
+ base.send :extend, ClassMethods
68
+ end
69
+
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,91 @@
1
+ require 'securerandom'
2
+
3
+ module PulseMeter
4
+ module Mixins
5
+ # Mixin with various useful functions
6
+ module Utils
7
+ # Tries to find a class with the name specified in the argument string
8
+ # @param const_name [String] class name
9
+ # @return [Class] if given class definde
10
+ # @return [NilClass] if given class is not defined
11
+ def constantize(const_name)
12
+ return unless const_name.respond_to?(:to_s)
13
+ const_name.to_s.split('::').reduce(Module, :const_get)
14
+ rescue NameError
15
+ nil
16
+ end
17
+
18
+ # Ensures that hash value specified by key can be converted to positive integer.
19
+ # In case it can makes in-place conversion and returns the value.
20
+ # @param options [Hash] hash to be looked at
21
+ # @param key [Object] hash key
22
+ # @param default [Object] default value to be returned
23
+ # @raise [ArgumentError] unless value is positive integer
24
+ # @return [Fixnum]
25
+ def assert_positive_integer!(options, key, default = nil)
26
+ value = options[key] || default
27
+ raise ArgumentError, "#{key} should be defined" unless value
28
+ raise ArgumentError, "#{key} should be integer" unless value.respond_to?(:to_i)
29
+ raise ArgumentError, "#{key} should be positive" unless value.to_i > 0
30
+ options[key] = value.to_i
31
+ end
32
+
33
+ # Ensures that hash value specified by key is can be converted to float
34
+ # and it is within given range.
35
+ # In case it can makes in-place conversion and returns the value.
36
+ # @param options [Hash] hash to be looked at
37
+ # @param key [Object] hash key
38
+ # @param from [Float] lower bound
39
+ # @param to [Float] upper bound
40
+ # @raise [ArgumentError] unless value is float within given range
41
+ # @return [Float]
42
+ def assert_ranged_float!(options, key, from, to)
43
+ f = options[key]
44
+ raise ArgumentError, "#{key} should be defined" unless f
45
+ raise ArgumentError, "#{key} should be float" unless f.respond_to?(:to_f)
46
+ f = f.to_f
47
+ raise ArgumentError, "#{key} should be between #{from} and #{to}" unless f >= from && f <= to
48
+ options[key] = f
49
+ end
50
+
51
+ # Generates uniq random string
52
+ # @return [String]
53
+ def uniqid
54
+ SecureRandom.hex(32)
55
+ end
56
+
57
+ # Capitalizes the first letter of each word in string
58
+ # @param str [String]
59
+ # @return [String]
60
+ # @raise [ArgumentError] unless passed value responds to to_s
61
+ def titleize(str)
62
+ raise ArgumentError unless str.respond_to?(:to_s)
63
+ str.to_s.split(/[\s_]+/).map(&:capitalize).join(' ')
64
+ end
65
+
66
+ # Converts string from snake_case to CamelCase
67
+ # @param str [String] string to be camelized
68
+ # @param first_letter_upper [TrueClass, FalseClass] says if the first letter must be uppercased
69
+ # @return [String]
70
+ # @raise [ArgumentError] unless passed value responds to to_s
71
+ def camelize(str, first_letter_upper = false)
72
+ raise ArgumentError unless str.respond_to?(:to_s)
73
+ terms = str.to_s.split(/_/)
74
+ first = terms.shift
75
+ (first_letter_upper ? first.capitalize : first.downcase) + terms.map(&:capitalize).join
76
+ end
77
+
78
+ # Deeply capitalizes Array values or Hash keys
79
+ def camelize_keys(item)
80
+ case item
81
+ when Array
82
+ item.map{|i| camelize_keys(i)}
83
+ when Hash
84
+ item.each_with_object({}) { |(k, v), h| h[camelize(k)] = camelize_keys(v)}
85
+ else
86
+ item
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,44 @@
1
+ require 'pulse-meter/sensor/base'
2
+ require 'pulse-meter/sensor/counter'
3
+ require 'pulse-meter/sensor/hashed_counter'
4
+ require 'pulse-meter/sensor/indicator'
5
+ require 'pulse-meter/sensor/timeline'
6
+ require 'pulse-meter/sensor/timelined/average'
7
+ require 'pulse-meter/sensor/timelined/counter'
8
+ require 'pulse-meter/sensor/timelined/hashed_counter'
9
+ require 'pulse-meter/sensor/timelined/min'
10
+ require 'pulse-meter/sensor/timelined/max'
11
+ require 'pulse-meter/sensor/timelined/percentile'
12
+ require 'pulse-meter/sensor/timelined/median'
13
+
14
+ # Top level sensor module
15
+ module PulseMeter
16
+
17
+ # Atomic sensor data
18
+ SensorData = Struct.new(:start_time, :value)
19
+
20
+ # General sensor exception
21
+ class SensorError < StandardError; end
22
+
23
+ # Exception to be raised when sensor name is malformed
24
+ class BadSensorName < SensorError
25
+ def initialize(name, options = {})
26
+ super("Bad sensor name: `#{name}', only a-z letters and _ are allowed")
27
+ end
28
+ end
29
+
30
+ # Exception to be raised when Redis is not initialized
31
+ class RedisNotInitialized < SensorError
32
+ def initialize
33
+ super("PulseMeter.redis is not set")
34
+ end
35
+ end
36
+
37
+ # Exception to be raised when sensor cannot be dumped
38
+ class DumpError < SensorError; end
39
+
40
+ # Exception to be raised when sensor cannot be restored
41
+ class RestoreError < SensorError; end
42
+
43
+ end
44
+
@@ -0,0 +1,75 @@
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
+ # Saves annotation to Redis
35
+ # @param description [String] Sensor annotation
36
+ def annotate(description)
37
+ redis.set(desc_key, description)
38
+ end
39
+
40
+ # Retrieves annotation from Redis
41
+ # @return [String] Sensor annotation
42
+ def annotation
43
+ redis.get(desc_key)
44
+ end
45
+
46
+ # Cleans up all sensor metadata in Redis
47
+ def cleanup
48
+ redis.del(desc_key)
49
+ cleanup_dump
50
+ end
51
+
52
+ # @abstract Processes event
53
+ # @param value [Object] value produced by some kind of event
54
+ def event(value)
55
+ # do nothing here
56
+ end
57
+
58
+ protected
59
+
60
+ # Forms Redis key to store annotation
61
+ def desc_key
62
+ "pulse_meter:desc:#{name}"
63
+ end
64
+
65
+ # For a block
66
+ # @yield Executes it within Redis multi
67
+ def multi
68
+ redis.multi
69
+ yield
70
+ redis.exec
71
+ end
72
+
73
+ end
74
+ end
75
+ end