pulse-meter 0.2.7 → 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,24 @@
1
+ module Enumerable
2
+ require 'csv'
3
+ require 'terminal-table'
4
+
5
+ def convert_time
6
+ map {|el| el.is_a?(Time) ? el.to_i : el}
7
+ end
8
+
9
+ def to_table(format = nil)
10
+ if "csv" == format.to_s
11
+ CSV.generate(:col_sep => ';') do |csv|
12
+ self.each {|row| csv << row.convert_time}
13
+ end
14
+ else
15
+ self.each_with_object(Terminal::Table.new) do |row, table|
16
+ table << if row.respond_to?(:map)
17
+ row.map(&:to_s)
18
+ else
19
+ row
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,30 +1,4 @@
1
- module Enumerable
2
- def convert_time
3
- map do |el|
4
- if el.is_a?(Time)
5
- el.to_i
6
- else
7
- el
8
- end
9
- end
10
- end
11
-
12
- def to_table(format = nil)
13
- if "csv" == format
14
- CSV.generate(:col_sep => ';') do |csv|
15
- self.each {|row| csv << row.convert_time}
16
- end
17
- else
18
- self.each_with_object(Terminal::Table.new) do |row, table|
19
- table << if row.respond_to?(:map)
20
- row.map(&:to_s)
21
- else
22
- row
23
- end
24
- end
25
- end
26
- end
27
- end
1
+ require 'pulse-meter/extensions/enumerable'
28
2
 
29
3
  module PulseMeter
30
4
  module Mixins
@@ -52,7 +26,7 @@ module PulseMeter
52
26
  data = [
53
27
  ["Name", "Class", "ttl", "raw data ttl", "interval", "reduce delay"],
54
28
  ]
55
- data << :separator unless format == 'csv'
29
+ data << :separator unless 'csv' == format.to_s
56
30
  all_sensors.each do |s|
57
31
  if s.kind_of? PulseMeter::Sensor::Timeline
58
32
  data << [s.name, s.class, s.ttl, s.raw_data_ttl, s.interval, s.reduce_delay]
@@ -1,11 +1,16 @@
1
1
  module PulseMeter
2
2
  module Sensor
3
+ # Constructs multiple sensors from configuration passed
3
4
  class Configuration
4
5
  include PulseMeter::Mixins::Utils
5
6
  include Enumerable
6
7
 
8
+ # @!attribute [r] sensors
9
+ # @return [Hash] hash of sensors with names as keys and sensors as values
7
10
  attr_reader :sensors
8
11
 
12
+ # Initializes sensors
13
+ # @param opts [Hash] sensors' configuration
9
14
  def initialize(opts = {})
10
15
  @sensors = {}
11
16
  opts.each do |name, opts|
@@ -13,6 +18,9 @@ module PulseMeter
13
18
  end
14
19
  end
15
20
 
21
+ # Adds sensor
22
+ # @param name [Symbol] sensor name
23
+ # @param opts [Hash] sensor options
16
24
  def add_sensor(name, opts)
17
25
  sensor_type = opts.respond_to?(:sensor_type) ? opts.sensor_type : opts[:sensor_type]
18
26
  klass_s = sensor_class(sensor_type)
@@ -22,16 +30,22 @@ module PulseMeter
22
30
  @sensors[name.to_s] = klass.new(name, symbolize_keys(args.to_hash))
23
31
  end
24
32
 
33
+ # Returns previously initialized sensor by name
34
+ # @param name [Symbol] sensor name
35
+ # @return [Sensor] sensor
25
36
  def sensor(name)
26
37
  @sensors[name.to_s]
27
38
  end
28
39
 
40
+ # Iterates over each sensor
29
41
  def each
30
42
  @sensors.each_value do |sensor|
31
43
  yield(sensor)
32
44
  end
33
45
  end
34
46
 
47
+ # Invokes event for any sensor
48
+ # @raise [ArgumentError] unless sensor exists
35
49
  def method_missing(name, *args)
36
50
  name = name.to_s
37
51
  if @sensors.has_key?(name)
