pulse-meter-client-backport 0.1.0

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 (50) 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 +3 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE +22 -0
  8. data/Procfile +3 -0
  9. data/README.md +98 -0
  10. data/Rakefile +53 -0
  11. data/bin/pulse +6 -0
  12. data/examples/readme_client_example.rb +52 -0
  13. data/lib/pulse-meter.rb +17 -0
  14. data/lib/pulse-meter/mixins/dumper.rb +76 -0
  15. data/lib/pulse-meter/mixins/utils.rb +93 -0
  16. data/lib/pulse-meter/sensor.rb +44 -0
  17. data/lib/pulse-meter/sensor/base.rb +75 -0
  18. data/lib/pulse-meter/sensor/counter.rb +36 -0
  19. data/lib/pulse-meter/sensor/hashed_counter.rb +31 -0
  20. data/lib/pulse-meter/sensor/indicator.rb +33 -0
  21. data/lib/pulse-meter/sensor/timeline.rb +180 -0
  22. data/lib/pulse-meter/sensor/timelined/average.rb +26 -0
  23. data/lib/pulse-meter/sensor/timelined/counter.rb +16 -0
  24. data/lib/pulse-meter/sensor/timelined/hashed_counter.rb +22 -0
  25. data/lib/pulse-meter/sensor/timelined/max.rb +25 -0
  26. data/lib/pulse-meter/sensor/timelined/median.rb +14 -0
  27. data/lib/pulse-meter/sensor/timelined/min.rb +25 -0
  28. data/lib/pulse-meter/sensor/timelined/percentile.rb +31 -0
  29. data/lib/pulse-meter/version.rb +3 -0
  30. data/pulse-meter-client-backport.gemspec +33 -0
  31. data/spec/pulse_meter/mixins/dumper_spec.rb +141 -0
  32. data/spec/pulse_meter/mixins/utils_spec.rb +125 -0
  33. data/spec/pulse_meter/sensor/base_spec.rb +97 -0
  34. data/spec/pulse_meter/sensor/counter_spec.rb +54 -0
  35. data/spec/pulse_meter/sensor/hashed_counter_spec.rb +39 -0
  36. data/spec/pulse_meter/sensor/indicator_spec.rb +43 -0
  37. data/spec/pulse_meter/sensor/timeline_spec.rb +45 -0
  38. data/spec/pulse_meter/sensor/timelined/average_spec.rb +6 -0
  39. data/spec/pulse_meter/sensor/timelined/counter_spec.rb +6 -0
  40. data/spec/pulse_meter/sensor/timelined/hashed_counter_spec.rb +8 -0
  41. data/spec/pulse_meter/sensor/timelined/max_spec.rb +7 -0
  42. data/spec/pulse_meter/sensor/timelined/median_spec.rb +7 -0
  43. data/spec/pulse_meter/sensor/timelined/min_spec.rb +7 -0
  44. data/spec/pulse_meter/sensor/timelined/percentile_spec.rb +17 -0
  45. data/spec/pulse_meter_spec.rb +16 -0
  46. data/spec/shared_examples/timeline_sensor.rb +274 -0
  47. data/spec/shared_examples/timelined_subclass.rb +23 -0
  48. data/spec/spec_helper.rb +21 -0
  49. data/spec/support/matchers.rb +45 -0
  50. metadata +276 -0
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.sw?
19
+ /.idea
data/.rbenv-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.2-p290
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color -fd
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use ruby-1.8.7-p357
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Ilya Averyanov
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Procfile ADDED
@@ -0,0 +1,3 @@
1
+ watch_coffee: bundle exec rake coffee:watch
2
+ web_sample: bundle exec rackup examples/basic.ru
3
+ sensor_sample: bundle exec ruby examples/basic_sensor_data.rb
data/README.md ADDED
@@ -0,0 +1,98 @@
1
+ [![Build Status](https://secure.travis-ci.org/savonarola/pulse-meter-client-backport.png)]
2
+ (http://travis-ci.org/savonarola/pulse-meter-client-backport)
3
+
4
+ # PulseMeter
5
+
6
+ PulseMeter is a gem for fast and convenient realtime aggregating of software internal stats through Redis.
7
+ This is a client part of it backported for ruby 1.8.x. It allows to create sensors and send data to them, but
8
+ neither reduce nor visualize.
9
+
10
+ It is supposed to use a standalone newer installation of ruby to perform reducing and visualisation.
11
+
12
+ ## Client usage
13
+
14
+ Just create sensor objects and write data. Some examples below.
15
+
16
+ require 'pulse-meter'
17
+ PulseMeter.redis = Redis.new
18
+
19
+ # static sensor examples
20
+
21
+ counter = PulseMeter::Sensor::Counter.new :my_counter
22
+ counter.event(1)
23
+ counter.event(2)
24
+ puts counter.value
25
+ # prints
26
+ # 3
27
+
28
+ indicator = PulseMeter::Sensor::Indicator.new :my_value
29
+ indicator.event(3.14)
30
+ indicator.event(2.71)
31
+ puts indicator.value
32
+ # prints
33
+ # 2.71
34
+
35
+ hashed_counter = PulseMeter::Sensor::HashedCounter.new :my_h_counter
36
+ hashed_counter.event(:x => 1)
37
+ hashed_counter.event(:y => 5)
38
+ hashed_counter.event(:y => 1)
39
+ p hashed_counter.value
40
+ # prints
41
+ # {"x"=>1, "y"=>6}
42
+
43
+ # timeline sensor examples
44
+
45
+ requests_per_minute = PulseMeter::Sensor::Timelined::Counter.new(:my_t_counter,
46
+ :interval => 60, # count for each minute
47
+ :ttl => 24 * 60 * 60 # keep data one day
48
+ )
49
+ requests_per_minute.event(1)
50
+ requests_per_minute.event(1)
51
+ sleep(60)
52
+ requests_per_minute.event(1)
53
+ requests_per_minute.timeline(2 * 60).each do |v|
54
+ puts "#{v.start_time}: #{v.value}"
55
+ end
56
+ # prints somewhat like
57
+ # 2012-05-24 11:06:00 +0400: 2
58
+ # 2012-05-24 11:07:00 +0400: 1
59
+
60
+ max_per_minute = PulseMeter::Sensor::Timelined::Max.new(:my_t_max,
61
+ :interval => 60, # max for each minute
62
+ :ttl => 24 * 60 * 60 # keep data one day
63
+ )
64
+ max_per_minute.event(3)
65
+ max_per_minute.event(1)
66
+ max_per_minute.event(2)
67
+ sleep(60)
68
+ max_per_minute.event(5)
69
+ max_per_minute.event(7)
70
+ max_per_minute.event(6)
71
+ max_per_minute.timeline(2 * 60).each do |v|
72
+ puts "#{v.start_time}: #{v.value}"
73
+ end
74
+ # prints somewhat like
75
+ # 2012-05-24 11:07:00 +0400: 3.0
76
+ # 2012-05-24 11:08:00 +0400: 7.0
77
+
78
+ ## Installation
79
+
80
+ Add this line to your application's Gemfile:
81
+
82
+ gem 'pulse-meter-client-backport'
83
+
84
+ And then execute:
85
+
86
+ $ bundle
87
+
88
+ Or install it yourself as:
89
+
90
+ $ gem install pulse-meter-client-backport
91
+
92
+ ## Contributing
93
+
94
+ 1. Fork it
95
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
96
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
97
+ 4. Push to the branch (`git push origin my-new-feature`)
98
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+ require "yard"
5
+ require 'yard/rake/yardoc_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ YARD::Rake::YardocTask.new(:yard)
10
+
11
+ ROOT = File.dirname(__FILE__)
12
+
13
+ task :default => :spec
14
+
15
+ namespace :coffee do
16
+ desc "Complile coffee to js"
17
+ task :compile do
18
+ system 'coffee', '-c', "#{ROOT}/lib/pulse-meter/visualize/public/"
19
+ puts "Done"
20
+ end
21
+
22
+ desc "Watch coffee files and recomplile them immediately"
23
+ task :watch do
24
+ system 'coffee', '--watch', '-c', "#{ROOT}/lib/pulse-meter/visualize/public/"
25
+ end
26
+
27
+ end
28
+
29
+ namespace :yard do
30
+ desc "Open doc index in a browser"
31
+ task :open do
32
+ system 'open', "#{ROOT}/doc/index.html"
33
+ end
34
+ end
35
+
36
+ namespace :example do
37
+ desc "Run minimal example"
38
+ task :minimal do
39
+ chdir(ROOT) do
40
+ system "bundle"
41
+ system "cd examples/minimal && bundle exec foreman start"
42
+ end
43
+ end
44
+
45
+ desc "Run full example"
46
+ task :full do
47
+ chdir(ROOT) do
48
+ system "bundle"
49
+ system "cd examples/full && bundle exec foreman start"
50
+ end
51
+ end
52
+
53
+ end
data/bin/pulse ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '../lib/'))
3
+ require 'pulse-meter'
4
+ require 'cmd'
5
+
6
+ Cmd::All.start
@@ -0,0 +1,52 @@
1
+ $: << File.join(File.absolute_path(__FILE__), '..', 'lib')
2
+
3
+ require 'pulse-meter'
4
+ PulseMeter.redis = Redis.new
5
+
6
+ # static sensor examples
7
+
8
+ counter = PulseMeter::Sensor::Counter.new :my_counter
9
+ counter.event(1)
10
+ counter.event(2)
11
+ puts counter.value
12
+
13
+ indicator = PulseMeter::Sensor::Indicator.new :my_value
14
+ indicator.event(3.14)
15
+ indicator.event(2.71)
16
+ puts indicator.value
17
+
18
+ hashed_counter = PulseMeter::Sensor::HashedCounter.new :my_h_counter
19
+ hashed_counter.event(:x => 1)
20
+ hashed_counter.event(:y => 5)
21
+ hashed_counter.event(:y => 1)
22
+ p hashed_counter.value
23
+
24
+
25
+ # timeline sensor examples
26
+
27
+ requests_per_minute = PulseMeter::Sensor::Timelined::Counter.new(:my_t_counter,
28
+ :interval => 60, # count for each minute
29
+ :ttl => 24 * 60 * 60 # keep data one day
30
+ )
31
+ requests_per_minute.event(1)
32
+ requests_per_minute.event(1)
33
+ sleep(60)
34
+ requests_per_minute.event(1)
35
+ requests_per_minute.timeline(2 * 60).each do |v|
36
+ puts "#{v.start_time}: #{v.value}"
37
+ end
38
+
39
+ max_per_minute = PulseMeter::Sensor::Timelined::Max.new(:my_t_max,
40
+ :interval => 60, # max for each minute
41
+ :ttl => 24 * 60 * 60 # keep data one day
42
+ )
43
+ max_per_minute.event(3)
44
+ max_per_minute.event(1)
45
+ max_per_minute.event(2)
46
+ sleep(60)
47
+ max_per_minute.event(5)
48
+ max_per_minute.event(7)
49
+ max_per_minute.event(6)
50
+ max_per_minute.timeline(2 * 60).each do |v|
51
+ puts "#{v.start_time}: #{v.value}"
52
+ end
@@ -0,0 +1,17 @@
1
+ require "redis"
2
+ require "pulse-meter/version"
3
+ require "pulse-meter/mixins/dumper"
4
+ require "pulse-meter/mixins/utils"
5
+ require "pulse-meter/sensor"
6
+
7
+ module PulseMeter
8
+ @@redis = nil
9
+
10
+ def self.redis
11
+ @@redis
12
+ end
13
+
14
+ def self.redis=(redis)
15
+ @@redis = redis
16
+ end
17
+ end
@@ -0,0 +1,76 @@
1
+ require 'yaml'
2
+
3
+ module PulseMeter
4
+ module Mixins
5
+ # Mixin with dumping utilities
6
+ module Dumper
7
+ # Prefix for Redis keys with dumped sensors' metadata
8
+ DUMP_REDIS_KEY = "pulse_meter:dump"
9
+
10
+ module InstanceMethods
11
+ # Serializes object and saves it to Redis
12
+ # @raise [DumpError] if dumping fails for any reason
13
+ def dump!
14
+ ensure_storability!
15
+ serialized_obj = self.to_yaml
16
+ redis.hset(DUMP_REDIS_KEY, self.name, serialized_obj)
17
+ rescue
18
+ raise DumpError, "object cannot be dumped"
19
+ end
20
+
21
+ # Ensures that object is dumpable
22
+ # @raise [DumpError] if object cannot be dumped
23
+ def ensure_storability!
24
+ raise DumpError, "#name attribute must be readable" unless self.respond_to?(:name)
25
+ raise DumpError, "#redis attribute must be available" unless self.respond_to?(:redis) && self.redis
26
+ end
27
+
28
+ # Cleans up object dump in Redis
29
+ def cleanup_dump
30
+ redis.hdel(DUMP_REDIS_KEY, self.name)
31
+ end
32
+ end
33
+
34
+ module ClassMethods
35
+ # Restores object from Redis
36
+ # @param name [String] object name
37
+ # @return [Object]
38
+ # @raise [RestoreError] if object cannot be restored for any reason
39
+ def restore(name)
40
+ serialized_obj = PulseMeter.redis.hget(DUMP_REDIS_KEY, name)
41
+ YAML::load(serialized_obj)
42
+ rescue
43
+ raise RestoreError, "cannot restore #{name}"
44
+ end
45
+
46
+ # Lists all dumped objects' names
47
+ # @return [Array<String>]
48
+ # @raise [RestoreError] if list cannot be retrieved for any reason
49
+ def list_names
50
+ PulseMeter.redis.hkeys(DUMP_REDIS_KEY)
51
+ rescue
52
+ raise RestoreError, "cannot get data from redis"
53
+ end
54
+
55
+ # Safely restores all dumped objects
56
+ # @return [Array<Object>]
57
+ def list_objects
58
+ objects = []
59
+ list_names.each do |name|
60
+ begin
61
+ objects << restore(name)
62
+ rescue
63
+ end
64
+ end
65
+ objects
66
+ end
67
+ end
68
+
69
+ def self.included(base)
70
+ base.send :include, InstanceMethods
71
+ base.send :extend, ClassMethods
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,93 @@
1
+ require 'securerandom'
2
+
3
+ module PulseMeter
4
+ module Mixins
5
+ # Mixin with various useful functions
6
+ module Utils
7
+ # Tries to find a class with the name specified in the argument string
8
+ # @param const_name [String] class name
9
+ # @return [Class] if given class definde
10
+ # @return [NilClass] if given class is not defined
11
+ def constantize(const_name)
12
+ return unless const_name.respond_to?(:to_s)
13
+ const_name.to_s.split('::').reduce(Module, :const_get)
14
+ rescue NameError
15
+ nil
16
+ end
17
+
18
+ # Ensures that hash value specified by key can be converted to positive integer.
19
+ # In case it can makes in-place conversion and returns the value.
20
+ # @param options [Hash] hash to be looked at
21
+ # @param key [Object] hash key
22
+ # @param default [Object] default value to be returned
23
+ # @raise [ArgumentError] unless value is positive integer
24
+ # @return [Fixnum]
25
+ def assert_positive_integer!(options, key, default = nil)
26
+ value = options[key] || default
27
+ raise ArgumentError, "#{key} should be defined" unless value
28
+ raise ArgumentError, "#{key} should be integer" unless value.respond_to?(:to_i)
29
+ raise ArgumentError, "#{key} should be positive" unless value.to_i > 0
30
+ options[key] = value.to_i
31
+ end
32
+
33
+ # Ensures that hash value specified by key is can be converted to float
34
+ # and it is within given range.
35
+ # In case it can makes in-place conversion and returns the value.
36
+ # @param options [Hash] hash to be looked at
37
+ # @param key [Object] hash key
38
+ # @param from [Float] lower bound
39
+ # @param to [Float] upper bound
40
+ # @raise [ArgumentError] unless value is float within given range
41
+ # @return [Float]
42
+ def assert_ranged_float!(options, key, from, to)
43
+ f = options[key]
44
+ raise ArgumentError, "#{key} should be defined" unless f
45
+ raise ArgumentError, "#{key} should be float" unless f.respond_to?(:to_f)
46
+ f = f.to_f
47
+ raise ArgumentError, "#{key} should be between #{from} and #{to}" unless f >= from && f <= to
48
+ options[key] = f
49
+ end
50
+
51
+ # Generates uniq random string
52
+ # @return [String]
53
+ def uniqid
54
+ SecureRandom.hex(32)
55
+ end
56
+
57
+ # Capitalizes the first letter of each word in string
58
+ # @param str [String]
59
+ # @return [String]
60
+ # @raise [ArgumentError] unless passed value responds to to_s
61
+ def titleize(str)
62
+ raise ArgumentError unless str.respond_to?(:to_s)
63
+ str.to_s.split(/[\s_]+/).map(&:capitalize).join(' ')
64
+ end
65
+
66
+ # Converts string from snake_case to CamelCase
67
+ # @param str [String] string to be camelized
68
+ # @param first_letter_upper [TrueClass, FalseClass] says if the first letter must be uppercased
69
+ # @return [String]
70
+ # @raise [ArgumentError] unless passed value responds to to_s
71
+ def camelize(str, first_letter_upper = false)
72
+ raise ArgumentError unless str.respond_to?(:to_s)
73
+ terms = str.to_s.split(/_/)
74
+ first = terms.shift
75
+ (first_letter_upper ? first.capitalize : first.downcase) + terms.map(&:capitalize).join
76
+ end
77
+
78
+ # Deeply capitalizes Array values or Hash keys
79
+ def camelize_keys(item)
80
+ case item
81
+ when Array
82
+ item.map{|i| camelize_keys(i)}
83
+ when Hash
84
+ h = {}
85
+ item.each{ |k, v| h[camelize(k)] = camelize_keys(v)}
86
+ h
87
+ else
88
+ item
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end