atlas_rb 1.27.1 → 1.27.2

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: 2c1b0723d54145c8be9868a1849502cf80adbc1a30b2f7b1ee39aadbfa1a2f3b
4
- data.tar.gz: 7ab2bb5a662b02dd48275e645f9c6b3c1226d6bc995231766bd5a4252f17b25a
3
+ metadata.gz: 3bf07fbfa75e57be6651cc352fc2216f8c5e4029a68393018fa14b8e6a34e6aa
4
+ data.tar.gz: 63d66617e658bc0693c7a07afdf0b38cac2067f464b3ba1e0466b3a8d7d1860e
5
5
  SHA512:
6
- metadata.gz: 64f43d675ad66dac85657c0460702f53f95c99760e7a44ce6f0dec019e375f41b197c214ca3dc4d89a94a1e0b829fa9d377fd4dd9432fe68cdc53d9aa490e38e
7
- data.tar.gz: dfc0ade69d92b8b100698091f6ec22d90eb428769bb61aebdc79a9e842840036bc45875bc330531be335d5a11459c823ff13a1ccd78e1b143b11009f083ca54b
6
+ metadata.gz: 2071b3face694f62a03db93362462bf77381cfdcee77c138ff5a2baabe47f000d94ede673ca1d28f160d0348506a844346f920b71457669bae326769aa9286e3
7
+ data.tar.gz: 823ca671c648afad8c4a82111d658a691c6bf757e02cb942594c0a1c6afec2a1b6fda628c954ca909497c7168a6384f845fcf4ed229a1f470d5fabb1bac212c8
data/.version CHANGED
@@ -1 +1 @@
1
- 1.27.1
1
+ 1.27.2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.27.2
4
+
5
+ Requires an Atlas that serves `GET /metrics/latency`.
6
+
7
+ ### Added — `AtlasRb::Metrics.latency`
8
+
9
+ Reads Atlas's own request latency for a window of up to 120 minutes: calls,
10
+ 5xx count, mean and approximate p50/p95/p99, and the slowest actions. Sent
11
+ with the acting NUID's signed assertion, which must name an admin. A
12
+ 5-second deadline, no retry and a fresh socket per call. Raises
13
+ `BackendUnavailableError` when Atlas's Redis is down and `ResourceError` on
14
+ any other non-200.
15
+
3
16
  ## 1.27.1
4
17
 
5
18
  Requires an Atlas that serves `GET /health` and the `backend_unavailable`
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- atlas_rb (1.27.1)
4
+ atlas_rb (1.27.2)
5
5
  faraday (~> 2.7)
6
6
  faraday-follow_redirects (~> 0.3.0)
7
7
  faraday-multipart (~> 1)
data/README.md CHANGED
@@ -857,6 +857,30 @@ It is not a `ResourceError`, which hosts treat as a refusal of the request.
857
857
  A streaming download (`Blob.content`, `Blob.version_content`) cannot raise
858
858
  this: its body has already gone to your block. Check the returned `status`.
859
859
 
860
+ ### Atlas's own latency
861
+
862
+ `AtlasRb::Metrics.latency` reports how long Atlas took to serve its own
863
+ requests over a recent window. Atlas gates it to admins and `:system`, so
864
+ `nuid` must name an admin:
865
+
866
+ ```ruby
867
+ latency = AtlasRb::Metrics.latency(minutes: 60, nuid: current_user.nuid)
868
+ latency["calls"] # => 18402
869
+ latency["server_errors"] # => 3
870
+ latency["p95_ms"] # => 98
871
+ latency["slowest_actions"].first # => { "action" => "works#mods", "calls" => 912, "p95_ms" => 188 }
872
+ ```
873
+
874
+ Atlas times each request from when Rails dispatches it until it returns, so
875
+ the figures leave out the network and your own waiting. If you also time your
876
+ Atlas calls, the difference is time spent on your side. The percentiles are
877
+ interpolated from histogram buckets, so they are approximate.
878
+
879
+ Like `Health.read`, it has a 5-second deadline and no retry. It raises
880
+ `BackendUnavailableError` (with `dependency == "cache"`) when Atlas's Redis is
881
+ down, `ResourceError` on any other non-200 such as a `403`, and `Faraday`
882
+ errors when nothing answers.
883
+
860
884
  ## End-to-end example
861
885
 
