pulse-meter 0.2.6 → 0.2.7

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -60,9 +60,12 @@ The following timeline sensors are available:
60
60
 
61
61
  * Average value
62
62
  * Counter
63
+ * Indicator
63
64
  * Hashed counter
65
+ * Hashed indicator
64
66
  * Max value
65
67
  * Min value
68
+ * Multifactor sensor
66
69
  * Median value
67
70
  * Percentile
68
71
  * Unique counter
@@ -16,7 +16,11 @@ module Enumerable
16
16
  end
17
17
  else
18
18
  self.each_with_object(Terminal::Table.new) do |row, table|
19
- table << row.map(&:to_s)
19
+ table << if row.respond_to?(:map)
20
+ row.map(&:to_s)
21
+ else
22
+ row
23
+ end
20
24
  end
21
25
  end
22
26
  end
@@ -15,6 +15,19 @@ module PulseMeter
15
15
  nil
16
16
  end
17
17
 
18
+ # Ensures that hash value specified by key is Array
19
+ # @param options [Hash] hash to be looked at
20
+ # @param key [Object] hash key
21
+ # @param default [Object] default value to be returned
22
+ # @raise [ArgumentError] unless value is Array
23
+ # @return [Array]
24
+ def assert_array!(options, key, default = nil)
25
+ value = options[key] || default
26
+ raise ArgumentError, "#{key} should be defined" unless value
27
+ raise ArgumentError, "#{key} should be array" unless value.is_a?(Array)
28
+ value
29
+ end
30
+
18
31
  # Ensures that hash value specified by key can be converted to positive integer.
19
32
  # In case it can makes in-place conversion and returns the value.
20
33
  # @param options [Hash] hash to be looked at
@@ -98,6 +111,18 @@ module PulseMeter
98
111
  item
99
112
  end
100
113
  end
114
+
115
+ # Yields block for each subset of given array
116
+ # @param array [Array] given array
117
+ def each_subset(array)
118
+ subsets_of(array).each {|subset| yield(subset)}
119
+ end
120
+
121
+ # Returs all array's subsets
122
+ # @param array [Array]
123
+ def subsets_of(array)
124
+ 0.upto(array.length).flat_map { |n| array.combination(n).to_a }
125
+ end
101
126
  end
102
127
  end
103
128
  end
@@ -2,6 +2,9 @@ module PulseMeter
2
2
  module Sensor
3
3
  class Configuration
4
4
  include PulseMeter::Mixins::Utils
5
+ include Enumerable
6
+
7
+ attr_reader :sensors
5
8
 
6
9
  def initialize(opts = {})
7
10
  @sensors = {}
@@ -23,6 +26,12 @@ module PulseMeter
23
26
  @sensors[name.to_s]
24
27
  end
25
28
 
29
+ def each
30
+ @sensors.each_value do |sensor|
31
+ yield(sensor)
32
+ end
33
+ end
34
+
26
35
  def method_missing(name, *args)
27
36
  name = name.to_s
28
37
  if @sensors.has_key?(name)
@@ -43,7 +52,6 @@ module PulseMeter
43
52
  entries.unshift('PulseMeter::Sensor').join('::')
44
53
  end
45
54
 
46
-
47
55
  end
48
56
  end
49
57
  end
