statue 0.2.7 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 60532afa98d1f352a73de3b13d62ba8ac557e2ff
4
- data.tar.gz: 403998b67478dba13d28bdcea552d4410502e7b0
3
+ metadata.gz: 4babeecc571be33fbda04a81c3eabc6455368aff
4
+ data.tar.gz: 6bced52052f52e3611df23562825e570a548d1d1
5
5
  SHA512:
6
- metadata.gz: d644f801e7d3cd830b07a1beb4b8354a608f4e994e3654785abdab1454098e2b687f5a0c3279bb2dbc0bb9d309ddb8eddaffe52f540a3bae735f56cb59e840db
7
- data.tar.gz: abab109194aee3868dd2c075c78dc4172d554405501fcf7f32c459c7c50dbf19d5c3b72f6016b5e8de593454c08f2a6f6caf39f3f903f8b65545bbc25394aaf3
6
+ metadata.gz: b73f850044ca23086c8f837fc3f969d94121390139292ab94ac28956a686567e27784b066ad000151629ba0f76492746dc127cb310f84a95609b5f8fe9f70087
7
+ data.tar.gz: bde0b2df82abc4ccc570bd87254cb176c435dbb3799cc24484622a502134b48351806be9ae3333e43b77d52940faddae1265d5c889543b56017948853d7024ad
@@ -1,3 +1,48 @@
1
+ ## [0.3.0] - Unreleased
2
+
3
+ ### Feature
4
+
5
+ - Extract duration measuring to `Statue::Clock` and be explicit that we are handling milliseconds.
6
+ - `RackStatistics` queue time is now taken from `X-Request-Start` header.
7
+
8
+ ### Backward incompatible changes
9
+
10
+ - All duration reports are now sent in milliseconds (as it should have always been). These includes:
11
+ - `Statue.report_duration`
12
+ - `RackStatistics` middleware metrics
13
+ - `Statue.stopwatch`
14
+ - Removed `Statue.duration`, you should use `Statue::Clock.duration_in_ms` (which is more explicit)
15
+ - `RackStatistics`: `request.<key>` metric was renamed to `request.<key>.runtime` to have more uniform names
16
+ - `RackStatistics`: now sends specific counter for when your application didn't handle the exception
17
+ - `UDPBacked`: now receives host/port named params or can be built from a string with
18
+ `UDPBacked.from_uri(<uri>)`
19
+
20
+ ## [0.2.7] - 2016-03-09
21
+
22
+ ## Fixes
23
+
24
+ - Make UDPBacked compatible with JRuby
25
+
26
+ ## [0.2.4] - 2016-02-04
27
+
28
+ ## Features
29
+
30
+ - Add support for gauges
31
+
32
+ ## [0.2.2] - 2016-02-02
33
+
34
+ ## Fixes
35
+
36
+ - Allow RackStatistics to handle not integer status codes
37
+
38
+ ## [0.2.1] - 2016-01-07
39
+
40
+ ## Features
41
+
42
+ - Add stopwatchs to report multiple partial times
43
+
1
44
  ## [0.2.0] - 2015-11-17
2
- ### Added
45
+
46
+ ### Features
47
+
3
48
  - Add support for multithreaded applications
@@ -7,9 +7,9 @@ require 'statue/stopwatch'
7
7
  module Statue
8
8
  extend self
9
9
 
10
- attr_accessor :namespace, :logger
11
-
12
- attr_accessor :backend
10
+ attr_accessor :namespace
11
+ attr_accessor :logger
12
+ attr_writer :backend
13
13
 
14
14
  def report_duration(metric_name, duration = nil, **options, &block)
15
15
  result = nil
@@ -29,13 +29,9 @@ module Statue
29
29
 
30
30
  def report_success_or_failure(metric_name, success_method: nil, **options, &block)
31
31
  result = block.call
32
- success = success_method ? result.public_send(success_method) : result
33
32
 
