graphite-api 0.0.3.beta3 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # GraphiteAPI ( [![Gem Version](https://fury-badge.herokuapp.com/rb/graphite-api.png)](http://badge.fury.io/rb/graphite-api) )
1
+ # GraphiteAPI
2
2
  A Ruby API toolkit for [Graphite](http://graphite.wikidot.com/).
3
3
 
4
4
  ## Description
@@ -7,12 +7,27 @@ A Ruby API toolkit for [Graphite](http://graphite.wikidot.com/).
7
7
  ## Package Content
8
8
  * Includes a **simple** client for ruby.
9
9
  * Ships with a **GraphiteAPI-Middleware**, which is a lightweight, event-driven, aggregator daemon.
10
- * only one dependency (EventMachine).
10
+ * Only one gem dependency ( EventMachine ).
11
11
  * Utilities like scheduling and caching.
12
12
 
13
13
  ## Key Features
14
- * **Multiple Graphite Servers Support** - GraphiteAPI-Middleware supports sending aggregated data to multiple graphite servers, useful for large data centers and backup purposes
14
+ * **Multiple Graphite Servers Support** - GraphiteAPI-Middleware supports sending aggregated data to multiple graphite servers, in a multiplex fashion, useful for large data centers and backup purposes
15
15
  * **Reanimation mode** - support cases which the same keys (same timestamps as well) can be received simultaneously and asynchronously from multiple input sources, in these cases GraphiteAPI-Middleware will "reanimate" old records (records that were already sent to Graphite server), and will send the sum of the reanimated record value + the value of the record that was just received to the graphite server; this new summed record should override the key with the new value on Graphite database.
16
+ * **non-blocking I/O** ( EventMachine aware ).
17
+ * **Thread-Safe** client.
18
+
19
+ ## Status
20
+ <table>
21
+ <tr>
22
+ <td> Version </td>
23
+ <td><a href="https://rubygems.org/gems/graphite-api"><img src=https://fury-badge.herokuapp.com/rb/graphite-api.png></a> </td>
24
+ </tr>
25
+ <tr>
26
+ <td> Build </td>
27
+ <td><a href="https://travis-ci.org/kontera-technologies/graphite-api"><img src=https://travis-ci.org/kontera-technologies/graphite-api.png?branch=master></a>
28
+ </td>
29
+ </tr>
30
+ </table>
16
31
 
17
32
  ## Installation
18
33
  Install stable version
@@ -30,55 +45,125 @@ rake install
30
45
  ```
31
46
 
32
47
  ## Client Usage
48
+ Creating a new client instance
49
+
50
+ ```ruby
51
+ require 'graphite-api'
52
+
53
+ GraphiteAPI::Client.new(
54
+ graphite: "graphite.example.com:2003", # not optional
55
+ prefix: ["example","prefix"], # add example.prefix to each key
56
+ slice: 60 # results are aggregated in 60 seconds slices
57
+ interval: 60 # send to graphite every 60 seconds
58
+ cache: 4 * 60 * 60 # set the max age in seconds for records reanimation
59
+ )
60
+ ```
61
+
62
+ Adding simple metrics
33
63
  ```ruby
34
- require 'graphite-api'
35
- require 'logger'
36
-
37
- # Turn on the logging ( optional )
38
- GraphiteAPI::Logger.logger = ::Logger.new(STDOUT)
39
- GraphiteAPI::Logger.logger.level = ::Logger::DEBUG
40
-
41
- # Setup client
42
- client = GraphiteAPI::Client.new(
43
- :graphite => "graphite.example.com:2003",
44
- :prefix => ["example","prefix"], # add example.prefix to each key
45
- :slice => 60.seconds # results are aggregated in 60 seconds slices
46
- :interval => 60.seconds # send to graphite every 60 seconds
47
- )
48
-
49
- # Simple
50
- client.webServer.web01.loadAvg 10.7
51
- # => example.prefix.webServer.web01.loadAvg 10.7 time.now.to_i
52
-
53
- # "Same Same But Different" ( http://en.wikipedia.org/wiki/Tinglish )
54
- client.metrics "webServer.web01.loadAvg" => 10.7
55
- # => example.prefix.webServer.web01.loadAvg 10.7 time.now.to_i
56
-
57
- # With event time
58
- client.webServer.web01.blaBlaBla(29.1, Time.at(9999999999))
59
- # => example.prefix.webServer.web01.blaBlaBla 29.1 9999999999
60
-
61
- # Multiple with event time
62
- client.metrics({
63
- "webServer.web01.loadAvg" => 10.7,
64
- "webServer.web01.memUsage" => 40
65
- },Time.at(1326067060))
66
- # => example.prefix.webServer.web01.loadAvg 10.7 1326067060
67
- # => example.prefix.webServer.web01.memUsage 40 1326067060
68
-
69
- # Timers
70
- client.every 10.seconds do |c|
71
- c.webServer.web01.uptime `uptime`.split.first.to_i
72
- # => example.prefix.webServer.web01.uptime 40 1326067060
73
- end
74
-
75
- client.every 52.minutes do |c|
76
- c.abcd.efghi.jklmnop.qrst 12
77
- # => example.prefix.abcd.efghi.jklmnop.qrst 12 1326067060
78
- end
79
-
80
- client.join # wait...
81
- ```
64
+ require 'graphite-api'
65
+
66
+ client = GraphiteAPI::Client.new( graphite: 'graphite:2003' )
67
+
68
+ client.metrics "webServer.web01.loadAvg" => 10.7
69
+ # => webServer.web01.loadAvg 10.7 time.now.to_i
70
+
71
+ client.metrics(
72
+ "webServer.web01.loadAvg" => 10.7,
73
+ "webServer.web01.memUsage" => 40
74
+ )
75
+ # => webServer.web01.loadAvg 10.7 1326067060
76
+ # => webServer.web01.memUsage 40 1326067060
77
+ ```
78
+
79
+ Adding metrics with timestamp
80
+ ```ruby
81
+ require 'graphite-api'
82
+
83
+ client = GraphiteAPI::Client.new( graphite: 'graphite:2003' )
84
+
85
+ client.metrics({
86
+ "webServer.web01.loadAvg" => 10.7,
87
+ "webServer.web01.memUsage" => 40
88
+ },Time.at(1326067060))
89
+ # => webServer.web01.loadAvg 10.7 1326067060
90
+ # => webServer.web01.memUsage 40 1326067060
91
+ ```
92
+
93
+ Some DSL sweetness
94
+ ```ruby
95
+ require 'graphite-api'
96
+
97
+ client = GraphiteAPI::Client.new( graphite: 'graphite:2003' )
98
+
99
+ client.webServer.web01.loadAvg 10.7
100
+ # => webServer.web01.loadAvg 10.7 time.now.to_i
101
+
102
+ client.webServer.web01.blaBlaBla(29.1, Time.at(9999999999))
103
+ # => webServer.web01.blaBlaBla 29.1 9999999999
104
+ ```
105
+
106
+ Built-in timers support
107
+ ```ruby
108
+ require 'graphite-api'
109
+
110
+ client = GraphiteAPI::Client.new( graphite: 'graphite:2003' )
111
+
112
+ # lets send the metric every 120 seconds
113
+ client.every(120) do |c|
114
+ c.webServer.web01.uptime `uptime`.split.first.to_i
115
+ end
116
+ ```
117
+
118
+ Built-in extension for time declarations stuff, like 2.minutes, 3.hours etc...
119
+ ```ruby
120
+ require 'graphite-api'
121
+ require 'graphite-api/core_ext/numeric'
122
+
123
+ client = GraphiteAPI::Client.new( graphite: 'graphite:2003' )
124
+
125
+ client.every 10.seconds do |c|
126
+ c.webServer.web01.uptime `uptime`.split.first.to_i
127
+ end
128
+
129
+ client.every 52.minutes do |c|
130
+ c.just.fake 12
131
+ end
132
+ ```
133
+
134
+ Make your own custom metrics daemons, using `client#join`
135
+ ```ruby
136
+ require 'graphite-api'
137
+ require 'graphite-api/core_ext/numeric'
138
+
139
+ client = GraphiteAPI::Client.new( graphite: 'graphite:2003' )
140
+
141
+ client.every 26.minutes do |c|
142
+ c.webServer.shuki.stats 10
143
+ c.webServer.shuki.x 97
144
+ c.webServer.shuki.y 121
145
+ end
146
+
147
+ client.join # wait for ever...
148
+ ```
149
+
150
+ Logging support
151
+
152
+ ```ruby
153
+ # Provide an external logger
154
+ require 'graphite-api'
155
+ require 'logger'
156
+
157
+ GraphiteAPI::Logger.logger = ::Logger.new(STDOUT)
158
+ GraphiteAPI::Logger.logger.level = ::Logger::DEBUG
159
+
160
+ # Or use the built-in one
161
+ GraphiteAPI::Logger.init(
162
+ :level => :debug,
163
+ :std => 'logger.out' # or STDOUT | STDERR
164
+ )
165
+ ```
166
+
82
167
  > more examples can be found [here](https://github.com/kontera-technologies/graphite-api/tree/master/examples).
83
168
 
84
169
  ## GraphiteAPI-Middleware Usage
@@ -142,21 +227,13 @@ client.bla.bla.value2 27
142
227
  > more examples can be found [here](https://github.com/kontera-technologies/graphite-api/tree/master/examples).
143
228
 
144
229
 
145
- ## Recommended Topologies
230
+ ## Example Setup
146
231
  <br/>
147
-
148
- <img src="https://raw.github.com/kontera-technologies/graphite-api/master/examples/graphite-middleware-star.jpg" align="center">
149
-
150
- <hr/>
151
- <br/>
152
-
153
- <img src="https://raw.github.com/kontera-technologies/graphite-api/master/examples/graphite-middleware-mesh.jpg" align="center">
154
-
155
- <hr/>
232
+ <img src="https://raw.github.com/kontera-technologies/graphite-api/master/examples/middleware_t1.png" align="center">
156
233
 
157
234
  ## TODO:
158
- * Documentation
159
- * Use Redis
235
+ * Better documentation
236
+ * Use Redis for caching
160
237
  * Multiple backends via client as well
161
238
 
162
239
  ## Bugs
data/Rakefile CHANGED
@@ -3,4 +3,8 @@ Dir.chdir File.dirname __FILE__
3
3
 
4
4
  require 'graphite-api'
5
5
 
6
+ def message msg
7
+ puts "*** #{msg} ***"
8
+ end
9
+
6
10
  Dir['tasks/**/*.rake'].each { |rake| load rake }
@@ -19,5 +19,4 @@ module GraphiteAPI
19
19
  GraphiteAPI::Version::VERSION
20
20
  end
21
21
 
22
- Dir.glob( "#{ROOT}/core-extensions/**" ).each &method( :require )
23
22
  end
@@ -8,11 +8,11 @@ module GraphiteAPI
8
8
  end
9
9
 
10
10
  def get time, key
11
- cache[time][key]
11
+ cache[time.to_i][key]
12
12
  end
13
13
 
14
14
  def set time, key, value
15
- cache[time][key] = value.to_f
15
+ cache[time.to_i][key] = value.to_f
16
16
  end
17
17
 
18
18
  def incr time, key, value
@@ -22,12 +22,12 @@ module GraphiteAPI
22
22
  private
23
23
 
24
24
  def cache
25
- @cache ||= Hash.new {|h,k| h[k] = Hash.new {|h1,k1| h1[k1] = 0}}
25
+ @cache ||= nested_zero_hash
26
26
  end
27
27
 
28
- def clean age
28
+ def clean max_age
29
29
  debug [:MemoryCache, :before_clean, cache]
30
- cache.delete_if {|t,k| Time.now.to_i - t > age}
30
+ cache.delete_if {|t,k| Time.now.to_i - t > max_age }
31
31
  debug [:MemoryCache, :after_clean, cache]
32
32
  end
33
33
 
@@ -11,15 +11,12 @@
11
11
  # :interval => 60.seconds # send to graphite every 60 seconds
12
12
  # )
13
13
  #
14
- # # Simple
15
14
  # client.webServer.web01.loadAvg 10.7
16
15
  # # => example.prefix.webServer.web01.loadAvg 10.7 time.now.to_i
17
16
 
18
- # # "Same Same But Different" ( http://en.wikipedia.org/wiki/Tinglish )
19
17
  # client.metrics "webServer.web01.loadAvg" => 10.7
20
18
  # # => example.prefix.webServer.web01.loadAvg 10.7 time.now.to_i
21
19
  #
22
- # # Multiple with event time
23
20
  # client.metrics({
24
21
  # "webServer.web01.loadAvg" => 10.7,
25
22
  # "webServer.web01.memUsage" => 40
@@ -23,8 +23,7 @@ module GraphiteAPI
23
23
 
24
24
  def puts message
25
25
  begin
26
- Logger.debug [:connector,:puts,[host,port].join(":"),message]
27
- return
26
+ debug [:connector,:puts,[host,port].join(":"),message]
28
27
  socket.puts message + "\n"
29
28
  rescue Errno::EPIPE, Errno::EINVAL
30
29
  @socket = nil
@@ -16,7 +16,6 @@
16
16
  # ["mem.usage", 190.0, 1326842520]
17
17
  # ["shuki.tuki", 999.0, 1326842520]
18
18
  # -----------------------------------------------------
19
-
20
19
  require 'thread'
21
20
  require 'set'
22
21
 
@@ -48,7 +47,7 @@ module GraphiteAPI
48
47
  streamer[client_id] += char
49
48
 
50
49
  if closed_stream? streamer[client_id]
51
- if valid_stream_message streamer[client_id]
50
+ if valid_stream_message? streamer[client_id]
52
51
  push stream_message_to_obj streamer[client_id]
53
52
  end
54
53
  streamer.delete client_id
@@ -62,16 +61,16 @@ module GraphiteAPI
62
61
  debug [:buffer,:add, obj]
63
62
  queue.push obj
64
63
  nil
65
- end
64
+ end
66
65
 
67
66
  alias_method :<<, :push
68
67
 
69
68
  def pull format = nil
70
- data = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = 0} }
69
+ data = nested_zero_hash
71
70
 
72
71
  counter = 0
73
72
  while new_records?
74
- break if ( counter += 1 ) > 10_000
73
+ break if ( counter += 1 ) > 1_000_000 # TODO: fix this
75
74
  hash = queue.pop
76
75
  time = normalize_time(hash[:time],options[:slice])
77
76
  hash[:metric].each { |k,v| data[time][k] += v.to_f }
@@ -91,7 +90,8 @@ module GraphiteAPI
91
90
  end
92
91
 
93
92
  def inspect
94
- "#<GraphiteAPI::SafeBuffer:#{object_id} @quque#size=#{queue.size} @streamer=#{streamer.inspect}>"
93
+ "#<GraphiteAPI::SafeBuffer:%s @quque#size=%s @streamer=%s>" %
94
+ [object_id,queue.size,streamer]
95
95
  end
96
96
 
97
97
  private
@@ -109,7 +109,7 @@ module GraphiteAPI
109
109
  string[-1,1] == END_OF_STREAM
110
110
  end
111
111
 
112
- def valid_stream_message message
112
+ def valid_stream_message? message
113
113
  message =~ VALID_MESSAGE
114
114
  end
115
115
 
@@ -28,7 +28,11 @@ module GraphiteAPI
28
28
  slice = 60 if slice.nil?
29
29
  ((time || Time.now).to_i / slice * slice).to_i
30
30
  end
31
-
31
+
32
+ def nested_zero_hash
33
+ Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = 0} }
34
+ end
35
+
32
36
  module_function
