speedshop-cloudwatch 0.2.0 → 0.2.1

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
  SHA256:
3
- metadata.gz: 19039fb6a3ef58a84cae232f0c6a501451c2ee7d52a49762aca926f9e22f733c
4
- data.tar.gz: f8084e387ec56912545f0c4b04edf6f26210f57fb015189578311ce5dfc421d2
3
+ metadata.gz: a4d2d47eedd3f9ad965073f7569d707bee70f2b4bb35f7b7f97f4197d9016b9c
4
+ data.tar.gz: d2bc25a79ba9893fa45a8a8843949ef42a7e70b5ab054ced8031446148bed927
5
5
  SHA512:
6
- metadata.gz: 2b62fe5780fd5646a230f51c8b5d4cd4dfb2ef47915cf9ddd983dbe0af00c97d5b52106c4e74729b771c3466a4d872ef2965653c5f2e3606a405158a71f65ffd
7
- data.tar.gz: e49872a3eb6a96dc6a51f14defcf77b6139d3a2bb7479e0c33600fbfb956f291337ab32f549e7beb40befa3094f3a51ee6859fd10008b42b7784c9f24c37ebc7
6
+ metadata.gz: ae73508f29611fba12c522b3e84d82331026638892d2b540ede54a951a9e5314254c59a1620f5d423c0f592a7017f526233b938873107348231faf0a8db1c4b6
7
+ data.tar.gz: eb65e33a190b8d4a33686dd2c63f59ba504b0f585a2cb3d730f09274e89809acfa9ba0034caaef272b4f02fe3a0029c1373f03f684c9d3db2e03e4f1e9b8eab2
data/CHANGELOG.md CHANGED
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.1] - 2026-06-09
9
+
10
+ ### Fixed
11
+ - Improved Rack request queue time parsing for common `X-Request-Start` and `X-Queue-Start` formats, including `t=` prefixes, seconds, milliseconds, microseconds, and comma-separated header values.
12
+ - Subtracted Puma request body wait time from Rack queue time when `env["puma.request_body_wait"]` is available, so slow uploads are not counted as upstream queueing.
13
+
8
14
  ## [0.2.0] - 2026-04-13
9
15
 
10
16
  ### Added
@@ -21,5 +27,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
21
27
  ### Added
22
28
  - Initial public release.
23
29
 
30
+ [0.2.1]: https://github.com/speedshop/speedshop-cloudwatch/compare/v0.2.0...v0.2.1
24
31
  [0.2.0]: https://github.com/speedshop/speedshop-cloudwatch/compare/v0.1.0...v0.2.0
25
32
  [0.1.0]: https://github.com/speedshop/speedshop-cloudwatch/releases/tag/v0.1.0
data/README.md CHANGED
@@ -144,7 +144,9 @@ If you're using Rails, we'll automatically insert the correct middleware into th
144
144
 
145
145
  If you're using some other Rack-based framework, insert the `Speedshop::Cloudwatch::Rack` high up (i.e. first) in the stack.
146
146
 
147
- You will need a reverse proxy, such as nginx, adding an `X-Request-Start` or `X-Queue-Start` header (containing the time since the Unix epoch in milliseconds) to incoming requests. See [New Relic's instructions](https://docs.newrelic.com/docs/apm/applications-menu/features/configure-request-queue-reporting/) for more about how to do this.
147
+ You will need a reverse proxy, such as nginx, adding an `X-Request-Start` or `X-Queue-Start` header to incoming requests. The header may use common queue-time formats such as epoch milliseconds (`1512379167574`), seconds with decimals (`t=1512379167.574`), or microseconds (`t=1570633834463123`). See [New Relic's instructions](https://docs.newrelic.com/docs/apm/applications-menu/features/configure-request-queue-reporting/) for more about how to do this.
148
+
149
+ When Puma exposes `env["puma.request_body_wait"]`, we subtract it from queue time so slow request-body uploads are not counted as upstream queueing.
148
150
 
149
151
  We report the following metrics:
150
152
 
@@ -162,7 +162,7 @@ module Speedshop
162
162
  description: "Time a request spent waiting in the reverse proxy before " \
163
163
  "reaching the application. High values indicate requests " \
164
164
  "backing up before reaching your application server.",
165
- source: "(Time.now.to_f * 1000) - HTTP_X_REQUEST_START"
165
+ source: "parsed X-Request-Start/X-Queue-Start timestamp minus Puma request body wait"
166
166
  )
167
167
  ],
