metrix 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/bin/metrix CHANGED
@@ -1,68 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
  $:.push(File.expand_path("../../lib", __FILE__))
3
- require "time"
4
- require "metrix"
5
- require "metrix/elastic_search"
6
- require "metrix/graphite"
7
- require "metrix/mongodb"
8
- require "syslog/logger"
9
- require "optparse"
3
+ require "metrix/cli"
10
4
 
11
- Metrix.logger = Syslog::Logger.new("metrix")
12
-
13
- metrics = []
14
- interval = 10
15
- graphite_host = nil
16
- opts = OptionParser.new do |o|
17
- o.on("--mongodb") do
18
- metrics << :mongodb
19
- end
20
-
21
- o.on("--es") do
22
- metrics << :es
23
- end
24
-
25
- o.on("--graphite-host HOST") do |value|
26
- graphite_host = value
27
- end
28
- end
29
-
30
- opts.parse(ARGV)
31
-
32
- if graphite_host.nil?
33
- puts "ERROR: graphite-host is required"
34
- abort opts.to_s
35
- end
36
-
37
- started = Time.now
38
- graphite = Graphite.new(graphite_host, 2003)
39
- cnt = 0
40
- while true
41
- begin
42
- cnt += 1
43
- now = Time.now.utc
44
- if metrics.include?(:es)
45
- Metrix.logger.info "fetching elasticsearch metrix"
46
- Metrix::ElasticSearch.from_uri("http://127.0.0.1:9200/_status").metrics.each do |k, v|
47
- graphite << "elastic_search.#{k} #{v} #{now.to_i}"
48
- end
49
- end
50
-
51
- if metrics.include?(:mongodb)
52
- Metrix.logger.info "fetching mongodb metrix"
53
- Metrix::Mongodb.from_uri("http://127.0.0.1:28017/serverStatus").metrics.each do |k, v|
54
- graphite << "mongodb.#{k} #{v} #{now.to_i}"
55
- end
56
- end
57
- graphite.flush
58
- diff = Time.now - now
59
- Metrix.logger.info "finished run in %.06f" % [diff]
60
- rescue SystemExit
61
- exit
62
- rescue => err
63
- logger.error "#{err.message}"
64
- logger.error "#{err.backtrace.inspect}"
65
- ensure
66
- sleep interval - (Time.now - started - cnt * interval)
67
- end
68
- end
5
+ Metrix::CLI.new(ARGV).run
data/lib/metrix/base.rb CHANGED
@@ -1,49 +1,42 @@
1
1
  require "net/http"
2
2
  require "json"
3
+ require "metrix/metric"
3
4
 
4
- class Base
5
- attr_reader :attributes
5
+ module Metrix
6
+ class Base
7
+ attr_reader :attributes, :time
6
8
 
7
- class << self
8
- def from_json(json)
9
- self.new(JSON.load(json))
10
- end
9
+ class << self
10
+ def ignore_metrics(*metrics)
11
+ @ignore = [metrics].flatten
12
+ end
11
13
 
12
- def from_uri(uri)
13
- from_json(Net::HTTP.get(URI(uri)))
14
+ def ignore
15
+ @ignore ||= []
16
+ end
14
17
  end
15
18
 
16
- def ignore_metrics(*metrics)
17
- @ignore = [metrics].flatten
19
+ def initialize(raw, time = Time.now)
20
+ @raw = raw
21
+ @time = time
18
22
  end
19
23
 
20
- def ignore
21
- @ignore ||= []
24
+ def metrics
25
+ unfiltered_metrics.reject { |k, v| ignore_metric?(k) }.map do |k, v|
26
+ Metric.new("#{prefix}.#{k}", v, @time, tags)
27
+ end
22
28
  end
23
- end
24
29
 
25
- def initialize(attributes)
26
- @attributes = attributes
27
- end
30
+ def tags
31
+ {}
32
+ end
28
33
 