33
37
 
34
38
  def expand_host host
@@ -1,5 +1,5 @@
1
1
  module GraphiteAPI
2
2
  class Version
3
- VERSION = "0.0.3.beta3"
3
+ VERSION = "0.0.3"
4
4
  end
5
5
  end
@@ -19,7 +19,8 @@ GraphiteAPI::GemSpec = Gem::Specification.new do |s|
19
19
  s.add_dependency 'eventmachine','>= 0.3.3'
20
20
  end
21
21
 
22
- task :gem => [:clobber_package]
22
+ task :gem => [:test,:clobber_package]
23
+
23
24
  Gem::PackageTask.new(GraphiteAPI::GemSpec) do |p|
24
25
  p.gem_spec = GraphiteAPI::GemSpec
25
26
  end
@@ -1,10 +1,42 @@
1
1
  require "rake/testtask"
2
2
 
3
- task(:test) { ENV['with_coverage'] = "true" }
3
+ task(:test => :functional) { ENV['with_coverage'] = "true" }
4
4
 
5
5
  Rake::TestTask.new(:test) do |t|
6
6
  t.libs << "tests"
7
7
  t.pattern = "tests/**/*_test.rb"
8
8
  end
9
9
 
10
+
11
+ task :functional do
12
+ some_failed = false
13
+
14
+ next unless ENV['SKIP_FUNC'].nil?
15
+
16
+ unless RUBY_COPYRIGHT.end_with?("Matsumoto")
17
+ puts("Functional tests are enabled only on MRI...")
18
+ next
19
+ end
20
+
21
+ message "Executing GraphiteAPI Functional Tests"
22
+ message "( You can skip them by passing SKIP_FUNC=true )"
23
+
24
+ Dir[File.expand_path("../../tests/functional/*",__FILE__)].each do |file|
25
+ next unless file.end_with?(".rb")
26
+ now = Time.now.to_i
27
+ name = File.basename(file)
28
+ message "Executing #{name}"
29
+ Process.waitpid(Process.spawn("ruby", File.expand_path(file)))
30
+ took = "took #{Time.now.to_i - now} seconds"
31
+ if $?.success?
32
+ message "[PASS] #{name}, #{took}"
33
+ else
34
+ message "[FAIL] #{name}, #{took}"
35
+ some_failed = true
36
+ end
37
+ end
38
+ message "Done Executing GraphiteAPI Functional Tests"
39
+ abort "Some functional tests failed..." if some_failed
40
+ end
41
+
10
42
  task :default => :test
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphite-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3.beta3
5
- prerelease: 6
4
+ version: 0.0.3
5
+ prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Eran Barak Levi
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-06 00:00:00.000000000 Z
12
+ date: 2013-03-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: eventmachine
@@ -37,14 +37,13 @@ files:
37
37
  - README.md
