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 +3 -0
- data/lib/pulse-meter/mixins/cmd.rb +5 -1
- data/lib/pulse-meter/mixins/utils.rb +25 -0
- data/lib/pulse-meter/sensor/configuration.rb +9 -1
- data/lib/pulse-meter/sensor/multi.rb +93 -0
- data/lib/pulse-meter/sensor/timelined/hashed_indicator.rb +30 -0
- data/lib/pulse-meter/sensor/timelined/indicator.rb +23 -0
- data/lib/pulse-meter/sensor.rb +4 -1
- data/lib/pulse-meter/version.rb +1 -1
- data/spec/pulse_meter/mixins/utils_spec.rb +43 -0
- data/spec/pulse_meter/sensor/configuration_spec.rb +28 -11
- data/spec/pulse_meter/sensor/multi_spec.rb +134 -0
- data/spec/pulse_meter/sensor/timelined/hashed_indicator_spec.rb +8 -0
- data/spec/pulse_meter/sensor/timelined/indicator_spec.rb +6 -0
- metadata +12 -6
- data/lib/pulse-meter/sensor/remote.rb +0 -60
- data/spec/pulse_meter/sensor/remote_spec.rb +0 -86
data/README.md
CHANGED
@@ -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
|
data/lib/pulse-meter/sensor.rb
CHANGED
@@ -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/
|
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'
|
data/lib/pulse-meter/version.rb
CHANGED
@@ -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
|
-
|
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
|
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.
|
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-
|
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/
|
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/
|
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: -
|
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/
|
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
|