34
- if success
35
- report_increment("#{metric_name}.success", **options)
36
- else
37
- report_increment("#{metric_name}.failure", **options)
38
- end
33
+ success = success_method ? result.public_send(success_method) : result
34
+ report_increment("#{metric_name}.#{success ? "success" : "failure"}", **options)
39
35
 
40
36
  result
41
37
  rescue
@@ -48,13 +44,7 @@ module Statue
48
44
  end
49
45
 
50
46
  def backend
51
- @backend ||= UDPBackend.new
52
- end
53
-
54
- def duration
55
- start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
56
- yield
57
- Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
47
+ @backend ||= UDPBackend.from_uri("statsd://127.0.0.1:8125")
58
48
  end
59
49
 
60
50
  def debug(text, &block)
@@ -4,7 +4,12 @@ module Statue
4
4
  class UDPBackend
5
5
  attr_reader :host, :port
6
6
 
7
- def initialize(host = nil, port = nil)
7
+ def self.from_uri(uri)
8
+ uri = URI(uri)
9
+ new(host: uri.host, port: uri.port)
10
+ end
11
+
12
+ def initialize(host:, port:)
8
13
  @host = host
9
14
  @port = port
10
15
  end
@@ -0,0 +1,16 @@
1
+ module Statue
2
+ module Clock
3
+ extend self
4
+
5
+ def now_in_ms
6
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1_000
7
+ end
8
+
9
+ def duration_in_ms
10
+ start = now_in_ms
11
+ yield
12
+ now_in_ms - start
13
+ end
14
+
15
+ end
16
+ end
@@ -1,3 +1,5 @@
1
+ require 'statue/clock'
2
+
1
3
  module Statue
2
4
  class Metric
3
5
  TYPES = {
@@ -9,6 +11,7 @@ module Statue
9
11
  }
10
12
 
11
13
  attr_accessor :type, :name, :value, :sample_rate
14
+ attr_reader :full_name, :type_description
12
15
 
13
16
  def self.counter(name, value = 1, **options)
14
17
  new(type: :c, value: value, name: name, **options)
@@ -19,15 +22,17 @@ module Statue
19
22
  end
20
23
 
21
24
  def self.measure(name, duration: nil, **options, &block)
22
- value = duration || Statue.duration(&block)
25
+ value = duration || Statue::Clock.duration_in_ms(&block)
23
26
  new(type: :ms, value: value, name: name, **options)
24
27
  end
25
28
 
26
29
  def initialize(type:, name:, value:, sample_rate: 1.0)
30
+ @type_description = TYPES[type] or raise ArgumentError, "invalid type: #{type}"
27
31
  @type = type
28
32
  @name = name
29
33
  @value = value
30
34
  @sample_rate = sample_rate
35
+ @full_name = Statue.namespace ? "#{Statue.namespace}.#{@name}" : @name
31
36
  end
32
37
 
33
38
  def to_s
@@ -37,15 +42,7 @@ module Statue
37
42
  end
38
43
 
39
44
  def inspect
40
- "#<StatsD::Instrument::Metric #{full_name} #{TYPES[type]}(#{value})@#{sample_rate}>"
41
- end
42
-
43
- def full_name
44
- if Statue.namespace
45
- "#{Statue.namespace}.#{@name}"
46
- else
47
- @name
48
- end
45
+ "#<StatsD::Instrument::Metric #{full_name} #{type_description}(#{value})@#{sample_rate}>"
49
46
  end
50
47
 
51
48
  end
@@ -4,7 +4,7 @@ module Statue
4
4
  # Middleware to send metrics about rack requests
5
5
  #
6
6
  # this middleware reports metrics with the following pattern:
7
- # `request.{env['REQUEST_METHOD']}.{path_name}
7
+ # `{env['REQUEST_METHOD']}.{path_name}
8
8
  #
9
9
  # where `path_name` can be configured when inserting the middleware like this:
10
10
  # `use RackStatistics, path_name: ->(env) { ... build the path name ... }`
@@ -17,17 +17,18 @@ module Statue
17
17
  #
