pulse-meter 0.3.2 → 0.4.0

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.
data/lib/pulse-meter.rb CHANGED
@@ -14,18 +14,25 @@ require "pulse-meter/command_aggregator/sync"
14
14
  module PulseMeter
15
15
  @@redis = nil
16
16
 
17
+ # Returns global Redis client
17
18
  def self.redis
18
19
  @@redis
19
20
  end
20
21
 
22
+ # Sets global Redis client
23
+ # @param redis [Redis] redis client
21
24
  def self.redis=(redis)
22
25
  @@redis = redis
23
26
  end
24
27
 
28
+ # Returns global command aggegator (i.e. object that accumulates Redis commands emitted by events and sends them into client)
25
29
  def self.command_aggregator
26
30
  @@command_aggregator ||= PulseMeter::CommandAggregator::Async.instance
27
31
  end
28
-
32
+
33
+ # Sets global command_aggregator
34
+ # @param type [Symbol] type of command aggegator (:async or :sync)
35
+ # @raise [ArgumentError] if type is none of :async, :sync
29
36
  def self.command_aggregator=(command_aggregator)
30
37
  @@command_aggregator = case command_aggregator
31
38
  when :sync; PulseMeter::CommandAggregator::Sync.instance
@@ -33,4 +40,25 @@ module PulseMeter
33
40
  else raise ArgumentError
34
41
  end
35
42
  end
43
+
44
+ # Sets global logger for all PulseMeter error messages
45
+ # @param logger [Logger] logger to be used
46
+ def self.logger=(new_logger)
47
+ @@logger = new_logger
48
+ end
49
+
50
+ # Returns global PulseMeter logger
51
+ def self.logger
52
+ @@logger ||= begin
53
+ logger = Logger.new($stderr)
54
+ logger.datetime_format = '%Y-%m-%d %H:%M:%S.%3N'
55
+ logger
56
+ end
57
+ end
58
+
59
+ # Sends error message to PulseMeter logger
60
+ # @param message [String] error message
61
+ def self.error(msg)
62
+ logger.error(msg)
63
+ end
36
64
  end
@@ -67,7 +67,7 @@ module PulseMeter
67
67
  end
68
68
  end
69
69
  rescue StandardError => e
70
- STDERR.puts "error in consumer_thread: #{e}, #{e.backtrace.join("\n")}"
70
+ PulseMeter.error "error in consumer thread: #{e}, #{e.backtrace.join("\n")}"
71
71
  end
72
72
  end
73
73
  end
@@ -5,59 +5,86 @@ module PulseMeter
5
5
  include PulseMeter::Mixins::Utils
6
6
  include Enumerable
7
7
 
8
- # @!attribute [r] sensors
9
- # @return [Hash] hash of sensors with names as keys and sensors as values
10
- attr_reader :sensors
11
-
12
8
  # Initializes sensors
13
9
  # @param opts [Hash] sensors' configuration
14
10
  def initialize(opts = {})
