metrix 0.0.1 → 0.0.2

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.
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