29
- def metrics
30
- map_attributes(attributes).reject { |k, v| self.class.ignore.include?(k) }
31
- end
34
+ def ignore_metric?(metric)
35
+ self.class.ignore.include?(metric)
36
+ end
32
37
 
33
- def map_attributes(attributes, prefix = nil)
34
- attributes.inject({}) do |hash, (k, v)|
35
- path = [prefix, k].compact.join(".")
36
- case v
37
- when Hash
38
- hash.merge!(map_attributes(v, path))
39
- when Array
40
- v.each_with_index do |array_value, i|
41
- hash.merge!(map_attributes(v, "#{path}i"))
42
- end
43
- when Numeric
44
- hash[path] = v
45
- end
46
- hash
38
+ def unfiltered_metrics
39
+ extract(attributes)
47
40
  end
48
41
  end
49
42
  end
data/lib/metrix/cli.rb ADDED
@@ -0,0 +1,141 @@
1
+ require "metrix"
2
+ require "metrix/elastic_search"
3
+ require "metrix/mongodb"
4
+ require "metrix/system"
5
+ require "metrix/load"
6
+ require "logger"
7
+
8
+ module Metrix
9
+ class CLI
10
+ attr_reader :reporter, :elastic_search_host, :mongodb_host, :interval
11
+
12
+ def initialize(args)
13
+ @args = args
14
+ @system = false
15
+ @interval = 10
16
+ require "syslog/logger"
17
+ Metrix.logger = Syslog::Logger.new("metrix")
18
+ end
19
+
20
+ def run
21
+ opts.parse(@args)
22
+ Metrix.logger.level = log_level
23
+ if self.reporter.nil?
24
+ puts "ERROR: at least one reporter must be specified"
25
+ abort opts.to_s
26
+ end
27
+ cnt = -1
28
+ started = Time.now
29
+ while true
30
+ begin
31
+ cnt += 1
32
+ now = Time.now.utc
33
+ reporter << Metrix::ElasticSearch.new(elastic_search_status) if elastic_search?
34
+ reporter << Metrix::Mongodb.new(mongodb_status) if mongodb?
35
+ if system?
36
+ reporter << Metrix::System.new(File.read("/proc/stat"))
37
+ reporter << Metrix::Load.new(File.read("/proc/loadavg"))
38
+ end
39
+
40
+ if processes?
41
+ Metrix::Process.all.each do |m|
42
+ reporter << m
43
+ end
44
+ end
45
+ reporter.flush
46
+ rescue SystemExit
47
+ exit
48
+ rescue => err
49
+ Metrix.logger.error "#{err.message}"
50
+ Metrix.logger.error "#{err.backtrace.inspect}"
51
+ ensure
52
+ sleep_for = @interval - (Time.now - started - cnt * interval)
53
+ Metrix.logger.info "finished run in %.06f, sleeping for %.06f" % [Time.now - now, sleep_for]
54
+ sleep sleep_for
55
+ end
56
+ end
57
+ end
58
+
59
+ def log_level
60
+ @log_level || Logger::INFO
61
+ end
62
+
63
+ def processes?
64
+ !!@processes
65
+ end
66
+
67
+ def elastic_search?
68
+ !!@elastic_search
69
+ end
70
+
71
+ def mongodb?
72
+ !!@mongodb
73
+ end
74
+
75
+ def elastic_search_status
76
+ Metrix.logger.info "fetching elasticsearch metrix"
77
+ Net::HTTP.get(URI("http://127.0.0.1:9200/_status"))
78
+ end
79
+
80
+ def mongodb_status
81
+ Metrix.logger.info "fetching mongodb metrix"
82
+ Net::HTTP.get(URI("http://127.0.0.1:28017/serverStatus"))
83
+ end
84
+
85
+ def system?
86
+ !!@system
87
+ end
88
+
89
+ def log_to_stdout
90
+ Metrix.logger = Logger.new(STDOUT)
91
+ Metrix.logger.level = Logger::INFO
92
+ end
93
+
94
+ def opts
95
+ require "optparse"
96
+ @opts ||= OptionParser.new do |o|
97
+ o.on("--mongodb") do
98
+ @mongodb = true
99
+ end
100
+
101
+ o.on("--elasticsearch") do
102
+ @elastic_search = true
103
+ end
104
+
105
+ o.on("--graphite-host HOST") do |value|
106
+ require "metrix/graphite"
107
+ @reporter = Metrix::Graphite.new(value, 2003)
108
+ end
109
+
110
+ o.on("--opentsdb-host HOST") do |value|
111
+ require "metrix/opentsdb"
112
+ @reporter = Metrix::OpenTSDB.new(value, 4242)
113
+ end
114
+
115
+ o.on("--stdout") do
116
+ require "metrix/reporter/stdout"
117
+ @reporter = Metrix::Reporter::Stdout.new
118
+ log_to_stdout
119
+ end
120
+
121
+ o.on("--debug") do
122
+ @log_level = Logger::DEBUG
123
+ end
124
+
125
+ o.on("--no-syslog") do
126
+ log_to_stdout
127
+ end
128
+
129
+ o.on("--processes") do
130
+ require "metrix/process"
131
+ @processes = true
132
+ end
133
+
134
+ o.on("--system") do
135
+ require "metrix/system"
136
+ @system = true
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -1,7 +1,11 @@
1
- require "metrix/base"
1
+ require "metrix/json"
2
2
 
