where_is_waldo 0.1.9 → 0.1.10

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: 96f9fb4253fa91bfaa947711aff7e9de6cf6511029c34fe4d24bcaa92be9958d
4
- data.tar.gz: f441dacf17ca081d1974d08e3eb3460dce8ca5423772689c72e730c58746b8e9
3
+ metadata.gz: ce4ac18944bec996440772eed972216ff64975a1be13b4f7f3d0230e5cc0807f
4
+ data.tar.gz: 80e1f0ba820d24dc5f5d74f7ef4cc31e00ba3f183145d24de17dc24e5bd4b78b
5
5
  SHA512:
6
- metadata.gz: df11e85496b2ae55c928695e1dad936a82b0d0b3abbcf8df7160932cf221f0ab4ab7c1cb8830cc5b710bbbab029cb82dfdb6d719292b0dc1863767151baf406c
7
- data.tar.gz: 3412d9fd5475df5fe17a29c8dbd910f3c920db8260971492aa479ab23797d98d0c726549328aa27bdbef675646ae750f948c30ac810c96eaadddf6d8e1badcdf
6
+ metadata.gz: 877c8e0adf85cb7c8e75f992939c372690d1a928ab3ef4b6a4d64479ba9801ccd74820235db112d4efcab473a0589097165b62edf07824247a1a6be82d6ba32a
7
+ data.tar.gz: d91ec5e623c3a8e8b7b149d5d9a80d6110ab5f4a8c81cc71bdaa73608afc593efe534bc2dc53c71df067837fb56c1cac9479df52a421f7364d1b545cc7657018
data/CHANGELOG.md CHANGED
@@ -3,6 +3,38 @@
3
3
  Notable changes to where_is_waldo. Format loosely follows