168
168
 
@@ -4,17 +4,57 @@ module Speedshop
4
4
  module Cloudwatch
5
5
  module Observations
6
6
  module Rack
7
+ class HeaderTimestampParser
8
+ MIN_EPOCH = Time.utc(2000, 1, 1).to_f
9
+ FUTURE_TOLERANCE = 30.0
10
+ DIVISORS = [1_000_000.0, 1_000.0, 1.0].freeze
11
+ NUMBER_RE = /[+-]?(?:\d+(?:\.\d+)?|\.\d+)/
12
+ T_EQUALS_RE = /t\s*=\s*(#{NUMBER_RE.source})/i
13
+
14
+ def parse(value, now:)
15
+ header_value = value.to_s.split(",", 2).first.to_s.strip
16
+ return if header_value.empty?
17
+
18
+ token = header_value[T_EQUALS_RE, 1] || header_value[NUMBER_RE, 0]
19
+ normalize(Float(token), now) if token
20
+ rescue ArgumentError, TypeError
21
+ end
22
+
23
+ private
24
+
25
+ def normalize(raw, now)
26
+ max = now + FUTURE_TOLERANCE
27
+ divisor = DIVISORS.find { |d| (raw / d).between?(MIN_EPOCH, max) }
28
+ raw / divisor if divisor
29
+ end
30
+ end
31
+
7
32
  module_function
8
33
 
9
34
  def request_queue_time(env, now_ms: current_time_ms)
10
- header = env["HTTP_X_REQUEST_START"] || env["HTTP_X_QUEUE_START"]
11
- return unless header
35
+ now = now_ms / 1_000.0
36
+ request_start = header_timestamp_parser.parse(env["HTTP_X_REQUEST_START"], now: now) ||
37
+ header_timestamp_parser.parse(env["HTTP_X_QUEUE_START"], now: now)
38
+ return unless request_start
12
39
 
13
- now_ms - header.gsub("t=", "").to_f
40
+ queue_time_ms = (now - request_start) * 1_000.0
41
+ return if queue_time_ms.negative?
42
+
43
+ [queue_time_ms - (request_body_wait_ms(env) || 0), 0.0].max
14
44
  end
15
45
 
16
46
  def current_time_ms
17
- Time.now.to_f * 1000
47
+ Time.now.to_f * 1_000.0
48
+ end
49
+
50
+ def header_timestamp_parser
51
+ @header_timestamp_parser ||= HeaderTimestampParser.new
52
+ end
53
+
54
+ def request_body_wait_ms(env)
55
+ wait_ms = Float(env["puma.request_body_wait"])
56
+ wait_ms if wait_ms.finite? && !wait_ms.negative?
57
+ rescue ArgumentError, TypeError
18
58
  end
19
59
  end
20
60
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Speedshop
4
4
  module Cloudwatch
5
- VERSION = "0.2.0"
5
+ VERSION = "0.2.1"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: speedshop-cloudwatch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nate Berkopec
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-04-13 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: aws-sdk-cloudwatch
@@ -94,7 +93,6 @@ metadata:
94
93
  allowed_push_host: https://rubygems.org
95
94
  homepage_uri: https://github.com/nateberkopec/speedshop-cloudwatch
96
95
  source_code_uri: https://github.com/nateberkopec/speedshop-cloudwatch
97
- post_install_message:
98
96
  rdoc_options: []
99
97
  require_paths:
100
98
  - lib
@@ -109,8 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
107
  - !ruby/object:Gem::Version
110
108
  version: '0'
111
109
  requirements: []
112
- rubygems_version: 3.1.6
113
- signing_key:
110
+ rubygems_version: 3.6.9
114
111
  specification_version: 4
115
112
  summary: Ruby application integration with AWS CloudWatch for Puma, Rack, Sidekiq,
116
113
  and ActiveJob