3
3
  module Metrix
4
- class ElasticSearch < Base
4
+ class ElasticSearch < Json
5
5
  ignore_metrics []
6
+
7
+ def prefix
8
+ "elasticsearch"
9
+ end
6
10
  end
7
11
  end
@@ -1,41 +1,42 @@
1
1
  require "socket"
2
+ require "metrix"
2
3
 
3
- class Graphite
4
- def initialize(host, port = 2003)
5
- @host = host
6
- @port = port
7
- end
8
-
9
- def <<(m)
10
- metrics << m
11
- flush if metrics.count > 90
12
- end
4
+ module Metrix
5
+ class Graphite
6
+ attr_reader :host, :port
13
7
 
14
- def flush
15
- if metrics.empty?
16
- logger.info "nothing to send"
17
- return
8
+ def initialize(host, port = 2003)
9
+ @host = host
10
+ @port = port
18
11
  end
19
- started = Time.now
20
- Socket.tcp(@host, @port) do |socket|
21
- metrics.each do |m|
22
- logger.debug "sending #{m}"
23
- socket.puts "metrix.#{hostname}.#{m}"
12
+
13
+ def <<(metric)
14
+ metric.metrics.each do |m|
15
+ logger.debug "adding #{m.to_graphite}"
16
+ buffers << m.to_graphite
17
+ flush if buffers.count > 90
24
18
  end
25
19
  end
26
- logger.info "sent #{metrics.count} in %.06fs" % [Time.now - started]
27
- metrics.clear
28
- end
29
20
 
30
- def hostname
31
- @hostname ||= `hostname`.strip
32
- end
21
+ def buffers
22
+ @buffers ||= []
23
+ end
33
24
 
34
- def metrics
35
- @metrics ||= []
36
- end
25
+ def flush
26
+ if buffers.empty?
27
+ logger.info "nothing to send"
28
+ return
29
+ end
30
+ started = Time.now
31
+ Socket.tcp(@host, @port) do |socket|
32
+ socket.puts(buffers.join("\n"))
33
+ end
34
+ logger.info "sent #{buffers.count} in %.06fs" % [Time.now - started]
35
+ buffers.clear
36
+ end
37
37
 
38
- def logger
39
- Metrix.logger
38
+ def logger
39
+ Metrix.logger
40
+ end
40
41
  end
41
42
  end
