leafy-rack 0.1.0

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: 32375b3191e4984cdda79aeaaae526f855cf073c
4
+ data.tar.gz: 884c3eec470ff217b30bf0aa8c1fec7fed4a153e
5
+ SHA512:
6
+ metadata.gz: 8a2b594c128067d6415ff542da326e467d2ea1acf8cf9bdfe2c82f78a487bbb335eb8c412eff89586ee3f345cee872ad435307957cf55eccfa559514dbaa5fe8
7
+ data.tar.gz: 80ecea5eada8befc856236ec8361c28714aa4597057c4a4a0ac090b39a0bbb923c8f2ab088163b2532fe0ae41d70444ac509aed0aee3c1ddd0781d38298e9e3b
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .yardoc
2
+ doc
3
+ pkg
4
+ *.lock
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ #-*- mode: ruby -*-
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'leafy-metrics', :path => '../leafy-metrics'
6
+ gem 'leafy-health', :path => '../leafy-health'
7
+
8
+ gemspec
9
+
10
+ # vim: syntax=Ruby
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Lookout
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,155 @@
1
+ # Leafy-Rack
2
+
3
+ ## installation
4
+
5
+ via rubygems
6
+ ```
7
+ gem install leafy-rack
8
+ ```
9
+ or add to your Gemfile
10
+ ```
11
+ gem 'leafy-rack
12
+ ```
13
+
14
+ installing the gem also takes care of the jar dependencies with jruby-1.7.16+
15
+
16
+ ## intro
17
+
18
+ there actually three parts to this gem
19
+
20
+ * serializers to write out json data from the collected data of ```Leafy::Health::Registry``` and ```Leafy::Metrics::Registry```
21
+
22
+ * instrumented class which is almost like a rack middleware but is threadsafe and is meant to be shared with **ALL** requests. with this sharing in can count the number of active requests.
23
+
24
+ * a collection of middleware
25
+
26
+ ## serializers for health and metrics data
27
+
28
+ are using the internal API of ```Leafy::Health::Registry``` or ```Leafy::Metrics::Registry``` to ```run_health_checks``` or retrieve the collect metrics and produces the json representation of these data.
29
+
30
+ registry = Leafy::Health::Registry.new
31
+ json_writer = Leafy::Json::HealthWriter.new
32
+ json_writer.to_json( registry.health.run_health_checks )
33
+
34
+ or
35
+
36
+ registry = Leafy::Metrics::Registry.new
37
+ json_writer = Leafy::Json::MetricsWriter.new
38
+ json_writer.to_json( registry.metrics )
39
+
40
+ both json writers can take a second argument to generate pretty prints:
41
+
42
+ json_writer.to_json( registry.health.run_health_checks, true )
43
+ json_writer.to_json( registry.metrics, true )
44
+
45
+ ## instrumented http response
46
+
47
+ the class ```Leafy::Instrumented::Instrumented``` has a call method which expect a block. the block needs to return the usual rack middleware result ```[status, headers, body]```.
48
+
49
+ typical usage of this inside a rack-middleware
50
+
51
+ metrics = Leafy::Metrics::Registry.new
52
+ instrumented = Leafy::Instrumented::Instrumented.new( metrics, 'myapp' )
53
+ instrumented.call do
54
+ @app.call( env )
55
+ end
56
+
57
+ see the ```Leafy::Rack::Instrumented``` for an example.
58
+
59
+ ## rack middleware
60
+
61
+ * instrumented middleware collecting metrics on response status, response time, and active requests
62
+ * json data of metrics snapshot
63
+ * json data of current health
64
+ * ping
65
+ * java thread-dump
66
+ * admin page with links to metrics, health, ping and thread-dump data
67
+
68
+ ### instrumented middleware
69
+
70
+ metrics = Leafy::Metrics::Registry.new
71
+ use Leafy::Rack::Instrumented, Leafy::Instrumented::Instrumented.new( metrics, 'webapp' )
72
+
73
+ note: when this instrumented middleware gets configured **after** any of the admin middleware (see below) then those admin requests are not going into the instrumented metrics.
74
+
75
+ ### metrics middleware
76
+
77
+ json data of a snapshot of metrics are under the path **/metrics**
78
+
79
+ metrics = Leafy::Metrics::Registry.new
80
+ use Leafy::Rack::Metrics, metrics
81
+
82
+ or with custom path
83
+
84
+ metrics = Leafy::Metrics::Registry.new
85
+ use Leafy::Rack::Metrics, metrics, '/admin/metrics'
86
+
87
+ ### health-checks middleware
88
+
89
+ json data of current health are under the path **/health**
90
+
91
+ health = Leafy::Health::Registry.new
92
+ use Leafy::Rack::Health, health
93
+
94
+ or with custom path
95
+
96
+ health = Leafy::Health::Registry.new
97
+ use Leafy::Rack::Health, health, '/admin/health'
98
+
99
+ ### ping middleware
100
+
101
+ under the path **/ping**
102
+
103
+ use Leafy::Rack::Ping
104
+
105
+ or with custom path
106
+
107
+ use Leafy::Rack::Ping, '/admin/ping'
108
+
109
+ ### java thread-dump middleware
110
+
111
+ under the path **/threads**
112
+
113
+ use Leafy::Rack::ThreadDump
114
+
115
+ or with custom path
116
+
117
+ use Leafy::Rack::ThreadDump, '/admin/threads'
118
+
119
+
120
+ ### admin page middleware
121
+
122
+ a simple page with links to metrics, health, ping and thread-dump data under the path **/admin**
123
+
124
+ metrics = Leafy::Metrics::Registry.new
125
+ health = Leafy::Health::Registry.new
126
+
127
+ use Leafy::Rack::Admin, metrics, health
128
+
129
+ or with custom path
130
+
131
+ metrics = Leafy::Metrics::Registry.new
132
+ health = Leafy::Health::Registry.new
133
+
134
+ use Leafy::Rack::Admin, metrics, health, '/hidden/admin'
135
+
136
+ ## example sinatra app
137
+
138
+ there is an [example sinatra application](https://github.com/lookout/leafy/tree/master/examples/hellowarld) which uses admin and instrumented middleware and adds some extra metrics inside the application.
139
+
140
+ ## developement
141
+
142
+ get all the gems and jars in place
143
+
144
+ gem install jar-dependencies --development
145
+ bundle install
146
+
147
+ please make sure you are using jar-dependencies > 0.1.8 !
148
+
149
+ for running all specs
150
+
151
+ rake
152
+
153
+ or
154
+
155
+ rspec spec/reporter_spec.rb
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ #-*- mode: ruby -*-
2
+
3
+ require "rspec/core/rake_task"
4
+ RSpec::Core::RakeTask.new
5
+
6
+ require "yard"
7
+ YARD::Rake::YardocTask.new do |t|
8
+ t.files = ['lib/**/*.rb']
9
+ t.options += ["--title", "Leafy Rack Middleware"]
10
+ end
11
+
12
+ task :default => [ :spec ]
13
+
14
+ # vim: syntax=Ruby
@@ -0,0 +1,29 @@
1
+ #-*- mode: ruby -*-
2
+
3
+ require File.expand_path( '../lib/leafy/rack/version', __FILE__ )
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'leafy-rack'
7
+ s.version = Leafy::Rack::VERSION
8
+ s.author = 'christian meier'
9
+ s.email = [ 'christian.meier@lookout.com' ]
10
+
11
+ s.license = 'MIT'
12
+ s.summary = %q(rack middleware to expose leafy metrics/health data and more)
13
+ s.homepage = 'https://github.com/lookout/leafy'
14
+ s.description = %q(rack middleware to expose leafy metrics/health data as well a ping rack and a thread-dump rack)
15
+
16
+ s.files = `git ls-files`.split($/)
17
+
18
+ s.requirements << 'jar io.dropwizard.metrics:metrics-json, 3.1.0'
19
+ s.requirements << 'jar io.dropwizard.metrics:metrics-jvm, 3.1.0'
20
+
21
+ s.add_runtime_dependency 'jar-dependencies', '~> 0.1.8'
22
+ s.add_runtime_dependency 'leafy-metrics', '~> 0.1.0'
23
+ s.add_runtime_dependency 'leafy-health', '~> 0.1.0'
24
+ s.add_development_dependency 'rspec', '~> 3.1.0'
25
+ s.add_development_dependency 'yard', '~> 0.8.7'
26
+ s.add_development_dependency 'rake', '~> 10.2'
27
+ end
28
+
29
+ # vim: syntax=Ruby
@@ -0,0 +1,38 @@
1
+ module Leafy
2
+ module Instrumented
3
+ class BasicInstrumented
4
+
5
+ NAME_PREFIX = "responseCodes"
6
+
7
+ def initialize( registry, name, meter_names_by_status_code = {} )
8
+ @meters_by_status_code = java.util.concurrent.ConcurrentHashMap.new
9
+ meter_names_by_status_code.each do |k,v|
10
+ @meters_by_status_code[ k ] = registry.register_meter( "#{name}.#{v}" )
11
+ end
12
+ @other = registry.register_meter( "#{name}.#{NAME_PREFIX}.other" )
13
+ @active = registry.register_counter( "#{name}.active_requests" )
14
+ @timer = registry.register_timer( "#{name}.requests" )
15
+ end
16
+
17
+ def call( &block )
18
+ raise "block needed" unless block_given?
19
+ @active.inc
20
+
21
+ @timer.time do
22
+
23
+ result = block.call
24
+ mark_meter_for_status_code result[0]
25
+ result
26
+
27
+ end
28
+ ensure
29
+ @active.dec
30
+ end
31
+
32
+ def mark_meter_for_status_code( status )
33
+ metric = @meters_by_status_code[ status ] || @other
34
+ metric.mark
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,27 @@
1
+ require 'leafy/instrumented/basic_instrumented'
2
+
3
+ module Leafy
4
+ module Instrumented
5
+ class Instrumented < BasicInstrumented
6
+
7
+ OK = 200;
8
+ CREATED = 201;
9
+ NO_CONTENT = 204;
10
+ RESET_CONTENT = 205;
11
+ BAD_REQUEST = 400;
12
+ NOT_FOUND = 404;
13
+ SERVER_ERROR = 500;
14
+
15
+ def initialize( registry, name )
16
+ super( registry, name,
17
+ { OK => "#{NAME_PREFIX}.ok",
18
+ CREATED => "#{NAME_PREFIX}.created",
19
+ NO_CONTENT => "#{NAME_PREFIX}.noContent",
20
+ RESET_CONTENT => "#{NAME_PREFIX}.resetContent",
21
+ BAD_REQUEST => "#{NAME_PREFIX}.badRequest",
22
+ NOT_FOUND => "#{NAME_PREFIX}.notFound",
23
+ SERVER_ERROR => "#{NAME_PREFIX}.serverError" } )
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,15 @@
1
+ require 'leafy/health'
2
+ require 'leafy/json/json_writer'
3
+
4
+ java_import com.codahale.metrics.json.HealthCheckModule
5
+
6
+ module Leafy
7
+ module Json
8
+ class HealthWriter < JsonWriter
9
+
10
+ def initialize
11
+ super( HealthCheckModule.new )
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,33 @@
1
+ java_import com.fasterxml.jackson.databind.ObjectMapper
2
+
3
+ module Leafy
4
+ module Json
5
+ class JsonWriter
6
+
7
+ def initialize( jacksonModule )
8
+ @mapper = ObjectMapper.new.registerModule( jacksonModule )
9
+ end
10
+
11
+ def data
12
+ raise 'need implementation'
13
+ end
14
+
15
+ def to_json( data, pretty = false )
16
+ # TODO make this stream
17
+ output = java.io.ByteArrayOutputStream.new
18
+ writer( pretty ).writeValue(output, data);
19
+ output.to_s
20
+ end
21
+
22
+ private
23
+
24
+ def writer( pretty )
25
+ if pretty
26
+ @mapper.writerWithDefaultPrettyPrinter
27
+ else
28
+ @mapper.writer
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,22 @@
1
+ require 'leafy/metrics'
2
+ require 'leafy/json/json_writer'
3
+
4
+ java_import com.codahale.metrics.MetricFilter
5
+ java_import com.codahale.metrics.json.MetricsModule
6
+
7
+ java_import java.util.concurrent.TimeUnit
8
+
9
+ module Leafy
10
+ module Json
11
+ class MetricsWriter < JsonWriter
12
+
13
+ def initialize
14
+ super( # make this configurable
15
+ MetricsModule.new(TimeUnit::SECONDS,
16
+ TimeUnit::SECONDS,
17
+ true,
18
+ MetricFilter::ALL) )
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,72 @@
1
+ require 'leafy/rack/metrics'
2
+ require 'leafy/rack/health'
3
+ require 'leafy/rack/ping'
4
+ require 'leafy/rack/thread_dump'
5
+
6
+ module Leafy
7
+ module Rack
8
+ class Admin
9
+
10
+ def self.response( path )
11
+ [
12
+ 200,
13
+ { 'Content-Type' => 'text/html' },
14
+ [ <<EOF
15
+ <DOCTYPE html>
16
+ <html>
17
+ <head>
18
+ <title>metrics</title>
19
+ </head>
20
+ <body>
21
+ <h1>menu</h1>
22
+ <ul>
23
+ <li><a href='#{path}/metrics'>metrics</a> (<a href='#{path}/metrics?pretty'>pretty</a>)</li>
24
+ <li><a href='#{path}/health'>health</a> (<a href='#{path}/health?pretty'>pretty</a>)</li>
25
+ <li><a href='#{path}/ping'>ping</a></li>
26
+ <li><a href='#{path}/threads'>thread-dump</a></li>
27
+ </ul>
28
+ </body>
29
+ </html>
30
+ EOF
31
+ ]
32
+ ]
33
+ end
34
+
35
+ def initialize(app, metrics_registry, health_registry, path = '/admin')
36
+ @app = app
37
+ @path = path
38
+ @metrics = metrics_registry
39
+ @health = health_registry
40
+ end
41
+
42
+ def call(env)
43
+ if ( path = env['PATH_INFO'] ).start_with? @path
44
+ dispatch( path.sub( /#{@path}/, ''), env )
45
+ else
46
+ @app.call( env )
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def dispatch( path, env )
53
+ case path
54
+ when '/metrics'
55
+ Metrics.response( @metrics.metrics, env )
56
+ when '/health'
57
+ Health.response( @health.health, env )
58
+ when '/ping'
59
+ Ping.response
60
+ when '/threads'
61
+ ThreadDump.response
62
+ when '/'
63
+ Admin.response( @path )
64
+ when ''
65
+ Admin.response( @path )
66
+ else # let the app deal with "not found"
67
+ @app.call( env )
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,36 @@
1
+ require 'leafy/rack'
2
+ require 'leafy/json/health_writer'
3
+
4
+ module Leafy
5
+ module Rack
6
+ class Health
7
+
8
+ WRITER = ::Leafy::Json::HealthWriter.new
9
+
10
+ def self.response( health, env )
11
+ data = health.run_health_checks
12
+ is_healthy = data.values.all? { |r| r.healthy? }
13
+ [
14
+ is_healthy ? 200 : 500,
15
+ { 'Content-Type' => 'application/json',
16
+ 'Cache-Control' => 'must-revalidate,no-cache,no-store' },
17
+ [ WRITER.to_json( data, env[ 'QUERY_STRING' ] == 'pretty' ) ]
18
+ ]
19
+ end
20
+
21
+ def initialize(app, registry, path = '/health')
22
+ @app = app
23
+ @path = path
24
+ @registry = registry
25
+ end
26
+
27
+ def call(env)
28
+ if env['PATH_INFO'] == @path
29
+ Health.response( @registry.health, env )
30
+ else
31
+ @app.call( env )
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,20 @@
1
+ require 'leafy/rack'
2
+ require 'leafy/instrumented/instrumented'
3
+
4
+ module Leafy
5
+ module Rack
6
+ class Instrumented
7
+
8
+ def initialize( app, instrumented )
9
+ @app = app
10
+ @instrumented = instrumented
11
+ end
12
+
13
+ def call( env )
14
+ @instrumented.call do
15
+ @app.call env
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,35 @@
1
+ require 'leafy/rack'
2
+ require 'leafy/metrics'
3
+ require 'leafy/json/metrics_writer'
4
+ module Leafy
5
+ module Rack
6
+
7
+ class Metrics
8
+
9
+ WRITER = ::Leafy::Json::MetricsWriter.new
10
+
11
+ def self.response( metrics, env )
12
+ [
13
+ 200,
14
+ { 'Content-Type' => 'application/json',
15
+ 'Cache-Control' => 'must-revalidate,no-cache,no-store' },
16
+ [ WRITER.to_json( metrics, env[ 'QUERY_STRING' ] == 'pretty' ) ]
17
+ ]
18
+ end
19
+
20
+ def initialize(app, registry, path = '/metrics')
21
+ @app = app
22
+ @path = path
23
+ @registry = registry
24
+ end
25
+
26
+ def call(env)
27
+ if env['PATH_INFO'] == @path
28
+ Metrics.response( @registry.metrics, env )
29
+ else
30
+ @app.call( env )
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,29 @@
1
+ require 'leafy/rack'
2
+ module Leafy
3
+ module Rack
4
+ class Ping
5
+
6
+ def self.response
7
+ [
8
+ 200,
9
+ { 'Content-Type' => 'text/plain',
10
+ 'Cache-Control' => 'must-revalidate,no-cache,no-store' },
11
+ [ 'pong' ]
12
+ ]
13
+ end
14
+
15
+ def initialize(app, path = '/ping')
16
+ @app = app
17
+ @path = path
18
+ end
19
+
20
+ def call(env)
21
+ if env['PATH_INFO'] == @path
22
+ Ping.response
23
+ else
24
+ @app.call( env )
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,56 @@
1
+ require 'leafy/rack'
2
+
3
+ java_import java.lang.management.ManagementFactory
4
+
5
+ module Leafy
6
+ module Rack
7
+ class ThreadDumpWriter
8
+
9
+ def initialize
10
+ begin
11
+ # Some PaaS like Google App Engine blacklist java.lang.managament
12
+ @threads = com.codahale.metrics.jvm.ThreadDump.new(ManagementFactory.getThreadMXBean());
13
+ rescue LoadError
14
+ # we won't be able to provide thread dump
15
+ end
16
+ end
17
+
18
+ def to_text
19
+ if @threads
20
+ # TODO make this stream
21
+ output = java.io.ByteArrayOutputStream.new
22
+ @threads.dump(output)
23
+ output.to_s
24
+ end
25
+ end
26
+ end
27
+
28
+ class ThreadDump
29
+
30
+ WRITER = ThreadDumpWriter.new
31
+
32
+ def self.response
33
+ dump = WRITER.to_text
34
+ [
35
+ 200,
36
+ { 'Content-Type' => 'text/plain',
37
+ 'Cache-Control' => 'must-revalidate,no-cache,no-store' },
38
+ [ dump ? dump : 'Sorry your runtime environment does not allow to dump threads.' ]
39
+ ]
40
+ end
41
+
42
+ def initialize(app, path = '/threads')
43
+ @app = app
44
+ @path = path
45
+ end
46
+
47
+ def call(env)
48
+ if env['PATH_INFO'] == @path
49
+ ThreadDump.response
50
+ else
51
+ @app.call( env )
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,6 @@
1
+ module Leafy
2
+ module Rack
3
+ VERSION = '0.1.0'.freeze
4
+ end
5
+ end
6
+
data/lib/leafy/rack.rb ADDED
@@ -0,0 +1 @@
1
+ require 'leafy-rack_jars'
data/lib/leafy-rack.rb ADDED
@@ -0,0 +1 @@
1
+ require 'leafy/rack'
@@ -0,0 +1,10 @@
1
+ # this is a generated file, to avoid over-writing it just delete this comment
2
+ require 'jar_dependencies'
3
+
4
+ require_jar( 'com.fasterxml.jackson.core', 'jackson-annotations', '2.4.0' )
5
+ require_jar( 'com.fasterxml.jackson.core', 'jackson-core', '2.4.2' )
6
+ require_jar( 'org.slf4j', 'slf4j-api', '1.7.7' )
7
+ require_jar( 'io.dropwizard.metrics', 'metrics-jvm', '3.1.0' )
8
+ require_jar( 'io.dropwizard.metrics', 'metrics-core', '3.1.0' )
9
+ require_jar( 'com.fasterxml.jackson.core', 'jackson-databind', '2.4.2' )
10
+ require_jar( 'io.dropwizard.metrics', 'metrics-json', '3.1.0' )