rack-server-timing 0.2.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 +7 -0
- data/README.md +80 -0
- data/Rakefile +10 -0
- data/lib/rack-server-timing/current.rb +16 -0
- data/lib/rack-server-timing/metric.rb +26 -0
- data/lib/rack-server-timing/middleware.rb +26 -0
- data/lib/rack-server-timing/recorder.rb +75 -0
- data/lib/roda/plugins/server_timing.rb +47 -0
- data/lib/sequel/extensions/server_timing.rb +21 -0
- metadata +107 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c161a8abce8a3ecdee2ba07e7eeeecb3663d4d90c4ac0d399755df9b1b4862bb
|
|
4
|
+
data.tar.gz: 13ff6d6d9b44386a64a4dba1bfb953e8b23651deaf2312d6f983ec4b01f1f837
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b16d0a90faa4add1a6cca311866e3a3069a92e641ff788db076cc36f46e9be63cad22969b0224361bc7b29d7af361d3fc624a8ea71c94318510b00889e07f27e
|
|
7
|
+
data.tar.gz: ac7e7003ebd3e2da9131ed846a52ece52a08789ff7261eb0aadf5d53cbdb876d6000ec4fbd71e5b479f2ec2910805618f5c0a5102b9c9310bf61fe6362d59102
|
data/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Server Timing for Rack
|
|
2
|
+
|
|
3
|
+
Easily record and emit Server-Timing headers in your Rack applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem "rack-server-timing"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
$ bundle
|
|
16
|
+
|
|
17
|
+
Or install it yourself as:
|
|
18
|
+
|
|
19
|
+
$ gem install rack-server-timing
|
|
20
|
+
|
|
21
|
+
## Usage in a Rack application
|
|
22
|
+
|
|
23
|
+
Simply require and add middleware:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
# config.ru
|
|
27
|
+
require "rack-server-timing/middleware"
|
|
28
|
+
|
|
29
|
+
use RackServerTiming::Middleware
|
|
30
|
+
|
|
31
|
+
run ->(env) {
|
|
32
|
+
[200, {}, ["It Works!"]]
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
And then record your metrics:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
env["rack.server_timing"].record("DB", 200)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Usage in a Roda application
|
|
43
|
+
|
|
44
|
+
Enable the plugin `server_timing` after `render` (if you wish to have the rendering profiled automatically).
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
class App < Roda
|
|
48
|
+
plugin :render # Optional
|
|
49
|
+
plugin :server_timing
|
|
50
|
+
end
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
A convenient `server_timing` helper is available to quickly `record` or `benchmark`
|
|
54
|
+
timing values.
|
|
55
|
+
|
|
56
|
+
## Sequel timing
|
|
57
|
+
|
|
58
|
+
Enable the `server_timing` extension in your database instance. This extension will automatically create a
|
|
59
|
+
null logging instance if there is no logging configured so there _may_ be a small performance hit.
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
DB = Sequel.connect
|
|
63
|
+
|
|
64
|
+
DB.extension :server_timing
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Contributing
|
|
68
|
+
|
|
69
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/adam12/rack-server-timing.
|
|
70
|
+
|
|
71
|
+
I love pull requests! If you fork this project and modify it, please ping me to see
|
|
72
|
+
if your changes can be incorporated back into this project.
|
|
73
|
+
|
|
74
|
+
That said, if your feature idea is nontrivial, you should probably open an issue to
|
|
75
|
+
[discuss it](http://www.igvita.com/2011/12/19/dont-push-your-pull-requests/)
|
|
76
|
+
before attempting a pull request.
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
module RackServerTiming
|
|
3
|
+
module Current
|
|
4
|
+
def self.clear!
|
|
5
|
+
Thread.current[:server_timing] = nil
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.recorder=(recorder)
|
|
9
|
+
Thread.current[:server_timing] = recorder
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.recorder
|
|
13
|
+
Thread.current[:server_timing]
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
module RackServerTiming
|
|
3
|
+
class Metric
|
|
4
|
+
attr_reader :name
|
|
5
|
+
attr_reader :duration
|
|
6
|
+
attr_reader :description
|
|
7
|
+
|
|
8
|
+
def initialize(name:, duration: nil, description: nil)
|
|
9
|
+
@name = name
|
|
10
|
+
@duration = duration
|
|
11
|
+
@description = description
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def increment(delta)
|
|
15
|
+
@duration += delta
|
|
16
|
+
self
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def formatted
|
|
20
|
+
String.new(name).tap do |output|
|
|
21
|
+
output << %Q|;dur=#{duration}| if duration
|
|
22
|
+
output << %Q|;desc="#{description}"| if description
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
require_relative "recorder"
|
|
3
|
+
require_relative "current"
|
|
4
|
+
|
|
5
|
+
module RackServerTiming
|
|
6
|
+
class Middleware
|
|
7
|
+
def initialize(app)
|
|
8
|
+
@app = app
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call(env)
|
|
12
|
+
recorder = Recorder.new
|
|
13
|
+
|
|
14
|
+
env["rack.server_timing"] = Current.recorder = recorder
|
|
15
|
+
status, headers, response = @app.call(env)
|
|
16
|
+
|
|
17
|
+
if (value = recorder.header_value) and not value.empty?
|
|
18
|
+
headers[recorder.header_name] ||= value
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
[status, headers, response]
|
|
22
|
+
ensure
|
|
23
|
+
Current.clear!
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
require "benchmark"
|
|
3
|
+
require_relative "metric"
|
|
4
|
+
|
|
5
|
+
module RackServerTiming
|
|
6
|
+
class Recorder
|
|
7
|
+
attr_reader :metrics
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@metrics = {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# Increment the metric of :name: by :duration:, creating the metric if
|
|
15
|
+
# it did not already exist.
|
|
16
|
+
#
|
|
17
|
+
# Accepts arguments as positional arguments or keyword arguments.
|
|
18
|
+
def increment(name = nil, duration = nil, description = nil, **kwargs)
|
|
19
|
+
name = kwargs[:name] if name.nil?
|
|
20
|
+
duration = kwargs[:duration] if duration.nil?
|
|
21
|
+
description = kwargs[:description] if description.nil?
|
|
22
|
+
|
|
23
|
+
if (metric = metrics[name])
|
|
24
|
+
metric.increment(duration)
|
|
25
|
+
else
|
|
26
|
+
metric = Metric.new(name: name, duration: duration, description: description)
|
|
27
|
+
metrics[name] = metric
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
# Record the metric of :name: with :duration: and optionally :description:.
|
|
33
|
+
#
|
|
34
|
+
# Accepts arguments as positional arguments or keyword arguments.
|
|
35
|
+
def record(name = nil, duration = nil, description = nil, **kwargs)
|
|
36
|
+
name ||= kwargs[:name]
|
|
37
|
+
duration ||= kwargs[:duration]
|
|
38
|
+
description ||= kwargs[:description]
|
|
39
|
+
|
|
40
|
+
metrics[name] = Metric.new(
|
|
41
|
+
name: name,
|
|
42
|
+
duration: duration,
|
|
43
|
+
description: description
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
# Record the metric of :name: with an optional :description:, evaluating the
|
|
49
|
+
# provided block for :duration:.
|
|
50
|
+
#
|
|
51
|
+
# Accepts arguments as positional arguments or keyword arguments.
|
|
52
|
+
def benchmark(name = nil, description = nil, **kwargs, &block)
|
|
53
|
+
result = nil
|
|
54
|
+
name ||= kwargs[:name]
|
|
55
|
+
description ||= kwargs[:description]
|
|
56
|
+
duration = Benchmark.realtime { result = block.call }
|
|
57
|
+
|
|
58
|
+
metrics[name] = Metric.new(
|
|
59
|
+
name: name,
|
|
60
|
+
duration: duration,
|
|
61
|
+
description: description,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
result
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def header_name
|
|
68
|
+
"Server-Timing"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def header_value
|
|
72
|
+
metrics.map {|_name, metric| metric.formatted }.join(", ")
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
require "rack-server-timing/middleware"
|
|
4
|
+
|
|
5
|
+
class Roda
|
|
6
|
+
module RodaPlugins
|
|
7
|
+
module ServerTiming
|
|
8
|
+
module InstanceMethods
|
|
9
|
+
# Shorthand to the +RackServerTiming::Recorder+ instance
|
|
10
|
+
def server_timing
|
|
11
|
+
env["rack.server_timing"]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Benchmark the provided block and return it's result
|
|
15
|
+
def benchmark(name:, description: nil)
|
|
16
|
+
result = nil
|
|
17
|
+
|
|
18
|
+
server_timing.benchmark(name: name, description: description) do
|
|
19
|
+
result = yield
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
result
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
module RenderMethods # :nodoc:
|
|
27
|
+
def render(*)
|
|
28
|
+
benchmark(name: "Render") { super }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def view(*)
|
|
32
|
+
benchmark(name: "Render") { super }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.configure(app) # :nodoc:
|
|
37
|
+
app.use RackServerTiming::Middleware
|
|
38
|
+
|
|
39
|
+
if app.opts.has_key?(:render)
|
|
40
|
+
app.send(:include, RenderMethods)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
register_plugin :server_timing, ServerTiming
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
require "sequel"
|
|
3
|
+
|
|
4
|
+
module RackServerTiming
|
|
5
|
+
module Sequel
|
|
6
|
+
def log_duration(duration, message)
|
|
7
|
+
RackServerTiming::Current.recorder.increment("DB", duration, "Database")
|
|
8
|
+
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.extended(base)
|
|
13
|
+
return unless base.loggers.empty?
|
|
14
|
+
|
|
15
|
+
require "logger"
|
|
16
|
+
base.loggers = [Logger.new("/dev/null")]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
::Sequel::Database.register_extension(:server_timing, RackServerTiming::Sequel)
|
|
21
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rack-server-timing
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Adam Daniels
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: benchmark
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: logger
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.2'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.2'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: cgi
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0.1'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0.1'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rack
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '2.0'
|
|
61
|
+
- - "<"
|
|
62
|
+
- !ruby/object:Gem::Version
|
|
63
|
+
version: '4.0'
|
|
64
|
+
type: :runtime
|
|
65
|
+
prerelease: false
|
|
66
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
67
|
+
requirements:
|
|
68
|
+
- - ">="
|
|
69
|
+
- !ruby/object:Gem::Version
|
|
70
|
+
version: '2.0'
|
|
71
|
+
- - "<"
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '4.0'
|
|
74
|
+
executables: []
|
|
75
|
+
extensions: []
|
|
76
|
+
extra_rdoc_files: []
|
|
77
|
+
files:
|
|
78
|
+
- README.md
|
|
79
|
+
- Rakefile
|
|
80
|
+
- lib/rack-server-timing/current.rb
|
|
81
|
+
- lib/rack-server-timing/metric.rb
|
|
82
|
+
- lib/rack-server-timing/middleware.rb
|
|
83
|
+
- lib/rack-server-timing/recorder.rb
|
|
84
|
+
- lib/roda/plugins/server_timing.rb
|
|
85
|
+
- lib/sequel/extensions/server_timing.rb
|
|
86
|
+
homepage: https://github.com/adam12/rack-server-timing
|
|
87
|
+
licenses:
|
|
88
|
+
- MIT
|
|
89
|
+
metadata: {}
|
|
90
|
+
rdoc_options: []
|
|
91
|
+
require_paths:
|
|
92
|
+
- lib
|
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
94
|
+
requirements:
|
|
95
|
+
- - ">="
|
|
96
|
+
- !ruby/object:Gem::Version
|
|
97
|
+
version: '0'
|
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '0'
|
|
103
|
+
requirements: []
|
|
104
|
+
rubygems_version: 4.0.3
|
|
105
|
+
specification_version: 4
|
|
106
|
+
summary: Server-Timing headers for Rack applications
|
|
107
|
+
test_files: []
|