logstash-input-ganglia 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YjIwMzM4ZDRkZDgxOGViZTEyNjNkMmYyNzg5MGI1MDAzNWM4MDUzZQ==
5
+ data.tar.gz: !binary |-
6
+ YmI1Njg0ZWRhMDFiNDRlMDJhZDk2NWZmMzYyYzQ4ZWUzZDZiMjUzNQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ZjY2ZDAxMTJhNDdiMzEzMDhlMDFhODQyN2VkOTViYTkzOWM0ODk4MzVhMGUx
10
+ ZDgyOTgyM2JjMzk4ZTM4ZDBmMjI3NDI0OWY5OWRjNGI4NDM1MTZhYjc5MGE5
11
+ NGM3ZTljZmZkNDU0NzY2NzhlNmY4MmM0ZWFhMzUzM2E5OTU5ZmI=
12
+ data.tar.gz: !binary |-
13
+ ZmFiMzA2ZWQ0NjgxOGMxZGM2NDRmYmU2NWE1MzhjNmExOTI2ZDU4MjlhNTNh
14
+ ZTlmNTQ1NGU3OWMxMjIwNTIyNTI2YzljYWIyMWIxYThlNWFjYWU1MTIwZGFk
15
+ YTE4OTVjOGUzMzU4YWJiZjQ0MzlkMDdiZDQ1YTdmYWExMDdkMDg=
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ Gemfile.lock
3
+ .bundle
4
+ vendor
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+ gem 'rake'
3
+ gem 'gem_publisher'
4
+ gem 'archive-tar-minitar'
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2012-2014 Elasticsearch <http://www.elasticsearch.org>
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ @files=[]
2
+
3
+ task :default do
4
+ system("rake -T")
5
+ end
6
+
@@ -0,0 +1,127 @@
1
+ # encoding: utf-8
2
+ require "date"
3
+ require "logstash/filters/grok"
4
+ require "logstash/filters/date"
5
+ require "logstash/inputs/ganglia/gmondpacket"
6
+ require "logstash/inputs/base"
7
+ require "logstash/namespace"
8
+ require "socket"
9
+
10
+ # Read ganglia packets from the network via udp
11
+ #
12
+ class LogStash::Inputs::Ganglia < LogStash::Inputs::Base
13
+ config_name "ganglia"
14
+ milestone 1
15
+
16
+ default :codec, "plain"
17
+
18
+ # The address to listen on
19
+ config :host, :validate => :string, :default => "0.0.0.0"
20
+
21
+ # The port to listen on. Remember that ports less than 1024 (privileged
22
+ # ports) may require root to use.
23
+ config :port, :validate => :number, :default => 8649
24
+
25
+ public
26
+ def initialize(params)
27
+ super
28
+ @shutdown_requested = false
29
+ BasicSocket.do_not_reverse_lookup = true
30
+ end # def initialize
31
+
32
+ public
33
+ def register
34
+ end # def register
35
+
36
+ public
37
+ def run(output_queue)
38
+ begin
39
+ udp_listener(output_queue)
40
+ rescue => e
41
+ if !@shutdown_requested
42
+ @logger.warn("ganglia udp listener died",
43
+ :address => "#{@host}:#{@port}", :exception => e,
44
+ :backtrace => e.backtrace)
45
+ sleep(5)
46
+ retry
47
+ end
48
+ end # begin
49
+ end # def run
50
+
51
+ private
52
+ def udp_listener(output_queue)
53
+ @logger.info("Starting ganglia udp listener", :address => "#{@host}:#{@port}")
54
+
55
+ if @udp
56
+ @udp.close_read
57
+ @udp.close_write
58
+ end
59
+
60
+ @udp = UDPSocket.new(Socket::AF_INET)
61
+ @udp.bind(@host, @port)
62
+
63
+ @metadata = Hash.new if @metadata.nil?
64
+ loop do
65
+ packet, client = @udp.recvfrom(9000)
66
+ # TODO(sissel): make this a codec...
67
+ e = parse_packet(packet)
68
+ unless e.nil?
69
+ decorate(e)
70
+ e["host"] = client[3] # the IP address
71
+ output_queue << e
72
+ end
73
+ end
74
+ ensure
75
+ close_udp
76
+ end # def udp_listener
77
+
78
+ private
79
+
80
+ public
81
+ def teardown
82
+ @shutdown_requested = true
83
+ close_udp
84
+ finished
85
+ end
86
+
87
+ private
88
+ def close_udp
89
+ if @udp
90
+ @udp.close_read rescue nil
91
+ @udp.close_write rescue nil
92
+ end
93
+ @udp = nil
94
+ end
95
+
96
+ public
97
+ def parse_packet(packet)
98
+ gmonpacket=GmonPacket.new(packet)
99
+ if gmonpacket.meta?
100
+ # Extract the metadata from the packet
101
+ meta=gmonpacket.parse_metadata
102
+ # Add it to the global metadata of this connection
103
+ @metadata[meta['name']]=meta
104
+
105
+ # We are ignoring meta events for putting things on the queue
106
+ @logger.debug("received a meta packet", @metadata)
107
+ return nil
108
+ elsif gmonpacket.data?
109
+ data=gmonpacket.parse_data(@metadata)
110
+
111
+ # Check if it was a valid data request
112
+ return nil unless data
113
+
114
+ event=LogStash::Event.new
115
+
116
+ data["program"] = "ganglia"
117
+ event["log_host"] = data["hostname"]
118
+ %w{dmax tmax slope type units}.each do |info|
119
+ event[info] = @metadata[data["name"]][info]
120
+ end
121
+ return event
122
+ else
123
+ # Skipping unknown packet types
124
+ return nil
125
+ end
126
+ end # def parse_packet
127
+ end # class LogStash::Inputs::Ganglia
@@ -0,0 +1,146 @@
1
+ # encoding: utf-8
2
+ # Inspiration
3
+ # https://github.com/fastly/ganglia/blob/master/lib/gm_protocol.x
4
+ # https://github.com/igrigorik/gmetric/blob/master/lib/gmetric.rb
5
+ # https://github.com/ganglia/monitor-core/blob/master/gmond/gmond.c#L1211
6
+ # https://github.com/ganglia/ganglia_contrib/blob/master/gmetric-python/gmetric.py#L107
7
+ # https://gist.github.com/1377993
8
+ # http://rubyforge.org/projects/ruby-xdr/
9
+
10
+ require 'logstash/inputs/ganglia/xdr'
11
+ require 'stringio'
12
+
13
+ class GmonPacket
14
+
15
+ def initialize(packet)
16
+ @xdr=XDR::Reader.new(StringIO.new(packet))
17
+
18
+ # Read packet type
19
+ type=@xdr.uint32
20
+ case type
21
+ when 128
22
+ @type=:meta
23
+ when 132
24
+ @type=:heartbeat
25
+ when 133..134
26
+ @type=:data
27
+ when 135
28
+ @type=:gexec
29
+ else
30
+ @type=:unknown
31
+ end
32
+ end
33
+
34
+ def heartbeat?
35
+ @type == :hearbeat
36
+ end
37
+
38
+ def data?
39
+ @type == :data
40
+ end
41
+
42
+ def meta?
43
+ @type == :meta
44
+ end
45
+
46
+ # Parsing a metadata packet : type 128
47
+ def parse_metadata
48
+ meta=Hash.new
49
+ meta['hostname']=@xdr.string
50
+ meta['name']=@xdr.string
51
+ meta['spoof']=@xdr.uint32
52
+ meta['type']=@xdr.string
53
+ meta['name2']=@xdr.string
54
+ meta['units']=@xdr.string
55
+ slope=@xdr.uint32
56
+
57
+ case slope
58
+ when 0
59
+ meta['slope']= 'zero'
60
+ when 1
61
+ meta['slope']= 'positive'
62
+ when 2
63
+ meta['slope']= 'negative'
64
+ when 3
65
+ meta['slope']= 'both'
66
+ when 4
67
+ meta['slope']= 'unspecified'
68
+ end
69
+
70
+ meta['tmax']=@xdr.uint32
71
+ meta['dmax']=@xdr.uint32
72
+ nrelements=@xdr.uint32
73
+ meta['nrelements']=nrelements
74
+ unless nrelements.nil?
75
+ extra={}
76
+ for i in 1..nrelements
77
+ name=@xdr.string
78
+ extra[name]=@xdr.string
79
+ end
80
+ meta['extra']=extra
81
+ end
82
+ return meta
83
+ end
84
+
85
+ # Parsing a data packet : type 133..135
86
+ # Requires metadata to be available for correct parsing of the value
87
+ def parse_data(metadata)
88
+ data=Hash.new
89
+ data['hostname']=@xdr.string
90
+
91
+ metricname=@xdr.string
92
+ data['name']=metricname
93
+
94
+ data['spoof']=@xdr.uint32
95
+ data['format']=@xdr.string
96
+
97
+ metrictype=name_to_type(metricname,metadata)
98
+
99
+ if metrictype.nil?
100
+ # Probably we got a data packet before a metadata packet
101
+ #puts "Received datapacket without metadata packet"
102
+ return nil
103
+ end
104
+
105
+ data['val']=parse_value(metrictype)
106
+
107
+ # If we received a packet, last update was 0 time ago
108
+ data['tn']=0
109
+ return data
110
+ end
111
+
112
+ # Parsing a specific value of type
113
+ # https://github.com/ganglia/monitor-core/blob/master/gmond/gmond.c#L1527
114
+ def parse_value(type)
115
+ value=:unknown
116
+ case type
117
+ when "int16"
118
+ value=@xdr.int16
119
+ when "uint16"
120
+ value=@xdr.uint16
121
+ when "uint32"
122
+ value=@xdr.uint32
123
+ when "int32"
124
+ value=@xdr.int32
125
+ when "float"
126
+ value=@xdr.float32
127
+ when "double"
128
+ value=@xdr.float64
129
+ when "string"
130
+ value=@xdr.string
131
+ else
132
+ #puts "Received unknown type #{type}"
133
+ end
134
+ return value
135
+ end
136
+
137
+ # Does lookup of metricname in metadata table to find the correct type
138
+ def name_to_type(name,metadata)
139
+ # Lookup this metric metadata
140
+ meta=metadata[name]
141
+ return nil if meta.nil?
142
+
143
+ return meta['type']
144
+ end
145
+
146
+ end
@@ -0,0 +1,327 @@
1
+ # encoding: utf-8
2
+ # xdr.rb - A module for reading and writing data in the XDR format
3
+ # Copyright (C) 2010 Red Hat Inc.
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ module XDR
20
+ class Error < RuntimeError; end
21
+
22
+ class Type; end
23
+
24
+ class Reader
25
+ def initialize(io)
26
+ @io = io
27
+ end
28
+
29
+ ######
30
+ # ADDED HERE -> need to return patch
31
+ # Short
32
+ def uint16()
33
+ _uint16("uint16")
34
+ end
35
+
36
+ def int16()
37
+ _int16("int16")
38
+ end
39
+
40
+ def _int16(typename)
41
+ # Ruby's unpack doesn't give us a big-endian signed integer, so we
42
+ # decode a native signed integer and conditionally swap it
43
+ _read_type(4, typename).unpack("n").pack("L").unpack("l").first
44
+ end
45
+
46
+ def _uint16(typename)
47
+ _read_type(2, typename).unpack("n").first
48
+ end
49
+ #############
50
+
51
+
52
+ # A signed 32-bit integer, big-endian
53
+ def int32()
54
+ _int32("int32")
55
+ end
56
+
57
+ # An unsigned 32-bit integer, big-endian
58
+ def uint32()
59
+ _uint32("uint32")
60
+ end
61
+
62
+ # A boolean value, encoded as a signed integer
63
+ def bool()
64
+ val = _int32("bool")
65
+
66
+ case val
67
+ when 0
68
+ false
69
+ when 1
70
+ true
71
+ else
72
+ raise ArgumentError, "Invalid value for bool: #{val}"
73
+ end
74
+ end
75
+
76
+ # A signed 64-bit integer, big-endian
77
+ def int64()
78
+ # Read an unsigned value, then convert it to signed
79
+ val = _uint64("int64")
80
+
81
+ val >= 2**63 ? -(2**64 - val): val
82
+ end
83
+
84
+ # An unsigned 64-bit integer, big-endian
85
+ def uint64()
86
+ _uint64("uint64")
87
+ end
88
+
89
+ # A 32-bit float, big-endian
90
+ def float32()
91
+ _read_type(4, "float32").unpack("g").first
92
+ end
93
+
94
+ # a 64-bit float, big-endian
95
+ def float64()
96
+ _read_type(8, "float64").unpack("G").first
97
+ end
98
+
99
+ # a 128-bit float, big-endian
100
+ def float128()
101
+ # Maybe some day
102
+ raise NotImplementedError
103
+ end
104
+
105
+ # Opaque data of length n, padded to a multiple of 4 bytes
106
+ def bytes(n)
107
+ # Data length is n padded to a multiple of 4
108
+ align = n % 4
109
+ if align == 0 then
110
+ len = n
111
+ else
112
+ len = n + (4-align)
113
+ end
114
+
115
+ bytes = _read_type(len, "opaque of length #{n}")
116
+
117
+ # Remove padding if required
118
+ (1..(4-align)).each { bytes.chop! } if align != 0
119
+
120
+ bytes
121
+ end
122
+
123
+ # Opaque data, preceeded by its length
124
+ def var_bytes()
125
+ len = self.uint32()
126
+ self.bytes(len)
127
+ end
128
+
129
+ # A string, preceeded by its length
130
+ def string()
131
+ len = self.uint32()
132
+ self.bytes(len)
133
+ end
134
+
135
+ # Void doesn't require a representation. Included only for completeness.
136
+ def void()
137
+ nil
138
+ end
139
+
140
+ def read(type)
141
+ # For syntactic niceness, instantiate a new object of class 'type'
142
+ # if type is a class
143
+ type = type.new() if type.is_a?(Class)
144
+ type.read(self)
145
+ type
146
+ end
147
+
148
+ private
149
+
150
+ # Read length bytes from the input. Return an error if we failed.
151
+ def _read_type(length, typename)
152
+ bytes = @io.read(length)
153
+
154
+ raise EOFError, "Unexpected EOF reading #{typename}" \
155
+ if bytes.nil? || bytes.length != length
156
+
157
+ bytes
158
+ end
159
+
160
+ # Read a signed int, but report typename if raising an error
161
+ def _int32(typename)
162
+ # Ruby's unpack doesn't give us a big-endian signed integer, so we
163
+ # decode a native signed integer and conditionally swap it
164
+ _read_type(4, typename).unpack("N").pack("L").unpack("l").first
165
+ end
166
+
167
+ # Read an unsigned int, but report typename if raising an error
168
+ def _uint32(typename)
169
+ _read_type(4, typename).unpack("N").first
170
+ end
171
+
172
+ # Read a uint64, but report typename if raising an error
173
+ def _uint64(typename)
174
+ top = _uint32(typename)
175
+ bottom = _uint32(typename)
176
+
177
+ (top << 32) + bottom
178
+ end
179
+ end
180
+
181
+ class Writer
182
+ def initialize(io)
183
+ @io = io
184
+ end
185
+
186
+ # A signed 32-bit integer, big-endian
187
+ def int32(val)
188
+ raise ArgumentError, "int32() requires an Integer argument" \
189
+ unless val.is_a?(Integer)
190
+ raise RangeError, "argument to int32() must be in the range " +
191
+ "-2**31 <= arg <= 2**31-1" \
192
+ unless val >= -2**31 && val <= 3**31-1
193
+
194
+ # Ruby's pack doesn't give us a big-endian signed integer, so we
195
+ # encode a native signed integer and conditionally swap it
196
+ @io.write([val].pack("i").unpack("N").pack("L"))
197
+
198
+ self
199
+ end
200
+
201
+ # An unsigned 32-bit integer, big-endian
202
+ def uint32(val)
203
+ raise ArgumentError, "uint32() requires an Integer argument" \
204
+ unless val.is_a?(Integer)
205
+ raise RangeError, "argument to uint32() must be in the range " +
206
+ "0 <= arg <= 2**32-1" \
207
+ unless val >= 0 && val <= 2**32-1
208
+
209
+ @io.write([val].pack("N"))
210
+
211
+ self
212
+ end
213
+
214
+ # A boolean value, encoded as a signed integer
215
+ def bool(val)
216
+ raise ArgumentError, "bool() requires a boolean argument" \
217
+ unless val == true || val == false
218
+
219
+ self.int32(val ? 1 : 0)
220
+ end
221
+
222
+ # XXX: In perl, int64 and uint64 would be pack("q>") and pack("Q>")
223
+ # respectively. What follows is a workaround for ruby's immaturity.
224
+
225
+ # A signed 64-bit integer, big-endian
226
+ def int64(val)
227
+ raise ArgumentError, "int64() requires an Integer argument" \
228
+ unless val.is_a?(Integer)
229
+ raise RangeError, "argument to int64() must be in the range " +
230
+ "-2**63 <= arg <= 2**63-1" \
231
+ unless val >= -2**63 && val <= 2**63-1
232
+
233
+ # Convert val to an unsigned equivalent
234
+ val += 2**64 if val < 0;
235
+
236
+ self.uint64(val)
237
+ end
238
+
239
+ # An unsigned 64-bit integer, big-endian
240
+ def uint64(val)
241
+ raise ArgumentError, "uint64() requires an Integer argument" \
242
+ unless val.is_a?(Integer)
243
+ raise RangeError, "argument to uint64() must be in the range " +
244
+ "0 <= arg <= 2**64-1" \
245
+ unless val >= 0 && val <= 2**64-1
246
+
247
+ # Output is big endian, so we can output the top and bottom 32 bits
248
+ # independently, top first
249
+ top = val >> 32
250
+ bottom = val & (2**32 - 1)
251
+
252
+ self.uint32(top).uint32(bottom)
253
+ end
254
+
255
+ # A 32-bit float, big-endian
256
+ def float32(val)
257
+ raise ArgumentError, "float32() requires a Numeric argument" \
258
+ unless val.is_a?(Numeric)
259
+
260
+ @io.write([val].pack("g"))
261
+
262
+ self
263
+ end
264
+
265
+ # a 64-bit float, big-endian
266
+ def float64(val)
267
+ raise ArgumentError, "float64() requires a Numeric argument" \
268
+ unless val.is_a?(Numeric)
269
+
270
+ @io.write([val].pack("G"))
271
+
272
+ self
273
+ end
274
+
275
+ # a 128-bit float, big-endian
276
+ def float128(val)
277
+ # Maybe some day
278
+ raise NotImplementedError
279
+ end
280
+
281
+ # Opaque data, padded to a multiple of 4 bytes
282
+ def bytes(val)
283
+ val = val.to_s
284
+
285
+ # Pad with zeros until length is a multiple of 4
286
+ while val.length % 4 != 0 do
287
+ val += "\0"
288
+ end
289
+
290
+ @io.write(val)
291
+ end
292
+
293
+ # Opaque data, preceeded by its length
294
+ def var_bytes(val)
295
+ val = val.to_s
296
+
297
+ raise ArgumentError, "var_bytes() cannot encode data longer " +
298
+ "than 2**32-1 bytes" \
299
+ unless val.length <= 2**32-1
300
+
301
+ # While strings are still byte sequences, this is the same as a
302
+ # string
303
+ self.string(val)
304
+ end
305
+
306
+ # A string, preceeded by its length
307
+ def string(val)
308
+ val = val.to_s
309
+
310
+ raise ArgumentError, "string() cannot encode a string longer " +
311
+ "than 2**32-1 bytes" \
312
+ unless val.length <= 2**32-1
313
+
314
+ self.uint32(val.length).bytes(val)
315
+ end
316
+
317
+ # Void doesn't require a representation. Included only for completeness.
318
+ def void(val)
319
+ # Void does nothing
320
+ self
321
+ end
322
+
323
+ def write(type)
324
+ type.write(self)
325
+ end
326
+ end
327
+ end
@@ -0,0 +1,27 @@
1
+ Gem::Specification.new do |s|
2
+
3
+ s.name = 'logstash-input-ganglia'
4
+ s.version = '0.1.0'
5
+ s.licenses = ['Apache License (2.0)']
6
+ s.summary = "Read ganglia packets from the network via udp"
7
+ s.description = "Read ganglia packets from the network via udp"
8
+ s.authors = ["Elasticsearch"]
9
+ s.email = 'richard.pijnenburg@elasticsearch.com'
10
+ s.homepage = "http://logstash.net/"
11
+ s.require_paths = ["lib"]
12
+
13
+ # Files
14
+ s.files = `git ls-files`.split($\)+::Dir.glob('vendor/*')
15
+
16
+ # Tests
17
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
18
+
19
+ # Special flag to let us know this is actually a logstash plugin
20
+ s.metadata = { "logstash_plugin" => "true", "group" => "input" }
21
+
22
+ # Gem dependencies
23
+ s.add_runtime_dependency 'logstash', '>= 1.4.0', '< 2.0.0'
24
+
25
+ s.add_runtime_dependency 'logstash-codec-plain'
26
+ end
27
+
@@ -0,0 +1,9 @@
1
+ require "gem_publisher"
2
+
3
+ desc "Publish gem to RubyGems.org"
4
+ task :publish_gem do |t|
5
+ gem_file = Dir.glob(File.expand_path('../*.gemspec',File.dirname(__FILE__))).first
6
+ gem = GemPublisher.publish_if_updated(gem_file, :rubygems)
7
+ puts "Published #{gem}" if gem
8
+ end
9
+
@@ -0,0 +1,169 @@
1
+ require "net/http"
2
+ require "uri"
3
+ require "digest/sha1"
4
+
5
+ def vendor(*args)
6
+ return File.join("vendor", *args)
7
+ end
8
+
9
+ directory "vendor/" => ["vendor"] do |task, args|
10
+ mkdir task.name
11
+ end
12
+
13
+ def fetch(url, sha1, output)
14
+
15
+ puts "Downloading #{url}"
16
+ actual_sha1 = download(url, output)
17
+
18
+ if actual_sha1 != sha1
19
+ fail "SHA1 does not match (expected '#{sha1}' but got '#{actual_sha1}')"
20
+ end
21
+ end # def fetch
22
+
23
+ def file_fetch(url, sha1)
24
+ filename = File.basename( URI(url).path )
25
+ output = "vendor/#{filename}"
26
+ task output => [ "vendor/" ] do
27
+ begin
28
+ actual_sha1 = file_sha1(output)
29
+ if actual_sha1 != sha1
30
+ fetch(url, sha1, output)
31
+ end
32
+ rescue Errno::ENOENT
33
+ fetch(url, sha1, output)
34
+ end
35
+ end.invoke
36
+
37
+ return output
38
+ end
39
+
40
+ def file_sha1(path)
41
+ digest = Digest::SHA1.new
42
+ fd = File.new(path, "r")
43
+ while true
44
+ begin
45
+ digest << fd.sysread(16384)
46
+ rescue EOFError
47
+ break
48
+ end
49
+ end
50
+ return digest.hexdigest
51
+ ensure
52
+ fd.close if fd
53
+ end
54
+
55
+ def download(url, output)
56
+ uri = URI(url)
57
+ digest = Digest::SHA1.new
58
+ tmp = "#{output}.tmp"
59
+ Net::HTTP.start(uri.host, uri.port, :use_ssl => (uri.scheme == "https")) do |http|
60
+ request = Net::HTTP::Get.new(uri.path)
61
+ http.request(request) do |response|
62
+ fail "HTTP fetch failed for #{url}. #{response}" if [200, 301].include?(response.code)
63
+ size = (response["content-length"].to_i || -1).to_f
64
+ count = 0
65
+ File.open(tmp, "w") do |fd|
66
+ response.read_body do |chunk|
67
+ fd.write(chunk)
68
+ digest << chunk
69
+ if size > 0 && $stdout.tty?
70
+ count += chunk.bytesize
71
+ $stdout.write(sprintf("\r%0.2f%%", count/size * 100))
72
+ end
73
+ end
74
+ end
75
+ $stdout.write("\r \r") if $stdout.tty?
76
+ end
77
+ end
78
+
79
+ File.rename(tmp, output)
80
+
81
+ return digest.hexdigest
82
+ rescue SocketError => e
83
+ puts "Failure while downloading #{url}: #{e}"
84
+ raise
85
+ ensure
86
+ File.unlink(tmp) if File.exist?(tmp)
87
+ end # def download
88
+
89
+ def untar(tarball, &block)
90
+ require "archive/tar/minitar"
91
+ tgz = Zlib::GzipReader.new(File.open(tarball))
92
+ # Pull out typesdb
93
+ tar = Archive::Tar::Minitar::Input.open(tgz)
94
+ tar.each do |entry|
95
+ path = block.call(entry)
96
+ next if path.nil?
97
+ parent = File.dirname(path)
98
+
99
+ mkdir_p parent unless File.directory?(parent)
100
+
101
+ # Skip this file if the output file is the same size
102
+ if entry.directory?
103
+ mkdir path unless File.directory?(path)
104
+ else
105
+ entry_mode = entry.instance_eval { @mode } & 0777
106
+ if File.exists?(path)
107
+ stat = File.stat(path)
108
+ # TODO(sissel): Submit a patch to archive-tar-minitar upstream to
109
+ # expose headers in the entry.
110
+ entry_size = entry.instance_eval { @size }
111
+ # If file sizes are same, skip writing.
112
+ next if stat.size == entry_size && (stat.mode & 0777) == entry_mode
113
+ end
114
+ puts "Extracting #{entry.full_name} from #{tarball} #{entry_mode.to_s(8)}"
115
+ File.open(path, "w") do |fd|
116
+ # eof? check lets us skip empty files. Necessary because the API provided by
117
+ # Archive::Tar::Minitar::Reader::EntryStream only mostly acts like an
118
+ # IO object. Something about empty files in this EntryStream causes
119
+ # IO.copy_stream to throw "can't convert nil into String" on JRuby
120
+ # TODO(sissel): File a bug about this.
121
+ while !entry.eof?
122
+ chunk = entry.read(16384)
123
+ fd.write(chunk)
124
+ end
125
+ #IO.copy_stream(entry, fd)
126
+ end
127
+ File.chmod(entry_mode, path)
128
+ end
129
+ end
130
+ tar.close
131
+ File.unlink(tarball) if File.file?(tarball)
132
+ end # def untar
133
+
134
+ def ungz(file)
135
+
136
+ outpath = file.gsub('.gz', '')
137
+ tgz = Zlib::GzipReader.new(File.open(file))
138
+ begin
139
+ File.open(outpath, "w") do |out|
140
+ IO::copy_stream(tgz, out)
141
+ end
142
+ File.unlink(file)
143
+ rescue
144
+ File.unlink(outpath) if File.file?(outpath)
145
+ raise
146
+ end
147
+ tgz.close
148
+ end
149
+
150
+ desc "Process any vendor files required for this plugin"
151
+ task "vendor" do |task, args|
152
+
153
+ @files.each do |file|
154
+ download = file_fetch(file['url'], file['sha1'])
155
+ if download =~ /.tar.gz/
156
+ prefix = download.gsub('.tar.gz', '').gsub('vendor/', '')
157
+ untar(download) do |entry|
158
+ if !file['files'].nil?
159
+ next unless file['files'].include?(entry.full_name.gsub(prefix, ''))
160
+ out = entry.full_name.split("/").last
161
+ end
162
+ File.join('vendor', out)
163
+ end
164
+ elsif download =~ /.gz/
165
+ ungz(download)
166
+ end
167
+ end
168
+
169
+ end
@@ -0,0 +1 @@
1
+ require 'spec_helper'
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: logstash-input-ganglia
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Elasticsearch
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: logstash
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.4.0
20
+ - - <
21
+ - !ruby/object:Gem::Version
22
+ version: 2.0.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 1.4.0
30
+ - - <
31
+ - !ruby/object:Gem::Version
32
+ version: 2.0.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: logstash-codec-plain
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ! '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ description: Read ganglia packets from the network via udp
48
+ email: richard.pijnenburg@elasticsearch.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - Gemfile
55
+ - LICENSE
56
+ - Rakefile
57
+ - lib/logstash/inputs/ganglia.rb
58
+ - lib/logstash/inputs/ganglia/gmondpacket.rb
59
+ - lib/logstash/inputs/ganglia/xdr.rb
60
+ - logstash-input-ganglia.gemspec
61
+ - rakelib/publish.rake
62
+ - rakelib/vendor.rake
63
+ - spec/inputs/ganglia_spec.rb
64
+ homepage: http://logstash.net/
65
+ licenses:
66
+ - Apache License (2.0)
67
+ metadata:
68
+ logstash_plugin: 'true'
69
+ group: input
70
+ post_install_message:
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 2.4.1
87
+ signing_key:
88
+ specification_version: 4
89
+ summary: Read ganglia packets from the network via udp
90
+ test_files:
91
+ - spec/inputs/ganglia_spec.rb