logstash-output-graphtastic 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
+ YmY2OWQ5YmJiMWNlNTRmMDcxNjBkZTg1MjJkZDUzZjhjN2E4MGYyNA==
5
+ data.tar.gz: !binary |-
6
+ MWRiZTc5OThhMWI1N2YyMjIwM2JlOGZhNWIyODExNjVkNWE5ZmVlYQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ OTdiZTIwM2ZkNTAyMzFkMTQzNDBhYzM4NzE4MmUyYjk0MDhhYTIwNDU1ZDhj
10
+ ZDk2NzUwNjNiYjYyZDY0MWI1YTIzYjQ5MjI4NDhiMzQxMjcyMWJkNmU0ODY4
11
+ YTA1YTQ0NDczMmE2NmMwNTlkN2E3YjYxYjllZmIzZmFhNDMxZjA=
12
+ data.tar.gz: !binary |-
13
+ NGFjNWMzNjI1ZGE0OGQzOWYwMzBkMzdlYWIzZjY2ZWNlNmUzZDRkODNiODdm
14
+ YmE3MWJmODk5YjIyZmNhYTNkNjNmNWVmNDcyMWE0ODQzMjRmZDk0NmIwMDQ2
15
+ NjUyZWYwMDYzMzA0NTgxNDBmMjgxZDAwMGU1MGVjMmEwMDdhOTQ=
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,186 @@
1
+ # encoding: utf-8
2
+ require "logstash/outputs/base"
3
+ require "logstash/namespace"
4
+
5
+
6
+ # A plugin for a newly developed Java/Spring Metrics application
7
+ # I didn't really want to code this project but I couldn't find
8
+ # a respectable alternative that would also run on any Windows
9
+ # machine - which is the problem and why I am not going with Graphite
10
+ # and statsd. This application provides multiple integration options
11
+ # so as to make its use under your network requirements possible.
12
+ # This includes a REST option that is always enabled for your use
13
+ # in case you want to write a small script to send the occasional
14
+ # metric data.
15
+ #
16
+ # Find GraphTastic here : https://github.com/NickPadilla/GraphTastic
17
+ class LogStash::Outputs::GraphTastic < LogStash::Outputs::Base
18
+
19
+ config_name "graphtastic"
20
+ milestone 2
21
+
22
+ # options are udp(fastest - default) - rmi(faster) - rest(fast) - tcp(don't use TCP yet - some problems - errors out on linux)
23
+ config :integration, :validate => ["udp","tcp","rmi","rest"], :default => "udp"
24
+
25
+ # if using rest as your end point you need to also provide the application url
26
+ # it defaults to localhost/graphtastic. You can customize the application url
27
+ # by changing the name of the .war file. There are other ways to change the
28
+ # application context, but they vary depending on the Application Server in use.
29
+ # Please consult your application server documentation for more on application
30
+ # contexts.
31
+ config :context, :validate => :string, :default => "graphtastic"
32
+
33
+ # metrics hash - you will provide a name for your metric and the metric
34
+ # data as key value pairs. so for example:
35
+ #
36
+ # metrics => { "Response" => "%{response}" }
37
+ #
38
+ # example for the logstash config
39
+ #
40
+ # metrics => [ "Response", "%{response}" ]
41
+ #
42
+ # NOTE: you can also use the dynamic fields for the key value as well as the actual value
43
+ config :metrics, :validate => :hash, :default => {}
44
+
45
+ # host for the graphtastic server - defaults to 127.0.0.1
46
+ config :host, :validate => :string, :default => "127.0.0.1"
47
+
48
+ # port for the graphtastic instance - defaults to 1199 for RMI, 1299 for TCP, 1399 for UDP, and 8080 for REST
49
+ config :port, :validate => :number
50
+
51
+ # number of attempted retry after send error - currently only way to integrate
52
+ # errored transactions - should try and save to a file or later consumption
53
+ # either by graphtastic utility or by this program after connectivity is
54
+ # ensured to be established.
55
+ config :retries, :validate => :number, :default => 1
56
+
57
+ # the number of metrics to send to GraphTastic at one time. 60 seems to be the perfect
58
+ # amount for UDP, with default packet size.
59
+ config :batch_number, :validate => :number, :default => 60
60
+
61
+ # setting allows you to specify where we save errored transactions
62
+ # this makes the most sense at this point - will need to decide
63
+ # on how we reintegrate these error metrics
64
+ # NOT IMPLEMENTED!
65
+ config :error_file, :validate => :string, :default => ""
66
+
67
+ public
68
+ def register
69
+ @batch = []
70
+ begin
71
+ if @integration.downcase == "rmi"
72
+ if RUBY_ENGINE != "jruby"
73
+ raise Exception.new("LogStash::Outputs::GraphTastic# JRuby is needed for RMI to work!")
74
+ end
75
+ require "java"
76
+ if @port.nil?
77
+ @port = 1199
78
+ end
79
+ registry = java.rmi.registry.LocateRegistry.getRegistry(@host, @port);
80
+ @remote = registry.lookup("RmiMetricService")
81
+ elsif @integration.downcase == "rest"
82
+ require "net/http"
83
+ if @port.nil?
84
+ @port = 8080
85
+ gem "mail" #outputs/email, # License: MIT License
86
+ end
87
+ @http = Net::HTTP.new(@host, @port)
88
+ end
89
+ @logger.info("GraphTastic Output Successfully Registered! Using #{@integration} Integration!")
90
+ rescue
91
+ @logger.error("*******ERROR : #{$!}")
92
+ end
93
+ end
94
+
95
+ public
96
+ def receive(event)
97
+ return unless output?(event)
98
+ # Set Intersection - returns a new array with the items that are the same between the two
99
+ if !@tags.empty? && (event["tags"] & @tags).size == 0
100
+ # Skip events that have no tags in common with what we were configured
101
+ @logger.debug("No Tags match for GraphTastic Output!")
102
+ return
103
+ end
104
+ @retry = 1
105
+ @logger.debug("Event found for GraphTastic!", :tags => @tags, :event => event)
106
+ @metrics.each do |name, metric|
107
+ postMetric(event.sprintf(name),event.sprintf(metric),(event["@timestamp"]*1000))# unix_timestamp is what I need in seconds - multiply by 1000 to make milliseconds.
108
+ end
109
+ end
110
+
111
+ def postMetric(name, metric, timestamp)
112
+ message = name+","+metric+","+timestamp.to_s
113
+ if @batch.length < @batch_number
114
+ @batch.push(message)
115
+ else
116
+ flushMetrics()
117
+ end
118
+ end
119
+
120
+ def flushMetrics()
121
+ begin
122
+ if @integration.downcase == "tcp"
123
+ flushViaTCP()
124
+ elsif @integration.downcase == "rmi"
125
+ flushViaRMI()
126
+ elsif @integration.downcase == "udp"
127
+ flushViaUDP()
128
+ elsif @integration.downcase == "rest"
129
+ flushViaREST()
130
+ else
131
+ @logger.error("GraphTastic Not Able To Find Correct Integration - Nothing Sent - Integration Type : ", :@integration => @integration)
132
+ end
133
+ @batch.clear
134
+ rescue
135
+ @logger.error("*******ERROR : #{$!}")
136
+ @logger.info("*******Attempting #{@retry} out of #{@retries}")
137
+ while @retry < @retries
138
+ @retry = @retry + 1
139
+ flushMetrics()
140
+ end
141
+ end
142
+ end
143
+
144
+ # send metrics via udp
145
+ def flushViaUDP()
146
+ if @port.nil?
147
+ @port = 1399
148
+ end
149
+ udpsocket.send(@batch.join(','), 0, @host, @port)
150
+ @logger.debug("GraphTastic Sent Message Using UDP : #{@batch.join(',')}")
151
+ end
152
+
153
+ # send metrics via REST
154
+ def flushViaREST()
155
+ request = Net::HTTP::Put.new("/#{@context}/addMetric/#{@batch.join(',')}")
156
+ response = @http.request(request)
157
+ if response == 'ERROR'
158
+ raise 'Error happend when sending metric to GraphTastic using REST!'
159
+ end
160
+ @logger.debug("GraphTastic Sent Message Using REST : #{@batch.join(',')}", :response => response.inspect)
161
+ end
162
+
163
+ # send metrics via RMI
164
+ def flushViaRMI()
165
+ if RUBY_ENGINE != "jruby"
166
+ raise Exception.new("LogStash::Outputs::GraphTastic# JRuby is needed for RMI to work!")
167
+ end
168
+ @remote.insertMetrics(@batch.join(','))
169
+ @logger.debug("GraphTastic Sent Message Using RMI : #{@batch.join(',')}")
170
+ end
171
+
172
+ # send metrics via tcp
173
+ def flushViaTCP()
174
+ # to correctly read the line we need to ensure we send \r\n at the end of every message.
175
+ if @port.nil?
176
+ @port = 1299
177
+ end
178
+ tcpsocket = TCPSocket.open(@host, @port)
179
+ tcpsocket.send(@batch.join(',')+"\r\n", 0)
180
+ tcpsocket.close
181
+ @logger.debug("GraphTastic Sent Message Using TCP : #{@batch.join(',')}")
182
+ end
183
+
184
+ def udpsocket; @socket ||= UDPSocket.new end
185
+
186
+ end
@@ -0,0 +1,26 @@
1
+ Gem::Specification.new do |s|
2
+
3
+ s.name = 'logstash-output-graphtastic'
4
+ s.version = '0.1.0'
5
+ s.licenses = ['Apache License (2.0)']
6
+ s.summary = "Send metrics to GraphTastic"
7
+ s.description = "Send metrics to GraphTastic"
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" => "output" }
21
+
22
+ # Gem dependencies
23
+ s.add_runtime_dependency 'logstash', '>= 1.4.0', '< 2.0.0'
24
+
25
+ end
26
+
@@ -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,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: logstash-output-graphtastic
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-06 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
+ description: Send metrics to GraphTastic
34
+ email: richard.pijnenburg@elasticsearch.com
35
+ executables: []
36
+ extensions: []
37
+ extra_rdoc_files: []
38
+ files:
39
+ - .gitignore
40
+ - Gemfile
41
+ - LICENSE
42
+ - Rakefile
43
+ - lib/logstash/outputs/graphtastic.rb
44
+ - logstash-output-graphtastic.gemspec
45
+ - rakelib/publish.rake
46
+ - rakelib/vendor.rake
47
+ - spec/outputs/graphtastic_spec.rb
48
+ homepage: http://logstash.net/
49
+ licenses:
50
+ - Apache License (2.0)
51
+ metadata:
52
+ logstash_plugin: 'true'
53
+ group: output
54
+ post_install_message:
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubyforge_project:
70
+ rubygems_version: 2.4.1
71
+ signing_key:
72
+ specification_version: 4
73
+ summary: Send metrics to GraphTastic
74
+ test_files:
75
+ - spec/outputs/graphtastic_spec.rb