15
- @sensors = {}
16
- opts.each do |name, opts|
17
- add_sensor(name, 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
18
22
  end
19
23
  end
20
24
 
21
- # Adds sensor
25
+ # Returns true value if sensor with specified name exists in configuration, false otherwise
22
26
  # @param name [Symbol] sensor name
23
- # @param opts [Hash] sensor options
24
- def add_sensor(name, opts)
25
- sensor_type = opts.respond_to?(:sensor_type) ? opts.sensor_type : opts[:sensor_type]
26
- klass_s = sensor_class(sensor_type)
27
- klass = constantize(klass_s)
28
- raise ArgumentError, "#{klass_s} is not a valid class for a sensor" unless klass
29
- args = (opts.respond_to?(:args) ? opts.args : opts[:args]) || {}
30
- @sensors[name.to_s] = klass.new(name, symbolize_keys(args.to_hash))
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
31
33
  end
32
34
 
33
- # Returns previously initialized sensor by name
35
+ # Adds sensor
34
36
  # @param name [Symbol] sensor name
35
- # @return [Sensor] sensor
36
- def sensor(name)
37
- @sensors[name.to_s]
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
38
42
  end
39
43
 
40
44
  # Iterates over each sensor
41
45
  def each
42
- @sensors.each_value do |sensor|
43
- yield(sensor)
46
+ with_resque do
47
+ sensors.each_value do |s|
48
+ yield(s)
49
+ end
44
50
  end
45
51
  end
46
52
 
47
- # Invokes event for any sensor
53
+ # Invokes event(_at) for any sensor
48
54
  # @raise [ArgumentError] unless sensor exists
49
55
  def method_missing(name, *args)
50
- name = name.to_s
51
- if @sensors.has_key?(name)
52
- @sensors[name].event(*args)
53
- elsif (name =~ /^(.*)_at$/) && @sensors.has_key?($1)
54
- @sensors[$1].event_at(*args)
55
- else
56
- raise ArgumentError, "Unknown sensor: `#{name}'"
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
57
64
  end
58
65
  end
59
66
 
60
- protected
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
61
88
 
62
89
  def sensor_class(sensor_type)
63
90
  entries = sensor_type.to_s.split('/').map do |entry|
@@ -66,6 +93,14 @@ module PulseMeter
66
93
  entries.unshift('PulseMeter::Sensor').join('::')
67
94
  end
68
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
+
69
104
  end
70
105
  end
71
106
  end
@@ -20,7 +20,8 @@ module PulseMeter
20
20
  end
21
21
 
22
22
  def sensor(name)
23
- sensors.sensor(name)
23
+ raise ArgumentError, 'need a block' unless block_given?
24
+ sensors.sensor(name){|s| yield(s)}
24
25
  end
25
26
 
26
27
  def event(factors_hash, value)
@@ -28,8 +29,9 @@ module PulseMeter
28
29
 
29
30
  each_factors_combination do |combination|
30
31
  factor_values = factor_values_for_combination(combination, factors_hash)
31
- sensor = get_or_create_sensor(combination, factor_values)
32
- sensor.event(value)
32
+ get_or_create_sensor(combination, factor_values) do |s|
33
+ s.event(value)
34
+ end
33
35
  end
34
36
  end
35
37
 
@@ -38,9 +40,8 @@ module PulseMeter
38
40
  end
39
41
 
40
42
  def sensor_for_factors(factor_names, factor_values)
41
- sensor(
42
- get_sensor_name(factor_names, factor_values)
43
- )
43
+ raise ArgumentError, 'need a block' unless block_given?
44
+ sensor(get_sensor_name(factor_names, factor_values)){|s| yield(s)}
44
45
  end
45
46
 
46
47
  protected
@@ -50,12 +51,15 @@ module PulseMeter
50
51
  end
51
52
 
52
53
  def get_or_create_sensor(factor_names, factor_values)
54
+ raise ArgumentError, 'need a block' unless block_given?
53
55
  name = get_sensor_name(factor_names, factor_values)
54
- unless sensor(name)
56
+ unless sensors.has_sensor?(name)
55
57
  sensors.add_sensor(name, configuration_options)
56
58
  dump!(false)
57
59
  end
58
- sensor(name)
60
+ sensor(name) do |s|
61
+ yield(s)
62
+ end
59
63
  end
60
64
 
61
65
  def ensure_valid_factors!(factors_hash)
@@ -1,3 +1,3 @@
1
1
  module PulseMeter
2
- VERSION = "0.3.2"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -16,38 +16,39 @@ describe PulseMeter::Sensor::Configuration do
16
16
  let(:cfg) {described_class.new}
17
17
 
18
18
  it "should create sensor available under passed name" do
19
- cfg.sensor(:foo).should be_nil
19
+ cfg.has_sensor?(:foo).should be_false
20
20
  cfg.add_sensor(:foo, sensor_type: 'counter')
21
- cfg.sensor(:foo).should_not be_nil
21
+ cfg.has_sensor?(:foo).should_not be_true
22
22
  end
23
23
 
24
24
  it "should have event shortcut for the sensor" do
25
25
  cfg.add_sensor(:foo, sensor_type: 'counter')
26
- sensor = cfg.sensor(:foo)
27
- sensor.should_receive(:event).with(321)
26
+ puts cfg.to_yaml
27
+ cfg.sensor(:foo){|s| s.should_receive(:event).with(321)}
28
28
  cfg.foo(321)
29
29
  end
30
30
 
31
31
  it "should have event_at shortcut for the sensor" do
32
32
  cfg.add_sensor(:foo, sensor_type: 'counter')
33
- sensor = cfg.sensor(:foo)
34
33
  now = Time.now
35
- sensor.should_receive(:event_at).with(now, 321)
34
+ cfg.sensor(:foo) do |sensor|
35
+ sensor.should_receive(:event_at).with(now, 321)
36
+ end
36
37
  cfg.foo_at(now, 321)
37
38
  end
38
39
 
39
40
  it "should create sensor with correct type" do
40
41
  cfg.add_sensor(:foo, sensor_type: 'counter')
41
- cfg.sensor(:foo).should be_kind_of(PulseMeter::Sensor::Counter)
42
+ cfg.sensor(:foo){|s| s.should be_kind_of(PulseMeter::Sensor::Counter)}
42
43
  end
43
44
 
44
- it "should raise exception if sensor type is bad" do
45
- expect{ cfg.add_sensor(:foo, sensor_type: 'baaaar') }.to raise_exception(ArgumentError)
45
+ it "should not raise exception if sensor type is bad" do
46
+ expect{ cfg.add_sensor(:foo, sensor_type: 'baaaar') }.not_to raise_exception
46
47
  end
47
48
 
48
49
  it "should pass args to created sensor" do
49
50
  cfg.add_sensor(:foo, sensor_type: 'counter', args: {annotation: "My Foo Counter"} )
50
- cfg.sensor(:foo).annotation.should == "My Foo Counter"
51
+ cfg.sensor(:foo){|s| s.annotation.should == "My Foo Counter" }
51
52
  end
52
53
 
53
54
  it "should accept hashie-objects" do
@@ -61,7 +62,7 @@ describe PulseMeter::Sensor::Configuration do
61
62
  end
62
63
 
63
64
  cfg.add_sensor(:foo, Dummy.new)
64
- cfg.sensor(:foo).annotation.should == "My Foo Counter"
65
+ cfg.sensor(:foo){|s| s.annotation.should == "My Foo Counter"}
65
66
  end
66
67
  end
67
68
 
@@ -78,22 +79,15 @@ describe PulseMeter::Sensor::Configuration do
78
79
  cfg1 = described_class.new(opts)
79
80
  cfg2 = described_class.new
80
81
  opts.each{|k,v| cfg2.add_sensor(k, v)}
81
- cfg1.to_yaml.should == cfg2.to_yaml
82
+ cfg1.sensors.to_yaml.should == cfg2.sensors.to_yaml
82
83
  end
83
84
  end
84
85
 
85
86
  describe "#sensor" do
86
- it "should give access to added sensors" do
87
- cfg = described_class.new(counter_config)
88
- cfg.sensor(:cnt).annotation.should == "MySensor"
89
- cfg.sensor("cnt").annotation.should == "MySensor"
90
- end
91
- end
92
-
93
- describe "#sensors" do
94
- it "returns hash of sensors" do
87
+ it "should give access to added sensors via block" do
95
88
  cfg = described_class.new(counter_config)
96
- cfg.sensors.should == {"cnt" => cfg.sensor(:cnt)}
89
+ cfg.sensor(:cnt){ |s| s.annotation.should == "MySensor" }
90
+ cfg.sensor("cnt"){ |s| s.annotation.should == "MySensor" }
97
91
  end
98
92
  end
99
93
 
@@ -102,7 +96,8 @@ describe PulseMeter::Sensor::Configuration do
102
96
  cfg = described_class.new(counter_config)
103
97
  sensors = {}
104
98
  cfg.each {|s| sensors[s.name.to_sym] = s}
105
- sensors.should == {:cnt => cfg.sensor(:cnt)}
99
+ sensor = cfg.sensor(:cnt){|s| s}
100
+ sensors.should == {:cnt => sensor}
106
101
  end
107
102
  end
108
103
  end
@@ -95,8 +95,9 @@ describe PulseMeter::Sensor::Multi do
95
95
  ["#{name}_f1_f1v1_f2_f2v1", 1],
96
96
  ["#{name}_f1_f1v2_f2_f2v1", 2]
97
97
  ].each do |sensor_name, sum|