@@ -0,0 +1,93 @@
1
+ module PulseMeter
2
+ module Sensor
3
+ class Multi < Base
4
+ include PulseMeter::Mixins::Utils
5
+ include Enumerable
6
+
7
+ attr_reader :name
8
+ attr_reader :factors
9
+ attr_reader :sensors
10
+ attr_reader :configuration_options
11
+
12
+ # TODO restore in initializer
13
+
14
+ def initialize(name, options)
15
+ @name = name
16
+ @factors = assert_array!(options, :factors)
17
+ @sensors = PulseMeter::Sensor::Configuration.new
18
+ @configuration_options = options[:configuration]
19
+ raise ArgumentError, "configuration option missing" unless @configuration_options
20
+ end
21
+
22
+ def sensor(name)
23
+ sensors.sensor(name)
24
+ end
25
+
26
+ def event(factors_hash, value)
27
+ ensure_valid_factors!(factors_hash)
28
+
29
+ each_factors_combination do |combination|
30
+ factor_values = factor_values_for_combination(combination, factors_hash)
31
+ sensor = get_or_create_sensor(combination, factor_values)
32
+ sensor.event(value)
33
+ end
34
+ end
35
+
36
+ def each
37
+ sensors.each {|s| yield(s)}
38
+ end
39
+
40
+ def sensor_for_factors(factor_names, factor_values)
41
+ sensor(
42
+ get_sensor_name(factor_names, factor_values)
43
+ )
44
+ end
45
+
46
+ protected
47
+
48
+ def is_subsensor?(sensor)
49
+ sensor.name.start_with?(get_sensor_name([], []).to_s)
50
+ end
51
+
52
+ def get_or_create_sensor(factor_names, factor_values)
53
+ name = get_sensor_name(factor_names, factor_values)
54
+ unless sensor(name)
55
+ sensors.add_sensor(name, configuration_options)
56
+ dump!(false)
57
+ end
58
+ sensor(name)
59
+ end
60
+
61
+ def ensure_valid_factors!(factors_hash)
62
+ factors.each do |factor_name|
63
+ unless factors_hash.has_key?(factor_name)
64
+ raise ArgumentError, "Value of factor #{factor_name} missing"
65
+ end
66
+ end
67
+ end
68
+
69
+ def each_factors_combination
70
+ each_subset(factors) do |combination|
71
+ yield(combination)
72
+ end
73
+ end
74
+
75
+ def factor_values_for_combination(combination, factors_hash)
76
+ combination.each_with_object([]) do |k, acc|
77
+ acc << factors_hash[k]
78
+ end
79
+ end
80
+
81
+ def get_sensor_name(factor_names, factor_values)
82
+ sensor_name = name.to_s
83
+ unless factor_names.empty?
84
+ factor_names.zip(factor_values).each do |n, v|
85
+ sensor_name << "_#{n}_#{v}"
86
+ end
87
+ end
88
+ sensor_name.to_sym
89
+ end
90
+
91
+ end
92
+ end
93
+ 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
+ redis.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
+ redis.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
@@ -1,14 +1,17 @@
1
1
  require 'pulse-meter/sensor/base'
2
+ require 'pulse-meter/sensor/configuration'
2
3
  require 'pulse-meter/sensor/counter'
3
4
  require 'pulse-meter/sensor/indicator'
4
5
  require 'pulse-meter/sensor/hashed_counter'
5
6
  require 'pulse-meter/sensor/hashed_indicator'
6
- require 'pulse-meter/sensor/remote'
7
+ require 'pulse-meter/sensor/multi'
7
8
  require 'pulse-meter/sensor/uniq_counter'
8
9
  require 'pulse-meter/sensor/timeline'
9
10
  require 'pulse-meter/sensor/timelined/average'
10
11
  require 'pulse-meter/sensor/timelined/counter'
12
+ require 'pulse-meter/sensor/timelined/indicator'
11
13
  require 'pulse-meter/sensor/timelined/hashed_counter'
14
+ require 'pulse-meter/sensor/timelined/hashed_indicator'
12
15
  require 'pulse-meter/sensor/timelined/min'
13
16
  require 'pulse-meter/sensor/timelined/max'
14
17
  require 'pulse-meter/sensor/timelined/percentile'
@@ -1,3 +1,3 @@
1
1
  module PulseMeter
2
- VERSION = "0.2.6"
2
+ VERSION = "0.2.7"
3
3
  end
@@ -72,6 +72,36 @@ describe PulseMeter::Mixins::Utils do
72
72
  end
73
73
  end
74
74
 
