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.
@@ -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,16 @@
1
+ require 'msgpack'
2
+
3
+ module MonitoringProtocols
4
+ module MsgPack
5
+
6
+ class Parser < JSON::Parser
7
+ def self._parse(buffer)
8
+ MessagePack.unpack(buffer)
9
+ end
10
+
11
+ end
12
+
13
+ end
14
+
15
+ register_parser(:msgpack, MsgPack::Parser)
16
+ 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,3 @@
1
+ module MonitoringProtocols
2
+ VERSION = "0.0.4"
3
+ 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