@@ -25,7 +25,10 @@ module PulseMeter
25
25
  # @param data [Hash] hash where keys represent counter keys
26
26
  # and values are increments for their keys
27
27
  def process_event(data)
28
- data.each_pair {|k, v| redis.hincrby(value_key, k, v.to_i)}
28
+ data.each_pair do |k, v|
29
+ redis.hincrby(value_key, k, v.to_i)
30
+ redis.hincrby(value_key, :total, v.to_i)
31
+ end
29
32
  end
30
33
 
31
34
  end
@@ -108,12 +108,17 @@ module PulseMeter
108
108
  # Returts sensor data within given time
109
109
  # @param from [Time] lower bound
110
110
  # @param till [Time] upper bound
111
+ # @param skip_optimization [Boolean] must be set to true to skip interval optimization
111
112
  # @return [Array<SensorData>]
112
113
  # @raise ArgumentError if argumets are not valid time objects
113
- def timeline_within(from, till)
114
+ def timeline_within(from, till, skip_optimization = false)
114
115
  raise ArgumentError unless from.kind_of?(Time) && till.kind_of?(Time)
115
116
  start_time, end_time = from.to_i, till.to_i
116
- actual_interval = optimized_inteval(start_time, end_time)
117
+ actual_interval = if skip_optimization
118
+ interval
119
+ else
120
+ optimized_interval(start_time, end_time)
121
+ end
117
122
  current_interval_id = get_interval_id(start_time) + actual_interval
118
123
  keys = []
119
124
  ids = []
@@ -134,19 +139,6 @@ module PulseMeter
134
139
  res
135
140
  end
136
141
 
137
- # Makes interval optimization so that the requested timespan contains less than MAX_TIMESPAN_POINTS values
138
- # @param start_time [Fixnum] unix timestamp of timespan start
139
- # @param end_time [Fixnum] unix timestamp of timespan start
140
- # @return [Fixnum] optimized interval in seconds.
141
- def optimized_inteval(start_time, end_time)
142
- res_interval = interval
143
- timespan = end_time - start_time
144
- while timespan / res_interval > MAX_TIMESPAN_POINTS - 1
145
- res_interval *= 2
146
- end
147
- res_interval
148
- end
149
-
150
142
  # Returns sensor data for given interval making in-memory summarization
151
143
  # and returns calculated value
152
144
  # @param interval_id [Fixnum]
@@ -174,11 +166,7 @@ module PulseMeter
174
166
  keys << raw_data_key(current_interval_id)
175
167
  current_interval_id += interval
176
168
  end
177
- if keys.empty?
178
- 0
179
- else
180
- redis.del(*keys)
181
- end
169
+ keys.empty? ? 0 : redis.del(*keys)
182
170
  end
183
171
 
184
172
  # Returns Redis key by which raw data for current interval is stored
@@ -255,6 +243,20 @@ module PulseMeter
255
243
  end
256
244
  end
257
245
 
246
+ # Makes interval optimization so that the requested timespan contains less than MAX_TIMESPAN_POINTS values
247
+ # @param start_time [Fixnum] unix timestamp of timespan start
248
+ # @param end_time [Fixnum] unix timestamp of timespan start
249
+ # @return [Fixnum] optimized interval in seconds.
250
+ def optimized_interval(start_time, end_time)
251
+ res_interval = interval
252
+ timespan = end_time - start_time
253
+ while timespan / res_interval > MAX_TIMESPAN_POINTS - 1
254
+ res_interval *= 2
255
+ end
256
+ res_interval
257
+ end
258
+
259
+
258
260
  end
259
261
  end
260
262
  end
@@ -7,7 +7,10 @@ module PulseMeter
7
7
  # Good replacement for multiple counters to be visualized together
8
8
  class HashedCounter < Timeline
9
9
  def aggregate_event(key, data)
10
- data.each_pair {|k, v| redis.hincrby(key, k, v)}
10
+ data.each_pair do |k, v|
11
+ redis.hincrby(key, k, v)
12
+ redis.hincrby(key, :total, v)
13
+ end
11
14
  end
12
15
 
13
16
  def summarize(key)