75
+ describe "#assert_array!" do
76
+ it "should extract value from hash by passed key" do
77
+ dummy.assert_array!({:val => [:foo]}, :val).should == [:foo]
78
+ end
79
+
80
+ context "when no default value given" do
81
+ it "should raise exception if th value is not an Array" do
82
+ expect{ dummy.assert_array!({:val => :bad}, :val) }.to raise_exception(ArgumentError)
83
+ end
84
+
85
+ it "should raise exception if the value is not defined" do
86
+ expect{ dummy.assert_array!({}, :val) }.to raise_exception(ArgumentError)
87
+ end
88
+ end
89
+
90
+ context "when default value given" do
91
+ it "should prefer value from options to default" do
92
+ dummy.assert_array!({:val => [:foo]}, :val, []).should == [:foo]
93
+ end
94
+
95
+ it "should use default value when there is no one in options" do
96
+ dummy.assert_array!({}, :val, []).should == []
97
+ end
98
+
99
+ it "should check default value if it is to be used" do
100
+ expect{dummy.assert_array!({}, :val, :bad)}.to raise_exception(ArgumentError)
101
+ end
102
+ end
103
+ end
104
+
75
105
  describe "#assert_ranged_float!" do
76
106
 
77
107
  it "should extract float value from hash by passed key" do
@@ -136,6 +166,19 @@ describe PulseMeter::Mixins::Utils do
136
166
  it "should convert symbolizable keys to symbols" do
137
167
  dummy.symbolize_keys({"a" => 5, 6 => 7}).should == {a: 5, 6 => 7}
138
168
  end
169
+ end
170
+
171
+ describe "#subsets_of" do
172
+ it "returns all subsets of given array" do
173
+ dummy.subsets_of([1, 2]).sort.should == [[], [1], [2], [1, 2]].sort
174
+ end
175
+ end
139
176
 
177
+ describe "#each_subset" do
178
+ it "iterates over each subset" do
179
+ subsets = []
180
+ dummy.each_subset([1, 2]) {|s| subsets << s}
181
+ subsets.sort.should == [[], [1], [2], [1, 2]].sort
182
+ end
140
183
  end
141
184
  end
@@ -1,6 +1,17 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe PulseMeter::Sensor::Configuration do
4
+ let(:counter_config) {
5
+ {
6
+ cnt: {
7
+ sensor_type: 'counter',
8
+ args: {
9
+ annotation: "MySensor"
10
+ }
11
+ },
12
+ }
13
+ }
14
+
4
15
  describe "#add_sensor" do
5
16
  let(:cfg) {described_class.new}
6
17
 
@@ -52,8 +63,6 @@ describe PulseMeter::Sensor::Configuration do
52
63
  cfg.add_sensor(:foo, Dummy.new)
53
64
  cfg.sensor(:foo).annotation.should == "My Foo Counter"
54
65
  end
55
-
56
-
57
66
  end
58
67
 
59
68
  describe ".new" do
@@ -75,17 +84,25 @@ describe PulseMeter::Sensor::Configuration do
75
84
 
76
85
  describe "#sensor" do
77
86
  it "should give access to added sensors" do
78
- opts = {
79
- cnt: {
80
- sensor_type: 'counter',
81
- args: {
82
- annotation: "MySensor"
83
- }
84
- },
85
- }
86
- cfg = described_class.new(opts)
87
+ cfg = described_class.new(counter_config)
87
88
  cfg.sensor(:cnt).annotation.should == "MySensor"
88
89
  cfg.sensor("cnt").annotation.should == "MySensor"
89
90
  end
90
91
  end
92
+
93
+ describe "#sensors" do
94
+ it "returns hash of sensors" do
95
+ cfg = described_class.new(counter_config)
96
+ cfg.sensors.should == {"cnt" => cfg.sensor(:cnt)}
97
+ end
98
+ end
99
+
100
+ describe "#each_sensor" do
101
+ it "yields block for each name/sensor pair" do
102
+ cfg = described_class.new(counter_config)
103
+ sensors = {}
104
+ cfg.each {|s| sensors[s.name.to_sym] = s}
105
+ sensors.should == {:cnt => cfg.sensor(:cnt)}
106
+ end
107
+ end
91
108
  end