98
- s = sensor.sensor(sensor_name)
99
- s.value.should == sum
98
+ sensor.sensor(sensor_name) { |s|
99
+ s.value.should == sum
100
+ }
100
101
  end
101
102
  end
102
103
  end
@@ -117,16 +118,18 @@ describe PulseMeter::Sensor::Multi do
117
118
 
118
119
  describe "#sensor_for_factors" do
119
120
  context "when sensor has already been created" do
120
- it "returns sensor for given combination of factors and their values" do
121
+ it "yields block with sensor for given combination of factors and their values" do
121
122
  sensor.event({f1: :f1v1, f2: :f2v1}, 1)
122
- sensor.sensor_for_factors([:f1, :f2], [:f1v1, :f2v1]).name.should == "#{name}_f1_f1v1_f2_f2v1"
123
- sensor.sensor_for_factors([:f1], [:f1v1]).name.should == "#{name}_f1_f1v1"
123
+ sensor.sensor_for_factors([:f1, :f2], [:f1v1, :f2v1]){|s| s.name.should == "#{name}_f1_f1v1_f2_f2v1"}
124
+ sensor.sensor_for_factors([:f1], [:f1v1]){|s| s.name.should == "#{name}_f1_f1v1"}
124
125
  end
125
126
  end
126
127
 
