pulse-meter 0.2.6 → 0.2.7

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/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