sensu-extensions-statsd 0.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: 424c1976524eb35b794354e8ed81a0b1d5aa3342
4
+ data.tar.gz: 6bff69108c15a02b257d9ca40353028861eb5877
5
+ SHA512:
6
+ metadata.gz: fe7847188110c91313a8197a6c2b330c73ae8f22830be67fb56295289545cb6bf04c3d5f914f4842f504487391d4d14f64828c7113daf9be9becde143a6481ca
7
+ data.tar.gz: 40bbcb68a42d323e862af5b30e349de9ed9a4bcd68f140839677c0208a59fd09ecd98e1bf07430b9e66b6ff4bbec822eb7c9bb4a62ec8dc18fc0c1fd3b61c49c
data/CHANGELOG.md ADDED
@@ -0,0 +1,30 @@
1
+ # Change Log
2
+
3
+ This project adheres to [Semantic Versioning](http://semver.org/).
4
+
5
+ This CHANGELOG follows the format listed at [Keep A Changelog](http://keepachangelog.com/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Fixed
10
+
11
+ ### Added
12
+
13
+ ### Changed
14
+
15
+ ## [1.0.0] - 2016-01-31
16
+
17
+ ### Fixed
18
+
19
+ ### Added
20
+
21
+ ### Changed
22
+
23
+ ## 0.0.1 - 2016-01-31
24
+
25
+ ### Added
26
+
27
+ - Initial release.
28
+
29
+ [Unreleased]: https://github.com/sensu-extensions/sensu-extensions-template/compare/v1.0.0...HEAD
30
+ [1.0.0]: https://github.com/sensu-extensions/sensu-extensions-template/compare/v0.0.1...v1.0.0
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2016 Sensu-Extensions
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # Sensu StatsD Extension
2
+
3
+ A StatsD implementation for the Sensu client. This check extension
4
+ creates a StatsD TCP & UDP listener, receives StatsD metrics, parses
5
+ them, and flushes them to the Graphite plaintext format for Sensu to
6
+ send to Graphite or another TSDB.
7
+
8
+ ## Installation
9
+
10
+ ```
11
+ sensu-install -e statsd
12
+ ```
13
+
14
+ Edit `/etc/sensu/conf.d/extensions.json` to load it.
15
+
16
+ ``` json
17
+ {
18
+ "extensions": {
19
+ "statsd": {
20
+ "version": "0.0.1"
21
+ }
22
+ }
23
+ }
24
+ ```
25
+
26
+ ## Configuration
27
+
28
+ Edit `/etc/sensu/conf.d/statsd.json` to change its configuration.
29
+
30
+ ``` json
31
+ {
32
+ "statsd": {
33
+ "bind": "0.0.0.0"
34
+ }
35
+ }
36
+ ```
37
+
38
+ |param|type|default|description|
39
+ |----|----|----|---|
40
+ |:bind|:string|"127.0.0.1"|IP to bind the StatsD sockets to|
41
+ |:port|:integer|8125|Port to bind the StatsD sockets to|
42
+ |:flush_interval|:integer|10|The StatsD flush interval|
43
+ |:send_interval|:integer|30|How often Graphite metrics are sent to Sensu|
44
+ |:percentile|:integer|90|The percentile to calculate for StatsD metrics|
45
+ |:add_client_prefix|:boolean|true|If the Sensu client name should prefix the Graphite metric path|
46
+ |:path_prefix|:string|"statsd"|The optional Graphite metric path prefix (after client name)|
47
+ |:add_path_prefix|:boolean|true|If the path_prefix should be used|
48
+ |:handler|:string|"graphite"|Handler to use for the Graphite metrics|
@@ -0,0 +1,174 @@
1
+ require "sensu/extension"
2
+
3
+ module Sensu
4
+ module Extension
5
+ class StatsDSimpleSocket < EM::Connection
6
+ attr_accessor :data
7
+
8
+ def receive_data(data)
9
+ @data << data
10
+ end
11
+ end
12
+
13
+ class StatsD < Check
14
+ def name
15
+ "statsd"
16
+ end
17
+
18
+ def description
19
+ "a statsd implementation"
20
+ end
21
+
22
+ def options
23
+ return @options if @options
24
+ @options = {
25
+ :bind => "127.0.0.1",
26
+ :port => 8125,
27
+ :flush_interval => 10,
28
+ :send_interval => 30,
29
+ :percentile => 90,
30
+ :add_client_prefix => true,
31
+ :path_prefix => "statsd",
32
+ :add_path_prefix => true,
33
+ :handler => "graphite"
34
+ }
35
+ @options.merge!(@settings[:statsd]) if @settings[:statsd].is_a?(Hash)
36
+ @options
37
+ end
38
+
39
+ def definition
40
+ {
41
+ :type => "metric",
42
+ :name => name,
43
+ :interval => options[:send_interval],
44
+ :standalone => true,
45
+ :output_format => "graphite_plaintext",
46
+ :handler => options[:handler]
47
+ }
48
+ end
49
+
50
+ def post_init
51
+ @flush_timers = []
52
+ @data = EM::Queue.new
53
+ @gauges = Hash.new { |h, k| h[k] = 0 }
54
+ @counters = Hash.new { |h, k| h[k] = 0 }
55
+ @timers = Hash.new { |h, k| h[k] = [] }
56
+ @metrics = []
57
+ setup_flush_timers
58
+ setup_parser
59
+ setup_statsd_socket
60
+ end
61
+
62
+ def run
63
+ output = ""
64
+ if @metrics
65
+ output << @metrics.join("\n") + "\n" unless @metrics.empty?
66
+ @logger.info("statsd collected metrics", :count => @metrics.count)
67
+ @metrics = []
68
+ end
69
+ yield output, 0
70
+ end
71
+
72
+ private
73
+
74
+ def add_metric(*args)
75
+ value = args.pop
76
+ path = []
77
+ path << @settings[:client][:name] if options[:add_client_prefix]
78
+ path << options[:path_prefix] if options[:add_path_prefix]
79
+ path = (path + args).join(".")
80
+ if path !~ /^[A-Za-z0-9\._-]*$/
81
+ @logger.info("invalid statsd metric", {
82
+ :reason => "metric path must only consist of alpha-numeric characters, periods, underscores, and dashes",
83
+ :path => path,
84
+ :value => value
85
+ })
86
+ else
87
+ @logger.debug("adding statsd metric", {
88
+ :path => path,
89
+ :value => value
90
+ })
91
+ @metrics << [path, value, Time.now.to_i].join(" ")
92
+ end
93
+ end
94
+
95
+ def flush!
96
+ @gauges.each do |name, value|
97
+ add_metric("gauges", name, value)
98
+ end
99
+ @gauges.clear
100
+ @counters.each do |name, value|
101
+ add_metric("counters", name, value)
102
+ end
103
+ @counters.clear
104
+ @timers.each do |name, values|
105
+ values.sort!
106
+ length = values.length
107
+ min = values.first
108
+ max = values.last
109
+ mean = min
110
+ max_at_threshold = min
111
+ percentile = options[:percentile]
112
+ if length > 1
113
+ threshold_index = ((100 - percentile) / 100.0) * length
114
+ threshold_count = length - threshold_index.round
115
+ valid_values = values.slice(0, threshold_count)
116
+ max_at_threshold = valid_values[-1]
117
+ sum = 0
118
+ valid_values.each { |v| sum += v }
119
+ mean = sum / valid_values.length
120
+ end
121
+ add_metric("timers", name, "lower", min)
122
+ add_metric("timers", name, "mean", mean)
123
+ add_metric("timers", name, "upper", max)
124
+ add_metric("timers", name, "upper_#{percentile}", max_at_threshold)
125
+ end
126
+ @timers.clear
127
+ @logger.debug("flushed statsd metrics")
128
+ end
129
+
130
+ def setup_flush_timers
131
+ @flush_timers << EM::PeriodicTimer.new(options[:flush_interval]) do
132
+ flush!
133
+ end
134
+ end
135
+
136
+ def setup_parser
137
+ parser = proc do |data|
138
+ begin
139
+ nv, type = data.strip.split("|")
140
+ name, value = nv.split(":")
141
+ case type
142
+ when "g"
143
+ @gauges[name] = Float(value)
144
+ when /^c/, "m"
145
+ _, raw_sample = type.split("@")
146
+ sample = (raw_sample ? Float(raw_sample) : 1)
147
+ @counters[name] += Integer(value) * (1 / sample)
148
+ when "ms", "h"
149
+ @timers[name] << Float(value)
150
+ end
151
+ rescue => error
152
+ @logger.error("statsd parser error", :error => error.to_s)
153
+ end
154
+ EM.next_tick do
155
+ @data.pop(&parser)
156
+ end
157
+ end
158
+ @data.pop(&parser)
159
+ end
160
+
161
+ def setup_statsd_socket
162
+ @logger.debug("binding statsd tcp and udp sockets", :options => options)
163
+ bind = options[:bind]
164
+ port = options[:port]
165
+ EM.start_server(bind, port, StatsDSimpleSocket) do |socket|
166
+ socket.data = @data
167
+ end
168
+ EM.open_datagram_socket(bind, port, StatsDSimpleSocket) do |socket|
169
+ socket.data = @data
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sensu-extensions-statsd
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Sensu-Extensions and contributors
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-12-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sensu-extension
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.6'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sensu-logger
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sensu-settings
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Check extension to run a StatsD implementation
98
+ email:
99
+ - "<sensu-users@googlegroups.com>"
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - CHANGELOG.md
105
+ - LICENSE
106
+ - README.md
107
+ - lib/sensu/extensions/statsd.rb
108
+ homepage: https://github.com/sensu-extensions/sensu-extensions-statsd
109
+ licenses: []
110
+ metadata: {}
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ requirements: []
126
+ rubyforge_project:
127
+ rubygems_version: 2.6.3
128
+ signing_key:
129
+ specification_version: 4
130
+ summary: Check extension to run a StatsD implementation
131
+ test_files: []