gri 10.0.0

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.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/MIT-LICENSE.txt +20 -0
  4. data/README.md +132 -0
  5. data/Rakefile +5 -0
  6. data/bin/grapher +17 -0
  7. data/bin/gri +5 -0
  8. data/bin/gricast +17 -0
  9. data/bin/grispark +5 -0
  10. data/bin/griwalk +9 -0
  11. data/bin/trad +19 -0
  12. data/bin/trad-http +17 -0
  13. data/gri.gemspec +21 -0
  14. data/lib/fluent/plugin/out_gri.rb +56 -0
  15. data/lib/gri/api.rb +28 -0
  16. data/lib/gri/app_collector.rb +154 -0
  17. data/lib/gri/app_walker.rb +23 -0
  18. data/lib/gri/blank.rb +49 -0
  19. data/lib/gri/builtindefs.rb +211 -0
  20. data/lib/gri/cast.rb +84 -0
  21. data/lib/gri/cgraph.rb +37 -0
  22. data/lib/gri/clist.rb +163 -0
  23. data/lib/gri/collector.rb +284 -0
  24. data/lib/gri/config.rb +134 -0
  25. data/lib/gri/ds_list.rb +166 -0
  26. data/lib/gri/format_helper.rb +112 -0
  27. data/lib/gri/gparams.rb +43 -0
  28. data/lib/gri/graph.rb +239 -0
  29. data/lib/gri/grapher.rb +70 -0
  30. data/lib/gri/ldb.rb +160 -0
  31. data/lib/gri/list.rb +242 -0
  32. data/lib/gri/log.rb +140 -0
  33. data/lib/gri/loop.rb +109 -0
  34. data/lib/gri/ltsv.rb +58 -0
  35. data/lib/gri/main.rb +107 -0
  36. data/lib/gri/mlog.rb +22 -0
  37. data/lib/gri/mmsgpack.rb +57 -0
  38. data/lib/gri/msnmp.rb +601 -0
  39. data/lib/gri/page.rb +235 -0
  40. data/lib/gri/pcollector.rb +209 -0
  41. data/lib/gri/plugin.rb +75 -0
  42. data/lib/gri/plugin/bootstrap.rb +65 -0
  43. data/lib/gri/plugin/cisco.rb +98 -0
  44. data/lib/gri/plugin/exec_collector.rb +89 -0
  45. data/lib/gri/plugin/juniper.rb +5 -0
  46. data/lib/gri/plugin/netsnmp.rb +8 -0
  47. data/lib/gri/plugin/ucdavis.rb +176 -0
  48. data/lib/gri/plugin/writer_fluentd.rb +26 -0
  49. data/lib/gri/polling_unit.rb +88 -0
  50. data/lib/gri/q.rb +5 -0
  51. data/lib/gri/request.rb +29 -0
  52. data/lib/gri/rrd.rb +438 -0
  53. data/lib/gri/scheduler.rb +68 -0
  54. data/lib/gri/sgraph.rb +147 -0
  55. data/lib/gri/spark.rb +94 -0
  56. data/lib/gri/tra_collector.rb +80 -0
  57. data/lib/gri/trad.rb +170 -0
  58. data/lib/gri/updater.rb +201 -0
  59. data/lib/gri/util_daemon.rb +19 -0
  60. data/lib/gri/util_marshal.rb +13 -0
  61. data/lib/gri/utils.rb +63 -0
  62. data/lib/gri/vendor.rb +76 -0
  63. data/lib/gri/version.rb +3 -0
  64. data/lib/gri/wmain.rb +67 -0
  65. data/lib/gri/writer.rb +184 -0
  66. data/mcollector +47 -0
  67. data/test/mock.rb +60 -0
  68. data/test/root/gra/.sysdb/sysdb.txt +3 -0
  69. data/test/root/gra/testhost/.records.txt +2 -0
  70. data/test/root/if.def +2 -0
  71. data/test/root/test.conf +4 -0
  72. data/test/root/testtab +9 -0
  73. data/test/root/testtab2 +2 -0
  74. data/test/root/tra/testhost/_/20130614 +20 -0
  75. data/test/test_app.rb +58 -0
  76. data/test/test_builtindefs.rb +24 -0
  77. data/test/test_collector.rb +58 -0
  78. data/test/test_config.rb +62 -0
  79. data/test/test_ds_list.rb +48 -0
  80. data/test/test_exec_collector.rb +33 -0
  81. data/test/test_format_helper.rb +68 -0
  82. data/test/test_graph.rb +69 -0
  83. data/test/test_ldb.rb +29 -0
  84. data/test/test_list.rb +65 -0
  85. data/test/test_log.rb +16 -0
  86. data/test/test_loop.rb +35 -0
  87. data/test/test_ltsv.rb +38 -0
  88. data/test/test_main.rb +19 -0
  89. data/test/test_mmsgpack.rb +27 -0
  90. data/test/test_msnmp.rb +147 -0
  91. data/test/test_page.rb +51 -0
  92. data/test/test_pcollector.rb +71 -0
  93. data/test/test_plugin.rb +62 -0
  94. data/test/test_plugin_cisco.rb +23 -0
  95. data/test/test_polling_unit.rb +58 -0
  96. data/test/test_request.rb +26 -0
  97. data/test/test_rrd.rb +53 -0
  98. data/test/test_rrd_updater.rb +139 -0
  99. data/test/test_scheduler.rb +31 -0
  100. data/test/test_tra_collector.rb +40 -0
  101. data/test/test_trad.rb +33 -0
  102. data/test/test_util_marshal.rb +17 -0
  103. data/test/test_utils.rb +15 -0
  104. data/test/test_vendor.rb +40 -0
  105. data/test/test_writer.rb +33 -0
  106. data/test/unittest_helper.rb +27 -0
  107. metadata +208 -0