@@ -0,0 +1,26 @@
1
+ require "metrix/base"
2
+
3
+ module Metrix
4
+ class Json < Base
5
+ def attributes
6
+ @attributes ||= JSON.load(@raw)
7
+ end
8
+
9
+ def extract(attributes, prefix = nil)
10
+ attributes.inject({}) do |hash, (k, v)|
11
+ path = [prefix, k].compact.join(".")
12
+ case v
13
+ when Hash
14
+ hash.merge!(extract(v, path))
15
+ when Array
16
+ v.each_with_index do |array_value, i|
17
+ hash.merge!(extract(v, "#{path}i"))
18
+ end
19
+ when Numeric
20
+ hash[path] = v
21
+ end
22
+ hash
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,38 @@
1
+ require "metrix/base"
2
+
3
+ module Metrix
4
+ class Load < Base
5
+ def initialize(data)
6
+ @data = data
7
+ @time = Time.now
8
+ end
9
+
10
+ def extract(data)
11
+ {
12
+ load1: load1,
13
+ load5: load5,
14
+ load15: load15,
15
+ }
16
+ end
17
+
18
+ def prefix
19
+ "system.load"
20
+ end
21
+
22
+ def load15
23
+ chunks.at(2).to_f
24
+ end
25
+
26
+ def load5
27
+ chunks.at(1).to_f
28
+ end
29
+
30
+ def load1
31
+ chunks.at(0).to_f
32
+ end
33
+
34
+ def chunks
35
+ @chunks ||= @data.split(" ")
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,37 @@
1
+ require "metrix"
2
+
3
+ module Metrix
4
+ class Metric
5
+ attr_reader :key, :value, :time, :tags
6
+
7
+ def initialize(key, value, time, tags = {})
8
+ @key = key
9
+ @value = value
10
+ @time = time
11
+ @tags = tags
12
+ end
13
+
14
+ def to_opentsdb
15
+ chunks = [:put, key, time.utc.to_i, value]
16
+ tags.merge(hostname: Metrix.hostname, database: database).each do |k, v|
17
+ chunks << "#{k}=#{v}" if v
18
+ end
19
+ chunks.join(" ")
20
+ end
21
+
22
+ def to_graphite
23
+ chunks = [graphite_prefix]
24
+ chunks << "databases.#{database}" if database
25
+ chunks << key
26
+ "#{chunks.join(".")} #{value} #{time.utc.to_i}"
27
+ end
28
+
29
+ def database
30
+ tags[:database]
31
+ end
32
+
33
+ def graphite_prefix
34
+ "metrix.#{Metrix.hostname}"
35
+ end
36
+ end
37
+ end
@@ -1,7 +1,38 @@
1
- require "metrix/base"
1
+ require "metrix/json"
2
+ require "metrix/metric"
2
3
 
3
4
  module Metrix
4
- class Mongodb < Base
5
+ class Mongodb < Json
5
6
  ignore_metrics %w(ok mem.bits pid uptime uptimeMillis uptimeEstimate)
7
+
8
+ def prefix
9
+ "mongodb"
10
+ end
11
+
12
+ DATABASE_RECORD_SET = /^recordStats\.(.*?)\.(.*)/
13
+ DATABASE_LOCK = /^locks/
14
+
15
+ def ignore_metric?(metric)
16
+ metric[DATABASE_RECORD_SET] ||
17
+ metric[DATABASE_LOCK] ||
18
+ super
19
+ end
20
+
21
+ def tagged_metrics
22
+ unfiltered_metrics.map do |k, v|
23
+ if k.match(DATABASE_RECORD_SET)
24
+ database = $1
25
+ Metric.new($2, "#{prefix}.locks.#{v}", time, database: database)
26
+ elsif k.match(DATABASE_LOCK)
27
+ chunks = k.split(".")
28
+ offset = 0
29
+ offset = 1 if chunks.at(1) == "" # for "." database
30
+ database = chunks.at(1 + offset)
31
+ metric = chunks[(2 + offset)..-1].join(".")
32
+ database = "." if database == ""
33
+ Metric.new("#{prefix}.recordStats.#{metric}", v, time, database: database)
34
+ end
35
+ end.compact
36
+ end
6
37
  end
7
38
  end