18
18
  # Counters:
19
19
  #
20
- # * <key>.status-XXX (where XXX is the status code)
21
- # * <key>.success (on any status 2XX)
22
- # * <key>.unmodified (on status 304)
23
- # * <key>.redirect (on any status 3XX)
24
- # * <key>.failure (on any status 4xx)
25
- # * <key>.error (on any status 5xx or when an exception is raised)
20
+ # * request.<key>.status-XXX (where XXX is the status code)
21
+ # * request.<key>.success (on any status 2XX)
22
+ # * request.<key>.unmodified (on status 304)
23
+ # * request.<key>.redirect (on any status 3XX)
24
+ # * request.<key>.failure (on any status 4xx)
25
+ # * request.<key>.error (on any status 5xx)
26
+ # * request.<key>.unhandled-exception (when an exception is raised that your application didn't handle)
26
27
  #
27
28
  # Timers (all measured from the middleware perspective):
28
29
  #
29
- # * <key> (request time)
30
- # * request.queue (queue time, depends on HTTP_X_QUEUE_START header)
30
+ # * request.<key>.runtime (request time)
31
+ # * request.queue (queue time, depends on HTTP_X_REQUEST_START header)
31
32
  #
32
33
  # To get accurate timers, the middleware should be as higher as
33
34
  # possible in your rack stack
@@ -35,25 +36,25 @@ module Statue
35
36
  class RackStatistics
36
37
  DEFAULT_PATH_NAME = lambda do |env|
37
38
  # Remove duplicate and trailing '/'
38
- path = env['REQUEST_PATH'].squeeze('/').chomp('/')
39
+ path = env['PATH_INFO'].squeeze('/').chomp('/')
39
40
  if path == ''
40
41
  'root'
41
42
  else
42
- # Skip leading '/'
43
+ # Skip leading '/' and replace statsd special characters by '-'
43
44
  env['REQUEST_PATH'][1..-1].tr('/,|', '-')
44
45
  end
45
46
  end
46
47
 
47
48
  def initialize(app, path_name: DEFAULT_PATH_NAME)
48
49
  @app = app
49
- @path_name = path_name
50
+ @path_name = path_name
50
51
  end
51
52
 
52
53
  def call(env)
53
54
  report_header_metrics(env)
54
55
 
55
56
  response = nil
56
- duration = Statue.duration do
57
+ duration = Statue::Clock.duration_in_ms do
57
58
  response = @app.call(env)
58
59
  end
59
60
 
@@ -67,9 +68,9 @@ module Statue
67
68
  private
68
69
 
69
70
  def report_header_metrics(env)
70
- if start_header = env['HTTP_X_QUEUE_START']
71
+ if start_header = (env['HTTP_X_REQUEST_START'] || env['HTTP_X_QUEUE_START'])
71
72
  queue_start = start_header[/t=([\d\.]+)/, 1].to_f
72
- Statue.report_duration 'request.queue', Time.now.to_f - queue_start
73
+ Statue.report_duration 'request.queue', (Time.now.to_f - queue_start) * 1_000
73
74
  end
74
75
  end
75
76
 
@@ -77,14 +78,14 @@ module Statue
77
78
  metric_name = metric_name(env)
78
79
  status, _headers, _body = response
79
80
 
80
- Statue.report_duration metric_name, duration
81
+ Statue.report_duration "#{metric_name}.runtime", duration
81
82
 
82
83
  Statue.report_increment "#{metric_name}.status-#{status}"
83
84
  Statue.report_increment "#{metric_name}.#{status_group(status)}"
84
85
  end
85
86
 
86
87
  def report_exception(env, _exception)
87
- Statue.report_increment "#{metric_name(env)}.error"
88
+ Statue.report_increment "#{metric_name(env)}.unhandled-exception"
88
89
  end
89
90
 
90
91
  def metric_name(env)
@@ -1,19 +1,19 @@
1
1
  module Statue
2
2
  class Stopwatch
3
3
 