@@ -1,3 +1,3 @@
1
1
  module PulseMeter
2
- VERSION = "0.2.7"
2
+ VERSION = "0.2.8"
3
3
  end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+ require 'pulse-meter/extensions/enumerable'
3
+
4
+ describe Enumerable do
5
+ let!(:time) {Time.new}
6
+ describe "#convert_time" do
7
+ it "converts Time objects to unixtime" do
8
+ [time].convert_time.should == [time.to_i]
9
+ end
10
+
11
+ it "does not change other members" do
12
+ [1, 2, 3].convert_time.should == [1, 2 ,3]
13
+ end
14
+ end
15
+
16
+ describe "#to_table" do
17
+ context "when format is csv" do
18
+ it "returns csv as string" do
19
+ [].to_table(:csv).should be_instance_of(String)
20
+ end
21
+
22
+ it "returns csv containing each subarray as a row" do
23
+ [[:a, :b], [:c, :d]].to_table(:csv).should == "a;b\nc;d\n"
24
+ end
25
+
26
+ it "converts Time objects to unixtime" do
27
+ [[time]].to_table(:csv).should == "#{time.to_i}\n"
28
+ end
29
+
30
+ it "takes format argument both as string and as symbol" do
31
+ [[:foo]].to_table("csv").should == "foo\n"
32
+ [[:foo]].to_table(:csv).should == "foo\n"
33
+ end
34
+ end
35
+
36
+ context "when format is table" do
37
+ it "return Terminal::Table instance" do
38
+ [].to_table.should be_instance_of(Terminal::Table)
39
+ end
40
+
41
+ it "returns table containing each subarray as a row" do
42
+ data = [[:a, :b], [:c, :d]]
43
+ table = [[:a, :b], [:c, :d]].to_table
44
+ table.rows.map do |row|
45
+ row.cells.map(&:to_s).map(&:strip).map(&:to_sym)
46
+ end.should == data
47
+ end
48
+ end
49
+
50
+ it "uses table format as default" do
51
+ [].to_table.should be_instance_of(Terminal::Table)
52
+ end
53
+
54
+ it "uses table format unless it is :csv or 'csv'" do
55
+ [].to_table(:unknown_format).should be_instance_of(Terminal::Table)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+
3
+ describe PulseMeter::Mixins::Cmd do
4
+ class Dummy
5
+ extend PulseMeter::Mixins::Cmd
6
+
7
+ def self.options
8
+ {host: :localhost, port: 6379, db: 0}
9
+ end
10
+ end
11
+
12
+ let(:dummy){ Dummy }
13
+ before {PulseMeter.redis = Redis.new}
14
+
15
+ describe "#fail!" do
16
+ it "prints given message and exits" do
17
+ STDOUT.should_receive(:puts).with(:msg)
18
+ lambda {dummy.fail!(:msg)}.should raise_error(SystemExit)
19
+ end
20
+ end
21
+
22
+ describe '#with_redis' do
23
+ it "initializes redies and yields a block" do
24
+ PulseMeter.redis = nil
25
+ dummy.with_redis do
26
+ PulseMeter.redis.should_not be_nil
27
+ end
28
+ end
29
+ end
30
+
31
+ describe "#with_safe_restore_of" do
32
+ it "restores sensor by name and passes it to block" do
33
+ sensor = PulseMeter::Sensor::Counter.new(:foo)
34
+ dummy.with_safe_restore_of(:foo) do |s|
35
+ s.should be_instance_of(sensor.class)
36
+ end
37
+ end
38
+
39
+ it "prints error and exits if sensor cannot be restored" do
40
+ STDOUT.should_receive(:puts).with("Sensor nonexistant is unknown or cannot be restored")
41
+ lambda {dummy.with_safe_restore_of(:nonexistant) {|s| s}}.should raise_error(SystemExit)
42
+ end
43
+ end
44
+
45
+ describe "#all_sensors" do
46
+ it "is just an alias to PulseMeter::Sensor::Timeline.list_objects" do
47
+ PulseMeter::Sensor::Timeline.should_receive(:list_objects)
48
+ dummy.all_sensors
49
+ end
50
+ end
51
+
52
+ describe "#all_sensors_table" do
53
+ before {PulseMeter.redis.flushall}
54
+ let(:init_values){ {:ttl => 1, :raw_data_ttl => 2, :interval => 3, :reduce_delay => 4} }
55
+ let!(:s1) {PulseMeter::Sensor::Counter.new(:s1)}
56
+ let!(:s2) {PulseMeter::Sensor::Timelined::Counter.new(:s2, init_values)}
57
+ let!(:table) {dummy.all_sensors_table}
58
+ let!(:csv) {dummy.all_sensors_table(:csv)}
59
+ let!(:parsed_csv) {CSV.parse(csv, col_sep: ";")}
60
+
61
+ def rows(format)
62
+ if "csv" == format.to_s
63
+ parsed_csv
64
+ else
65
+ table.rows.map do |row|
66
+ row.cells.map(&:to_s).map(&:strip)
67
+ end
68
+ end
69
+ end
70
+
71
+ def sensor_row(name, format)
72
+ rows(format).select {|row| row[0] == name}.first
73
+ end
74
+
75
+ [:csv, :table].each do |format|
76
+ context "when format is #{format}" do
77
+
78
+ if "csv" == format.to_s
79
+ it "returns csv as string" do
80
+ csv.should be_instance_of(String)
81
+ end
82
+ else
83
+ it "returns Terminal::Table instance" do
84
+ table.should be_instance_of(Terminal::Table)
85
+ end
86
+ end
87
+
88
+ it "has title row" do
89
+ rows(format)[0].should == ["Name", "Class", "ttl", "raw data ttl", "interval", "reduce delay"]
90
+ end
91
+
92
+ it "has one row for each sensor (and a title)" do
93
+ rows(format).count.should == 3
94
+ end
95
+
96
+ it "can display timelined sensors" do
97
+ sensor_row("s2", format).should == [
98
+ s2.name, s2.class, s2.ttl, s2.raw_data_ttl, s2.interval, s2.reduce_delay
99
+ ].map(&:to_s)
100
+ end
101
+
102
+ it "can display static sensors" do
103
+ sensor_row("s1", format).should == [
104
+ s1.name, s1.class, "", "", "", ""
105
+ ].map(&:to_s)
106
+ end
107
+
108
+ end
109
+ end
110
+ end
111
+
112
+ end
@@ -15,6 +15,10 @@ describe PulseMeter::Sensor::HashedCounter do
15
15
  expect{ sensor.event({"foo" => 10.4}) }.to change{ sensor.value["foo"] }.from(0).to(10)
