logstash-input-ganglia 0.1.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.
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