4
- def initialize(name:, now: clock_now, reporter: Statue)
4
+ def initialize(name:, now: Clock.now_in_ms, reporter: Statue)
5
5
  @reporter = reporter
6
6
  @name = name
7
7
  @start = @partial = now
8
8
  end
9
9
 
10
- def partial(suffix = nil, now: clock_now, **options)
10
+ def partial(suffix = nil, now: Clock.now_in_ms, **options)
11
11
  previous, @partial = @partial, now
12
12
 
13
13
  @reporter.report_duration(metric_name(suffix || "runtime.partial"), @partial - previous, **options)
14
14
  end
15
15
 
16
- def stop(suffix = nil, now: clock_now, report_partial: false, **options)
16
+ def stop(suffix = nil, now: Clock.now_in_ms, report_partial: false, **options)
17
17
  partial(report_partial.is_a?(String) ? report_partial : nil, now: now, **options) if report_partial
18
18
 
19
19
  previous, @start = @start, now
@@ -21,7 +21,7 @@ module Statue
21
21
  @reporter.report_duration(metric_name(suffix || "runtime.total"), @start - previous, **options)
22
22
  end
23
23
 
24
- def reset(now: clock_now)
24
+ def reset(now: Clock.now_in_ms)
25
25
  @start = @partial = now
26
26
  end
27
27
 
@@ -31,9 +31,5 @@ module Statue
31
31
  "#{@name}.#{suffix}"
32
32
  end
33
33
 
34
- def clock_now
35
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
36
- end
37
-
38
34
  end
39
35
  end
@@ -1,3 +1,3 @@
1
1
  module Statue
2
- VERSION = '0.2.7'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -19,5 +19,7 @@ Gem::Specification.new do |spec|
19
19
 
20
20
  spec.add_development_dependency "bundler", "~> 1.7"
21
21
  spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_development_dependency "rack", "~> 1.6"
23
+ spec.add_development_dependency "rack-test", "~> 0.6.3"
22
24
  spec.add_development_dependency "minitest"
23
25
  end