@@ -0,0 +1,33 @@
1
+ require "socket"
2
+
3
+ module Metrix
4
+ class OpenTSDB
5
+ def initialize(host, port = 4242)
6
+ @host = host
7
+ @port = port
8
+ end
9
+
10
+ def <<(metric)
11
+ metric.metrics.each do |m|
12
+ Metrix.logger.debug "buffering #{m.to_opentsdb}"
13
+ buffers << m.to_opentsdb
14
+ flush if buffers.count >= 90
15
+ end
16
+ rescue => err
17
+ Metrix.logger.error "#{err.message} #{err.inspect}"
18
+ end
19
+
20
+ def flush
21
+ return if buffers.empty?
22
+ Metrix.logger.info "sending #{buffers.count} to #{@host}:#{@port}"
23
+ Socket.tcp(@host, @port) do |socket|
24
+ socket.puts buffers.join("\n")
25
+ end
26
+ buffers.clear
27
+ end
28
+
29
+ def buffers
30
+ @buffers ||= []
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,94 @@
1
+ require "metrix/base"
2
+
3
+ module Metrix
4
+ class Process < Base
5
+ attr_reader :time
6
+
7
+ class << self
8
+ def all
9
+ Dir.glob("/proc/*").select do |path|
10
+ File.directory?(path) && File.basename(path)[/^\d+$/]
11
+ end.map do |path|
12
+ Metrix::Process.new(File.read(path + "/stat"))
13
+ end
14
+ end
15
+ end
16
+
17
+ def name
18
+ comm.gsub(/^\(/, "").gsub(/\)$/, "")
19
+ end
20
+
21
+ def chunks
22
+ @chunks ||= @raw.split(" ").map { |c| cast_int(c) }
23
+ end
24
+
25
+ def tags
26
+ {
27
+ name: name,
28
+ pid: pid,
29
+ ppid: ppid,
30
+ }
31
+ end
32
+
33
+ def unfiltered_metrics
34
+ {
35
+ minflt: minflt,
36
+ cminflt: cminflt,
37
+ majflt: majflt,
38
+ cmajflt: cmajflt,
39
+ utime: utime,
40
+ stime: stime,
41
+ cutime: cutime,
42
+ sctime: sctime,
43
+ num_threads: num_threads,
44
+ vsize: vsize,
45
+ rss: rss,
46
+ }
47
+ end
48
+
49
+ def prefix
50
+ "system.process"
51
+ end
52
+
53
+ def cast_int(str)
54
+ Integer(str) rescue str
55
+ end
56
+
57
+ {
58
+ :pid => 0,
59
+ :comm => 1,
60
+ :state => 2,
61
+ :ppid => 3,
62
+ :pgrp => 4,
63
+ :session => 5,
64
+ :tty_nr => 6,
65
+ :tpgid => 7,
66
+ :flags => 8,
67
+ :minflt => 9,
68
+ :cminflt => 10,
69
+ :majflt => 11,
70
+ :cmajflt => 12,
71
+ :utime => 13,
72
+ :stime => 14,
73
+ :cutime => 15,
74
+ :sctime => 16,
75
+ :priority => 17,
76
+ :nice => 18,
77
+ :num_threads => 19,
78
+ :itrealvalue => 20,
79
+ :starttime => 21,
80
+ :vsize => 22,
81
+ :rss => 23,
82
+ :rsslim => 24,
83
+ :startcode => 25,
84
+ :endcode => 26,
85
+ :startstac => 27,
86
+ :guest_time => 42,
87
+ :cguest_time => 43,
88
+ }.each do |k, v|
89
+ define_method(k) do
90
+ chunks.at(v)
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,17 @@
1
+ module Metrix
2
+ module Reporter
3
+ class Stdout
4
+ def initialize
5
+ end
6
+
7
+ def <<(metric)
8
+ metric.metrics.each do |m|
9
+ Metrix.logger.info "#{m.key} #{m.value} #{m.tags.inspect}"
10
+ end
11
+ end
12
+
13
+ def flush
14
+ end
15
+ end
16
+ end
17
+ end