4
4
  [Keep a Changelog](https://keepachangelog.com/).
5
5
 
6
+ ## 0.1.10
7
+
8
+ ### Added
9
+
10
+ - **Presence heartbeats are kept out of APM as their own transactions.**
11
+ `WhereIsWaldo::PresenceChannel#heartbeat` now runs inside
12
+ `WhereIsWaldo::Apm.ignoring_transaction`, so when a host app runs New Relic
13
+ the per-tab-per-interval heartbeat — the highest-volume, least-interesting
14
+ cable action — no longer surfaces as an APM transaction. Left unhandled it
15
+ inflates throughput and flatters average response time (heartbeats are
16
+ uniformly fast) while telling operators nothing the presence store can't
17
+ already answer.
18
+
19
+ - Guarded, no new dependency: `Apm` no-ops unless a supported agent
20
+ (New Relic today) is loaded, so consumers without APM pay nothing.
21
+ - Failures stay visible: an ignored New Relic transaction skips `commit!`,
22
+ where exceptions are normally harvested, so the wrapper hands any heartbeat
23
+ exception straight to the error collector and re-raises. Ignoring the
24
+ routine transaction never hides a heartbeat failure.
25
+ - Opt out with `config.ignore_heartbeat_apm = false` to report heartbeats
26
+ like any other action.
27
+ - Scope: New Relic only. The underlying exposure is agent-general — any APM
28
+ that opens a transaction per `perform_action` sees the same heartbeat
29
+ volume — but New Relic is the only agent seen in production so far.
30
+ Detection is isolated in `Apm.agent` so another vendor can be added there
31
+ without touching callers.
32
+
33
+ Note: the companion New Relic issue — the agent pinning the WebSocket upgrade
34
+ transaction onto pooled ActionCable worker threads — is a general
35
+ newrelic_rpm × ActionCable defect and stays a host-app shim; it is not
36
+ something the gem patches.
37
+
6
38
  ## 0.1.9
7
39
 
8
40
  Security/maintenance release — dependency bumps only, no library code changes.
data/README.md CHANGED
@@ -113,6 +113,14 @@ WhereIsWaldo.configure do |config|
113
113
  config.timeout = 90 # seconds until offline
114
114
  config.heartbeat_interval = 30
115
115
 
116
+ # Observability: when true (default) and the host app runs a supported APM
117
+ # agent (New Relic today), presence heartbeats are kept out of APM as their
118
+ # own transactions. Heartbeats are the highest-volume, least-interesting cable
119
+ # action, so reporting each one inflates throughput and flatters average
120
+ # response time without adding signal. Failures are still reported. Set false
121
+ # to report heartbeats like any other action.
122
+ config.ignore_heartbeat_apm = true
123
+
116
124
  # Optional: custom subject data in presence hash. NOTE: with the roster
117
125
  # enabled, these fields are broadcast to every member of the org — see
118
126
  # Security.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.9
1
+ 0.1.10
@@ -32,7 +32,20 @@ module WhereIsWaldo
32
32
  publish_roster_change
33
33
  end
34
34
 
35
+ # Kept out of APM as its own transaction by default — heartbeats are the
36
+ # highest-volume, least-interesting cable action, so reporting each one
37
+ # distorts throughput and average response time without adding signal. The
38
+ # wrapper no-ops unless the host runs a supported agent; see
39
+ # WhereIsWaldo::Apm and config.ignore_heartbeat_apm. The real work lives in
40
+ # perform_heartbeat, which is private so a client can't invoke it directly
41
+ # as a channel action (only public methods are processable actions).
35
42
  def heartbeat(data)
43
+ WhereIsWaldo::Apm.ignoring_transaction { perform_heartbeat(data) }
44
+ end
45
+
46
+ private
47
+
48
+ def perform_heartbeat(data)
36
49
  return if presence_suppressed?
37
50
 
38
51
  data = data.with_indifferent_access
@@ -58,8 +71,6 @@ module WhereIsWaldo
58
71
  publish_roster_change
59
72
  end
60
73
 
61
- private
62
-
63
74
  # Memoized per-subscription. Host apps configure suppress_presence_proc
64
75
  # to gate WHICH subscriptions register presence — e.g. a support-user
65
76
  # impersonation tab is a legitimate WS consumer but shouldn't count as
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhereIsWaldo
4
+ # APM (New Relic) integration seam.
5
+ #
6
+ # The gem takes NO hard dependency on newrelic_rpm. Every method here no-ops
7
+ # unless a supported agent is loaded in the host app, so a consumer without
8
+ # New Relic pays nothing and nothing breaks.
9
+ #
10
+ # Why this exists: ActionCable dispatches each client action through New
11
+ # Relic's ActionCableSubscriber, which starts one APM transaction per
12
+ # `perform_action`. Presence heartbeats are the highest-volume, least
13
+ # interesting action Waldo has — one per open tab per heartbeat interval,
14
+ # uniformly fast — so left alone they dominate transaction throughput and
15
+ # flatten the host app's average response time while telling operators nothing
16
+ # the presence store can't already answer. `ignoring_transaction` keeps them
17
+ # out of APM's headline numbers without hiding failures.
18
+ #
19
+ # (The companion half of this problem — the agent pinning the WebSocket
20
+ # upgrade transaction onto pooled ActionCable worker threads — is a general
21
+ # newrelic_rpm x ActionCable defect, not Waldo's to fix here. Host apps carry
22
+ # that shim until the agent resets pooled-thread state itself.)
23
+ #
24
+ # Scope: this handles New Relic ONLY. The underlying exposure is agent-general
25
+ # — any APM/tracer that opens a transaction per `perform_action` (Datadog,
26
+ # Scout, AppSignal, Skylight, Sentry, OpenTelemetry) sees the same heartbeat
27
+ # flood, and several share the same pooled-thread context-leak. Waldo doesn't
28
+ # cause that (it spawns no threads and patches nothing global); its heartbeat
29
+ # volume amplifies whatever the host's agent already does. Detection is
30
+ # isolated in `agent` so another vendor can be added there without touching
31
+ # callers.
32
+ module Apm
33
+ module_function
34
+
35
+ # The New Relic agent module, or nil when newrelic_rpm isn't loaded (or is
36
+ # too old to expose the API we use). Isolated behind one method so specs can
37
+ # drive both the present and absent paths without defining a global
38
+ # ::NewRelic constant.
39
+ def agent
40
+ return nil unless defined?(::NewRelic::Agent)
41
+ return nil unless ::NewRelic::Agent.respond_to?(:ignore_transaction)
42
+
43
+ ::NewRelic::Agent
44
+ end
45
+
46
+ # Run a unit of work without letting it surface as its own APM transaction,
47
+ # and return the block's value.
48
+ #
49
+ # A plain `yield` when there's no agent, or when the host has opted out via
50
+ # `config.ignore_heartbeat_apm = false` — in either case the transaction is
51
+ # reported normally.
52
+ #
53
+ # New Relic's `Transaction#ignore!` makes `#finish` skip `commit!`, and
54
+ # `commit!` is where transaction-attached exceptions are harvested
55
+ # (`record_exceptions`). So ignoring on its own would ALSO swallow a
56
+ # heartbeat failure. We hand any exception straight to the error collector —
57
+ # which reports immediately rather than at commit — and re-raise, so the
58
+ # caller's own error handling is unchanged.
59
+ def ignoring_transaction
60
+ nr = agent
61
+ return yield unless nr && WhereIsWaldo.config.ignore_heartbeat_apm
62
+
63
+ nr.ignore_transaction
64
+
65
+ begin
66
+ yield
67
+ rescue StandardError => e
68
+ nr.instance&.error_collector&.notice_error(e)
69
+ raise
70
+ end
71
+ end
72
+ end
73
+ end
@@ -27,6 +27,15 @@ module WhereIsWaldo
27
27
  # :heartbeat_interval - expected heartbeat frequency (default: 30)
28
28
  attr_accessor :timeout, :heartbeat_interval
29
29
 
30
+ # Observability
31
+ # :ignore_heartbeat_apm - when true (default) and a supported APM agent
32
+ # (New Relic) is loaded in the host app, presence heartbeats are kept out
33
+ # of APM as their own transactions. Heartbeats are the highest-volume,
34
+ # least-interesting cable action, so reporting each one distorts
35
+ # throughput and average response time without adding signal. Set false
36
+ # to report them like any other action. See WhereIsWaldo::Apm.
37
+ attr_accessor :ignore_heartbeat_apm
38
+
30
39
  # ActionCable settings
31
40
  # :channel_name - defaults to 'WhereIsWaldo::PresenceChannel'
32
41
  # :authenticate_proc - proc to authenticate connection, receives request
@@ -120,6 +129,9 @@ module WhereIsWaldo
120
129
  @timeout = 90
121
130
  @heartbeat_interval = 30
122
131
 
132
+ # Observability defaults
133
+ @ignore_heartbeat_apm = true
134
+
123
135
  # ActionCable defaults
124
136
  @channel_name = "WhereIsWaldo::PresenceChannel"
125
137
  @authenticate_proc = nil
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "where_is_waldo/version"
4
4
  require "where_is_waldo/configuration"
5
+ require "where_is_waldo/apm"
5
6
  require "where_is_waldo/engine" if defined?(Rails)
6
7
 
7
8
  module WhereIsWaldo
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: where_is_waldo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Gibson
@@ -215,6 +215,7 @@ files:
215
215
  - lib/generators/where_is_waldo/install/templates/migration.rb.tt
216
216
  - lib/generators/where_is_waldo/install/templates/presence_channel.rb.tt
217
217
  - lib/where_is_waldo.rb
218
+ - lib/where_is_waldo/apm.rb
218
219
  - lib/where_is_waldo/configuration.rb
219
220
  - lib/where_is_waldo/engine.rb
220
221
  - lib/where_is_waldo/version.rb