data/lib/gri/loop.rb ADDED
@@ -0,0 +1,109 @@
1
+ module GRI
2
+ class Loop
3
+ attr_reader :collectors
4
+
5
+ def initialize
6
+ @collectors = {}
7
+
8
+ @rs = []
9
+ @ws = []
10
+ @handlers = {}
11
+ @times = {}
12
+ @tos = {}
13
+ @pt = Time.now
14
+ @procs = []
15
+ end
16
+
17
+ def attach collector
18
+ @collectors[collector] = true
19
+ collector.attach self
20
+ end
21
+
22
+ def detach collector
23
+ collector.on_detach
24
+ @collectors.delete collector
25
+ del_ios = @handlers.select {|k, v| v == collector}
26
+ del_ios.each {|io, handler|
27
+ @handlers.delete io
28
+ @times.delete io
29
+ @tos.delete io
30
+ @rs.delete io
31
+ @ws.delete io
32
+ }
33
+ @on_detach.call
34
+ end
35
+
36
+ def on_detach &on_detach
37
+ @on_detach = on_detach
38
+ end
39
+
40
+ def next_tick &cb
41
+ @procs.push cb
42
+ end
43
+
44
+ def watch io, flag, to, handler
45
+ now = Time.now
46
+ @handlers[io] = handler
47
+ @times[io] = now
48
+ @tos[io] = to
49
+ case flag
50
+ when :r
51
+ @rs.push io
52
+ when :w
53
+ @ws.push io
54
+ when :rw
55
+ @rs.push io
56
+ @ws.push io
57
+ end
58
+ end
59
+
60
+ def has_active_watchers?
61
+ !(@rs.empty? and @ws.empty? and @collectors.empty? and @procs.empty?)
62
+ end
63
+
64
+ def run
65
+ while has_active_watchers?
66
+ run_once
67
+ end
68
+ end
69
+
70
+ def run_once
71
+ while (cb = @procs.shift)
72
+ cb.call
73
+ end
74
+ if (a = IO.select(@rs, @ws, nil, 1))
75
+ rs, ws = a
76
+ for io in rs
77
+ @rs.delete io
78
+ if (h = @handlers[io])
79
+ h.on_readable io
80
+ end
81
+ end
82
+ for io in ws
83
+ @ws.delete io
84
+ if (h = @handlers[io])
85
+ h.on_writable io
86
+ end
87
+ end
88
+ end
89
+ if @pt.sec != (now = Time.now).sec
90
+ for io in @rs + @ws
91
+ if (t = @times[io]) and (to = @tos[io]) > 0 and (now - t >= to)
92
+ @rs.delete io
93
+ @ws.delete io
94
+ if (h = @handlers[io])
95
+ h.retry
96
+ end
97
+ end
98
+ end
99
+ for c in @collectors.keys
100
+ if c.attached_at and c.attached_at + c.timeout < now
101
+ c.on_timeout
102
+ detach c
103
+ end
104
+ end
105
+ @pt = now
106
+ end
107
+ end
108
+ end
109
+ end
data/lib/gri/ltsv.rb ADDED
@@ -0,0 +1,58 @@
1
+ # conding: us-ascii
2
+
3
+ module LTSV
4
+ def escape18 s
5
+ s.to_s.gsub(/\n/, "\\n").gsub(/\r/, "\\r").gsub(/\t/, "\\t")
6
+ end
7
+
8
+ def escape19 s
9
+ s.to_s.force_encoding(Encoding::ASCII_8BIT).
10
+ gsub(/\n/, "\\n").gsub(/\r/, "\\r").gsub(/\t/, "\\t")
11
+ end
12
+
13
+ alias_method :escape,
14
+ (''.respond_to? :force_encoding) ? :escape19 : :escape18
15
+ extend LTSV
16
+
17
+ def serialize value
18
+ h = value.to_hash
19
+ h.map {|k, v| "#{k}:#{escape v}"}.join("\t")
20
+ end
21
+
22
+ def dump_to_io values, io
23
+ (Hash === values) and values = values.values
24
+ for value in values
25
+ io.puts serialize(value)
26
+ end
27
+ end
28
+
29
+ def dump_to_file values, path
30
+ tmp_path = path + ".tmp#{$$}"
31
+ open(tmp_path, 'w') {|f|
32
+ dump_to_io values, f
33
+ }
34
+ File.rename tmp_path, path
35
+ end
36
+
37
+ def parse_string line
38
+ h = {}
39
+ for item in line.split("\t")
40
+ k, v = item.split(':', 2)
41
+ next unless k
42
+ h[k] = case v
43
+ when nil; nil
44
+ when ''; nil
45
+ else v
46
+ end
47
+ end
48
+ h
49
+ end
50
+
51
+ def load_from_file path
52
+ File.open(path, 'rb') {|f| parse_io f}
53
+ end
54
+
55
+ def parse_io io
56
+ io.map {|line| parse_string line.chomp}
57
+ end
58
+ end
data/lib/gri/main.rb ADDED
@@ -0,0 +1,107 @@
1
+ require 'optparse'
2
+ require 'socket'
3
+
4
+ require 'rubygems'
5
+
6
+ require 'gri/config'
7
+ require 'gri/builtindefs'
8
+ require 'gri/plugin'
9
+ require 'gri/app_collector'
10
+ require 'gri/pcollector'
11
+ require 'gri/tra_collector'
12
+ require 'gri/updater'
13
+ require 'gri/writer'
14
+ require 'gri/log'
15
+
16
+ module GRI
17
+ class Main
18
+ attr_reader :config, :options
19
+
20
+ def initialize
21
+ @options = {}
22
+ optparser = optparse @options
23
+ optparser.parse!
24
+ config_path = options[:config_path] || GRI::Config::DEFAULT_PATH
25
+ @config = GRI::Config.init config_path
26
+ @options.each {|k, v| @config.setvar k.to_s, v}
27
+ root_dir = @config['root-dir'] ||= Config::ROOT_PATH
28
+
29
+ plugin_dirs = @config.getvar('plugin-dir') || [root_dir + '/plugin']
30
+ GRI::Plugin.load_plugins plugin_dirs, @config
31
+
32
+ log_dir = @config['log-dir'] || root_dir + '/log'
33
+ Dir.mkdir log_dir unless File.exist? log_dir
34
+ Log.init "#{log_dir}/#{optparser.program_name}.log",
35
+ :log_level=>@config['log-level']
36
+
37
+ @config['tra-dir'] ||= root_dir + '/tra'
38
+ @config['gra-dir'] ||= root_dir + '/gra'
39
+ end
40
+
41
+ def run
42
+ @config['para'] = true unless config.has_key? 'para'
43
+ if @config['walker']
44
+ app = AppWalker.new @config
45
+ writer = Writer.create 'stdout', @config.to_h
46
+ app.writers.push writer
47
+ elsif @config['collector']
48
+ # collector
49
+ app = AppCollector.new @config
50
+ writer = Writer.create 'ldb', @config.to_h
51
+ app.writers.push writer
52
+ else
53
+ # minigri
54
+ app = AppCollector.new @config
55
+ wopts = @config.to_h.update :merge_p=>true
56
+ writer = Writer.create 'rrd', wopts
57
+ app.writers.push writer
58
+ end
59
+ if @options['writers']
60
+ for w in @options['writers']
61
+ writer = Writer.create w, @config.to_h
62
+ app.writers.push writer if writer
63
+ end
64
+ end
65
+ app.run
66
+ if app.metrics[:nometrics].zero? and !$debug
67
+ hostname = Socket.gethostname rescue 'unknown'
68
+ hostname = hostname.split(/\./).first
69
+ t = Time.now.to_i
70
+ interval = @config['interval'] || 300
71
+ records = app.metrics.map {|k, v|
72
+ {'_interval'=>interval, '_host'=>"GRIMETRICS-#{hostname}",
73
+ '_time'=>t, '_key'=>"num_#{k}", 'num'=>v}}
74
+ writer = Writer.create 'ldb', @config.to_h
75
+ writer.write records
76
+ end
77
+ end
78
+
79
+ def optparse opts
80
+ op = OptionParser.new
81
+ op.on('--debug') {$debug = true; STDOUT.sync = true;
82
+ opts['log-level'] = 'debug'}
83
+ op.on('--Doption=STR') {|arg| (opts['Doption'] ||= []).push arg}
84
+
85
+ op.on('--add-writer=WRITER') {|arg| (opts['writers'] ||= []).push arg}
86
+ op.on('-O OPT_STR') {|arg| (opts['O'] ||= []).push arg}
87
+ op.on('--collector') {opts['collector'] = true}
88
+ op.on('--config-path=PATH') {|arg| opts[:config_path] = arg}
89
+ op.on('--duration=SEC', Integer) {|arg| opts['duration'] = arg}
90
+ op.on('--gritab-path=PATH') {|arg| opts['gritab-path'] = arg}
91
+ op.on('--host-pat=PAT', '-h') {|arg| (opts['host-pat'] ||= []).push arg}
92
+ op.on('--interval=SEC', Integer) {|arg| opts['interval'] = arg}
93
+ op.on('--log-level=LEVEL') {|arg| opts['log-level'] = arg}
94
+ op.on('--nop') {opts['nop'] = true}
95
+ op.on('--para') {opts['para'] = true}
96
+ op.on('-p', '--plugin-dir=DIR') {|arg|
97
+ (opts['plugin-dir'] ||= []).push arg}
98
+ op.on('--rrdupdater', '--updater') {opts['updater'] = true}
99
+ op.on('--single') {opts['para'] = false}
100
+ op.on('--tra=URL') {|arg| opts['tra'] = arg}
101
+
102
+ op.on('-c COMMUNITY') {|arg| opts['community'] = arg}
103
+ op.on('-v VER') {|arg| opts['version'] = arg}
104
+ op
105
+ end
106
+ end
107
+ end
data/lib/gri/mlog.rb ADDED
@@ -0,0 +1,22 @@
1
+ module Log
2
+ extend Log
3
+
4
+ def init(*args)
5
+ end
6
+ def write grp, str, severity=nil
7
+ end
8
+ def puts(*args)
9
+ end
10
+ def pp(*args)
11
+ end
12
+ def debug(*args, &block)
13
+ end
14
+ def info(*args, &block)
15
+ end
16
+ def warn(*args, &block)
17
+ end
18
+ def error(*args, &block)
19
+ end
20
+ def fatal(*args, &block)
21
+ end
22
+ end
@@ -0,0 +1,57 @@
1
+ # coding: us-ascii
2
+
3
+ class Fixnum
4
+ def to_msgpack
5
+ if (self >= -32 and self <= 127)
6
+ [self].pack('c')
7
+ elsif self < 4294967296
8
+ "\xd2"+[self].pack('N')
9
+ else
10
+ "\xcf"+[(self>>32), (self&0xffffffff)].pack('N2')
11
+ end
12
+ end
13
+ end
14
+
15
+ class Bignum
16
+ def to_msgpack
17
+ "\xcf"+[(self>>32), (self&0xffffffff)].pack('N2')
18
+ end
19
+ end
20
+
21
+ class Float
22
+ def to_msgpack
23
+ if self < 4294967296
24
+ "\xce"+[(self/65536).to_i, self%65536].pack('n2')
25
+ else
26
+ "\xcf"+[(self/281474976710656).to_i,
27
+ (self/4294967296%65536).to_i,
28
+ (self%4294967296/65536).to_i, self%65536].pack('n4')
29
+ end
30
+ end
31
+ end
32
+
33
+ class Nil
34
+ def to_msgpack
35
+ "\xc0"
36
+ end
37
+ end
38
+
39
+ class String
40
+ def to_msgpack
41
+ "\xdb"+[self.size].pack('N')+self
42
+ end
43
+ end
44
+
45
+ class Array
46
+ def to_msgpack
47
+ "\xdc" + [self.size].pack('n') +
48
+ self.map {|e| e.to_msgpack}.join('')
49
+ end
50
+ end
51
+
52
+ class Hash
53
+ def to_msgpack
54
+ "\xde" + [self.size].pack('n') +
55
+ self.map {|k, v| k.to_msgpack + v.to_msgpack}.join('')
56
+ end
57
+ end
data/lib/gri/msnmp.rb ADDED
@@ -0,0 +1,601 @@
1
+ # coding: us-ascii
2
+
3
+ class String
4
+ unless ''.respond_to? :getbyte
5
+ def getbyte idx
6
+ self[idx]
7
+ end
8
+ end
9
+
10
+ unless ''.respond_to? :slice!
11
+ def slice!(arg1, arg2 = 1)
12
+ return nil if arg2 < 0
13
+ if arg1.class == Fixnum
14
+ rval = self[arg1, arg2]
15
+ len = self.length
16
+ rpos = arg1 + arg2
17
+ rpos += len if arg1 < 0
18
+ rlen = len - rpos
19
+ region_l = self[0...arg1]
20
+ region_r = self[rpos, rlen]
21
+ region_r = '' if region_r == nil
22
+ self.replace(region_l + region_r)
23
+ elsif arg1.class == String
24
+ rval = arg1
25
+ self.gsub!(arg1, "")
26
+ else
27
+ return nil
28
+ end
29
+ rval
30
+ end
31
+ end
32
+ end
33
+
34
+ module BER
35
+ class C64
36
+ def initialize s
37
+ @s = s
38
+ @a = [0, 0, 0, 0, 0, 0, 0, 0]
39
+ j = 7
40
+ (s.size-1).downto(0) {|i| @a[j] = s.getbyte(i); j -= 1}
41
+ end
42
+
43
+ def to_s
44
+ '0x' + @a.map {|b| sprintf "%02x", b}.join('')
45
+ end
46
+
47
+ def to_binary
48
+ @a.pack('C*')
49
+ end
50
+
51
+ def to_msgpack
52
+ "\xcf" + @a.pack('C*')
53
+ end
54
+
55
+ def to_i
56
+ n = 0
57
+ 0.upto(7) {|i| n = n * 256 + @a[i]}
58
+ n
59
+ end
60
+ end
61
+
62
+ def enc tag, val
63
+ case tag
64
+ when 0x02
65
+ return enc_int(val)
66
+ when 0x04
67
+ return enc_str(val)
68
+ end
69
+ end
70
+
71
+ def enc_len n
72
+ if n < 128 then
73
+ return n.chr
74
+ end
75
+ e_len = ''
76
+ while n > 0
77
+ e_len = (n & 0xff).chr + e_len
78
+ n = n >> 8
79
+ end
80
+ return (e_len.size | 0x80).chr + e_len
81
+ end
82
+
83
+ def enc_int n
84
+ ebuf = ''
85
+ if n < 0 then
86
+ begin
87
+ ebuf = (n & 0xff).chr + ebuf
88
+ n = n >> 8
89
+ end until (n == -1) and (ebuf.getbyte(0) & 0x80 == 0x80)
90
+ else
91
+ begin
92
+ ebuf = (n & 0xff).chr + ebuf
93
+ n = n >> 8
94
+ end while n > 0
95
+ end
96
+ return "\x02" + enc_len(ebuf.size) + ebuf
97
+ end
98
+
99
+ def enc_str s
100
+ return "\x04" + enc_len(s.size) + s
101
+ end
102
+
103
+ def enc_a_oid a
104
+ a.shift; a.shift
105
+ e = '+' # 1.3
106
+ until a.empty?
107
+ subid = a.shift.to_i
108
+ s = ''
109
+ if subid == 0 then
110
+ e += "\x00"
111
+ else
112
+ while subid > 0
113
+ if s.empty?
114
+ s = (subid & 0x7f).chr + s
115
+ else
116
+ s = (subid & 0x7f | 0x80).chr + s
117
+ end
118
+ subid = subid >> 7
119
+ end
120
+ e += s
121
+ end
122
+ end
123
+ e
124
+ end
125
+
126
+ def enc_v_oid oid
127
+ a = oid.split('.')
128
+ enc_a_oid a
129
+ end
130
+
131
+ def enc_oid oid
132
+ e = enc_v_oid oid
133
+ return "\x06" + enc_len(e.size) + e
134
+ end
135
+
136
+ def enc_oid_list oid_list
137
+ s = ''
138
+ for e in oid_list
139
+ vb = enc_oid(e) + "\x05\x00" # BER.enc_null
140
+ s = s + "\x30" + enc_len(vb.size) + vb
141
+ end
142
+ varbind = "\x30" + enc_len(s.size) + s
143
+ end
144
+
145
+ def enc_varbind enoid
146
+ s = "\x06" + enc_len(enoid.size) + enoid + "\x05\x00"
147
+ s = "\x30" + enc_len(s.size) + s
148
+ return "\x30" + enc_len(s.size) + s
149
+ end
150
+
151
+ def cat_enoid enoids
152
+ s = ''
153
+ for enoid in enoids
154
+ vb = "\x06" + enc_len(enoid.size) + enoid + "\x05\x00"
155
+ s = s + "\x30" + enc_len(vb.size) + vb
156
+ end
157
+ varbind = "\x30" + enc_len(s.size) + s
158
+ end
159
+
160
+ def dec_oid msg
161
+ msg = msg.clone
162
+ if msg.getbyte(0) == 0x2b
163
+ oid = [1, 3]
164
+ msg.slice! 0
165
+ else
166
+ oid = []
167
+ end
168
+ until msg.empty?
169
+ c = msg.getbyte 0
170
+ msg.slice! 0
171
+ if c > 127
172
+ id = 0
173
+ while c > 127
174
+ id = (id << 7) + (c & 0x7f)
175
+ c = msg.getbyte 0
176
+ msg.slice! 0
177
+ end
178
+ id = (id << 7) + c
179
+ oid.push id
180
+ else
181
+ oid.push c
182
+ end
183
+ end
184
+ return oid
185
+ end
186
+
187
+ def dec_int s
188
+ if (s.getbyte(0) & 0x80) == 0x80
189
+ n = -1
190
+ else
191
+ n = 0
192
+ end
193
+ while s.size > 0
194
+ n = n << 8 | s.getbyte(0)
195
+ s = s[1..-1]
196
+ end
197
+ return n
198
+ end
199
+
200
+ def dec_cnt s
201
+ n = 0
202
+ while s.size > 0
203
+ n = n << 8 | s.getbyte(0)
204
+ s = s[1..-1]
205
+ end
206
+ return n
207
+ end
208
+
209
+ def dec_cnt64 s
210
+ C64.new(s)
211
+ end
212
+
213
+ Object.const_defined?(:RUBY_VERSION) and
214
+ alias_method(:dec_cnt64, :dec_cnt)
215
+
216
+ def tlv data
217
+ tag = data.getbyte 0
218
+ if (len = data.getbyte(1)) < 128
219
+ val = data[2, len]
220
+ remain = data[(len + 2)..-1]
221
+ elsif len == 0x81
222
+ len = data.getbyte 2
223
+ val = data[3, len]
224
+ remain = data[(len + 3)..-1]
225
+ else
226
+ n = len & 0x7f
227
+ len = 0
228
+ for i in 1..n
229
+ len = len * 256 + data.getbyte(i + 1)
230
+ end
231
+ val = data[n + 2, len]
232
+ remain = data[(len + n + 2).. -1]
233
+ end
234
+ return tag, val, remain
235
+ end
236
+
237
+ def dec_msg msg
238
+ if msg[1, 1] < "\x80"
239
+ idx = msg.getbyte(6) + 7
240
+ pdutype, pdu, msg = tlv msg[idx..-1]
241
+ elsif msg[1, 1] == "\x81"
242
+ idx = msg.getbyte(7) + 8
243
+ pdutype, pdu, msg = tlv msg[idx..-1]
244
+ else
245
+ tag, val, msg = tlv msg
246
+ tag, ver, msg = tlv val
247
+ tag, comm, msg = tlv msg
248
+ pdutype, pdu, msg = tlv msg
249
+ end
250
+ idlen = pdu.getbyte 1
251
+ enc_reqid = pdu[0, idlen + 2]
252
+ error_status = pdu.getbyte(idlen + 4)
253
+ error_index = pdu.getbyte(idlen + 7)
254
+ tag, varbind, msg = tlv pdu[idlen+8..-1]
255
+ return enc_reqid, error_status, error_index, varbind
256
+ end
257
+
258
+ def dec_varbind_block msg
259
+ while msg.size > 0
260
+ tag,val,msg=tlv(msg)
261
+ tag,val1,msg0=tlv(val)
262
+ tag,val2,msg1=tlv(msg0)
263
+ case tag
264
+ when 0x02
265
+ val2=dec_int(val2)
266
+ when 0x05 #Null
267
+ val2=nil
268
+ when 0x06 #OID
269
+ #val2=dec_oid(val2)
270
+ when 0x40 # IP Address
271
+ val2 = val2.unpack('CCCC').join('.')
272
+ when 0x41, 0x42, 0x43 #Counter or Gauge or Tick
273
+ val2 = dec_cnt val2
274
+ when 0x46 #Counter64
275
+ val2 = dec_cnt64 val2
276
+ when 0x80
277
+ next # skip
278
+ end
279
+ yield val1, tag, val2
280
+ end
281
+ end
282
+
283
+ def dec_varbind msg
284
+ list=[]
285
+ val1 = tag = val2 = nil #XXX
286
+ dec_varbind_block(msg) {|val1, tag, val2|
287
+ list.push([val1,tag,val2])
288
+ }
289
+ return list
290
+ end
291
+
292
+ extend BER
293
+ end
294
+
295
+ class SNMP
296
+ OIDS = {
297
+ 'iso'=>'1',
298
+ 'org'=>'1.3',
299
+ 'dod'=>'1.3.6',
300
+ 'internet'=>'1.3.6.1',
301
+ 'directory'=>'1.3.6.1.1',
302
+ 'mgmt'=>'1.3.6.1.2',
303
+ 'mib-2'=>'1.3.6.1.2.1',
304
+ 'system'=>'1.3.6.1.2.1.1',
305
+ 'sysDescr'=>'1.3.6.1.2.1.1.1',
306
+ 'sysObjectID'=>'1.3.6.1.2.1.1.2',
307
+ 'sysUpTime'=>'1.3.6.1.2.1.1.3',
308
+ 'sysContact'=>'1.3.6.1.2.1.1.4',
309
+ 'sysName'=>'1.3.6.1.2.1.1.5',
310
+ 'sysLocation'=>'1.3.6.1.2.1.1.6',
311
+ 'sysServices'=>'1.3.6.1.2.1.1.7',
312
+
313
+ 'interfaces'=>'1.3.6.1.2.1.2',
314
+ 'ifNumber'=>'1.3.6.1.2.1.2.1',
315
+ 'ifTable'=>'1.3.6.1.2.1.2.2',
316
+ 'ifEntry'=>'1.3.6.1.2.1.2.2.1',
317
+ 'ifIndex'=>'1.3.6.1.2.1.2.2.1.1',
318
+ 'ifInOctets'=>'1.3.6.1.2.1.2.2.1.10',
319
+ 'ifInUcastPkts'=>'1.3.6.1.2.1.2.2.1.11',
320
+ 'ifInNUcastPkts'=>'1.3.6.1.2.1.2.2.1.12',
321
+ 'ifInDiscards'=>'1.3.6.1.2.1.2.2.1.13',
322
+ 'ifInErrors'=>'1.3.6.1.2.1.2.2.1.14',
323
+ 'ifInUnknownProtos'=>'1.3.6.1.2.1.2.2.1.15',
324
+ 'ifOutOctets'=>'1.3.6.1.2.1.2.2.1.16',
325
+ 'ifOutUcastPkts'=>'1.3.6.1.2.1.2.2.1.17',
326
+ 'ifOutNUcastPkts'=>'1.3.6.1.2.1.2.2.1.18',
327
+ 'ifOutDiscards'=>'1.3.6.1.2.1.2.2.1.19',
328
+ 'ifDescr'=>'1.3.6.1.2.1.2.2.1.2',
329
+ 'ifOutErrors'=>'1.3.6.1.2.1.2.2.1.20',
330
+ 'ifOutQLen'=>'1.3.6.1.2.1.2.2.1.21',
331
+ 'ifSpecific'=>'1.3.6.1.2.1.2.2.1.22',
332
+ 'ifType'=>'1.3.6.1.2.1.2.2.1.3',
333
+ 'ifMtu'=>'1.3.6.1.2.1.2.2.1.4',
334
+ 'ifSpeed'=>'1.3.6.1.2.1.2.2.1.5',
335
+ 'ifPhysAddress'=>'1.3.6.1.2.1.2.2.1.6',
336
+ 'ifAdminStatus'=>'1.3.6.1.2.1.2.2.1.7',
337
+ 'ifOperStatus'=>'1.3.6.1.2.1.2.2.1.8',
338
+ 'ifLastChange'=>'1.3.6.1.2.1.2.2.1.9',
339
+
340
+ 'ipAddrTable'=>'1.3.6.1.2.1.4.20',
341
+ 'ipAddrEntry'=>'1.3.6.1.2.1.4.20.1',
342
+ 'ipAdEntIfIndex'=>'1.3.6.1.2.1.4.20.1.2',
343
+ 'ipAdEntNetMask'=>'1.3.6.1.2.1.4.20.1.3',
344
+ 'ipNetToMediaIfIndex'=>'1.3.6.1.2.1.4.22.1.1',
345
+ 'ipNetToMediaPhysAddress'=>'1.3.6.1.2.1.4.22.1.2',
346
+ 'ipNetToMediaNetAddress'=>'1.3.6.1.2.1.4.22.1.3',
347
+ 'ipNetToMediaType'=>'1.3.6.1.2.1.4.22.1.4',
348
+
349
+ 'tcp'=>'1.3.6.1.2.1.6',
350
+ 'tcpCurrEstab'=>'1.3.6.1.2.1.6.9.0',
351
+ 'tcpActiveOpens'=>'1.3.6.1.2.1.6.5.0',
352
+ 'tcpPassiveOpens'=>'1.3.6.1.2.1.6.6.0',
353
+ 'tcpAttemptFails'=>'1.3.6.1.2.1.6.7.0',
354
+ 'tcpEstabResets'=>'1.3.6.1.2.1.6.8.0',
355
+ 'tcpInSegs'=>'1.3.6.1.2.1.6.10.0',
356
+ 'tcpOutSegs'=>'1.3.6.1.2.1.6.11.0',
357
+ 'tcpRetransSegs'=>'1.3.6.1.2.1.6.12.0',
358
+ 'tcpInErrs'=>'1.3.6.1.2.1.6.14.0',
359
+ 'tcpOutRsts'=>'1.3.6.1.2.1.6.15.0',
360
+
361
+ 'udp'=>'1.3.6.1.2.1.7',
362
+ 'udpInDatagrams' => '1.3.6.1.2.1.7.1.0',
363
+ 'udpNoPorts' => '1.3.6.1.2.1.7.2.0',
364
+ 'udpInErrors' => '1.3.6.1.2.1.7.3.0',
365
+ 'udpOutDatagrams' => '1.3.6.1.2.1.7.4.0',
366
+
367
+ 'host'=>'1.3.6.1.2.1.25',
368
+ 'hrSystem'=>'1.3.6.1.2.1.25.1',
369
+ 'hrSystemNumUsers'=>'1.3.6.1.2.1.25.1.5.0',
370
+ 'hrSystemProcesses'=>'1.3.6.1.2.1.25.1.6.0',
371
+ 'hrSystemMaxProcesses'=>'1.3.6.1.2.1.25.1.7.0',
372
+ 'hrSystem'=>'1.3.6.1.2.1.25.1',
373
+ 'hrStorage'=>'1.3.6.1.2.1.25.2',
374
+ 'hrStorageTypes'=>'1.3.6.1.2.1.25.2.1',
375
+ 'hrStorageFixedDisk'=>'1.3.6.1.2.1.25.2.1.4',
376
+ 'hrMemorySize'=>'1.3.6.1.2.1.25.2.2.0',
377
+ 'hrStorageTable'=>'1.3.6.1.2.1.25.2.3',
378
+ 'hrStorageEntry'=>'1.3.6.1.2.1.25.2.3.1',
379
+ 'hrStorageIndex'=>'1.3.6.1.2.1.25.2.3.1.1',
380
+ 'hrStorageType'=>'1.3.6.1.2.1.25.2.3.1.2',
381
+ 'hrStorageDescr'=>'1.3.6.1.2.1.25.2.3.1.3',
382
+ 'hrStorageAllocationUnits'=>'1.3.6.1.2.1.25.2.3.1.4',
383
+ 'hrStorageSize'=>'1.3.6.1.2.1.25.2.3.1.5',
384
+ 'hrStorageUsed'=>'1.3.6.1.2.1.25.2.3.1.6',
385
+
386
+ 'hrSWRunName'=>'1.3.6.1.2.1.25.4.2.1.2',
387
+ 'hrSWRunPath'=>'1.3.6.1.2.1.25.4.2.1.4',
388
+ 'hrSWRunParameters'=>'1.3.6.1.2.1.25.4.2.1.5',
389
+ 'hrSWRunPerfCPU'=>'1.3.6.1.2.1.25.5.1.1.1',
390
+ 'hrSWRunPerfMem'=>'1.3.6.1.2.1.25.5.1.1.2',
391
+
392
+ 'ifMIB'=>'1.3.6.1.2.1.31',
393
+ 'ifXEntry'=>'1.3.6.1.2.1.31.1.1.1',
394
+ 'ifName'=>'1.3.6.1.2.1.31.1.1.1.1',
395
+ 'ifInMulticastPkts'=>'1.3.6.1.2.1.31.1.1.1.2',
396
+ 'ifInBroadcastPkts'=>'1.3.6.1.2.1.31.1.1.1.3',
397
+ 'ifOutMulticastPkts'=>'1.3.6.1.2.1.31.1.1.1.4',
398
+ 'ifOutBroadcastPkts'=>'1.3.6.1.2.1.31.1.1.1.5',
399
+ 'ifHCInOctets'=>'1.3.6.1.2.1.31.1.1.1.6',
400
+ 'ifHCInUcastPkts'=>'1.3.6.1.2.1.31.1.1.1.7',
401
+ 'ifHCInMulticastPkts'=>'1.3.6.1.2.1.31.1.1.1.8',
402
+ 'ifHCInBroadcastPkts'=>'1.3.6.1.2.1.31.1.1.1.9',
403
+ 'ifHCOutOctets'=>'1.3.6.1.2.1.31.1.1.1.10',
404
+ 'ifHCOutUcastPkts'=>'1.3.6.1.2.1.31.1.1.1.11',
405
+ 'ifHCOutMulticastPkts'=>'1.3.6.1.2.1.31.1.1.1.12',
406
+ 'ifHCOutBroadcastPkts'=>'1.3.6.1.2.1.31.1.1.1.13',
407
+ 'ifLinkUpDownTrapEnable'=>'1.3.6.1.2.1.31.1.1.1.14',
408
+ 'ifHighSpeed'=>'1.3.6.1.2.1.31.1.1.1.15',
409
+ 'ifPromiscuousMode'=>'1.3.6.1.2.1.31.1.1.1.16',
410
+ 'ifConnectorPresent'=>'1.3.6.1.2.1.31.1.1.1.17',
411
+ 'ifAlias'=>'1.3.6.1.2.1.31.1.1.1.18',
412
+
413
+ 'entityMIB'=>'1.3.6.1.2.1.47',
414
+ 'entPhysicalEntry'=>'1.3.6.1.2.1.47.1.1.1.1',
415
+ 'entPhysicalDescr'=>'1.3.6.1.2.1.47.1.1.1.1.2',
416
+ 'entPhysicalName'=>'1.3.6.1.2.1.47.1.1.1.1.7',
417
+
418
+ 'experimental'=>'1.3.6.1.3',
419
+ 'private'=>'1.3.6.1.4',
420
+ 'enterprises'=>'1.3.6.1.4.1',
421
+ }
422
+ ROIDS = {}
423
+ RECV_SIZE = 2000
424
+
425
+ attr_reader :sock
426
+ attr_accessor :state, :numeric
427
+
428
+ def self.add_oid sym, oid
429
+ OIDS[sym] = oid
430
+ end
431
+
432
+ def self.update h
433
+ OIDS.merge! h
434
+ end
435
+
436
+ def initialize host, port=nil
437
+ port ||= 161
438
+ if ROIDS.empty?
439
+ for sym, oid in OIDS
440
+ ROIDS[BER.enc_v_oid(oid)] = sym
441
+ end
442
+ end
443
+ @host = host
444
+ @port = port
445
+ @request_id = 1000
446
+ @retries = 3
447
+ @timeout = 4
448
+ @ver = "\x02\x01\x00" # INT: 0 (ver 1)
449
+ @community = "\x04\x06public"
450
+ @numeric = false
451
+ @state = :IDLE
452
+ @sock = nil
453
+ end
454
+
455
+ def connect
456
+ @sock = UDPSocket.new
457
+ @sock.connect @host, @port
458
+ end
459
+
460
+ def close
461
+ @sock.close
462
+ end
463
+
464
+ def version=(ver)
465
+ ver = {'1'=>0, '2c'=>1}[ver] || 0
466
+ @ver = BER.enc_int ver
467
+ end
468
+
469
+ def community=(community)
470
+ @community = BER.enc_str community
471
+ end
472
+
473
+ def enoid2name enoid
474
+ a = BER.dec_oid enoid
475
+ n = a.size
476
+ n0 = n
477
+
478
+ while n > 1
479
+ if (s = ROIDS[BER.enc_a_oid(a[0, n])])
480
+ return ([s] + a[n, n0-n]).join('.')
481
+ end
482
+ n -= 1
483
+ end
484
+ s || a.join('.')
485
+ end
486
+
487
+ def make_msg cmd, err_index, varbind
488
+ @request_id += 1
489
+ @enc_request_id = BER.enc_int @request_id
490
+ s = @enc_request_id + "\x02\x01\x00" + err_index + varbind
491
+ s = @ver + @community + cmd + BER.enc_len(s.size) + s
492
+ s = "\x30" + BER.enc_len(s.size) + s
493
+ end
494
+
495
+ def make_req st, arg
496
+ case st
497
+ when :GETBULK_REQ
498
+ vb = "\x06" + BER.enc_len(arg.size) + arg + "\x05\x00"
499
+ vb = "\x30" + BER.enc_len(vb.size) + vb
500
+ varbind = "\x30" + BER.enc_len(vb.size) + vb
501
+ s = make_msg "\xa5", "\x02\x01\x0c", varbind
502
+ when :GETNEXT_REQ
503
+ vb = "\x06" + BER.enc_len(arg.size) + arg + "\x05\x00"
504
+ vb = "\x30" + BER.enc_len(vb.size) + vb
505
+ varbind = "\x30" + BER.enc_len(vb.size) + vb
506
+ s = make_msg "\xa1", "\x02\x01\x00", varbind
507
+ when :GET_REQ
508
+ s = make_msg "\xa0", "\x02\x01\x00", arg
509
+ end
510
+ s
511
+ end
512
+
513
+ def walk_start enoid, &cb
514
+ @req_enoid = enoid
515
+ @cb = cb
516
+ @state = (@ver == "\002\001\001") ? :GETBULK_REQ : :GETNEXT_REQ
517
+ end
518
+
519
+ def _walk enoid, &cb
520
+ walk_start enoid, &cb
521
+ req_loop enoid
522
+ end
523
+
524
+ def walk oid, &cb
525
+ noid = OIDS[oid] || oid
526
+ enoid = BER.enc_v_oid noid
527
+ _walk(enoid) {|enoid, tag, val|
528
+ oid = @numeric ? (BER.dec_oid(enoid).join('.')) : (enoid2name enoid)
529
+ cb.call oid, tag, val
530
+ }
531
+ end
532
+
533
+ def get_start varbind, &cb
534
+ @req_enoid = ''
535
+ @cb = cb
536
+ @state = :GET_REQ
537
+ end
538
+
539
+ def get_varbind varbind, &cb
540
+ get_start varbind, &cb
541
+ req_loop varbind
542
+ end
543
+
544
+ def get oid_list, &cb
545
+ oids = oid_list.map {|s| BER.enc_v_oid(OIDS[s]||s)}
546
+ varbind = BER.cat_enoid oids
547
+ get_varbind(varbind) {|enoid, tag, val|
548
+ oid = @numeric ? (BER.dec_oid(enoid).join('.')) : (enoid2name enoid)
549
+ cb.call oid, tag, val
550
+ }
551
+ end
552
+
553
+ def req_loop arg
554
+ connect unless @sock
555
+ until @state == :SUCCESS or @state == :IDLE
556
+ s = make_req @state, arg
557
+ @retry_count = 0
558
+ while true
559
+ @sock.send s, 0
560
+ if (a = IO.select([@sock], nil, nil, @timeout))
561
+ msg = @sock.recv RECV_SIZE, 0
562
+ arg = recv_msg msg
563
+ if arg
564
+ @state = :SUCCESS if @state == :GET_REQ
565
+ break
566
+ end
567
+ else
568
+ if (@retry_count += 1) >= @retries
569
+ @state = :IDLE
570
+ return
571
+ end
572
+ end
573
+ end
574
+ end
575
+ end
576
+
577
+ def recv_msg msg
578
+ enc_request_id, @error_status, @error_index, varbind = BER.dec_msg msg
579
+ return unless enc_request_id == @enc_request_id
580
+ unless @error_status == 0
581
+ @state = :IDLE
582
+ return
583
+ end
584
+
585
+ vars = BER.dec_varbind varbind
586
+ if vars.empty?
587
+ @state = :SUCCESS
588
+ return
589
+ end
590
+ for var in vars
591
+ if (var[1] != 130) and var[0].index(@req_enoid) #130=endOfMibView
592
+ @cb.call(*var)
593
+ else
594
+ @state = :SUCCESS
595
+ end
596
+ end
597
+ enoid = vars[-1][0]
598
+ rescue StandardError
599
+ raise SystemCallError, 'broken packet'
600
+ end
601
+ end