@@ -0,0 +1,140 @@
1
+ require 'test_helper'
2
+ require 'statue/rack_statistics'
3
+ require 'rack/test'
4
+
5
+ describe Statue::RackStatistics do
6
+ include Rack::Test::Methods
7
+ def app
8
+ Statue::RackStatistics.new(App.new)
9
+ end
10
+
11
+ def find_metric(name)
12
+ Statue.backend.captures.find { |m| m.name == name }
13
+ end
14
+
15
+ class App
16
+ def call(env)
17
+ raise "Uncaught error" if env['raise_error']
18
+ [
19
+ env['status'] || 200,
20
+ {},
21
+ []
22
+ ]
23
+ end
24
+ end
25
+
26
+ after do
27
+ Statue.backend.captures.clear
28
+ end
29
+
30
+ describe "request statistics on normal processing" do
31
+
32
+ it "sends queue time" do
33
+ header "X-Request-Start", "t=#{Time.now.to_f - 1}"
34
+ get "/"
35
+
36
+ queue_time = find_metric("request.queue")
37
+ assert queue_time, "Didn't report queue time"
38
+ assert_equal "measure", queue_time.type_description
39
+
40
+ # Measured time can't be less than 1000 ms because we set it to 1 sec before now.
41
+ # Allow a range of 30ms for ruby to do processing and avoid false positives
42
+ assert_in_delta 1015, queue_time.value, 15 # within range 1000-1030
43
+ end
44
+
45
+ it "sends request runtime" do
46
+ get "/"
47
+
48
+ runtime_time = find_metric("request.GET.root.runtime")
49
+ assert runtime_time, "Didn't report request runtime"
50
+ assert_equal "measure", runtime_time.type_description
51
+ # Allow a range of 30ms for ruby to do processing and avoid false positives
52
+ assert_in_delta 15, runtime_time.value, 15 # within range 1000-1030
53
+ end
54
+
55
+ it "sends counter for status-xxx" do
56
+ get "/"
57
+
58
+ runtime_time = find_metric("request.GET.root.status-200")
59
+ assert runtime_time, "Didn't report status-200 counter"
60
+ assert_equal "increment", runtime_time.type_description
61
+ assert_equal 1, runtime_time.value
62
+ end
63
+
64
+ it "sends counter for status group" do
65
+ get "/"
66
+
67
+ runtime_time = find_metric("request.GET.root.success")
68
+ assert runtime_time, "Didn't report sucess counter"
69
+ assert_equal "increment", runtime_time.type_description
70
+ assert_equal 1, runtime_time.value
71
+ end
72
+
73
+ it "sends counter for other status codes" do
74
+ get "/", {}, { 'status' => 502 }
75
+
76
+ runtime_time = find_metric("request.GET.root.status-502")
77
+ assert runtime_time, "Didn't report status-502 counter"
78
+ assert_equal "increment", runtime_time.type_description
79
+ assert_equal 1, runtime_time.value
80
+ end
81
+
82
+ it "sends counter for other status groups" do
83
+ get "/", {}, { 'status' => 502 }
84
+
85
+ runtime_time = find_metric("request.GET.root.error")
86
+ assert runtime_time, "Didn't report error counter"
87
+ assert_equal "increment", runtime_time.type_description
88
+ assert_equal 1, runtime_time.value
89
+ end
90
+
91
+ it "sends all metrics and only those" do
92
+ header "X-Request-Start", "t=#{Time.now.to_f}"
93
+ get "/"
94
+
95
+ metrics = Statue.backend.captures.map(&:name)
96
+ expected_metrics = %w[
97
+ request.queue
98
+ request.GET.root.runtime
99
+ request.GET.root.status-200
100
+ request.GET.root.success
101
+ ]
102
+ missing = expected_metrics - metrics
103
+ extra = metrics - expected_metrics
104
+
105
+ assert missing.empty?, "expect not to be missing, but these were missing: #{missing.inspect}"
106
+ assert extra.empty?, "expected no other metric to be found, but found these: #{extra.inspect}"
107
+ end
108
+
109
+ end
110
+
111
+ describe "request statistics on normal processing" do
112
+
113
+ it "sends queue time" do
114
+ header "X-Request-Start", "t=#{Time.now.to_f - 1}"
115
+ assert_raises do
116
+ get "/", {}, { 'raise_error' => true }
117
+ end
118
+
119
+ queue_time = find_metric("request.queue")
120
+ assert queue_time, "Didn't report queue time"
121
+ assert_equal "measure", queue_time.type_description
122
+
123
+ # Measured time can't be less than 1000 ms because we set it to 1 sec before now.
124
+ # Allow a range of 30ms for ruby to do processing and avoid false positives
125
+ assert_in_delta 1015, queue_time.value, 15 # within range 1000-1030
126
+ end
127
+
128
+ it "sends counter for other status codes" do
129
+ assert_raises do
130
+ get "/", {}, { 'raise_error' => true }
131
+ end
132
+
133
+ runtime_time = find_metric("request.GET.root.unhandled-exception")
134
+ assert runtime_time, "Didn't report unhandled-exception counter"
135
+ assert_equal "increment", runtime_time.type_description
136
+ assert_equal 1, runtime_time.value
137
+ end
138
+
139
+ end
140
+ end
@@ -24,8 +24,8 @@ describe Statue do
24
24
  describe ".report_duration" do
25
25
 
26
26
  it "adds a measure metric to the backend using the block call duration" do
27
- Statue.stub(:duration, 1.5) do
28
- result = Statue.report_duration("some.timer") { nil }
27
+ Statue::Clock.stub(:duration_in_ms, 1.5) do
28
+ _result = Statue.report_duration("some.timer") { nil }
29
29
 
30
30
  assert_equal 1, Statue.backend.captures.size
31
31
  assert_equal "some.timer:1.5|ms", Statue.backend.captures.first.to_s
