metriksd 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ gem 'rake', '0.8.7'
6
+
7
+ gem 'metriksd_reporter', :path => '../metriksd_reporter'
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) Eric Lindvall
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,30 @@
1
+ # Metriks Server
2
+
3
+ An experimental server for ruby metrics library called [metriks][].
4
+
5
+ ## Highlights
6
+
7
+ * Similar to [statsd][]
8
+ * Receives data via UDP
9
+ * Aggregates results from multiple data points and sends batches to a datastore
10
+ * Receives metrics as a snappy-encoded packet of msgpack'd hashes
11
+ * Each packet can contain more than one metric
12
+ * Intended to be used with [metriksd_reporter][] and [metriks][]
13
+
14
+ ## Usage
15
+
16
+ Run the server:
17
+
18
+ $ metriksd -c config.yml
19
+
20
+
21
+
22
+ # License
23
+
24
+ Copyright (c) 2012 Eric Lindvall
25
+
26
+ Published under the MIT License, see LICENSE
27
+
28
+ [statsd]: https://github.com/etsy/statsd
29
+ [metriks]: https://github.com/eric/metriks
30
+ [metriksd_reporter]: https://github.com/eric/metriksd_reporter
@@ -0,0 +1,150 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'date'
4
+
5
+ #############################################################################
6
+ #
7
+ # Helper functions
8
+ #
9
+ #############################################################################
10
+
11
+ def name
12
+ @name ||= Dir['*.gemspec'].first.split('.').first
13
+ end
14
+
15
+ def version
16
+ line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
17
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
18
+ end
19
+
20
+ def date
21
+ Date.today.to_s
22
+ end
23
+
24
+ def rubyforge_project
25
+ name
26
+ end
27
+
28
+ def gemspec_file
29
+ "#{name}.gemspec"
30
+ end
31
+
32
+ def gem_file
33
+ "#{name}-#{version}.gem"
34
+ end
35
+
36
+ def replace_header(head, header_name)
37
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
38
+ end
39
+
40
+ #############################################################################
41
+ #
42
+ # Standard tasks
43
+ #
44
+ #############################################################################
45
+
46
+ task :default => :test
47
+
48
+ require 'rake/testtask'
49
+ Rake::TestTask.new(:test) do |test|
50
+ test.libs << 'lib' << 'test'
51
+ test.pattern = 'test/**/*_test.rb'
52
+ test.verbose = true
53
+ end
54
+
55
+ desc "Generate RCov test coverage and open in your browser"
56
+ task :coverage do
57
+ require 'rcov'
58
+ sh "rm -fr coverage"
59
+ sh "rcov test/*_test.rb"
60
+ sh "open coverage/index.html"
61
+ end
62
+
63
+ require 'rake/rdoctask'
64
+ Rake::RDocTask.new do |rdoc|
65
+ rdoc.rdoc_dir = 'rdoc'
66
+ rdoc.title = "#{name} #{version}"
67
+ rdoc.rdoc_files.include('README*')
68
+ rdoc.rdoc_files.include('lib/**/*.rb')
69
+ end
70
+
71
+ desc "Open an irb session preloaded with this library"
72
+ task :console do
73
+ sh "irb -rubygems -r ./lib/#{name}.rb"
74
+ end
75
+
76
+ #############################################################################
77
+ #
78
+ # Custom tasks (add your own tasks here)
79
+ #
80
+ #############################################################################
81
+
82
+
83
+
84
+ #############################################################################
85
+ #
86
+ # Packaging tasks
87
+ #
88
+ #############################################################################
89
+
90
+ desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
91
+ task :release => :build do
92
+ unless `git branch` =~ /^\* master$/
93
+ puts "You must be on the master branch to release!"
94
+ exit!
95
+ end
96
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
97
+ sh "git tag v#{version}"
98
+ sh "git push origin master"
99
+ sh "git push origin v#{version}"
100
+ sh "gem push pkg/#{name}-#{version}.gem"
101
+ end
102
+
103
+ desc "Build #{gem_file} into the pkg directory"
104
+ task :build => :gemspec do
105
+ sh "mkdir -p pkg"
106
+ sh "gem build #{gemspec_file}"
107
+ sh "mv #{gem_file} pkg"
108
+ end
109
+
110
+ desc "Generate #{gemspec_file}"
111
+ task :gemspec => :validate do
112
+ # read spec file and split out manifest section
113
+ spec = File.read(gemspec_file)
114
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
115
+
116
+ # replace name version and date
117
+ replace_header(head, :name)
118
+ replace_header(head, :version)
119
+ replace_header(head, :date)
120
+ #comment this out if your rubyforge_project has a different name
121
+ replace_header(head, :rubyforge_project)
122
+
123
+ # determine file list from git ls-files
124
+ files = `git ls-files`.
125
+ split("\n").
126
+ sort.
127
+ reject { |file| file =~ /^\./ }.
128
+ reject { |file| file =~ /^(rdoc|pkg)/ }.
129
+ map { |file| " #{file}" }.
130
+ join("\n")
131
+
132
+ # piece file back together and write
133
+ manifest = " s.files = %w[\n#{files}\n ]\n"
134
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
135
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
136
+ puts "Updated #{gemspec_file}"
137
+ end
138
+
139
+ desc "Validate #{gemspec_file}"
140
+ task :validate do
141
+ libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
142
+ unless libfiles.empty?
143
+ puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
144
+ exit!
145
+ end
146
+ unless Dir['VERSION*'].empty?
147
+ puts "A `VERSION` file at root level violates Gem best practices."
148
+ exit!
149
+ end
150
+ end
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'metriksd'
4
+
5
+ Metriksd::Cli.new(ARGV).parse
@@ -0,0 +1,12 @@
1
+ registry:
2
+ interval: 60
3
+
4
+ reporter:
5
+ type: librato_metrics
6
+ interval: 60
7
+ email: a@b.com
8
+ api_key: aaaabff
9
+
10
+ server:
11
+ type: udp
12
+ port: 2006
@@ -0,0 +1,6 @@
1
+
2
+ module Metriksd
3
+ VERSION = '0.5.0'
4
+ end
5
+
6
+ require 'metriksd/cli'
@@ -0,0 +1,55 @@
1
+ require 'metriksd/config'
2
+ require 'optparse'
3
+
4
+ module Metriksd
5
+ class Cli
6
+ def initialize(argv)
7
+ @argv = argv.dup
8
+ end
9
+
10
+ def parse
11
+ config_file = nil
12
+
13
+ opts = OptionParser.new do |opts|
14
+ opts.banner = "Usage: #{File.basename($0)} [options]"
15
+
16
+ opts.on("-c", "--config FILE", "Read configuration file") do |v|
17
+ config_file = v
18
+ end
19
+
20
+ opts.separator ""
21
+ opts.separator "Common options:"
22
+
23
+ opts.on_tail("-h", "--help", "Show this message") do
24
+ puts opts
25
+ exit(1)
26
+ end
27
+
28
+ opts.on_tail("--version", "Show version") do
29
+ puts Metriksd::VERSION
30
+ exit(0)
31
+ end
32
+ end
33
+
34
+ rest = opts.parse(@argv)
35
+
36
+ unless config_file
37
+ puts "Error: config file must be specified\n\n"
38
+ puts opts
39
+ exit(1)
40
+ end
41
+
42
+ config = Metriksd::Config.new
43
+ config.load_file(config_file)
44
+ config.start
45
+ config.join
46
+ rescue Interrupt
47
+ exit(0)
48
+ ensure
49
+ if config
50
+ config.stop
51
+ config.join
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,88 @@
1
+ require 'metriksd/registry'
2
+ require 'metriksd/udp_server'
3
+ require 'metriksd/librato_metrics_reporter'
4
+
5
+ module Metriksd
6
+ class Config
7
+ attr_reader :servers, :reporters
8
+
9
+ def initialize
10
+ @servers = []
11
+ @reporters = []
12
+ end
13
+
14
+ def load_file(file)
15
+ load(YAML::load_file(file).with_indifferent_access)
16
+ end
17
+
18
+ def load(config)
19
+ config = config.with_indifferent_access
20
+
21
+ registries = [ config[:registry] ].flatten.compact
22
+ if registries.respond_to?(:to_hash)
23
+ registries = registries.to_hash.values
24
+ end
25
+
26
+ registries.each do |registry_config|
27
+ registry_config = registry_config.with_indifferent_access
28
+
29
+ reporter_config = registry_config.delete(:reporter)
30
+ unless reporter_config
31
+ raise ArgumentError, "Must provide a 'reporter'"
32
+ end
33
+ reporter_config = reporter_config.with_indifferent_access
34
+
35
+ server_config = registry_config.delete(:server)
36
+ unless server_config
37
+ raise "Must provide a 'server'"
38
+ end
39
+ server_config = server_config.with_indifferent_access
40
+
41
+ registry = Metriksd::Registry.new(registry_config)
42
+
43
+ @reporters << reporter_class(reporter_config.delete(:type)).new(registry, reporter_config)
44
+ @servers << server_class(server_config.delete(:type)).new(registry, server_config)
45
+ end
46
+ end
47
+
48
+ def start
49
+ (@servers + @reporters).each do |t|
50
+ t.start
51
+ end
52
+ end
53
+
54
+ def stop
55
+ (@servers + @reporters).each do |t|
56
+ t.stop
57
+ end
58
+ end
59
+
60
+ def join
61
+ (@servers + @reporters).each do |t|
62
+ t.join
63
+ end
64
+ end
65
+
66
+ def reporter_class(type)
67
+ case type.to_s
68
+ when 'librato_metrics'
69
+ Metriksd::LibratoMetricsReporter
70
+ when '', nil
71
+ raise "No reporter 'type' was specified"
72
+ else
73
+ raise "Unknown reporter 'type': #{type.inspect}"
74
+ end
75
+ end
76
+
77
+ def server_class(type)
78
+ case type.to_s
79
+ when 'udp'
80
+ Metriksd::UdpServer
81
+ when '', nil
82
+ raise "No server 'type' was specified"
83
+ else
84
+ raise "Unknown server 'type': #{type.inspect}"
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,29 @@
1
+ require 'active_support/core_ext/hash'
2
+
3
+ module Metriksd
4
+ class Data
5
+ attr_reader :time, :client_id, :name, :payload
6
+
7
+ def initialize(data)
8
+ data = HashWithIndifferentAccess.new(data)
9
+
10
+ unless @time = data.delete(:time)
11
+ raise ArgumentError, "No 'time' was found"
12
+ end
13
+
14
+ unless @client_id = data.delete(:client_id)
15
+ raise ArgumentError, "No 'client_id' was found"
16
+ end
17
+
18
+ unless @name = data.delete(:name)
19
+ raise ArgumentError, "No 'name' was found"
20
+ end
21
+
22
+ @payload = data
23
+ end
24
+
25
+ def [](key)
26
+ @payload[key]
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,76 @@
1
+ require 'librato/metrics'
2
+
3
+ module Metriksd
4
+ class LibratoMetricsReporter
5
+ attr_reader :client, :queue
6
+
7
+ def initialize(registry, options = {})
8
+ missing_keys = %w(email api_key) - options.keys.map(&:to_s)
9
+ unless missing_keys.empty?
10
+ raise ArgumentError, "Missing required options: #{missing_keys * ', '}"
11
+ end
12
+
13
+ @registry = registry
14
+ @client = Librato::Metrics::Client.new
15
+ @client.authenticate options[:email], options[:api_key]
16
+ @queue = @client.new_queue
17
+
18
+ @interval = options[:interval] || @registry.interval
19
+ @intervel_offset = options[:interval_offset] || 2
20
+ end
21
+
22
+ def start
23
+ @thread = Thread.new do
24
+ Thread.current.abort_on_exception = true
25
+
26
+ @running = true
27
+
28
+ while @running
29
+ sleep_until_deadline
30
+ flush
31
+ end
32
+ end
33
+ end
34
+
35
+ def stop
36
+ @running = false
37
+ end
38
+
39
+ def join
40
+ if @thread
41
+ @thread.join
42
+ end
43
+ end
44
+
45
+ def flush
46
+ timeslices = @registry.dirty_timeslices
47
+
48
+ timeslices.each do |timeslice|
49
+ rollup = Metriksd::LibratoMetricsReporter::TimesliceRollup.new(timeslice)
50
+ @queue.add rollup.to_hash
51
+ end
52
+
53
+ unless queue.empty?
54
+ @queue.submit
55
+ end
56
+ end
57
+
58
+ def sleep_until_deadline
59
+ now = Time.now.to_f
60
+ rounded = now - (now % @interval)
61
+ next_rounded = rounded + @interval + @intervel_offset
62
+ sleep_time = next_rounded - Time.now.to_f
63
+
64
+ # Allow this to be interrupted
65
+ while sleep_time > 0 && @running
66
+ s = [ sleep_time, 1 ].min
67
+
68
+ sleep(s)
69
+
70
+ sleep_time = next_rounded - Time.now.to_f
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ require 'metriksd/librato_metrics_reporter/timeslice_rollup'
@@ -0,0 +1,144 @@
1
+ module Metriksd
2
+ class LibratoMetricsReporter::TimesliceRollup
3
+ class AverageGauge
4
+ attr_accessor :name, :source, :count, :sum, :sum_of_squares, :min, :max
5
+
6
+ def initialize(name, source)
7
+ @name = name
8
+ @source = source
9
+ @count = 0
10
+ @sum = 0
11
+ @sum_of_squares = 0
12
+ @min = nil
13
+ @max = nil
14
+ end
15
+
16
+ def mark(value)
17
+ @count += 1
18
+ @sum += value
19
+ @sum_of_squares += value ** 2
20
+
21
+ if !@min || value < @min
22
+ @min = value
23
+ end
24
+
25
+ if !@max || value > @max
26
+ @max = value
27
+ end
28
+ end
29
+
30
+ def to_hash
31
+ {
32
+ :name => name,
33
+ :source => source,
34
+ :count => count,
35
+ :sum => sum,
36
+ :sum_of_squares => sum_of_squares,
37
+ :min => min,
38
+ :max => max
39
+ }
40
+ end
41
+ end
42
+
43
+ class SumGauge
44
+ attr_accessor :name, :source, :value
45
+
46
+ def initialize(name, source)
47
+ @name = name
48
+ @source = source
49
+ @value = 0
50
+ end
51
+
52
+ def mark(value)
53
+ @value += value
54
+ end
55
+
56
+ def to_hash
57
+ {
58
+ :name => name,
59
+ :source => source,
60
+ :value => value
61
+ }
62
+ end
63
+ end
64
+
65
+ def initialize(timeslice)
66
+ @timeslice = timeslice
67
+ end
68
+
69
+ def process
70
+ return if @gauges
71
+ @gauges = {}
72
+
73
+ @timeslice.flush.each do |data|
74
+ case data[:type]
75
+ when 'counter'
76
+ add_counter(data)
77
+ when 'timer'
78
+ add_timer(data)
79
+ when 'utilization_timer'
80
+ add_utilization_timer(data)
81
+ when 'meter'
82
+ add_meter(data)
83
+ else
84
+ raise "Unknown data type: #{data[:type].inspect}"
85
+ end
86
+ end
87
+ end
88
+
89
+ def to_hash
90
+ process
91
+
92
+ @gauges.map do |name, gauge|
93
+ [ gauge.name, gauge.to_hash.merge(:measure_time => @timeslice.time) ]
94
+ end
95
+ end
96
+
97
+ def add_counter(data)
98
+ counter(data.name, data[:source], data[:count])
99
+ end
100
+
101
+ def add_timer(data)
102
+ average_gauge(data.name + '.mean', data[:source], data[:mean])
103
+ sum_gauge(data.name + '.one_minute_rate', data[:source], data[:one_minute_rate])
104
+
105
+ if data[:median]
106
+ average_gauge(data.name + '.median', data[:source], data[:median])
107
+ end
108
+
109
+ if data["95th_percentile"]
110
+ average_gauge(data.name + '.95th_percentile', data[:source], data["95th_percentile"])
111
+ end
112
+ end
113
+
114
+ def add_utilization_timer(data)
115
+ average_gauge(data.name + '.mean', data[:source], data[:mean])
116
+ sum_gauge(data.name + '.one_minute_rate', data[:source], data[:one_minute_rate])
117
+ average_gauge(data.name + '.one_minute_utilization', data[:source], data[:one_minute_utilization])
118
+
119
+ if data[:median]
120
+ average_gauge(data.name + '.median', data[:source], data[:median])
121
+ end
122
+
123
+ if data["95th_percentile"]
124
+ average_gauge(data.name + '.95th_percentile', data[:source], data["95th_percentile"])
125
+ end
126
+ end
127
+
128
+ def add_meter(data)
129
+ sum_gauge(data.name, data[:source], data[:one_minute_rate])
130
+ end
131
+
132
+ def average_gauge(name, source, value)
133
+ key = [ name, source ].join('/')
134
+ @gauges[key] ||= AverageGauge.new(name, source)
135
+ @gauges[key].mark(value)
136
+ end
137
+
138
+ def sum_gauge(name, source, value)
139
+ key = [ name, source ].join('/')
140
+ @gauges[key] ||= SumGauge.new(name, source)
141
+ @gauges[key].mark(value)
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,64 @@
1
+ require 'metriksd/data'
2
+ require 'metriksd/timeslice'
3
+
4
+ module Metriksd
5
+ class Registry
6
+ attr_reader :interval, :window
7
+
8
+ def initialize(options = {})
9
+ @interval = options[:interval] || 60
10
+ @window = options[:window] || 10 * @interval
11
+
12
+ @ignore_current_timeslice = options.fetch(:ignore_current_timeslice, true)
13
+
14
+ @mutex = Mutex.new
15
+
16
+ @timeslices = Hash.new do |h,k|
17
+ h[k] = Timeslice.new(k)
18
+ end
19
+ end
20
+
21
+ def dirty?
22
+ @timeslices.any? { |_, t| t.dirty? }
23
+ end
24
+
25
+ def push(data)
26
+ t = rounded_time(data.time)
27
+ timeslice = nil
28
+
29
+ @mutex.synchronize do
30
+ @timeslices[t] << data
31
+ end
32
+ end
33
+ alias_method :<<, :push
34
+
35
+ def dirty_timeslices
36
+ trim
37
+
38
+ @mutex.synchronize do
39
+ if @ignore_current_timeslice
40
+ current_time = rounded_time(Time.now)
41
+ end
42
+
43
+ @timeslices.values.select do |t|
44
+ t.dirty? && (!current_time || current_time != t.time)
45
+ end
46
+ end
47
+ end
48
+
49
+ def rounded_time(time)
50
+ time = time.to_i
51
+ time - (time % @interval)
52
+ end
53
+
54
+ def trim
55
+ oldest_time = Time.now.to_i - @window
56
+
57
+ @mutex.synchronize do
58
+ @timeslices.delete_if do |time, _|
59
+ time < oldest_time
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,33 @@
1
+ module Metriksd
2
+ class Timeslice
3
+ attr_reader :time
4
+
5
+ def initialize(time)
6
+ @time = time
7
+ @mutex = Mutex.new
8
+ @dirty = false
9
+ @records = {}
10
+ end
11
+
12
+ def dirty?
13
+ @mutex.synchronize do
14
+ @dirty
15
+ end
16
+ end
17
+
18
+ def flush
19
+ @mutex.synchronize do
20
+ @dirty = false
21
+ @records.values
22
+ end
23
+ end
24
+
25
+ def push(data)
26
+ @mutex.synchronize do
27
+ @records["#{data.client_id}/#{data.name}"] = data
28
+ @dirty = true
29
+ end
30
+ end
31
+ alias_method :<<, :push
32
+ end
33
+ end
@@ -0,0 +1,77 @@
1
+ require 'socket'
2
+ require 'eventmachine'
3
+ require 'logger'
4
+ require 'snappy'
5
+ require 'msgpack'
6
+
7
+ require 'metriksd/registry'
8
+
9
+ module Metriksd
10
+ class UdpServer
11
+ class Handler < EventMachine::Connection
12
+ def initialize(proc)
13
+ @proc = proc
14
+ super
15
+ end
16
+
17
+ def receive_data(data)
18
+ @proc.call(data)
19
+ end
20
+ end
21
+
22
+ attr_reader :logger, :port, :host, :registry
23
+
24
+ def initialize(registry, options = {})
25
+ missing_keys = %w(port) - options.keys.map(&:to_s)
26
+ unless missing_keys.empty?
27
+ raise ArgumentError, "Missing required options: #{missing_keys * ', '}"
28
+ end
29
+
30
+ @registry = registry
31
+ @port = options[:port]
32
+ @host = options[:host] || '0.0.0.0'
33
+ @logger = options[:logger] || Logger.new(STDERR)
34
+ @recvbuf = options[:recvbuf] || 1024 * 1024
35
+
36
+ @unpacker = MessagePack::Unpacker.new
37
+ end
38
+
39
+ def start
40
+ unless EventMachine.reactor_running?
41
+ Thread.new do
42
+ EventMachine.epoll = true if EventMachine.epoll?
43
+ EventMachine.kqueue = true if EventMachine.kqueue?
44
+ EventMachine.run
45
+ end
46
+ end
47
+
48
+ EventMachine.next_tick do
49
+ handler = proc do |data|
50
+ begin
51
+ unmarshal(data)
52
+ rescue => e
53
+ logger.error "Error in metriks server: #{e.class}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
54
+ end
55
+ end
56
+
57
+ EventMachine.open_datagram_socket(@host, @port, Handler, handler)
58
+ end
59
+ end
60
+
61
+ def stop
62
+ EventMachine.stop
63
+ end
64
+
65
+ def join
66
+ if EventMachine.reactor_thread?
67
+ EventMachine.reactor_thread.join
68
+ end
69
+ end
70
+
71
+ def unmarshal(data)
72
+ @unpacker.feed_each(Snappy.inflate(data)) do |payload|
73
+ @registry << Data.new(payload)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,87 @@
1
+ ## This is the rakegem gemspec template. Make sure you read and understand
2
+ ## all of the comments. Some sections require modification, and others can
3
+ ## be deleted if you don't need them. Once you understand the contents of
4
+ ## this file, feel free to delete any comments that begin with two hash marks.
5
+ ## You can find comprehensive Gem::Specification documentation, at
6
+ ## http://docs.rubygems.org/read/chapter/20
7
+ Gem::Specification.new do |s|
8
+ s.specification_version = 2 if s.respond_to? :specification_version=
9
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
+ s.rubygems_version = '1.3.5'
11
+
12
+ ## Leave these as is they will be modified for you by the rake gemspec task.
13
+ ## If your rubyforge_project name is different, then edit it and comment out
14
+ ## the sub! line in the Rakefile
15
+ s.name = 'metriksd'
16
+ s.version = '0.5.0'
17
+ s.date = '2012-07-29'
18
+
19
+ ## Make sure your summary is short. The description may be as long
20
+ ## as you like.
21
+ s.summary = "Server for handling metrics from metriks"
22
+ s.description = ""
23
+
24
+ ## List the primary authors. If there are a bunch of authors, it's probably
25
+ ## better to set the email to an email list or something. If you don't have
26
+ ## a custom homepage, consider using your GitHub URL or the like.
27
+ s.authors = ["Eric Lindvall"]
28
+ s.email = 'eric@sevenscale.com'
29
+ s.homepage = 'https://github.com/eric/metriks_server'
30
+
31
+ ## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
32
+ ## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
33
+ s.require_paths = %w[lib]
34
+
35
+ ## If your gem includes any executables, list them here.
36
+ s.executables = ["metriksd"]
37
+
38
+ ## Specify any RDoc options here. You'll want to add your README and
39
+ ## LICENSE files to the extra_rdoc_files list.
40
+ s.rdoc_options = ["--charset=UTF-8"]
41
+ s.extra_rdoc_files = %w[README.md LICENSE]
42
+
43
+ ## List your runtime dependencies here. Runtime dependencies are those
44
+ ## that are needed for an end user to actually USE your code.
45
+ s.add_dependency('librato-metrics', '~> 0.7')
46
+ s.add_dependency('msgpack', '~> 0.4')
47
+ s.add_dependency('snappy')
48
+ s.add_dependency('activesupport')
49
+ s.add_dependency('eventmachine', '~> 1.0.0.rc.4')
50
+
51
+ ## List your development dependencies here. Development dependencies are
52
+ ## those that are only needed during development
53
+ s.add_development_dependency('metriksd_reporter')
54
+
55
+ ## Leave this section as-is. It will be automatically generated from the
56
+ ## contents of your Git repository via the gemspec task. DO NOT REMOVE
57
+ ## THE MANIFEST COMMENTS, they are used as delimiters by the task.
58
+ # = MANIFEST =
59
+ s.files = %w[
60
+ Gemfile
61
+ LICENSE
62
+ README.md
63
+ Rakefile
64
+ bin/metriksd
65
+ examples/config.yml
66
+ lib/metriksd.rb
67
+ lib/metriksd/cli.rb
68
+ lib/metriksd/config.rb
69
+ lib/metriksd/data.rb
70
+ lib/metriksd/librato_metrics_reporter.rb
71
+ lib/metriksd/librato_metrics_reporter/timeslice_rollup.rb
72
+ lib/metriksd/registry.rb
73
+ lib/metriksd/timeslice.rb
74
+ lib/metriksd/udp_server.rb
75
+ metriksd.gemspec
76
+ test/config_test.rb
77
+ test/librato_metrics_reporter_test.rb
78
+ test/metriksd_reporter_test.rb
79
+ test/test_helper.rb
80
+ test/udp_server_test.rb
81
+ ]
82
+ # = MANIFEST =
83
+
84
+ ## Test files will be grabbed from the file list. Make sure the path glob
85
+ ## matches what you actually use.
86
+ s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
87
+ end
@@ -0,0 +1,32 @@
1
+ require 'test_helper'
2
+ require 'metriksd/config'
3
+
4
+ class ConfigTest < Test::Unit::TestCase
5
+ def setup
6
+ @config = Metriksd::Config.new
7
+ end
8
+
9
+ def test_broken_config
10
+ assert_raises ArgumentError do
11
+ @config.load(:registry => {})
12
+ end
13
+ end
14
+
15
+ def test_working_config
16
+ @config.load :registry => {
17
+ :reporter => {
18
+ :type => 'librato_metrics',
19
+ :email => 'a@b.com',
20
+ :api_key => 'a' * 10
21
+ },
22
+ :server => {
23
+ :type => 'udp',
24
+ :port => 39283
25
+ }
26
+ }
27
+
28
+ @config.start
29
+ @config.stop
30
+ @config.join
31
+ end
32
+ end
@@ -0,0 +1,40 @@
1
+ require 'test_helper'
2
+ require 'metriksd/librato_metrics_reporter'
3
+
4
+ class LibratoMetricsReporterTest < Test::Unit::TestCase
5
+ def setup
6
+ @registry = Metriksd::Registry.new(:ignore_current_timeslice => false)
7
+ @reporter = Metriksd::LibratoMetricsReporter.new(@registry, :email => 'x', :api_key => 'y')
8
+ @reporter.client.persistence = :test
9
+ end
10
+
11
+ def test_empty_flush
12
+ assert !@reporter.flush
13
+ end
14
+
15
+ def test_flush
16
+ data = Metriksd::Data.new(:client_id => $$, :time => Time.now.to_i, :type => 'meter', :name => 'b', :source => 'a', :one_minute_rate => 3.4)
17
+ @registry.push(data)
18
+
19
+ # There is something to flush the first time
20
+ assert @reporter.flush
21
+
22
+ # There is nothing to flush the next time
23
+ assert !@reporter.flush
24
+ end
25
+
26
+ def test_start
27
+ @reporter = Metriksd::LibratoMetricsReporter.new(@registry, :email => 'x', :api_key => 'y', :interval => 0.1)
28
+ @reporter.client.persistence = :test
29
+
30
+ @reporter.start
31
+
32
+ data = Metriksd::Data.new(:client_id => $$, :time => Time.now.to_i, :type => 'meter', :name => 'b', :source => 'a', :one_minute_rate => 3.4)
33
+ @registry.push(data)
34
+
35
+ @reporter.stop
36
+ @reporter.join
37
+
38
+ assert @reporter.queue.persister.persisted.length == 1, @reporter.queue.persister.persisted.inspect
39
+ end
40
+ end
@@ -0,0 +1,50 @@
1
+ require 'test_helper'
2
+ require 'metriksd/config'
3
+ require 'metriksd_reporter'
4
+ require 'metriks'
5
+
6
+ class MetriksdReporterTest < Test::Unit::TestCase
7
+ def test_working_config
8
+ @port = 39283
9
+
10
+ @server_config = Metriksd::Config.new
11
+
12
+ @server_config.load :registry => {
13
+ :ignore_current_timeslice => false,
14
+ :reporter => {
15
+ :type => 'librato_metrics',
16
+ :email => 'a@b.com',
17
+ :api_key => 'a' * 10
18
+ },
19
+ :server => {
20
+ :type => 'udp',
21
+ :port => @port
22
+ }
23
+ }
24
+
25
+ @server_config.start
26
+
27
+ # Wait for eventmachine
28
+ thr = Thread.current; EventMachine.next_tick { thr.wakeup }; Thread.stop
29
+
30
+ @server_reporter = @server_config.reporters.first
31
+ @server_reporter.client.persistence = :test
32
+ @server_persister = @server_reporter.queue.persister
33
+
34
+ @client_registry = Metriks::Registry.new
35
+ @client_reporter = MetriksdReporter.new(:host => '127.0.0.1', :port => @port,
36
+ :registry => @client_registry, :extras => { :source => Socket.gethostname })
37
+
38
+ @client_reporter.start
39
+ @client_registry.timer('client.test.metric').update(5.3)
40
+
41
+ @client_reporter.flush
42
+ @client_reporter.stop
43
+
44
+ @server_config.stop
45
+ @server_config.join
46
+
47
+ assert_not_nil @server_persister.persisted, @server_persister.inspect
48
+ assert @server_persister.persisted.length == 1, @server_persister.persisted.inspect
49
+ end
50
+ end
@@ -0,0 +1,8 @@
1
+ require 'test/unit'
2
+ require 'pp'
3
+
4
+ # require 'mocha'
5
+
6
+ require 'metriksd'
7
+
8
+ Thread.abort_on_exception = true
@@ -0,0 +1,27 @@
1
+ require 'test_helper'
2
+
3
+ class UdpServerTest < Test::Unit::TestCase
4
+ def setup
5
+ @registry = Metriksd::Registry.new
6
+ @server = Metriksd::UdpServer.new(@registry, :port => 30000 + rand(1000))
7
+ @server.start
8
+ end
9
+
10
+ def teardown
11
+ @server.stop
12
+ @server.join
13
+ end
14
+
15
+ def test_data
16
+ # Wait for eventmachine
17
+ thr = Thread.current; EventMachine.next_tick { thr.wakeup }; Thread.stop
18
+
19
+ data = Snappy.deflate({ :name => 'a', :client_id => $$, :time => Time.now.to_i, :anything => 'yay' }.to_msgpack)
20
+ socket = UDPSocket.new
21
+ socket.send data, 0, '127.0.0.1', @server.port
22
+
23
+ sleep 0.1
24
+
25
+ assert @server.registry.dirty?, @server.registry.inspect
26
+ end
27
+ end
metadata ADDED
@@ -0,0 +1,160 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: metriksd
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 5
8
+ - 0
9
+ version: 0.5.0
10
+ platform: ruby
11
+ authors:
12
+ - Eric Lindvall
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2012-07-29 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ type: :runtime
22
+ version_requirements: &id001 !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ segments:
27
+ - 0
28
+ - 7
29
+ version: "0.7"
30
+ name: librato-metrics
31
+ requirement: *id001
32
+ prerelease: false
33
+ - !ruby/object:Gem::Dependency
34
+ type: :runtime
35
+ version_requirements: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ~>
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 0
41
+ - 4
42
+ version: "0.4"
43
+ name: msgpack
44
+ requirement: *id002
45
+ prerelease: false
46
+ - !ruby/object:Gem::Dependency
47
+ type: :runtime
48
+ version_requirements: &id003 !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ segments:
53
+ - 0
54
+ version: "0"
55
+ name: snappy
56
+ requirement: *id003
57
+ prerelease: false
58
+ - !ruby/object:Gem::Dependency
59
+ type: :runtime
60
+ version_requirements: &id004 !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ name: activesupport
68
+ requirement: *id004
69
+ prerelease: false
70
+ - !ruby/object:Gem::Dependency
71
+ type: :runtime
72
+ version_requirements: &id005 !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ segments:
77
+ - 1
78
+ - 0
79
+ - 0
80
+ - rc
81
+ - 4
82
+ version: 1.0.0.rc.4
83
+ name: eventmachine
84
+ requirement: *id005
85
+ prerelease: false
86
+ - !ruby/object:Gem::Dependency
87
+ type: :development
88
+ version_requirements: &id006 !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ name: metriksd_reporter
96
+ requirement: *id006
97
+ prerelease: false
98
+ description: ""
99
+ email: eric@sevenscale.com
100
+ executables:
101
+ - metriksd
102
+ extensions: []
103
+
104
+ extra_rdoc_files:
105
+ - README.md
106
+ - LICENSE
107
+ files:
108
+ - Gemfile
109
+ - LICENSE
110
+ - README.md
111
+ - Rakefile
112
+ - bin/metriksd
113
+ - examples/config.yml
114
+ - lib/metriksd.rb
115
+ - lib/metriksd/cli.rb
116
+ - lib/metriksd/config.rb
117
+ - lib/metriksd/data.rb
118
+ - lib/metriksd/librato_metrics_reporter.rb
119
+ - lib/metriksd/librato_metrics_reporter/timeslice_rollup.rb
120
+ - lib/metriksd/registry.rb
121
+ - lib/metriksd/timeslice.rb
122
+ - lib/metriksd/udp_server.rb
123
+ - metriksd.gemspec
124
+ - test/config_test.rb
125
+ - test/librato_metrics_reporter_test.rb
126
+ - test/metriksd_reporter_test.rb
127
+ - test/test_helper.rb
128
+ - test/udp_server_test.rb
129
+ has_rdoc: true
130
+ homepage: https://github.com/eric/metriks_server
131
+ licenses: []
132
+
133
+ post_install_message:
134
+ rdoc_options:
135
+ - --charset=UTF-8
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ segments:
143
+ - 0
144
+ version: "0"
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ segments:
150
+ - 0
151
+ version: "0"
152
+ requirements: []
153
+
154
+ rubyforge_project:
155
+ rubygems_version: 1.3.6
156
+ signing_key:
157
+ specification_version: 2
158
+ summary: Server for handling metrics from metriks
159
+ test_files:
160
+ - test/test_helper.rb