16
16
  expect{ sensor.event({"foo" => 15.1}) }.to change{ sensor.value["foo"] }.from(10).to(25)
17
17
  end
18
+
19
+ it "should increment total value" do
20
+ expect{ sensor.event({"foo" => 1, "bar" => 2}) }.to change{sensor.value["total"]}.from(0).to(3)
21
+ end
18
22
  end
19
23
 
20
24
  describe "#value" do
@@ -24,8 +28,8 @@ describe PulseMeter::Sensor::HashedCounter do
24
28
 
25
29
  it "should store redis hash by value_key" do
26
30
  sensor.event({"foo" => 1})
27
- sensor.value.should == {"foo" => 1}
28
- redis.hgetall(sensor.value_key).should == {"foo" => "1"}
31
+ sensor.value.should == {"foo" => 1, "total" => 1}
32
+ redis.hgetall(sensor.value_key).should == {"foo" => "1", "total" => "1"}
29
33
  end
30
34
  end
31
35
 
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe PulseMeter::Sensor::Timelined::HashedCounter do
4
4
  it_should_behave_like "timeline sensor", {}, {:foo => 1}
5
- it_should_behave_like "timelined subclass", [{:foo => 1}, {:foo => 2}], {:foo => 3}.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
5
+ it_should_behave_like "timelined subclass", [{:foo => 1}, {:foo => 2}], {:foo => 3, :total => 3}.to_json
6
+ it_should_behave_like "timelined subclass", [{:foo => 1}, {:foo => :bad_value}], {:foo => 1, :total => 1}.to_json
7
+ it_should_behave_like "timelined subclass", [{:foo => 1}, {:boo => 2}], {:foo => 1, :boo => 2, :total => 3}.to_json
8
8
  end