@@ -0,0 +1,134 @@
1
+ require 'spec_helper'
2
+
3
+ describe PulseMeter::Sensor::Multi do
4
+ let(:name){ :foo }
5
+ let(:annotation) { "Multi sensor" }
6
+ let(:type) {'counter'}
7
+ let(:factors) {[:f1, :f2]}
8
+ let(:configuration) {
9
+ {
10
+ sensor_type: type,
11
+ args: {
12
+ annotation: annotation
13
+ }
14
+ }
15
+ }
16
+ let(:init_values) { { factors: factors, configuration: configuration } }
17
+ let!(:sensor) { described_class.new(name, init_values) }
18
+ let!(:redis){ PulseMeter.redis }
19
+
20
+ describe '#initialize' do
21
+ context "when factors are not corretly passed" do
22
+ it "raises ArgumentError" do
23
+ expect {described_class.new(name, {factors: :not_array, configuration: configuration})}.to raise_exception(ArgumentError)
24
+ expect {described_class.new(name, {configuration: configuration})}.to raise_exception(ArgumentError)
25
+ end
26
+ end
27
+
28
+ context "when configuration missing" do
29
+ it "raises ArgumentError" do
30
+ expect {described_class.new(name, {factors: factors})}.to raise_exception(ArgumentError)
31
+ end
32
+ end
33
+ end
34
+
35
+ describe "#factors" do
36
+ it "returns factors passed to constructor" do
37
+ sensor.factors.should == factors
38
+ end
39
+ end
40
+
41
+ describe "#configuration_options" do
42
+ it "returns configuration option passed to constructor" do
43
+ sensor.configuration_options.should == configuration
44
+ end
45
+ end
46
+
47
+ describe "#sensors" do
48
+ it "returns PulseMeter::Sensor::Configuration instance" do
49
+ sensor.sensors.should be_instance_of(PulseMeter::Sensor::Configuration)
50
+ end
51
+ end
52
+
53
+ describe "#event" do
54
+
55
+ it "raises ArgumentError unless all factors' values given" do
56
+ expect {sensor.event({f1: :v1}, 1)}.to raise_exception(ArgumentError)
57
+ end
58
+
59
+
60
+ context "when sensors must be created" do
61
+ let(:factor_values) { {f1: :v1, f2: :v2} }
62
+
63
+ it "implicitly creates them" do
64
+ expect {sensor.event(factor_values, 1)}.to change{sensor.sensors.to_a.count}
65
+ end
66
+
67
+ it "assigns names based on factors' names and values" do
68
+ sensor.event(factor_values, 1)
69
+ names = sensor.sensors.to_a.map(&:name)
70
+ names.sort.should == [
71
+ "#{name}",
72
+ "#{name}_f1_v1",
73
+ "#{name}_f2_v2",
74
+ "#{name}_f1_v1_f2_v2"
75
+ ].sort
76
+ end
77
+
78
+ it "creates sensors of given type with configuration options passed" do
79
+ sensor.event(factor_values, 1)
80
+ sensor.sensors.each do |s|
81
+ s.should be_instance_of(PulseMeter::Sensor::Counter)
82
+ s.annotation.should == annotation
83
+ end
84
+ end
85
+ end
86
+
87
+ it "sends event to all combinations of factors and values" do
88
+ sensor.event({f1: :f1v1, f2: :f2v1}, 1)
89
+ sensor.event({f1: :f1v2, f2: :f2v1}, 2)
90
+ [
91
+ ["#{name}", 3],
92
+ ["#{name}_f1_f1v1", 1],
93
+ ["#{name}_f1_f1v2", 2],
94
+ ["#{name}_f2_f2v1", 3],
95
+ ["#{name}_f1_f1v1_f2_f2v1", 1],
96
+ ["#{name}_f1_f1v2_f2_f2v1", 2]
97
+ ].each do |sensor_name, sum|
98
+ s = sensor.sensor(sensor_name)
99
+ s.value.should == sum
100
+ end
101
+ end
102
+ end
103
+
104
+ describe "#each" do
105
+ it "when used by Enumerable it lists all ever created subsensors of multisensor" do
106
+ sensor.event({f1: :f1v1, f2: :f2v1}, 1)
107
+ restored_sensor = PulseMeter::Sensor::Base.restore(name)
108
+
109
+ restored_sensor.to_a.map(&:name).sort.should == [
110
+ "#{name}",
111
+ "#{name}_f1_f1v1",
112
+ "#{name}_f2_f2v1",
113
+ "#{name}_f1_f1v1_f2_f2v1",
114
+ ].sort
115
+ end
116
+ end
117
+
118
+ describe "#sensor_for_factors" do
119
+ context "when sensor has already been created" do
120
+ it "returns sensor for given combination of factors and their values" do
121
+ 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"
124
+ end
125
+ end
126
+
127
+ context "when such a sensor was not created" do
128
+ it "returns nil" do
129
+ sensor.sensor_for_factors([:foo], [:bar]).should be_nil
130
+ end
131
+ end
132
+ end
133
+
134
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe PulseMeter::Sensor::Timelined::HashedIndicator do
4
+ it_should_behave_like "timeline sensor", {}, {:foo => 1}
5
+ it_should_behave_like "timelined subclass", [{:foo => 1}, {:foo => 2}], {:foo => 2}.to_json
6
+ it_should_behave_like "timelined subclass", [{:foo => 1}, {:foo => :bad_value}], {:foo => 1}.to_json
7
+ it_should_behave_like "timelined subclass", [{:foo => 1}, {:boo => 2}], {:foo => 1, :boo => 2}.to_json
8
+ end
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+
3
+ describe PulseMeter::Sensor::Timelined::Indicator do
4
+ it_should_behave_like "timeline sensor"
5
+ it_should_behave_like "timelined subclass", [1, 5, 2], 2
6
+ end
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.2.6
4
+ version: 0.2.7
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-07-20 00:00:00.000000000 Z
13
+ date: 2012-07-23 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: gon-sinatra
@@ -343,11 +343,13 @@ files:
343
343
  - lib/pulse-meter/sensor/hashed_counter.rb
