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