shutl-rack-statsd 0.3.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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b16dd54d851a3e493490aadc249e55ea3256dcdf
4
+ data.tar.gz: 5b93ada52698c0537660416b8c79b0d8fc90714e
5
+ SHA512:
6
+ metadata.gz: b8b7b81c02dd798ee59eea32ca4c488072e259d95eb516d24f3de2201206095f717c6c81aa05558bd95db84e123e00683ccc02ae91164b360a3cab1d8aee3b98
7
+ data.tar.gz: 032548b4457da19cd0515f898dbc21b082d77d8426be819e959c6cf23d8a411f0beb150da77340cee83a011dea94cf7a4711d75cda0f774b230da94bc8ca8219
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License
2
+
3
+ Copyright (c) GitHub, Inc
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
13
+ all 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
21
+ THE SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,18 @@
1
+ **NOTE: This repository is no longer supported or updated by GitHub. If you wish to continue to develop this code yourself, we recommend you fork it.**
2
+
3
+ # RackStatsD
4
+
5
+ Some tiny middleware for monitoring Rack apps in production.
6
+
7
+ * RackStatsD::RequestStatus - Adds a status URL for health checks.
8
+ * RackStatsD::RequestHostname - Shows which what code is running on
9
+ which node for a given request.
10
+ * RackStatsD::ProcessUtilization - Tracks how long Unicorns spend
11
+ processing requests. Optionally sends metrics to a StatsD server.
12
+
13
+ Note: The request tracking code isn't thread safe. It should work fine
14
+ for apps on Unicorn.
15
+
16
+ This code has been extracted from GitHub.com and is used on
17
+ http://git.io currently.
18
+
data/Rakefile ADDED
@@ -0,0 +1,140 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'date'
4
+
5
+ #############################################################################
6
+ #
7
+ # Helper functions
8
+ #
9
+ #############################################################################
10
+
11
+ def name
12
+ @name ||= Dir['*.gemspec'].first.split('.').first
13
+ end
14
+
15
+ def version
16
+ line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
17
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
18
+ end
19
+
20
+ def date
21
+ Date.today.to_s
22
+ end
23
+
24
+ def rubyforge_project
25
+ name
26
+ end
27
+
28
+ def gemspec_file
29
+ "#{name}.gemspec"
30
+ end
31
+
32
+ def gem_file
33
+ "shutl-#{name}-#{version}.gem"
34
+ end
35
+
36
+ def replace_header(head, header_name)
37
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
38
+ end
39
+
40
+ #############################################################################
41
+ #
42
+ # Standard tasks
43
+ #
44
+ #############################################################################
45
+
46
+ task :default => :test
47
+
48
+ if false
49
+ require 'rake/testtask'
50
+ Rake::TestTask.new(:test) do |test|
51
+ test.libs << 'lib' << 'test'
52
+ test.pattern = 'test/**/*_test.rb'
53
+ test.verbose = true
54
+ end
55
+ else
56
+ task :test do
57
+ puts "haven't setup tests yet"
58
+ end
59
+ end
60
+
61
+ desc "Open an irb session preloaded with this library"
62
+ task :console do
63
+ sh "irb -rubygems -r ./lib/#{name}.rb"
64
+ end
65
+
66
+ #############################################################################
67
+ #
68
+ # Custom tasks (add your own tasks here)
69
+ #
70
+ #############################################################################
71
+
72
+
73
+
74
+ #############################################################################
75
+ #
76
+ # Packaging tasks
77
+ #
78
+ #############################################################################
79
+
80
+ desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
81
+ task :release => :build do
82
+ unless `git branch` =~ /^\* master$/
83
+ puts "You must be on the master branch to release!"
84
+ exit!
85
+ end
86
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
87
+ sh "git tag v#{version}"
88
+ sh "git push origin master"
89
+ sh "git push origin v#{version}"
90
+ sh "gem push pkg/#{name}-#{version}.gem"
91
+ end
92
+
93
+ desc "Build #{gem_file} into the pkg directory"
94
+ task :build => :gemspec do
95
+ sh "mkdir -p pkg"
96
+ sh "gem build #{gemspec_file}"
97
+ sh "mv #{gem_file} pkg"
98
+ end
99
+
100
+ desc "Generate #{gemspec_file}"
101
+ task :gemspec => :validate do
102
+ # read spec file and split out manifest section
103
+ spec = File.read(gemspec_file)
104
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
105
+
106
+ # replace name version and date
107
+ replace_header(head, :version)
108
+ replace_header(head, :date)
109
+ #comment this out if your rubyforge_project has a different name
110
+ replace_header(head, :rubyforge_project)
111
+
112
+ # determine file list from git ls-files
113
+ files = `git ls-files`.
114
+ split("\n").
115
+ sort.
116
+ reject { |file| file =~ /^\./ }.
117
+ reject { |file| file =~ /^(rdoc|pkg)/ }.
118
+ map { |file| " #{file}" }.
119
+ join("\n")
120
+
121
+ # piece file back together and write
122
+ manifest = " s.files = %w[\n#{files}\n ]\n"
123
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
124
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
125
+ puts "Updated #{gemspec_file}"
126
+ end
127
+
128
+ desc "Validate #{gemspec_file}"
129
+ task :validate do
130
+ libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
131
+ unless libfiles.empty?
132
+ puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
133
+ exit!
134
+ end
135
+ unless Dir['VERSION*'].empty?
136
+ puts "A `VERSION` file at root level violates Gem best practices."
137
+ exit!
138
+ end
139
+ end
140
+
data/changelog.md ADDED
@@ -0,0 +1,15 @@
1
+ ## v0.2.0
2
+
3
+ _Back from the Dead_ Release
4
+
5
+ * Response times are tracked by HTTP status. [@mheffner]
6
+ * RequestStatus is Lint compliant. [@kb]
7
+ * Way more status codes are tracked. [@adelcambre]
8
+
9
+ ## v0.1.1
10
+
11
+ * Fixes for RequestStatus response. [@atmos]
12
+
13
+ ## v0.1.0
14
+
15
+ Initial Release
@@ -0,0 +1,292 @@
1
+ module RackStatsD
2
+ VERSION = "0.3.0"
3
+
4
+ # Simple middleware to add a quick status URL for tools like Nagios.
5
+ class RequestStatus
6
+ REQUEST_METHOD = 'REQUEST_METHOD'.freeze
7
+ GET = 'GET'.freeze
8
+ PATH_INFO = 'PATH_INFO'.freeze
9
+ STATUS_PATH = '/status'
10
+ HEADERS = {"Content-Type" => "text/plain"}.freeze
11
+
12
+ # Initializes the middleware.
13
+ #
14
+ # # Responds with "OK" on /status
15
+ # use RequestStatus, "OK"
16
+ #
17
+ # You can change what URL to look for:
18
+ #
19
+ # use RequestStatus, "OK", "/ping"
20
+ #
21
+ # You can also check internal systems and return something more informative.
22
+ #
23
+ # use RequestStatus, lambda {
24
+ # status = MyApp.status # A Hash of some live counters or something
25
+ # [200, {"Content-Type" => "application/json"}, status.to_json]
26
+ # }
27
+ #
28
+ # app - The next Rack app in the pipeline.
29
+ # callback_or_response - Either a Proc or a Rack response.
30
+ # status_path - Optional String path that returns the status.
31
+ # Default: "/status"
32
+ #
33
+ # Returns nothing.
34
+ def initialize(app, callback_or_response, status_path = nil)
35
+ @app = app
36
+ @status_path = (status_path || STATUS_PATH).freeze
37
+ @callback = callback_or_response
38
+ end
39
+
40
+ def call(env)
41
+ if env[REQUEST_METHOD] == GET
42
+ if env[PATH_INFO] == @status_path
43
+ if @callback.respond_to?(:call)
44
+ return @callback.call
45
+ else
46
+ return [200, HEADERS, [@callback.to_s]]
47
+ end
48
+ end
49
+ end
50
+
51
+ @app.call env
52
+ end
53
+ end
54
+
55
+ # Simple middleware that adds the current host name and current git SHA to
56
+ # the response headers. This can help diagnose problems by letting you
57
+ # know what code is running from what machine.
58
+ class RequestHostname
59
+ # Initializes the middlware.
60
+ #
61
+ # app - The next Rack app in the pipeline.
62
+ # options - Hash of options.
63
+ # :host - String hostname.
64
+ # :revision - String SHA that describes the version of code
65
+ # this process is running.
66
+ #
67
+ # Returns nothing.
68
+ def initialize(app, options = {})
69
+ @app = app
70
+ @host = options.key?(:host) ? options[:host] : `hostname -s`.chomp
71
+ @sha = options[:revision] || '<none>'
72
+ end
73
+
74
+ def call(env)
75
+ status, headers, body = @app.call(env)
76
+ headers['X-Node'] = @host if @host
77
+ headers['X-Revision'] = @sha
78
+ [status, headers, body]
79
+ end
80
+ end
81
+
82
+ # Middleware that tracks the amount of time this process spends processing
83
+ # requests, as opposed to being idle waiting for a connection. Statistics
84
+ # are dumped to rack.errors every 5 minutes.
85
+ #
86
+ # NOTE This middleware is not thread safe. It should only be used when
87
+ # rack.multiprocess is true and rack.multithread is false.
88
+ class ProcessUtilization
89
+ REQUEST_METHOD = 'REQUEST_METHOD'.freeze
90
+ VALID_METHODS = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'].freeze
91
+
92
+ # Initializes the middleware.
93
+ #
94
+ # app - The next Rack app in the pipeline.
95
+ # domain - The String domain name the app runs in.
96
+ # revision - The String SHA that describes the current version of code.
97
+ # options - Hash of options.
98
+ # :window - The Integer number of seconds before the
99
+ # horizon resets.
100
+ # :stats - Optional StatsD client.
101
+ # :hostname - Optional String hostname. Set to nil
102
+ # to exclude.
103
+ # :stats_prefix - Optional String prefix for StatsD keys.
104
+ # Default: "rack"
105
+ def initialize(app, domain, revision, options = {})
106
+ @app = app
107
+ @domain = domain
108
+ @revision = revision
109
+ @window = options[:window] || 100
110
+ @horizon = nil
111
+ @active_time = nil
112
+ @requests = nil
113
+ @total_requests = 0
114
+ @worker_number = nil
115
+ @track_gc = GC.respond_to?(:time)
116
+
117
+ if @stats = options[:stats]
118
+ prefix = [options[:stats_prefix] || :rack]
119
+ if options.has_key?(:hostname)
120
+ prefix << options[:hostname] unless options[:hostname].nil?
121
+ else
122
+ prefix << `hostname -s`.chomp
123
+ end
124
+ @stats_prefix = prefix.join(".")
125
+ end
126
+ end
127
+
128
+ # the app's domain name - shown in proctitle
129
+ attr_accessor :domain
130
+
131
+ # the currently running git revision as a 7-sha
132
+ attr_accessor :revision
133
+
134
+ # time when we began sampling. this is reset every once in a while so
135
+ # averages don't skew over time.
136
+ attr_accessor :horizon
137
+
138
+ # total number of requests that have been processed by this worker since
139
+ # the horizon time.
140
+ attr_accessor :requests
141
+
142
+ # decimal number of seconds the worker has been active within a request
143
+ # since the horizon time.
144
+ attr_accessor :active_time
145
+
146
+ # total requests processed by this worker process since it started
147
+ attr_accessor :total_requests
148
+
149
+ # the unicorn worker number
150
+ attr_accessor :worker_number
151
+
152
+ # the amount of time since the horizon
153
+ def horizon_time
154
+ Time.now - horizon
155
+ end
156
+
157
+ # decimal number of seconds this process has been active since the horizon
158
+ # time. This is the inverse of the active time.
159
+ def idle_time
160
+ horizon_time - active_time
161
+ end
162
+
163
+ # percentage of time this process has been active since the horizon time.
164
+ def percentage_active
165
+ (active_time / horizon_time) * 100
166
+ end
167
+
168
+ # percentage of time this process has been idle since the horizon time.
169
+ def percentage_idle
170
+ (idle_time / horizon_time) * 100
171
+ end
172
+
173
+ # number of requests processed per second since the horizon
174
+ def requests_per_second
175
+ requests / horizon_time
176
+ end
177
+
178
+ # average response time since the horizon in milliseconds
179
+ def average_response_time
180
+ (active_time / requests.to_f) * 1000
181
+ end
182
+
183
+ # called exactly once before the first request is processed by a worker
184
+ def first_request
185
+ reset_horizon
186
+ record_worker_number
187
+ end
188
+
189
+ # resets the horizon and all dependent variables
190
+ def reset_horizon
191
+ @horizon = Time.now
192
+ @active_time = 0.0
193
+ @requests = 0
194
+ end
195
+
196
+ # extracts the worker number from the unicorn procline
197
+ def record_worker_number
198
+ if $0 =~ /^.* worker\[(\d+)\].*$/
199
+ @worker_number = $1.to_i
200
+ else
201
+ @worker_number = nil
202
+ end
203
+ end
204
+
205
+ # the generated procline
206
+ def procline
207
+ "unicorn %s[%s] worker[%02d]: %5d reqs, %4.1f req/s, %4dms avg, %5.1f%% util" % [
208
+ domain,
209
+ revision,
210
+ worker_number.to_i,
211
+ total_requests.to_i,
212
+ requests_per_second.to_f,
213
+ average_response_time.to_i,
214
+ percentage_active.to_f
215
+ ]
216
+ end
217
+
218
+ # called immediately after a request to record statistics, update the
219
+ # procline, and dump information to the logfile
220
+ def record_request(status, env)
221
+ now = Time.now
222
+ diff = (now - @start)
223
+ @active_time += diff
224
+ @requests += 1
225
+
226
+ $0 = procline
227
+
228
+ if @stats
229
+ @stats.timing("#{@stats_prefix}.response_time", diff * 1000)
230
+ if VALID_METHODS.include?(env[REQUEST_METHOD])
231
+ stat = "#{@stats_prefix}.response_time.#{env[REQUEST_METHOD].downcase}"
232
+ @stats.timing(stat, diff * 1000)
233
+ end
234
+
235
+ if status
236
+ @stats.increment "#{@stats_prefix}.status_code.#{status}"
237
+ end
238
+ if @track_gc && GC.time > 0
239
+ @stats.timing "#{@stats_prefix}.gc.time", GC.time / 1000
240
+ @stats.count "#{@stats_prefix}.gc.collections", GC.collections
241
+ end
242
+ end
243
+
244
+ reset_horizon if now - horizon > @window
245
+ rescue => boom
246
+ warn "ProcessUtilization#record_request failed: #{boom}"
247
+ end
248
+
249
+ # Body wrapper. Yields to the block when body is closed. This is used to
250
+ # signal when a response is fully finished processing.
251
+ class Body
252
+ def initialize(body, &block)
253
+ @body = body
254
+ @block = block
255
+ end
256
+
257
+ def each(&block)
258
+ if @body.respond_to?(:each)
259
+ @body.each(&block)
260
+ else
261
+ block.call(@body)
262
+ end
263
+ end
264
+
265
+ def close
266
+ @body.close if @body.respond_to?(:close)
267
+ @block.call
268
+ nil
269
+ end
270
+ end
271
+
272
+ # Rack entry point.
273
+ def call(env)
274
+ @start = Time.now
275
+ GC.clear_stats if @track_gc
276
+
277
+ @total_requests += 1
278
+ first_request if @total_requests == 1
279
+
280
+ env['process.request_start'] = @start.to_f
281
+ env['process.total_requests'] = total_requests
282
+
283
+ # newrelic X-Request-Start
284
+ env.delete('HTTP_X_REQUEST_START')
285
+
286
+ status, headers, body = @app.call(env)
287
+ body = Body.new(body) { record_request(status, env) }
288
+ [status, headers, body]
289
+ end
290
+ end
291
+ end
292
+
@@ -0,0 +1,64 @@
1
+ ## This is the rakegem gemspec template. Make sure you read and understand
2
+ ## all of the comments. Some sections require modification, and others can
3
+ ## be deleted if you don't need them. Once you understand the contents of
4
+ ## this file, feel free to delete any comments that begin with two hash marks.
5
+ ## You can find comprehensive Gem::Specification documentation, at
6
+ ## http://docs.rubygems.org/read/chapter/20
7
+ require_relative './lib/rack-statsd'
8
+
9
+ Gem::Specification.new do |s|
10
+ s.specification_version = 2 if s.respond_to? :specification_version=
11
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
+ s.rubygems_version = '1.3.5'
13
+
14
+ ## Leave these as is they will be modified for you by the rake gemspec task.
15
+ ## If your rubyforge_project name is different, then edit it and comment out
16
+ ## the sub! line in the Rakefile
17
+ s.name = 'shutl-rack-statsd'
18
+ s.version = RackStatsD::VERSION
19
+ s.date = '2015-09-07'
20
+ s.rubyforge_project = 'rack-statsd'
21
+
22
+ ## Make sure your summary is short. The description may be as long
23
+ ## as you like.
24
+ s.summary = "Tools for monitoring Rack apps in production."
25
+ s.description = "Tools for monitoring Rack apps in production."
26
+
27
+ ## List the primary authors. If there are a bunch of authors, it's probably
28
+ ## better to set the email to an email list or something. If you don't have
29
+ ## a custom homepage, consider using your GitHub URL or the like.
30
+ s.authors = ["Ryan Tomayko", "Rick Olson"]
31
+ s.email = 'technoweenie@gmail.com'
32
+ s.homepage = 'https://github.com/github/rack-statsd'
33
+
34
+ ## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
35
+ ## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
36
+ s.require_paths = %w[lib]
37
+
38
+ ## List your runtime dependencies here. Runtime dependencies are those
39
+ ## that are needed for an end user to actually USE your code.
40
+ #s.add_dependency('rack', "~> 1.2.6")
41
+
42
+ ## List your development dependencies here. Development dependencies are
43
+ ## those that are only needed during development
44
+ s.add_development_dependency('rack-test')
45
+
46
+ ## Leave this section as-is. It will be automatically generated from the
47
+ ## contents of your Git repository via the gemspec task. DO NOT REMOVE
48
+ ## THE MANIFEST COMMENTS, they are used as delimiters by the task.
49
+ # = MANIFEST =
50
+ s.files = %w[
51
+ LICENSE
52
+ README.md
53
+ Rakefile
54
+ changelog.md
55
+ lib/rack-statsd.rb
56
+ rack-statsd.gemspec
57
+ ]
58
+ # = MANIFEST =
59
+
60
+ ## Test files will be grabbed from the file list. Make sure the path glob
61
+ ## matches what you actually use.
62
+ s.test_files = s.files.select { |path| path =~ /^test\/.*_test\.rb/ }
63
+ end
64
+
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shutl-rack-statsd
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Tomayko
8
+ - Rick Olson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-09-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack-test
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ description: Tools for monitoring Rack apps in production.
29
+ email: technoweenie@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - LICENSE
35
+ - README.md
36
+ - Rakefile
37
+ - changelog.md
38
+ - lib/rack-statsd.rb
39
+ - rack-statsd.gemspec
40
+ homepage: https://github.com/github/rack-statsd
41
+ licenses: []
42
+ metadata: {}
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ requirements: []
58
+ rubyforge_project: rack-statsd
59
+ rubygems_version: 2.4.6
60
+ signing_key:
61
+ specification_version: 2
62
+ summary: Tools for monitoring Rack apps in production.
63
+ test_files: []
64
+ has_rdoc: