pulse-meter 0.0.1

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.
Files changed (96) hide show
  1. data/.gitignore +19 -0
  2. data/.rbenv-version +1 -0
  3. data/.rspec +1 -0
  4. data/.rvmrc +1 -0
  5. data/.travis.yml +4 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE +22 -0
  8. data/Procfile +3 -0
  9. data/README.md +440 -0
  10. data/Rakefile +53 -0
  11. data/bin/pulse +6 -0
  12. data/examples/basic.ru +109 -0
  13. data/examples/basic_sensor_data.rb +38 -0
  14. data/examples/full/Procfile +2 -0
  15. data/examples/full/client.rb +82 -0
  16. data/examples/full/server.ru +114 -0
  17. data/examples/minimal/Procfile +2 -0
  18. data/examples/minimal/client.rb +16 -0
  19. data/examples/minimal/server.ru +20 -0
  20. data/examples/readme_client_example.rb +52 -0
  21. data/lib/cmd.rb +150 -0
  22. data/lib/pulse-meter.rb +17 -0
  23. data/lib/pulse-meter/mixins/dumper.rb +72 -0
  24. data/lib/pulse-meter/mixins/utils.rb +91 -0
  25. data/lib/pulse-meter/sensor.rb +44 -0
  26. data/lib/pulse-meter/sensor/base.rb +75 -0
  27. data/lib/pulse-meter/sensor/counter.rb +36 -0
  28. data/lib/pulse-meter/sensor/hashed_counter.rb +31 -0
  29. data/lib/pulse-meter/sensor/indicator.rb +33 -0
  30. data/lib/pulse-meter/sensor/timeline.rb +180 -0
  31. data/lib/pulse-meter/sensor/timelined/average.rb +26 -0
  32. data/lib/pulse-meter/sensor/timelined/counter.rb +16 -0
  33. data/lib/pulse-meter/sensor/timelined/hashed_counter.rb +22 -0
  34. data/lib/pulse-meter/sensor/timelined/max.rb +25 -0
  35. data/lib/pulse-meter/sensor/timelined/median.rb +14 -0
  36. data/lib/pulse-meter/sensor/timelined/min.rb +25 -0
  37. data/lib/pulse-meter/sensor/timelined/percentile.rb +31 -0
  38. data/lib/pulse-meter/version.rb +3 -0
  39. data/lib/pulse-meter/visualize/app.rb +43 -0
  40. data/lib/pulse-meter/visualize/dsl.rb +0 -0
  41. data/lib/pulse-meter/visualize/dsl/errors.rb +46 -0
  42. data/lib/pulse-meter/visualize/dsl/layout.rb +55 -0
  43. data/lib/pulse-meter/visualize/dsl/page.rb +50 -0
  44. data/lib/pulse-meter/visualize/dsl/sensor.rb +21 -0
  45. data/lib/pulse-meter/visualize/dsl/widget.rb +84 -0
  46. data/lib/pulse-meter/visualize/layout.rb +54 -0
  47. data/lib/pulse-meter/visualize/page.rb +30 -0
  48. data/lib/pulse-meter/visualize/public/css/application.css +19 -0
  49. data/lib/pulse-meter/visualize/public/css/bootstrap.css +4883 -0
  50. data/lib/pulse-meter/visualize/public/css/bootstrap.min.css +729 -0
  51. data/lib/pulse-meter/visualize/public/favicon.ico +0 -0
  52. data/lib/pulse-meter/visualize/public/img/glyphicons-halflings-white.png +0 -0
  53. data/lib/pulse-meter/visualize/public/img/glyphicons-halflings.png +0 -0
  54. data/lib/pulse-meter/visualize/public/js/application.coffee +262 -0
  55. data/lib/pulse-meter/visualize/public/js/application.js +279 -0
  56. data/lib/pulse-meter/visualize/public/js/backbone-min.js +38 -0
  57. data/lib/pulse-meter/visualize/public/js/bootstrap.js +1835 -0
  58. data/lib/pulse-meter/visualize/public/js/highcharts.js +203 -0
  59. data/lib/pulse-meter/visualize/public/js/jquery-1.7.2.min.js +4 -0
  60. data/lib/pulse-meter/visualize/public/js/json2.js +487 -0
  61. data/lib/pulse-meter/visualize/public/js/underscore-min.js +32 -0
  62. data/lib/pulse-meter/visualize/sensor.rb +60 -0
  63. data/lib/pulse-meter/visualize/views/main.haml +40 -0
  64. data/lib/pulse-meter/visualize/widget.rb +68 -0
  65. data/lib/pulse-meter/visualizer.rb +30 -0
  66. data/lib/test_helpers/matchers.rb +36 -0
  67. data/pulse-meter.gemspec +39 -0
  68. data/spec/pulse_meter/mixins/dumper_spec.rb +158 -0
  69. data/spec/pulse_meter/mixins/utils_spec.rb +134 -0
  70. data/spec/pulse_meter/sensor/base_spec.rb +97 -0
  71. data/spec/pulse_meter/sensor/counter_spec.rb +54 -0
  72. data/spec/pulse_meter/sensor/hashed_counter_spec.rb +39 -0
  73. data/spec/pulse_meter/sensor/indicator_spec.rb +43 -0
  74. data/spec/pulse_meter/sensor/timeline_spec.rb +58 -0
  75. data/spec/pulse_meter/sensor/timelined/average_spec.rb +6 -0
  76. data/spec/pulse_meter/sensor/timelined/counter_spec.rb +6 -0
  77. data/spec/pulse_meter/sensor/timelined/hashed_counter_spec.rb +8 -0
  78. data/spec/pulse_meter/sensor/timelined/max_spec.rb +7 -0
  79. data/spec/pulse_meter/sensor/timelined/median_spec.rb +7 -0
  80. data/spec/pulse_meter/sensor/timelined/min_spec.rb +7 -0
  81. data/spec/pulse_meter/sensor/timelined/percentile_spec.rb +17 -0
  82. data/spec/pulse_meter/visualize/app_spec.rb +27 -0
  83. data/spec/pulse_meter/visualize/dsl/layout_spec.rb +64 -0
  84. data/spec/pulse_meter/visualize/dsl/page_spec.rb +75 -0
  85. data/spec/pulse_meter/visualize/dsl/sensor_spec.rb +30 -0
  86. data/spec/pulse_meter/visualize/dsl/widget_spec.rb +127 -0
  87. data/spec/pulse_meter/visualize/layout_spec.rb +55 -0
  88. data/spec/pulse_meter/visualize/page_spec.rb +150 -0
  89. data/spec/pulse_meter/visualize/sensor_spec.rb +120 -0
  90. data/spec/pulse_meter/visualize/widget_spec.rb +113 -0
  91. data/spec/pulse_meter/visualizer_spec.rb +42 -0
  92. data/spec/pulse_meter_spec.rb +16 -0
  93. data/spec/shared_examples/timeline_sensor.rb +279 -0
  94. data/spec/shared_examples/timelined_subclass.rb +23 -0
  95. data/spec/spec_helper.rb +29 -0
  96. metadata +435 -0
