sensu-extensions-influxdb 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1274c88a4a5ac95ad0fd334e410db0c8b05c5b09
4
+ data.tar.gz: 7263d85b3abc0409346a72636f19f0f03789b57b
5
+ SHA512:
6
+ metadata.gz: 91e9bfd2aedd0a2855c8820d9dd8dd75278216db86a7b0780c9317574798c8aace7cdc6584f7e8f3a50f2496e55a3a5e162c5005d3d504cb762d91a01a6e26a1
7
+ data.tar.gz: c470b545e5e484e0cc643a750269e4790be696b48751e820e9f1641107db165e4a395ced4c6b84a63222d2883b70f6bec3dd63144bc1ce41795cf140de2580ca
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## [2.0.1] - 2017-05-14
4
+ ### Added
5
+ - Released as Gem
6
+
7
+ ## [1.7] - 2017-04-24
8
+ ### Added
9
+ - Event tags support (@ganeshk254)
10
+ - SSL support (@PxSonny)
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Johnny Horvi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,190 @@
1
+ sensu-influxdb-extension ![circle ci build status](https://circleci.com/gh/jhrv/sensu-influxdb-extension.png?circle-token=:circle-token)
2
+ ========================
3
+
4
+ [Sensu](https://sensuapp.org/) extension for sending metrics to [InfluxDB](https://influxdb.com/) using [line protocol](https://docs.influxdata.com/influxdb/latest/write_protocols/line_protocol_reference)
5
+
6
+ It handles both metrics on the graphite message format "key value timestamp\n" as well as line protocol directly by setting the extension in **proxy mode**.
7
+
8
+ # How it works
9
+
10
+ For every sensu-event, it will grab the output and transform each line into a line protocol data point. The point will contain tags defined on the check and sensu client (optional).
11
+ It will buffer up points until it reaches the configured length or maximum age (see **buffer_size** and **buffer_max_age**), and then post the data directly to the [InfluxDB write endpoint](https://docs.influxdata.com/influxdb/latest/tools/api/#write).
12
+
13
+ Example line of graphite data-format ([metric_path] [value] [timestamp]\n):
14
+
15
+ will be transformed into the following data-point ([line protocol](https://influxdb.com/docs/v0.9/write_protocols/line.html))...
16
+
17
+ ```
18
+ key_a[,<tags>] value=6996 1435216969\n...
19
+ ```
20
+
21
+ # Proxy mode
22
+
23
+ If the extension is configured to be in proxy mode, it will skip the transformation step and assume that the data is valid [line protocol](https://docs.influxdata.com/influxdb/latest/write_protocols/line_protocol_reference).
24
+ It will not take into account any tags defined in the sensu-configuration.
25
+
26
+ # Getting started
27
+
28
+ 1) Add the *sensu-influxdb-extension.rb* to the sensu extensions folder (/etc/sensu/extensions)
29
+
30
+ 2) Create your InfluxDB configuration for Sensu (or copy and edit *influxdb-extension.json.tmpl*) inside the sensu config folder (/etc/sensu/conf.d).
31
+
32
+ Example of a minimal configuration file
33
+ ```
34
+ {
35
+ "influxdb-extension": {
36
+ "hostname": "influxdb.mydomain.tld",
37
+ "database": "metrics",
38
+ }
39
+ }
40
+ ```
41
+
42
+ ## Full list of configuration options
43
+
44
+ | variable | default value |
45
+ | ----------------- | --------------------- |
46
+ | hostname | none (required) |
47
+ | port | 8086 |
48
+ | database | none (required) |
49
+ | proxy_mode | false |
50
+ | buffer_size | 100 (lines) |
51
+ | buffer_max_age | 10 (seconds) |
52
+ | ssl | false |
53
+ | ssl_ca_file (*) | none |
54
+ | ssl_verify | true |
55
+ | precision | s (**) |
56
+ | retention_policy | none |
57
+ | username | none |
58
+ | password | none |
59
+
60
+ (*) Optional file with trusted CA certificates
61
+ (**) s = seconds. Other valid options are n, u, ms, m, h. See [influxdb docs](https://influxdb.com/docs/v0.9/write_protocols/write_syntax.html) for more details
62
+
63
+
64
+ 3) Add the extension to your sensu-handler configuration
65
+
66
+ ```
67
+ "handlers": {
68
+ "metrics": {
69
+ "type": "set",
70
+ "handlers": [ "influxdb-extension" ]
71
+ }
72
+ ...
73
+ }
74
+
75
+ ```
76
+
77
+ 4) Configure your metric/check-definitions to use this handler
78
+
79
+ ```
80
+ "checks": {
81
+ "metric_cpu": {
82
+ "type": "metric",
83
+ "command": "/etc/sensu/plugins/metrics/cpu-usage.rb",
84
+ "handlers": [ "metrics" ],
85
+ ...
86
+ }
87
+ ```
88
+
89
+ 5) Restart your sensu-server and sensu-client(s)
90
+
91
+ You should see the following output in the sensu-server logs if all is working correctly:
92
+
93
+ ```
94
+ {"timestamp":"2015-06-21T13:37:04.256753+0200","level":"info","message":"influxdb-extension:
95
+ Successfully initialized config: hostname: ....
96
+ ```
97
+
98
+ # Tags (optional)
99
+
100
+ If you want to tag your InfluxDB measurements (great for querying, as tags are indexed), you can define this on the sensu-client as well as on the checks definition.
101
+
102
+ Example sensu-client definition:
103
+
104
+ ```
105
+ {
106
+ "client": {
107
+ "name": "app_env_hostname",
108
+ "address": "my-app-in-env.domain.tld",
109
+ "subscriptions": [],
110
+ "tags": {
111
+ "environment": "dev",
112
+ "application": "myapp",
113
+ "hostname": "my-app-in-env.domain.tld"
114
+ }
115
+ }
116
+ }
117
+ ```
118
+
119
+ Example check definition:
120
+
121
+ ```
122
+ {
123
+ "checks": {
124
+ "metric_cpu": {
125
+ "command": "/opt/sensu/embedded/bin/ruby /path/to/script.rb",
126
+ "interval": 20,
127
+ "standalone": true,
128
+ "type": "metric",
129
+ "handlers": [
130
+ "metrics"
131
+ ],
132
+ "tags": {
133
+ "mytag": "xyz"
134
+ }
135
+ }
136
+ }
137
+ }
138
+ ```
139
+
140
+ ... will turn into the following tags for that point: **,environment=dev,application=myapp,hostname=my-app-in-env.domain.tld,mytag=xyz**
141
+
142
+ If both the client and the check tags have the same key, the one defined on the check will overwrite/win the merge.
143
+
144
+ The tags will be sorted alphabetically for InfluxDB performance, and tags with empty values will be skipped.
145
+
146
+ ### Event Output Tags
147
+
148
+ This extension already provides check level and client level tags and now can provide event output tags. This will help us reducing number of sensu checks and provide better flexibility and control.
149
+
150
+ Example -
151
+ Let's say we configured the sensu check output to be :
152
+
153
+ ```
154
+ app.downloads.eventtags.platform.iOS 26 1476047752
155
+ app.downloads.eventtags.platform.android 52 1476047752
156
+ app.downloads.eventtags.platform.others 12 1476047752
157
+ ```
158
+
159
+ The extension will split the output of the measurement on eventtags. Then it will slice the second part into tag key and values. From above example, the transformed output will be -
160
+ ```
161
+ measurement = app.downloads, tags = platform => iOS , value = 26, timestamp = 1476047752
162
+ measurement = app.downloads, tags = platform => android , value = 52, timestamp = 1476047752
163
+ measurement = app.downloads, tags = platform => others , value = 12, timestamp = 1476047752
164
+ ```
165
+
166
+ You can create multiple tags also, for example :
167
+
168
+ ```
169
+ app.downloads.eventtags.platform.iOS.device.iPad 92 1476047752
170
+ ```
171
+ will be transformed to :
172
+ ```
173
+ measurement = app.downloads, tags = platform => iOS;device => iPad , value = 92, timestamp = 1476047752
174
+ ```
175
+
176
+ The event output tags will be merged with client and check definition tags and sent to InfluxDB as usual.
177
+
178
+ # Performance
179
+
180
+ The extension will buffer up points until it reaches the configured **buffer_size** length or **buffer_max_age**, and then post all the points in the buffer to InfluxDB.
181
+ Depending on your load, you will want to tune these configurations to match your environment.
182
+
183
+ Example:
184
+ If you set the **buffer_size** to 1000, and you have a event-frequency of 100 per second, it will give you about a ten second lag before the data is available through the InfluxDB query API.
185
+
186
+ **buffer_size** / event-frequency = latency
187
+
188
+ However, if you set the **buffer_max_age** to 5 seconds, it will flush the buffer each time it exeeds this limit.
189
+
190
+ I recommend testing different **buffer_size**s and **buffer_max_age**s depending on your environment and requirements.
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ require "sensu/extension"
5
+ require "net/http"
6
+ require "json"
7
+
8
+ module Sensu::Extension
9
+ class InfluxDB < Handler
10
+
11
+ @@extension_name = "influxdb-extension"
12
+
13
+ def name
14
+ @@extension_name
15
+ end
16
+
17
+ def description
18
+ "Transforms and sends metrics to InfluxDB"
19
+ end
20
+
21
+ def post_init
22
+ influxdb_config = settings[@@extension_name] || Hash.new
23
+ validate_config(influxdb_config)
24
+
25
+ hostname = influxdb_config[:hostname] || "127.0.0.1"
26
+ port = influxdb_config[:port] || "8086"
27
+ database = influxdb_config[:database]
28
+ ssl = influxdb_config[:ssl] || false
29
+ ssl_ca_file = influxdb_config[:ssl_ca_file]
30
+ ssl_verify = if influxdb_config.key?(:ssl_verify) then influxdb_config[:ssl_verify] else true end
31
+ precision = influxdb_config[:precision] || "s"
32
+ retention_policy = influxdb_config[:retention_policy]
33
+ rp_queryparam = if retention_policy.nil? then "" else "&rp=#{retention_policy}" end
34
+ protocol = if ssl then "https" else "http" end
35
+ username = influxdb_config[:username]
36
+ password = influxdb_config[:password]
37
+ auth_queryparam = if username.nil? or password.nil? then "" else "&u=#{username}&p=#{password}" end
38
+ @BUFFER_SIZE = if influxdb_config.key?(:buffer_size) then influxdb_config[:buffer_size].to_i else 100 end
39
+ @BUFFER_MAX_AGE = if influxdb_config.key?(:buffer_max_age) then influxdb_config[:buffer_max_age].to_i else 10 end
40
+ @PROXY_MODE = influxdb_config[:proxy_mode] || false
41
+
42
+ string = "#{protocol}://#{hostname}:#{port}/write?db=#{database}&precision=#{precision}#{rp_queryparam}#{auth_queryparam}"
43
+ @uri = URI(string)
44
+ @http = Net::HTTP::new(@uri.host, @uri.port)
45
+ if ssl
46
+ @http.ssl_version = :TLSv1
47
+ @http.use_ssl = true
48
+ @http.verify_mode = if ssl_verify then OpenSSL::SSL::VERIFY_PEER else OpenSSL::SSL::VERIFY_NONE end
49
+ @http.ca_file = ssl_ca_file
50
+ end
51
+ @buffer = []
52
+ @buffer_flushed = Time.now.to_i
53
+
54
+ @logger.info("#{@@extension_name}: successfully initialized config: hostname: #{hostname}, port: #{port}, database: #{database}, uri: #{@uri.to_s}, username: #{username}, buffer_size: #{@BUFFER_SIZE}, buffer_max_age: #{@BUFFER_MAX_AGE}")
55
+ end
56
+
57
+ def run(event)
58
+ begin
59
+
60
+ if buffer_too_old? or buffer_too_big?
61
+ flush_buffer
62
+ end
63
+
64
+ event = JSON.parse(event)
65
+ output = event["check"]["output"]
66
+
67
+ if not @PROXY_MODE
68
+ client_tags = event["client"]["tags"] || Hash.new
69
+ check_tags = event["check"]["tags"] || Hash.new
70
+ tags = create_tags(client_tags.merge(check_tags))
71
+ end
72
+
73
+ output.split(/\r\n|\n/).each do |point|
74
+ if not @PROXY_MODE
75
+ measurement, field_value, timestamp = point.split(/\s+/)
76
+
77
+ if not is_number?(timestamp)
78
+ @logger.debug("invalid timestamp, skipping line in event #{event}")
79
+ next
80
+ end
81
+
82
+ # Get event output tags
83
+ if measurement.include?('eventtags')
84
+ only_measurement, tagstub = measurement.split('.eventtags.',2)
85
+ event_tags = Hash.new()
86
+ tagstub.split('.').each_slice(2) do |key, value|
87
+ event_tags[key] = value
88
+ end
89
+ measurement = only_measurement
90
+ tags = create_tags(client_tags.merge(check_tags).merge(event_tags))
91
+ end
92
+
93
+ point = "#{measurement}#{tags} value=#{field_value} #{timestamp}"
94
+ end
95
+
96
+ @buffer.push(point)
97
+ @logger.debug("#{@@extension_name}: stored point in buffer (#{@buffer.length}/#{@BUFFER_SIZE})")
98
+ end
99
+ yield 'ok', 0
100
+ rescue => e
101
+ @logger.error("#{@@extension_name}: unable to handle event #{event} - #{e}")
102
+ yield 'error', 2
103
+ end
104
+ end
105
+
106
+ def create_tags(tags)
107
+ begin
108
+ # sorting tags alphabetically in order to increase influxdb performance
109
+ sorted_tags = Hash[tags.sort]
110
+
111
+ tag_string = ""
112
+
113
+ sorted_tags.each do |tag, value|
114
+ next if value.to_s.empty? # skips tags without values
115
+ tag_string += ",#{tag}=#{value}"
116
+ end
117
+
118
+ @logger.debug("#{@@extension_name}: created tags: #{tag_string}")
119
+ tag_string
120
+ rescue => e
121
+ @logger.debug("#{@@extension_name}: unable to create tag string from #{tags} - #{e.backtrace.to_s}")
122
+ ""
123
+ end
124
+ end
125
+
126
+ def send_to_influxdb(payload)
127
+ request = Net::HTTP::Post.new(@uri.request_uri)
128
+ request.body = payload
129
+
130
+ @logger.debug("#{@@extension_name}: writing payload #{payload} to endpoint #{@uri.to_s}")
131
+ response = @http.request(request)
132
+ @logger.debug("#{@@extension_name}: influxdb http response code = #{response.code}, body = #{response.body}")
133
+ end
134
+
135
+ def flush_buffer
136
+ payload = @buffer.join("\n")
137
+ send_to_influxdb(payload)
138
+ @buffer = []
139
+ @buffer_flushed = Time.now.to_i
140
+ end
141
+
142
+ def buffer_too_old?
143
+ buffer_age = Time.now.to_i - @buffer_flushed
144
+ buffer_age >= @BUFFER_MAX_AGE
145
+ end
146
+
147
+ def buffer_too_big?
148
+ @buffer.length >= @BUFFER_SIZE
149
+ end
150
+
151
+ def validate_config(config)
152
+ if config.nil?
153
+ raise ArgumentError, "no configuration for #{@@extension_name} provided. exiting..."
154
+ end
155
+
156
+ ["hostname", "database"].each do |required_setting|
157
+ if config[required_setting].nil?
158
+ raise ArgumentError, "required setting #{required_setting} not provided to extension. this should be provided as json element with key #{@@extension_name}. exiting..."
159
+ end
160
+ end
161
+ end
162
+
163
+ def is_number?(input)
164
+ true if Integer(input) rescue false
165
+ end
166
+ end
167
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sensu-extensions-influxdb
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Johnny Horvi
8
+ - Terje Sannum
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2017-05-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sensu-extension
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - '>='
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - '>='
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: bundler
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: '1.6'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ version: '1.6'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rake
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: sensu-logger
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: sensu-settings
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ description: InfluxDB extension for Sensu
99
+ email:
100
+ - johnny@horvi.no
101
+ - terje@offpiste.org
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - lib/sensu/extensions/influxdb.rb
107
+ - LICENSE
108
+ - README.md
109
+ - CHANGELOG.md
110
+ homepage: https://github.com/jhrv/sensu-influxdb-extension
111
+ licenses: []
112
+ metadata: {}
113
+ post_install_message:
114
+ rdoc_options: []
115
+ require_paths:
116
+ - lib
117
+ required_ruby_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - '>='
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ requirements: []
128
+ rubyforge_project:
129
+ rubygems_version: 2.0.14.1
130
+ signing_key:
131
+ specification_version: 4
132
+ summary: InfluxDB extension for Sensu
133
+ test_files: []