@@ -39,7 +39,7 @@ describe Statue do
39
39
  end
40
40
 
41
41
  it "adds a measure metric to the backend using the fixed value" do
42
- result = Statue.report_duration("some.timer", 2.5)
42
+ _result = Statue.report_duration("some.timer", 2.5)
43
43
 
44
44
  assert_equal 1, Statue.backend.captures.size
45
45
  assert_equal "some.timer:2.5|ms", Statue.backend.captures.first.to_s
@@ -59,7 +59,7 @@ describe Statue do
59
59
  describe ".report_gauge" do
60
60
 
61
61
  it "adds a gauge metric to the backend using the fixed value" do
62
- result = Statue.report_gauge("some.gauge", 23)
62
+ _result = Statue.report_gauge("some.gauge", 23)
63
63
 
64
64
  assert_equal 1, Statue.backend.captures.size
65
65
  assert_equal "some.gauge:23|g", Statue.backend.captures.first.to_s
@@ -8,7 +8,7 @@ describe Statue::Stopwatch do
8
8
  describe "#partial" do
9
9
  it "reports the duration between start and the partial call" do
10
10
  stopwatch = Statue::Stopwatch.new(name: "my_watch", now: 0)
11
- stopwatch.partial(now: 42)
11
+ stopwatch.partial(now: 42) # 42 milliseconds after
12
12
 
13
13
  assert_equal 1, Statue.backend.captures.size
14
14
  assert_equal "my_watch.runtime.partial:42|ms", Statue.backend.captures.first.to_s
@@ -47,7 +47,7 @@ describe Statue::Stopwatch do
47
47
  stopwatch.stop(now: 21)
48
48
 
49
49
  assert_equal 21, Statue.backend.captures.size
50
- *partials, total = Statue.backend.captures
50
+ *_partials, total = Statue.backend.captures
51
51
  assert_equal "my_watch.runtime.total:21|ms", total.to_s
52
52
  end
53
53
 
@@ -70,7 +70,7 @@ describe Statue::Stopwatch do
70
70
  stopwatch.stop(now: 21, report_partial: "runtime.final_lap")
71
71
 
72
72
  assert_equal 22, Statue.backend.captures.size
73
- *partials, special_partial, total = Statue.backend.captures
73
+ *_partials, special_partial, _total = Statue.backend.captures
74
74
  assert_equal "my_watch.runtime.final_lap:1|ms", special_partial.to_s
75
75
  end
76
76
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: statue
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.7
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Juan Barreneche
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-09 00:00:00.000000000 Z
11
+ date: 2017-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,6 +38,34 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rack-test
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.6.3
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.6.3
41
69
  - !ruby/object:Gem::Dependency
42
70
  name: minitest
43
71
  requirement: !ruby/object:Gem::Requirement
@@ -72,11 +100,13 @@ files:
72
100
  - lib/statue/backends/logger.rb
73
101
  - lib/statue/backends/null.rb
74
102
  - lib/statue/backends/udp.rb
103
+ - lib/statue/clock.rb
75
104
  - lib/statue/metric.rb
76
105
  - lib/statue/rack_statistics.rb
77
106
  - lib/statue/stopwatch.rb
78
107
  - lib/statue/version.rb
79
108
  - statue.gemspec
109
+ - test/rack_statistics_test.rb
80
110
  - test/statue_test.rb
81
111
  - test/stopwatch_test.rb
82
112
  - test/test_helper.rb
@@ -100,11 +130,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
100
130
  version: '0'
101
131
  requirements: []
102
132
  rubyforge_project:
103
- rubygems_version: 2.4.8
133
+ rubygems_version: 2.5.2
104
134
  signing_key:
105
135
  specification_version: 4
106
136
  summary: Easily track application metrics into Statsie
107
137
  test_files:
138
+ - test/rack_statistics_test.rb
108
139
  - test/statue_test.rb
109
140
  - test/stopwatch_test.rb
110
141
  - test/test_helper.rb
142
+ has_rdoc: