monitoring_protocols 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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