344
344
  - lib/pulse-meter/sensor/hashed_indicator.rb
345
345
  - lib/pulse-meter/sensor/indicator.rb
346
- - lib/pulse-meter/sensor/remote.rb
346
+ - lib/pulse-meter/sensor/multi.rb
347
347
  - lib/pulse-meter/sensor/timeline.rb
348
348
  - lib/pulse-meter/sensor/timelined/average.rb
349
349
  - lib/pulse-meter/sensor/timelined/counter.rb
350
350
  - lib/pulse-meter/sensor/timelined/hashed_counter.rb
351
+ - lib/pulse-meter/sensor/timelined/hashed_indicator.rb
352
+ - lib/pulse-meter/sensor/timelined/indicator.rb
351
353
  - lib/pulse-meter/sensor/timelined/max.rb
352
354
  - lib/pulse-meter/sensor/timelined/median.rb
353
355
  - lib/pulse-meter/sensor/timelined/min.rb
@@ -411,11 +413,13 @@ files:
411
413
  - spec/pulse_meter/sensor/hashed_counter_spec.rb
412
414
  - spec/pulse_meter/sensor/hashed_indicator_spec.rb
413
415
  - spec/pulse_meter/sensor/indicator_spec.rb
414
- - spec/pulse_meter/sensor/remote_spec.rb
416
+ - spec/pulse_meter/sensor/multi_spec.rb
415
417
  - spec/pulse_meter/sensor/timeline_spec.rb
416
418
  - spec/pulse_meter/sensor/timelined/average_spec.rb
417
419
  - spec/pulse_meter/sensor/timelined/counter_spec.rb