@@ -0,0 +1,134 @@
1
+ require 'spec_helper'
2
+
3
+ describe PulseMeter::Mixins::Utils do
4
+ class Dummy
5
+ include PulseMeter::Mixins::Utils
6
+ end
7
+
8
+ let(:dummy){ Dummy.new }
9
+
10
+ describe '#constantize' do
11
+ context "when argument is a string with a valid class name" do
12
+ it "should return class" do
13
+ dummy.constantize("PulseMeter::Mixins::Utils").should == PulseMeter::Mixins::Utils
14
+ end
15
+ end
16
+ context "when argument is a string with invalid class name" do
17
+ it "should return nil" do
18
+ dummy.constantize("Pumpkin::Eater").should be_nil
19
+ end
20
+ end
21
+ context "when argument is not a string" do
22
+ it "should return nil" do
23
+ dummy.constantize({}).should be_nil
24
+ end
25
+ end
26
+ end
27
+
28
+ describe "#assert_positive_integer!" do
29
+ it "should extract integer value from hash by passed key" do
30
+ dummy.assert_positive_integer!({:val => 4}, :val).should == 4
31
+ end
32
+
33
+ context "when no default value given" do
34
+ context "when the value by the passed key is not integer" do
35
+ it "should convert non-integers to integers" do
36
+ dummy.assert_positive_integer!({:val => 4.4}, :val).should == 4
37
+ end
38
+
39
+ it "should change the original value to the obtained integer" do
40
+ h = {:val => 4.4}
41
+ dummy.assert_positive_integer!(h, :val).should == 4
42
+ h[:val].should == 4
43
+ end
44
+
45
+ it "should raise exception if the original value cannot be converted to integer"do
46
+ expect{ dummy.assert_positive_integer!({:val => :bad_int}, :val) }.to raise_exception(ArgumentError)
47
+ end
48
+ end
49
+
50
+ it "should raise exception if the value is not positive" do
51
+ expect{ dummy.assert_positive_integer!({:val => -1}, :val) }.to raise_exception(ArgumentError)
52
+ end
53
+
54
+ it "should raise exception if the value is not defined" do
55
+ expect{ dummy.assert_positive_integer!({}, :val) }.to raise_exception(ArgumentError)
56
+ end
57
+ end
58
+
59
+ context "when default value given" do
60
+ it "should prefer value from options to default" do
61
+ dummy.assert_positive_integer!({:val => 4}, :val, 22).should == 4
62
+ end
63
+
64
+ it "should use default value when there is no one in options" do
65
+ dummy.assert_positive_integer!({}, :val, 22).should == 22
66
+ end
67
+
68
+ it "should check default value if it is to be used" do
69
+ expect{dummy.assert_positive_integer!({}, :val, :bad)}.to raise_exception(ArgumentError)
70
+ expect{dummy.assert_positive_integer!({}, :val, -1)}.to raise_exception(ArgumentError)
71
+ end
72
+ end
73
+ end
74
+
75
+ describe "#assert_ranged_float!" do
76
+
77
+ it "should extract float value from hash by passed key" do
78
+ dummy.assert_ranged_float!({:val => 4}, :val, 0, 100).should be_generally_equal(4)
79
+ end
80
+
81
+ context "when the value by the passed key is not float" do
82
+ it "should convert non-floats to floats" do
83
+ dummy.assert_ranged_float!({:val => "4.0000"}, :val, 0, 100).should be_generally_equal(4)
84
+ end
85
+
86
+ it "should change the original value to the obtained float" do
87
+ h = {:val => "4.000"}
88
+ dummy.assert_ranged_float!(h, :val, 0, 100).should be_generally_equal(4)
89
+ h[:val].should be_generally_equal(4)
90
+ end
91
+
92
+ it "should raise exception if the original value cannot be converted to float" do
93
+ expect{ dummy.assert_ranged_float!({:val => :bad_float}, :val, 0, 100) }.to raise_exception(ArgumentError)
94
+ end
95
+ end
96
+
97
+ it "should raise exception if the value is not within range" do
98
+ expect{ dummy.assert_ranged_float!({:val => -0.1}, :val, 0, 100) }.to raise_exception(ArgumentError)
99
+ expect{ dummy.assert_ranged_float!({:val => 100.1}, :val, 0, 100) }.to raise_exception(ArgumentError)
100
+ end
101
+
102
+ it "should raise exception if the value is not defined" do
103
+ expect{ dummy.assert_ranged_float!({}, :val) }.to raise_exception(ArgumentError)
104
+ end
105
+ end
106
+
107
+ describe "#uniqid" do
108
+ it "should return uniq strings" do
109
+ uniq_values = (1..1000).map{|_| dummy.uniqid}
110
+ uniq_values.uniq.count.should == uniq_values.count
111
+ end
112
+ end
113
+
114
+ describe "#titleize" do
115
+ it "should convert identificator to title" do
116
+ dummy.titleize("aaa_bbb").should == 'Aaa Bbb'
117
+ dummy.titleize(:aaa_bbb).should == 'Aaa Bbb'
118
+ dummy.titleize("aaa bbb").should == 'Aaa Bbb'
119
+ end
120
+ end
121
+
122
+ describe "#camelize" do
123
+ it "should camelize string" do
124
+ dummy.camelize("aa_bb_cc").should == "aaBbCc"
125
+ dummy.camelize("aa_bb_cc", true).should == "AaBbCc"
126
+ end
127
+ end
128
+
129
+ describe "#camelize_keys" do
130
+ it "should deeply camelize keys in hashes" do
131
+ dummy.camelize_keys({ :aa_bb_cc => [ { :dd_ee => 123 }, 456 ] }).should =={ 'aaBbCc' => [ { 'ddEe' => 123 }, 456 ] }
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,97 @@
1
+ require 'spec_helper'
2
+
3
+ describe PulseMeter::Sensor::Base do
4
+ let(:name){ :some_sensor }
5
+ let(:description) {"Le awesome description"}
6
+ let!(:sensor){ described_class.new(name) }
7
+ let(:redis){ PulseMeter.redis }
8
+
9
+ describe '#initialize' do
10
+ context 'when PulseMeter.redis is not initialized' do
11
+ it "should raise RedisNotInitialized exception" do
12
+ PulseMeter.redis = nil
13
+ expect{ described_class.new(:foo) }.to raise_exception(PulseMeter::RedisNotInitialized)
14
+ end
15
+ end
16
+
17
+ context 'when PulseMeter.redis is initialized' do
18
+
19
+ context 'when passed sensor name is bad' do
20
+ it "should raise BadSensorName exception" do
21
+ ['name with whitespace', 'name|with|bad|characters'].each do |bad_name|
22
+ expect{ described_class.new(bad_name) }.to raise_exception(PulseMeter::BadSensorName)
23
+ end
24
+ end
25
+ end
26
+
27
+ context 'when passed sensor name is valid' do
28
+ it "should successfully create object" do
29
+ described_class.new(:foo).should_not be_nil
30
+ end
31
+
32
+ it "should initialize attributes #redis and #name" do
33
+ sensor = described_class.new(:foo)
34
+ sensor.name.should == 'foo'
35
+ sensor.redis.should == PulseMeter.redis
36
+ end
37
+
38
+ it "should save dump to redis automatically to let the object be restored by name" do
39
+ described_class.restore(name).should be_instance_of(described_class)
40
+ end
41
+
42
+ it "should annotate object if annotation given" do
43
+ described_class.new(:foo, :annotation => "annotation")
44
+ sensor = described_class.restore(:foo)
45
+ sensor.annotation.should == "annotation"
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ describe '#annotate' do
52
+
53
+ it "should store sensor annotation in redis" do
54
+ expect {sensor.annotate(description)}.to change{redis.keys('*').count}.by(1)
55
+ end
56
+
57
+ end
58
+
59
+ describe '#annotation' do
60
+ context "when sensor was annotated" do
61
+ it "should return stored annotation" do
62
+ sensor.annotate(description)
63
+ sensor.annotation.should == description
64
+ end
65
+ end
66
+
67
+ context "when sensor was not annotated" do
68
+ it "should return nil" do
69
+ sensor.annotation.should be_nil
70
+ end
71
+ end
72
+
73
+ context "after sensor data was cleaned" do
74
+ it "should return nil" do
75
+ sensor.annotate(description)
76
+ sensor.cleanup
77
+ sensor.annotation.should be_nil
78
+ end
79
+ end
80
+ end
81
+
82
+ describe "#cleanup" do
83
+ it "should remove from redis all sensor data" do
84
+ sensor.event(123)
85
+ sensor.annotate(description)
86
+ sensor.cleanup
87
+ redis.keys('*').should be_empty
88
+ end
89
+ end
90
+
91
+ describe "#event" do
92
+ it "should actually do nothing for base sensor" do
93
+ sensor.event(nil)
94
+ end
95
+ end
96
+
97
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ describe PulseMeter::Sensor::Counter do
4
+ let(:name){ :some_counter }
5
+ let(:sensor){ described_class.new(name) }
6
+ let(:redis){ PulseMeter.redis }
7
+
8
+ describe "#event" do
9
+ it "should increment sensor value by passed value" do
10
+ expect{ sensor.event(10) }.to change{ sensor.value }.from(0).to(10)
11
+ expect{ sensor.event(15) }.to change{ sensor.value }.from(10).to(25)
12
+ end
13
+
14
+ it "should truncate increment value" do
15
+ expect{ sensor.event(10.4) }.to change{ sensor.value }.from(0).to(10)
16
+ expect{ sensor.event(15.1) }.to change{ sensor.value }.from(10).to(25)
17
+ end
18
+ end
19
+
20
+ describe "#value_key" do
21
+ it "should be composed of sensor name and pulse_meter:value: prefix" do
22
+ sensor.value_key.should == "pulse_meter:value:#{name}"
23
+ end
24
+ end
25
+
26
+ describe "#value" do
27
+ it "should have initial value 0" do
28
+ sensor.value.should == 0
29
+ end
30
+
31
+ it "should store stringified value by value_key" do
32
+ sensor.event(123)
33
+ sensor.value.should == 123
34
+ redis.get(sensor.value_key).should == '123'
35
+ end
36
+ end
37
+
38
+ describe "#incr" do
39
+ it "should increment value by 1" do
40
+ expect{ sensor.incr }.to change{ sensor.value }.from(0).to(1)
41
+ expect{ sensor.incr }.to change{ sensor.value }.from(1).to(2)
42
+ end
43
+ end
44
+
45
+ describe "#cleanup" do
46
+ it "should remove all sensor data" do
47
+ sensor.annotate("My Counter")
48
+ sensor.event(123)
49
+ sensor.cleanup
50
+ redis.keys('*').should be_empty
51
+ end
52
+ end
53
+
54
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe PulseMeter::Sensor::HashedCounter do
4
+ let(:name){ :some_counter }
5
+ let(:sensor){ described_class.new(name) }
6
+ let(:redis){ PulseMeter.redis }
7
+
8
+ describe "#event" do
9
+ it "should increment sensor value by passed value" do
10
+ expect{ sensor.event({"foo" => 10}) }.to change{ sensor.value["foo"] }.from(0).to(10)
11
+ expect{ sensor.event({"foo" => 15}) }.to change{ sensor.value["foo"] }.from(10).to(25)
12
+ end
13
+
14
+ it "should truncate increment value" do
15
+ expect{ sensor.event({"foo" => 10.4}) }.to change{ sensor.value["foo"] }.from(0).to(10)
16
+ expect{ sensor.event({"foo" => 15.1}) }.to change{ sensor.value["foo"] }.from(10).to(25)
17
+ end
18
+ end
19
+
20
+ describe "#value" do
21
+ it "should have initial value 0" do
22
+ sensor.value["foo"].should == 0
23
+ end
24
+
25
+ it "should store redis hash by value_key" do
26
+ sensor.event({"foo" => 1})
27
+ sensor.value.should == {"foo" => 1}
28
+ redis.hgetall(sensor.value_key).should == {"foo" => "1"}
29
+ end
30
+ end
31
+
32
+ describe "#incr" do
33
+ it "should increment key value by 1" do
34
+ expect{ sensor.incr("foo") }.to change{ sensor.value["foo"] }.from(0).to(1)
35
+ expect{ sensor.incr("foo") }.to change{ sensor.value["foo"] }.from(1).to(2)
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe PulseMeter::Sensor::Indicator do
4
+ let(:name){ :some_value }
5
+ let(:sensor){ described_class.new(name) }
6
+ let(:redis){ PulseMeter.redis }
7
+
8
+ describe "#event" do
9
+ it "should set sensor value to passed value" do
10
+ expect{ sensor.event(10.4) }.to change{ sensor.value }.from(0).to(10.4)
11
+ expect{ sensor.event(15.1) }.to change{ sensor.value }.from(10.4).to(15.1)
12
+ end
13
+ end
14
+
15
+ describe "#value_key" do
16
+ it "should be composed of sensor name and pulse_meter:value: prefix" do
17
+ sensor.value_key.should == "pulse_meter:value:#{name}"
18
+ end
19
+ end
20
+
21
+ describe "#value" do
22
+ it "should have initial value 0" do
23
+ sensor.value.should == 0
24
+ end
25
+
26
+ it "should store stringified value by value_key" do
27
+ sensor.event(123)
28
+ sensor.value.should == 123
29
+ redis.get(sensor.value_key) == '123'
30
+ end
31
+ end
32
+
33
+ describe "#cleanup" do
34
+ it "should remove all sensor data" do
35
+ sensor.annotate("My Indicator")
36
+ sensor.event(123)
37
+ sensor.cleanup
38
+ redis.keys('*').should be_empty
39
+ end
40
+ end
41
+
42
+ end
43
+
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe PulseMeter::Sensor::Timeline do
4
+ let(:name){ :some_value_with_history }
5
+ let(:ttl){ 100 }
6
+ let(:raw_data_ttl){ 10 }
7
+ let(:interval){ 5 }
8
+ let(:reduce_delay){ 3 }
9
+ let(:good_init_values){ {:ttl => ttl, :raw_data_ttl => raw_data_ttl, :interval => interval, :reduce_delay => reduce_delay} }
10
+ let(:sensor){ described_class.new(name, good_init_values) }
11
+ let(:redis){ PulseMeter.redis }
12
+
13
+ it_should_behave_like "timeline sensor"
14
+
15
+ describe '#new' do
16
+ INIT_VALUE_NAMES = {
17
+ :with_defaults => [:raw_data_ttl, :reduce_delay],
18
+ :without_defaults => [:ttl, :interval]
19
+ }
20
+
21
+ shared_examples_for "error raiser" do |value_names, bad_values|
22
+ value_names.each do |value|
23
+ bad_values.each do |bad_value|
24
+ it "should raise exception if a bad value #{bad_value.inspect} passed for #{value.inspect}" do
25
+ expect{ described_class.new(name, good_init_values.merge(value => bad_value)) }.to raise_exception(ArgumentError)
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ it "should initialize #ttl #raw_data_ttl #interval and #name attributes" do
32
+ sensor.name.should == name.to_s
33
+
34
+ sensor.ttl.should == ttl
35
+ sensor.raw_data_ttl.should == raw_data_ttl
36
+ sensor.interval.should == interval
37
+ end
38
+
39
+ it_should_behave_like "error raiser", INIT_VALUE_NAMES[:without_defaults], [:bad, -1, nil]
40
+ it_should_behave_like "error raiser", INIT_VALUE_NAMES[:with_defaults], [:bad, -1]
41
+
42
+ INIT_VALUE_NAMES[:with_defaults].each do |value|
43
+ it "should not raise exception if #{value.inspect} is not defined" do
44
+ values = good_init_values
45
+ values.delete(value)
46
+ expect {described_class.new(name, good_init_values)}.not_to raise_exception(ArgumentError)
47
+ end
48
+
49
+ it "should assign default value to #{value.inspect} if it is not defined" do
50
+ values = good_init_values
51
+ values.delete(value)
52
+ obj = described_class.new(name, good_init_values)
53
+ obj.send(value).should be_kind_of(Fixnum)
54
+ end
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+
3
+ describe PulseMeter::Sensor::Timelined::Average do
4
+ it_should_behave_like "timeline sensor"
5
+ it_should_behave_like "timelined subclass", [1, 2], 1.5
6
+ end
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+
3
+ describe PulseMeter::Sensor::Timelined::Counter do
4
+ it_should_behave_like "timeline sensor"
5
+ it_should_behave_like "timelined subclass", [1, 2], 3
6
+ end