862
886
  JSON responses come back as `AtlasRb::Mash` (a `Hashie::Mash` subclass), so
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtlasRb
4
+ # Atlas's own operational figures. Admin- or `:system`-gated in Atlas.
5
+ class Metrics
6
+ extend AtlasRb::FaradayHelper
7
+
8
+ # Seconds to wait for a socket to open.
9
+ OPEN_TIMEOUT = 2
10
+
11
+ # Seconds to wait for the report. It reads at most 120 small Redis hashes,
12
+ # so a slower answer means Atlas is struggling, and an admin page should
13
+ # not wait on it.
14
+ READ_TIMEOUT = 5
15
+
16
+ # How long Atlas took to serve its own requests (`GET /metrics/latency`).
17
+ #
18
+ # Measured inside Atlas, from when Rails dispatches the action until it
19
+ # returns, so it leaves out the network and the caller's own waiting. A
20
+ # caller that also times its Atlas calls can subtract to see how much of
21
+ # its wait was its own. The percentiles are interpolated from histogram
22
+ # buckets, so they are approximate.
23
+ #
24
+ # No retry, and a fresh socket per call: like {AtlasRb::Health.read}, it is
25
+ # polled rarely and should fail fast rather than hide a struggling Atlas.
26
+ #
27
+ # @param minutes [Integer] the window, 1 to 120. Atlas clamps it.
28
+ # @param nuid [String, nil] the acting NUID, signed into the assertion
29
+ # `sub`. It must name an admin; falls through to
30
+ # {AtlasRb.config#default_nuid}.
31
+ # @param on_behalf_of [String, nil] optional NUID carried as a signed `obo`
32
+ # claim.
33
+ # @return [AtlasRb::Mash] `window_minutes`, `calls`, `server_errors`,
34
+ # `mean_ms`, `p50_ms`, `p95_ms`, `p99_ms` (Integer, or nil with no calls),
35
+ # and `slowest_actions`, an Array of `action` / `calls` / `p95_ms`.
36
+ # @raise [AtlasRb::BackendUnavailableError] when Atlas answers that Redis,
37
+ # which holds the figures, is down (`dependency == "cache"`).
38
+ # @raise [AtlasRb::ResourceError] on any other non-200, notably a `403`
39
+ # for a caller who is not an admin.
40
+ # @raise [Faraday::ConnectionFailed, Faraday::TimeoutError] when Atlas does
41
+ # not answer at all.
42
+ #
43
+ # @example
44
+ # latency = AtlasRb::Metrics.latency(minutes: 15, nuid: current_user.nuid)
45
+ # latency.p95_ms # => 98
46
+ def self.latency(minutes: 60, nuid: nil, on_behalf_of: nil)
47
+ resp = metrics_connection(nuid, on_behalf_of).get("/metrics/latency", minutes: minutes)
48
+ return AtlasRb::Mash.new(JSON.parse(resp.body)) if resp.status == 200
49
+
50
+ raise AtlasRb::ResourceError.new("#{request_target(resp)} → #{resp.status}: #{resp.body}", response: resp)
51
+ end
52
+
53
+ class << self
54
+ private
55
+
56
+ def metrics_connection(nuid, on_behalf_of)
57
+ headers = auth_headers(nuid, on_behalf_of).merge("Content-Type" => "application/json")
58
+ Faraday.new(url: ENV.fetch("ATLAS_URL", nil), headers: headers) do |f|
59
+ f.options.open_timeout = OPEN_TIMEOUT
60
+ f.options.timeout = READ_TIMEOUT
61
+ instrument(f)
62
+ f.use AtlasRb::Middleware::RaiseOnBackendUnavailable
63
+ f.adapter :net_http
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
data/lib/atlas_rb.rb CHANGED
@@ -48,6 +48,7 @@ require_relative "atlas_rb/system/work"
48
48
  require_relative "atlas_rb/audit_event"
49
49
  require_relative "atlas_rb/maintenance"
50
50
  require_relative "atlas_rb/health"
51
+ require_relative "atlas_rb/metrics"
51
52
 
52
53
  # Ruby client for the Atlas API — Northeastern University's institutional
53
54
  # digital repository.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: atlas_rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.27.1
4
+ version: 1.27.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Cliff
@@ -173,6 +173,7 @@ files:
173
173
  - lib/atlas_rb/health.rb
174
174
  - lib/atlas_rb/maintenance.rb
175
175
  - lib/atlas_rb/mash.rb
176
+ - lib/atlas_rb/metrics.rb
176
177
  - lib/atlas_rb/middleware/raise_on_backend_unavailable.rb
177
178
  - lib/atlas_rb/middleware/raise_on_read_error.rb
178
179
  - lib/atlas_rb/middleware/raise_on_read_only_mode.rb