418
420
  - spec/pulse_meter/sensor/timelined/hashed_counter_spec.rb
421
+ - spec/pulse_meter/sensor/timelined/hashed_indicator_spec.rb
422
+ - spec/pulse_meter/sensor/timelined/indicator_spec.rb
419
423
  - spec/pulse_meter/sensor/timelined/max_spec.rb
420
424
  - spec/pulse_meter/sensor/timelined/median_spec.rb
421
425
  - spec/pulse_meter/sensor/timelined/min_spec.rb
@@ -468,7 +472,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
468
472
  version: '0'
469
473
  segments:
470
474
  - 0
471
- hash: -2459703575174503606
475
+ hash: -450227772948230282
472
476
  requirements: []
473
477
  rubyforge_project:
474
478
  rubygems_version: 1.8.24
@@ -485,11 +489,13 @@ test_files:
485
489
  - spec/pulse_meter/sensor/hashed_counter_spec.rb
486
490
  - spec/pulse_meter/sensor/hashed_indicator_spec.rb
487
491
  - spec/pulse_meter/sensor/indicator_spec.rb
488
- - spec/pulse_meter/sensor/remote_spec.rb
492
+ - spec/pulse_meter/sensor/multi_spec.rb
489
493
  - spec/pulse_meter/sensor/timeline_spec.rb
490
494
  - spec/pulse_meter/sensor/timelined/average_spec.rb
491
495
  - spec/pulse_meter/sensor/timelined/counter_spec.rb
492
496
  - spec/pulse_meter/sensor/timelined/hashed_counter_spec.rb
497
+ - spec/pulse_meter/sensor/timelined/hashed_indicator_spec.rb
498
+ - spec/pulse_meter/sensor/timelined/indicator_spec.rb
493
499
  - spec/pulse_meter/sensor/timelined/max_spec.rb
494
500
  - spec/pulse_meter/sensor/timelined/median_spec.rb
495
501
  - spec/pulse_meter/sensor/timelined/min_spec.rb