@@ -242,12 +242,32 @@ shared_examples_for "timeline sensor" do |extra_init_values, default_event|
242
242
  end
243
243
  end
244
244
 
245
- it "should not return more than #{PulseMeter::Sensor::Timeline::MAX_TIMESPAN_POINTS} points" do
246
- max = PulseMeter::Sensor::Timeline::MAX_TIMESPAN_POINTS
247
- (1..10).each do |i|
248
- timespan = sensor.interval * max * (2**i)
249
- sensor.timeline_within(Time.now, Time.now - timespan).size.should < max
245
+ context "to avoid getting to much data" do
246
+ let(:max) {PulseMeter::Sensor::Timeline::MAX_TIMESPAN_POINTS}
250
247
 
248
+ it "should skip some points not to exceed MAX_TIMESPAN_POINTS" do
249
+ count = max * 2
250
+ sensor.timeline_within(
251
+ Time.at(@start_of_interval - 1),
252
+ Time.at(@start_of_interval + count * interval)
253
+ ).size.should < max
254
+ end
255
+
256
+ it "should not skip any points when timeline orginal size is less then MAX_TIMESPAN_POINTS" do
257
+ count = max - 1
258
+ sensor.timeline_within(
259
+ Time.at(@start_of_interval - 1),
260
+ Time.at(@start_of_interval + count * interval)
261
+ ).size.should == count
262
+ end
263
+
264
+ it "should give full data in case skip_optimization parameter set to true" do
265
+ count = max * 2
266
+ sensor.timeline_within(
267
+ Time.at(@start_of_interval - 1),
268
+ Time.at(@start_of_interval + count * interval),
269
+ true
270
+ ).size.should == count
251
271
  end
252
272
  end
253
273
  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.7
4
+ version: 0.2.8
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-23 00:00:00.000000000 Z
13
+ date: 2012-08-02 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: gon-sinatra
@@ -333,6 +333,7 @@ files:
333
333
  - examples/server_config.yml
334
334
  - lib/cmd.rb
335
335
  - lib/pulse-meter.rb
336
+ - lib/pulse-meter/extensions/enumerable.rb
336
337
  - lib/pulse-meter/mixins/cmd.rb
337
338
  - lib/pulse-meter/mixins/dumper.rb
338
339
  - lib/pulse-meter/mixins/utils.rb
@@ -405,6 +406,8 @@ files:
405
406
  - lib/pulse-meter/visualize/widgets/timeline.rb
406
407
  - lib/pulse-meter/visualizer.rb
407
408
  - pulse-meter.gemspec
409
+ - spec/pulse_meter/extensions/enumerable_spec.rb
410
+ - spec/pulse_meter/mixins/cmd_spec.rb
408
411
  - spec/pulse_meter/mixins/dumper_spec.rb
409
412
  - spec/pulse_meter/mixins/utils_spec.rb
410
413
  - spec/pulse_meter/sensor/base_spec.rb
@@ -472,7 +475,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
472
475
  version: '0'
473
476
  segments:
474
477
  - 0
475
- hash: -450227772948230282
478
+ hash: -2299028715226414900
476
479
  requirements: []
477
480
  rubyforge_project:
478
481
  rubygems_version: 1.8.24
@@ -481,6 +484,8 @@ specification_version: 3
481
484
  summary: Lightweight Redis-based metrics aggregator and processor with CLI and simple
482
485
  and customizable WEB interfaces
483
486
  test_files:
487
+ - spec/pulse_meter/extensions/enumerable_spec.rb
488
+ - spec/pulse_meter/mixins/cmd_spec.rb
484
489
  - spec/pulse_meter/mixins/dumper_spec.rb
485
490
  - spec/pulse_meter/mixins/utils_spec.rb
486
491
  - spec/pulse_meter/sensor/base_spec.rb