monitoring_protocols 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/Gemfile +18 -0
- data/Guardfile +8 -0
- data/LICENSE +22 -0
- data/Rakefile +27 -0
- data/lib/monitoring_protocols.rb +22 -0
- data/lib/monitoring_protocols/builder.rb +10 -0
- data/lib/monitoring_protocols/collectd/builder.rb +96 -0
- data/lib/monitoring_protocols/collectd/msg.rb +106 -0
- data/lib/monitoring_protocols/collectd/parser.rb +175 -0
- data/lib/monitoring_protocols/core.rb +23 -0
- data/lib/monitoring_protocols/data_struct.rb +98 -0
- data/lib/monitoring_protocols/json/builder.rb +41 -0
- data/lib/monitoring_protocols/json/parser.rb +115 -0
- data/lib/monitoring_protocols/msgpack/parser.rb +16 -0
- data/lib/monitoring_protocols/parser.rb +22 -0
- data/lib/monitoring_protocols/struct.rb +97 -0
- data/lib/monitoring_protocols/version.rb +3 -0
- data/monitoring_protocols.gemspec +19 -0
- data/specs/factories.rb +44 -0
- data/specs/spec_helper.rb +15 -0
- data/specs/unit/collectd/builder_spec.rb +58 -0
- data/specs/unit/collectd/msg_spec.rb +93 -0
- data/specs/unit/collectd/parser_spec.rb +212 -0
- data/specs/unit/core_spec.rb +19 -0
- data/specs/unit/data_struct_spec.rb +89 -0
- data/specs/unit/json/builder_spec.rb +49 -0
- data/specs/unit/json/parser_spec.rb +353 -0
- data/specs/unit/msgpack/parser_spec.rb +322 -0
- data/specs/unit/parser_spec.rb +18 -0
- data/specs/unit/struct_spec.rb +83 -0
- metadata +102 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
module MonitoringProtocols
|
2
|
+
@@parsers = {}
|
3
|
+
@@builders = {}
|
4
|
+
|
5
|
+
def self.register_parser(name, parser_class)
|
6
|
+
@@parsers[name] = parser_class
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.register_builder(name, builder_class)
|
10
|
+
@@builders[name] = builder_class
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.get_parser(protocol)
|
14
|
+
klass = @@parsers[protocol.to_sym]
|
15
|
+
klass ? klass.new : nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.get_builder(protocol)
|
19
|
+
klass = @@builders[protocol.to_sym]
|
20
|
+
klass ? klass.new : nil
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
|
2
|
+
require 'bigdecimal'
|
3
|
+
|
4
|
+
class BigDecimal
|
5
|
+
def to_msgpack(pack)
|
6
|
+
pack.write(to_f)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module MonitoringProtocols
|
11
|
+
class DataStruct
|
12
|
+
|
13
|
+
include Comparable
|
14
|
+
|
15
|
+
def initialize(*args)
|
16
|
+
merge_data_from!(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Merge new data in the structure.
|
21
|
+
#
|
22
|
+
# @param [Object,Hash] opts_or_obj Source
|
23
|
+
# @param [Array] only_fields an array of symbol
|
24
|
+
# specifying which fields to copy
|
25
|
+
# @param [Boolean] allow_nil If false nil values from
|
26
|
+
# the source will not be copied in object
|
27
|
+
#
|
28
|
+
def merge_data_from!(opts_or_obj = {}, only_fields = nil, allow_nil = false)
|
29
|
+
keys_left = list_keys(opts_or_obj)
|
30
|
+
|
31
|
+
self.class.attributes.select{|attr_name| selected_field?(attr_name, only_fields) }.each do |attr_name|
|
32
|
+
v = opts_or_obj.is_a?(Hash) ? (opts_or_obj[attr_name.to_s] || opts_or_obj[attr_name]) : opts_or_obj.send(attr_name)
|
33
|
+
if allow_nil || !v.nil?
|
34
|
+
send("#{attr_name}=", v)
|
35
|
+
end
|
36
|
+
|
37
|
+
keys_left.delete(attr_name)
|
38
|
+
end
|
39
|
+
|
40
|
+
unless keys_left.empty?
|
41
|
+
raise ArgumentError, "unknown keys: #{keys_left}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def list_keys(what)
|
46
|
+
if what.respond_to?(:keys)
|
47
|
+
what.keys.clone
|
48
|
+
else
|
49
|
+
[]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def selected_field?(field, list)
|
54
|
+
list.nil? || list.include?(field.to_sym)
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_h
|
58
|
+
h = {}
|
59
|
+
self.class.attributes.each do |attr_name|
|
60
|
+
h[attr_name] = send(attr_name)
|
61
|
+
end
|
62
|
+
h
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_a
|
66
|
+
self.class.attributes.map{|attr_name| send(attr_name) }
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_msgpack(pack)
|
70
|
+
pack.write(to_h)
|
71
|
+
end
|
72
|
+
|
73
|
+
def <=>(other)
|
74
|
+
self.to_a <=> other.to_a
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
class <<self
|
79
|
+
def properties(*names)
|
80
|
+
names.each do |name|
|
81
|
+
attr_accessor(name)
|
82
|
+
(@attributes ||= []) << name
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
alias :property :properties
|
87
|
+
|
88
|
+
def attributes
|
89
|
+
if (superclass <= DataStruct) && (superclass.attributes)
|
90
|
+
superclass.attributes + @attributes
|
91
|
+
else
|
92
|
+
@attributes
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'oj'
|
2
|
+
|
3
|
+
module MonitoringProtocols
|
4
|
+
module JSON
|
5
|
+
KEYS = %i(host app_name res_name metric_name value).freeze
|
6
|
+
COMPRESSABLE_KEYS = %i(host app_name).freeze
|
7
|
+
|
8
|
+
class Builder < Builder
|
9
|
+
def build_packet
|
10
|
+
# first we need to find common properties
|
11
|
+
json = {type: 'datapoints'}
|
12
|
+
|
13
|
+
COMPRESSABLE_KEYS.each do |attr_name|
|
14
|
+
v = @points[0].send(attr_name)
|
15
|
+
if @points.all?{|p| p.send(attr_name) == v }
|
16
|
+
json[attr_name] = v
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
points_left = @points
|
21
|
+
|
22
|
+
|
23
|
+
# find the root key
|
24
|
+
keys_left = KEYS.select{|key| !json.has_key?(key) }
|
25
|
+
|
26
|
+
raise "unsupported" unless keys_left.size == 3
|
27
|
+
|
28
|
+
# now fill the rest
|
29
|
+
until points_left.empty?
|
30
|
+
p = points_left.pop()
|
31
|
+
|
32
|
+
json[p.res_name] ||= {}
|
33
|
+
json[p.res_name][p.metric_name] = p.value
|
34
|
+
end
|
35
|
+
|
36
|
+
Oj.dump(json, symbol_keys: false, mode: :compat)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'oj'
|
2
|
+
|
3
|
+
module MonitoringProtocols
|
4
|
+
module JSON
|
5
|
+
|
6
|
+
# {
|
7
|
+
# type: 'datapoints',
|
8
|
+
# host: 'linux1'
|
9
|
+
# app_name: 'system',
|
10
|
+
#
|
11
|
+
# cpu: {
|
12
|
+
# 'user' => 43,
|
13
|
+
# 'sys' => 3.4,
|
14
|
+
# 'nice' => 6.0
|
15
|
+
# },
|
16
|
+
#
|
17
|
+
# memory: {
|
18
|
+
# 'total' => 2048,
|
19
|
+
# 'used' => 400
|
20
|
+
# }
|
21
|
+
# }
|
22
|
+
|
23
|
+
# {
|
24
|
+
# type: 'datapoints',
|
25
|
+
# app_name: 'system',
|
26
|
+
#
|
27
|
+
# linux1: =>
|
28
|
+
# cpu: {
|
29
|
+
# 'user' => 43,
|
30
|
+
# 'sys' => 3.4,
|
31
|
+
# 'nice' => 6.0
|
32
|
+
# },
|
33
|
+
#
|
34
|
+
# memory: {
|
35
|
+
# 'total' => 2048,
|
36
|
+
# 'used' => 400
|
37
|
+
# }
|
38
|
+
# }
|
39
|
+
# }
|
40
|
+
#
|
41
|
+
class Parser < Parser
|
42
|
+
|
43
|
+
def self._parse(buffer)
|
44
|
+
Oj.load(buffer, symbol_keys: false)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.parse(buffer)
|
48
|
+
packets = []
|
49
|
+
|
50
|
+
data = _parse(buffer)
|
51
|
+
|
52
|
+
msg_type = data.delete('type')
|
53
|
+
|
54
|
+
if msg_type == 'datapoints'
|
55
|
+
parse_datapoints(data) do |pkt|
|
56
|
+
packets << pkt
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
packets
|
61
|
+
|
62
|
+
rescue Oj::ParseError, MonitoringProtocols::ParseError
|
63
|
+
puts "Unable to parse: #{buffer}"
|
64
|
+
[]
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
def self.recursive_parse(point_data, next_fields, field_index, json_document, &block)
|
69
|
+
root_field = next_fields[field_index]
|
70
|
+
|
71
|
+
json_document.each do |name, value|
|
72
|
+
if root_field == :metric_name
|
73
|
+
point_data[root_field.to_sym] = name
|
74
|
+
point_data[:value] = parse_and_validate_value(value)
|
75
|
+
|
76
|
+
msg = DataPoint.new(point_data)
|
77
|
+
block.call(msg)
|
78
|
+
else
|
79
|
+
point_data[root_field] = name
|
80
|
+
recursive_parse(point_data, next_fields, field_index + 1, value, &block)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.parse_datapoints(data, &block)
|
86
|
+
common_data = {}
|
87
|
+
|
88
|
+
common_data[:host] = data.delete('host')
|
89
|
+
common_data[:app_name] = data.delete('app_name')
|
90
|
+
common_data[:res_name] = data.delete('app_name')
|
91
|
+
common_data[:first] = data.delete('first')
|
92
|
+
|
93
|
+
if time = data.delete('time')
|
94
|
+
common_data[:time] = Time.parse(time)
|
95
|
+
else
|
96
|
+
common_data[:time] = Time.now.utc()
|
97
|
+
end
|
98
|
+
|
99
|
+
# find which field we expect as the toplevel of the
|
100
|
+
# json document
|
101
|
+
next_fields = [:host, :app_name, :res_name, :metric_name]
|
102
|
+
field_index = 0
|
103
|
+
while (field_index < next_fields.size) && (common_data[next_fields[field_index]] != nil)
|
104
|
+
field_index+= 1
|
105
|
+
end
|
106
|
+
|
107
|
+
recursive_parse(common_data, next_fields, field_index, data, &block)
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
register_parser(:json, JSON::Parser)
|
115
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module MonitoringProtocols
|
2
|
+
|
3
|
+
ParseError = Class.new(RuntimeError)
|
4
|
+
|
5
|
+
class Parser
|
6
|
+
|
7
|
+
def parse(data)
|
8
|
+
self.class.parse(data)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
def self.parse_and_validate_value(v)
|
13
|
+
case v
|
14
|
+
when Fixnum, Bignum then v
|
15
|
+
when Float, BigDecimal then ((v * 1000).to_i) / 1000.0
|
16
|
+
else
|
17
|
+
raise ParseError, "invalid value: #{v} [#{v.class}]"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'msgpack'
|
3
|
+
require File.expand_path('../data_struct', __FILE__)
|
4
|
+
|
5
|
+
module MonitoringProtocols
|
6
|
+
|
7
|
+
class NetworkMessage < DataStruct
|
8
|
+
|
9
|
+
##
|
10
|
+
# Return a generic structure representing
|
11
|
+
# the content of this message.
|
12
|
+
#
|
13
|
+
# @return [DataPoint,Notification]
|
14
|
+
#
|
15
|
+
def convert_content
|
16
|
+
raise 'reimplement in subclass'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class CommonData < DataStruct
|
21
|
+
properties(
|
22
|
+
:time,
|
23
|
+
:host,
|
24
|
+
:app_name,
|
25
|
+
:res_name,
|
26
|
+
:metric_name,
|
27
|
+
)
|
28
|
+
|
29
|
+
def time=(val)
|
30
|
+
case val
|
31
|
+
when Time then @time = val
|
32
|
+
when Numeric then @time = Time.at(val).getutc()
|
33
|
+
else
|
34
|
+
raise "invalid type for time: #{val}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def measure_id(sep = '-')
|
39
|
+
[host, app_name, res_name, metric_name].join(sep)
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def to_h
|
44
|
+
super.merge(
|
45
|
+
time: time ? time.iso8601 : nil
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
def convert_content
|
50
|
+
[self]
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
class DataPoint < CommonData
|
56
|
+
properties(
|
57
|
+
:value,
|
58
|
+
:first
|
59
|
+
)
|
60
|
+
|
61
|
+
def data?; true; end
|
62
|
+
|
63
|
+
def to_h
|
64
|
+
super.merge(
|
65
|
+
type: 'datapoint'
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
class Notification < CommonData
|
72
|
+
SEVERITY = [:info, :warn, :error].freeze
|
73
|
+
|
74
|
+
properties(
|
75
|
+
:severity,
|
76
|
+
:message
|
77
|
+
)
|
78
|
+
|
79
|
+
def severity=(val)
|
80
|
+
if val.is_a?(Fixnum)
|
81
|
+
@severity = SEVERITY[val]
|
82
|
+
else
|
83
|
+
@severity = val
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def data?; false; end
|
88
|
+
|
89
|
+
def to_h
|
90
|
+
super.merge(
|
91
|
+
type: 'notification'
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/monitoring_protocols/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Julien Ammous"]
|
6
|
+
gem.email = ["schmurfy@gmail.com"]
|
7
|
+
gem.description = %q{...}
|
8
|
+
gem.summary = %q{....}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.name = "monitoring_protocols"
|
14
|
+
gem.require_paths = ["lib"]
|
15
|
+
gem.version = MonitoringProtocols::VERSION
|
16
|
+
|
17
|
+
gem.add_dependency 'msgpack'
|
18
|
+
gem.add_dependency 'oj'
|
19
|
+
end
|