@@ -1,60 +0,0 @@
1
- require 'socket'
2
- require 'json'
3
-
4
- module PulseMeter
5
- module Sensor
6
-
7
- # Remote sensor, i.e. a simple UDP proxy for sending data without
8
- # taking in account backend performance issues
9
- class Remote < Base
10
-
11
- DEFAULT_PORT = 27182
12
- DEFAULT_HOST = 'localhost'
13
-
14
- # @!attribute [r] name
15
- # @return [String] sensor name
16
- attr_reader :name
17
-
18
- # Initializes sensor and creates UDP socket
19
- # @param name [String] sensor name
20
- # @option options [Symbol] :host host for remote pulse-meter daemon
21
- # @option options [Symbol] :port port for remote pulse-meter daemon
22
- # @raise [BadSensorName] if sensor name is malformed
23
- # @raise [ConnectionError] if invalid host or port are provided
24
- def initialize(name, options={})
25
- @name = name.to_s
26
- raise BadSensorName, @name unless @name =~ /\A\w+\z/
27
- @host = options[:host].to_s || DEFAULT_HOST
28
- @port = options[:port].to_i || DEFAULT_PORT
29
- @socket = UDPSocket.new
30
- end
31
-
32
- # Send value to remote sensor
33
- # @param value value for remote sensor
34
- # @raise [ConnectionError] if remote daemon is not available
35
- # @raise [MessageTooLarge] if event data is too large to be serialized into a UDP datagram
36
- def event(value)
37
- events(name => value)
38
- end
39
-
40
- # Send values to multiple remote sensors
41
- # @param event_data hash with remote sensor names as keys end event value for each value as sensor
42
- def events(event_data)
43
- raise ArgumentError unless event_data.is_a?(Hash)
44
- socket_action do
45
- @socket.send(event_data.to_json, 0, @host, @port)
46
- end
47
- end
48
-
49
- private
50
-
51
- def socket_action
52
- yield
53
- rescue SocketError, Errno::EADDRNOTAVAIL, Errno::EINVAL => exc
54
- raise PulseMeter::Remote::ConnectionError, exc.to_s
55
- rescue Errno::EMSGSIZE => exc
56
- raise PulseMeter::Remote::MessageTooLarge, exc.to_s
57
- end
58
- end
59
- end
60
- end
@@ -1,86 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe PulseMeter::Sensor::Remote do
4
-
5
- def data_sent_to(host, port)
6
- socket = UDPSocket.new
7
- socket.bind(host, port)
8
- yield
9
- data, _ = socket.recvfrom(65000)
10
- socket.close
11
- data
12
- end
13
-
14
- let(:host){'localhost'}
15
- let(:port){56789}
16
- let(:sensor){described_class.new(:some_remote_sensor, host: host, port: port)}
17
-
18
- describe "#new" do
19
- it "should raise exception if sensor name is bad" do
20
- expect{described_class.new("aa bb")}.to raise_exception(PulseMeter::BadSensorName)
21
- end
22
- end
23
-
24
- describe "#event" do
25
- it "should send event data to remote host and port" do
26
- data_sent_to(host, port) {
27
- sensor.event(123)
28
- }.should_not be_empty
29
- end
30
-
31
- it "should use sensor name as a single key and sent data as its value" do
32
- data = data_sent_to(host, port) do
33
- sensor.event(123)
34
- end
35
- JSON.parse(data).should == {sensor.name => 123}
36
- end
37
-
38
- it "should raise MessageTooLarge if message is too long" do
39
- expect{ sensor.event("123" * 100000) }.to raise_exception(PulseMeter::Remote::MessageTooLarge)
40
- end
41
-
42
-
43
- it "should raise PulseMeter::Remote::ConnectionError if remote host is invalid" do
44
- expect{described_class.new("xxx", host: "bad host").event(123)}.to raise_exception(PulseMeter::Remote::ConnectionError)
45
- end
46
-
47
- it "should raise PulseMeter::Remote::ConnectionError if remote port is invalid" do
48
- expect{described_class.new("xxx", port: -123).event(123)}.to raise_exception(PulseMeter::Remote::ConnectionError)
49
- expect{described_class.new("xxx", port: 'bad port').event(123)}.to raise_exception(PulseMeter::Remote::ConnectionError)
50
- end
51
-
52
- end
53
-
54
- describe "#events" do
55
- it "should send event data to remote host and port" do
56
- data_sent_to(host, port) {
57
- sensor.events(a: 1, b: 2)
58
- }.should_not be_empty
59
- end
60
-
61
- it "should use sensor name as a single key and sent data as its value" do
62
- data = data_sent_to(host, port) do
63
- sensor.events(a: 1, b: 2)
64
- end
65
- JSON.parse(data).should == {"a" => 1, "b" => 2}
66
- end
67
-
68
- it "should raise ArgumentError if argument is not a hash" do
69
- expect{ sensor.events(1213) }.to raise_exception(ArgumentError)
70
- end
71
-
72
- it "should raise MessageTooLarge if message is too long" do
73
- expect{ sensor.events(a: "x" * 100000) }.to raise_exception(PulseMeter::Remote::MessageTooLarge)
74
- end
75
-
76
- it "should raise PulseMeter::Remote::ConnectionError if remote host is invalid" do
77
- expect{described_class.new("xxx", host: "bad host").events(a: 123)}.to raise_exception(PulseMeter::Remote::ConnectionError)
78
- end
79
-
80
- it "should raise PulseMeter::Remote::ConnectionError if remote port is invalid" do
81
- expect{described_class.new("xxx", port: -123).events(a: 123)}.to raise_exception(PulseMeter::Remote::ConnectionError)
82
- expect{described_class.new("xxx", port: 'bad port').events(a: 123)}.to raise_exception(PulseMeter::Remote::ConnectionError)
83
- end
84
- end
85
-
86
- end