38
38
  - Rakefile
39
39
  - bin/graphite-middleware
40
- - lib/core-extensions/numeric.rb
41
- - lib/graphite-api/buffer.rb
42
40
  - lib/graphite-api/cache/memory.rb
43
41
  - lib/graphite-api/cache.rb
44
42
  - lib/graphite-api/cli.rb
45
43
  - lib/graphite-api/client.rb
46
44
  - lib/graphite-api/connector.rb
47
45
  - lib/graphite-api/connector_group.rb
46
+ - lib/graphite-api/core_ext/numeric.rb
48
47
  - lib/graphite-api/logger.rb
49
48
  - lib/graphite-api/middleware.rb
50
49
  - lib/graphite-api/reactor.rb
@@ -70,9 +69,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
70
69
  required_rubygems_version: !ruby/object:Gem::Requirement
71
70
  none: false
72
71
  requirements:
73
- - - ! '>'
72
+ - - ! '>='
74
73
  - !ruby/object:Gem::Version
75
- version: 1.3.1
74
+ version: '0'
76
75
  requirements: []
77
76
  rubyforge_project: graphite-api
78
77
  rubygems_version: 1.8.25
@@ -1,137 +0,0 @@
1
- # -----------------------------------------------------
2
- # Buffer Object
3
- # Handle Socket & Client data streams
4
- # -----------------------------------------------------
5
- # Usage:
6
- # buff = GraphiteAPI::Buffer.new(GraphiteAPI::Utils.default_options)
7
- # buff << {:metric => {"load_avg" => 10},:time => Time.now}
8
- # buff << {:metric => {"load_avg" => 30},:time => Time.now}
9
- # buff.stream "mem.usage 1"
10
- # buff.stream "90 1326842563\n"
11
- # buff.stream "shuki.tuki 999 1326842563\n"
12
- # buff.pull.each {|o| p o}
13
- #
14
- # Produce:
15
- # ["load_avg", 40.0, 1326881160]
16
- # ["mem.usage", 190.0, 1326842520]
17
- # ["shuki.tuki", 999.0, 1326842520]
18
- # -----------------------------------------------------
19
- require 'set'
20
-
21
- module GraphiteAPI
22
- class Buffer
23
- include Utils
24
-
25
- CLOSING_STREAM = "\n" # end of message - when streaming to buffer obj
26
- CHARS_TO_IGNORE = %w(\r) # skip these chars when parsing new message
27
- FLOATS_ROUND_BY = 2 # round(x) after summing floats
28
-
29
- VALID_MESSAGE = /^[\w|\.]+ \d+(?:\.|\d)* \d+$/
30
-
31
- def initialize options
32
- @options = options
33
- @keys_to_sync = Hash.new { |h,k| h[k] = Set.new }
34
- @streamer_buff = Hash.new {|h,k| h[k] = ""}
35
- @reanimation_mode = !options[:cache].nil?
36
- start_cleaner if reanimation_mode
37
- end
38
-
39
- private_reader :options, :keys_to_sync, :reanimation_mode, :streamer_buff
40
-
41
- def push hash
42
- debug [:buffer,:add, hash]
43
- time = normalize_time(hash[:time],options[:slice])
44
- hash[:metric].each { |k,v| cache_set(time,k,v) }
45
- end
46
-
47
- alias_method :<<, :push
48
-
49
- def stream message, client_id = nil
50
- message.gsub(/\t/,' ').each_char do |char|
51
- next if invalid_char? char
52
- streamer_buff[client_id] += char
53
-
54
- if closed_stream? streamer_buff[client_id]
55
- if valid streamer_buff[client_id]
56
- push build_metric *streamer_buff[client_id].split
57
- end
58
- streamer_buff.delete client_id
59
- end
60
- end
61
- end
62
-
63
- def pull as = nil
64
- Array.new.tap do |data|
65
- keys_to_sync.each do |time,keys|
66
- keys.each do |key|
67
- data.push cache_get(time, key, as)
68
- end
69
- end
70
- clear
71
- end
72
-
73
- end
74
-
75
- def new_records?
76
- !keys_to_sync.empty?
77
- end
78
-
79
- private
80
-
81
- def closed_stream? string
82
- string[-1,1] == CLOSING_STREAM
83
- end
84
-
85
- def invalid_char? char
86
- CHARS_TO_IGNORE.include? char
87
- end
88
-
89
- def cache_set time, key, value
90
- buffer_cache[time][key] = sum buffer_cache[time][key], value.to_f
91
- keys_to_sync[time].add key
92
- end
93
-
94
- def sum float1, float2
95
- ("%.#{FLOATS_ROUND_BY}f" % (float1 + float2)).to_f
96
- end
97
-
98
- def cache_get time, key, as
99
- metric = [prefix + key,buffer_cache[time][key],time]
100
- as == :string ? metric.join(" ") : metric
101
- end
102
-
103
- def build_metric key, value, time
104
- { :metric => { key => value },:time => Time.at(time.to_i) }
105
- end
106
-
107
- def clear
108
- keys_to_sync.clear
109
- buffer_cache.clear unless reanimation_mode
110
- end
111
-
112
- def valid message
113
- message =~ VALID_MESSAGE
114
- end
115
-
116
- def prefix
117
- @prefix ||= options[:prefix].empty? ? '' : prefix_to_s
118
- end
119
-
120
- def prefix_to_s
121
- Array(options[:prefix]).join('.') << '.'
122
- end
123
-
124
- def buffer_cache
125
- @buffer_cache ||= Hash.new {|h,k| h[k] = Hash.new {|h1,k1| h1[k1] = 0}}
126
- end
127
-
128
- def clean age
129
- [buffer_cache,keys_to_sync].each {|o| o.delete_if {|t,k| Time.now.to_i - t > age}}
130
- end
131
-
132
- def start_cleaner
133
- Reactor::every(options[:cleaner_interval]) { clean(options[:cache]) }
134
- end
135
-
136
- end
137
- end