127
128
  context "when such a sensor was not created" do
128
- it "returns nil" do
129
- sensor.sensor_for_factors([:foo], [:bar]).should be_nil
129
+ it "does not yields block" do
130
+ yielded = false
131
+ sensor.sensor_for_factors([:foo], [:bar]){ yielded = true }
132
+ yielded.should be_false
130
133
  end
131
134
  end
132
135
  end
@@ -50,4 +50,23 @@ describe PulseMeter do
50
50
  ca1.should == ca2
51
51
  end
52
52
  end
53
+
54
+ describe "::logger" do
55
+ it "should return PulseMeter logger" do
56
+ PulseMeter.logger = 123
57
+ PulseMeter.logger.should == 123
58
+ end
59
+
60
+ it "should return default logger" do
61
+ PulseMeter.logger = nil
62
+ PulseMeter.logger.should be_kind_of(Logger)
63
+ end
64
+ end
65
+
66
+ describe "::error" do
67
+ it "should delegate error message to logger" do
68
+ PulseMeter.logger.should_receive(:error)
69
+ PulseMeter.error("foo")
70
+ end
71
+ end
53
72
  end
data/spec/spec_helper.rb CHANGED
@@ -29,6 +29,7 @@ RSpec.configure do |config|
29
29
  config.before(:each) do
30
30
  PulseMeter.redis = MockRedis.new
31
31
  Timecop.return
32
+ PulseMeter.logger = Logger.new("/dev/null")
32
33
  end
33
34
  config.filter_run :focus => true
34
35
  config.run_all_when_everything_filtered = true
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pulse-meter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-11-05 00:00:00.000000000 Z
13
+ date: 2012-11-11 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: gon-sinatra
@@ -605,9 +605,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
605
605
  - - ! '>='
606
606
  - !ruby/object:Gem::Version
607
607
  version: '0'
608
- segments:
609
- - 0
610
- hash: 284340155524977771
611
608
  requirements: []
612
609
  rubyforge_project:
613
610
  rubygems_version: 1.8.23