pulse-meter 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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