dead_bro 0.2.20 → 0.2.21
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 +4 -4
- data/CHANGELOG.md +39 -0
- data/lib/dead_bro/cache_subscriber.rb +5 -2
- data/lib/dead_bro/elasticsearch_subscriber.rb +8 -5
- data/lib/dead_bro/http_instrumentation.rb +7 -1
- data/lib/dead_bro/redis_subscriber.rb +17 -2
- data/lib/dead_bro/sql_subscriber.rb +5 -0
- data/lib/dead_bro/version.rb +1 -1
- data/lib/dead_bro/view_rendering_subscriber.rb +11 -4
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 96a0bc8cf707ea8af5d3bcd0be76fa02e28d09031c8a7897a311754cda8f7440
|
|
4
|
+
data.tar.gz: 4a1407455415636db2858a28f0f77a8bb530d45fa916a359f85ae22f5f4294bb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 493d8d79e5eedd0d0443ddc3922ace02ed2f606220e7cff07e07484c9a628a85b54a5160551cb816630e06db34886e22ebae5c6c95e3697784b67a8c71b5b1ad
|
|
7
|
+
data.tar.gz: 715064a097fadfb5ef97b96be9086f760f91efa004d3d339978f87ab87b6dce935ba5111c60c92a9f1849ec5a17a80e83efbd9f9cab031a963392f03a1055b2e
|
data/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,39 @@
|
|
|
3
3
|
### Added
|
|
4
4
|
- Monitor thread now sends a synchronous heartbeat on startup before the first collection tick. This ensures remote settings — including `monitor_enabled` — are applied from the very first reporting cycle, so Sidekiq workers and other non-web processes that have not yet sent any metrics still receive the correct configuration immediately on boot rather than waiting up to 60 seconds for the first scheduled tick.
|
|
5
5
|
|
|
6
|
+
## [0.2.21] - 2026-06-02
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- **Per-span timing for the Request Trace view.** Every instrumented event now includes a `start_offset_ms` field — the wall-clock milliseconds from rack entry to when that event started. This powers the waterfall visualisation in the DeadBro dashboard without any configuration changes.
|
|
10
|
+
- `SqlSubscriber`: `start_offset_ms` is computed from `TRACKING_START_TIME_KEY` using the `started` timestamp provided by `ActiveSupport::Notifications`. Stored on both the raw per-query hash and on the first-occurrence aggregate entry (so the bar is positioned at the actual time the query first ran, not a fabricated cumulative offset).
|
|
11
|
+
- `ViewRenderingSubscriber`: same pattern applied to template, partial, and collection render events. `start_offset_ms` is captured for the first render of each unique identifier and stored on the aggregate.
|
|
12
|
+
- `CacheSubscriber`: `start_offset_ms` derived from the `started` `ActiveSupport::Notifications` timestamp, added to every cache event hash and passed through `build_event`.
|
|
13
|
+
- `RedisSubscriber`: `wall_start = Time.now` is captured at the entry point of each instrumented block (`call`, `call_pipeline`, `call_multi`). The monotonic clock is still used for duration accuracy; `wall_start` is used only for the offset relative to `TRACKING_START_TIME_KEY`. Also applied to the `ActiveSupport::Notifications` fallback path in `install_notifications_subscription!`.
|
|
14
|
+
- `ElasticsearchSubscriber`: `start_offset_ms` added to `build_event`; the `record` method (called from `HttpInstrumentation` for Net::HTTP-based ES requests) now accepts a `start_offset_ms:` keyword argument. The `ActiveSupport::Notifications` subscription path (`request.elasticsearch` / `request.elastic_transport`) computes it from `started`.
|
|
15
|
+
- `HttpInstrumentation`: `wall_start = Time.now` captured alongside the existing monotonic `start_time`. `start_offset_ms` is included in the HTTP outgoing payload and forwarded to `ElasticsearchSubscriber.record` for requests routed to an ES host.
|
|
16
|
+
|
|
17
|
+
## [0.2.20] - 2026-05-29
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- Monitor thread sends a synchronous heartbeat immediately on startup (before the first scheduled collection tick) so that remote settings — including `monitor_enabled` — are applied from the very first reporting cycle. Sidekiq workers and other non-web processes now receive the correct configuration on boot rather than waiting up to 60 seconds for the first tick.
|
|
21
|
+
- `gem_version` field added to every heartbeat payload so the dashboard can display and compare the running gem version per application.
|
|
22
|
+
- `process_kind` included in all system monitor payloads, linking server metrics to the correct process type.
|
|
23
|
+
- `post_heartbeat` now accepts a `sync: true` keyword for situations that require a blocking network call before proceeding (used by the monitor startup path).
|
|
24
|
+
|
|
25
|
+
## [0.2.19] - 2026-05-28
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- **Error fingerprinting**: every unhandled exception payload now includes a stable `fingerprint` string derived from the exception class, a normalised version of the message (numeric IDs and UUIDs stripped), and the top application stack frame. Identical errors that differ only in record IDs or UUIDs produce the same fingerprint, enabling reliable grouping and deduplication on the server.
|
|
29
|
+
- `DeadBro.process_kind` auto-detects the type of the current Ruby process by inspecting `$PROGRAM_NAME` and `/proc/self/cmdline`: returns `"web"` (Puma/Passenger/Unicorn/Falcon), `"worker"` (Sidekiq/GoodJob/SolidQueue/DelayedJob), `"console"`, `"task"`, or `"app"` as a fallback. The value is memoised after the first call.
|
|
30
|
+
- `process_kind` included in error event payloads so the backend knows whether an exception came from a web request or a background worker.
|
|
31
|
+
|
|
32
|
+
## [0.2.18] - 2026-05-27
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- **N+1 detection in the gem**: SQL queries are normalised (bind parameters, numeric literals, and `IN (...)` lists replaced with `?`) and counted per request. When the same normalised query fires 5 or more times, it is flagged as `n_plus_one: true` on its aggregate entry. A backtrace is captured exactly at the N+1 threshold rather than on every execution, keeping overhead low while still pointing to the callsite.
|
|
36
|
+
- **SQL aggregation**: instead of shipping a raw array of every query, the gem now groups queries by normalised SQL and sends one aggregate entry per unique pattern with `count`, `total_duration_ms`, `min_duration_ms`, `max_duration_ms`, `total_allocations`, and `cached_count`. This reduces payload size on N+1-heavy requests and makes the SQL breakdown directly usable without server-side grouping.
|
|
37
|
+
- **View rendering aggregation**: template, partial, and collection renders are aggregated per identifier (last three path segments). Each entry carries `count`, `total_duration_ms`, `min_duration_ms`, `max_duration_ms`, `rendered_at_min/max`, and cache hit counts. Aggregation happens on the thread-local stack so there is no GC pressure from intermediate arrays.
|
|
38
|
+
|
|
6
39
|
## [0.2.17] - 2026-05-25
|
|
7
40
|
|
|
8
41
|
### Added
|
|
@@ -13,6 +46,12 @@
|
|
|
13
46
|
### Added
|
|
14
47
|
- `ArObjectTracker`: subscribes to Rails' built-in `instantiation.active_record` notification to count the total number of ActiveRecord model instances hydrated during each request or background job. The count is reported as `ar_instantiation_count` in every payload. Uses a thread-local counter with an idempotent `subscribe!` guard, matching the same start/stop lifecycle as `GcTracker`. No monkey-patching required — Rails emits this event natively with a `record_count` field that accumulates correctly across batch loads.
|
|
15
48
|
|
|
49
|
+
## [0.2.15] - 2026-05-24
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
- **`GcTracker`**: records a GC snapshot at the start and end of every request and background job. Reports `gc_minor_runs`, `gc_major_runs`, `gc_allocated_objects`, `gc_time_ms`, and `heap_pages_increase` as a `gc_pressure` hash in every payload. Uses `GC.stat` and `GC::Profiler` with an idempotent subscribe guard; overhead is negligible when no GC cycles occur.
|
|
53
|
+
- **`SqlAllocListener`**: measures GC allocation deltas per SQL event by snapshotting `GC.stat[:total_allocated_objects]` in the notification `start` callback and diffing in `finish`. The delta is stored by notification ID and merged into the corresponding query's `allocations` field, allowing the dashboard to surface allocation-heavy queries independently of their duration.
|
|
54
|
+
|
|
16
55
|
## [0.2.14] - 2026-05-23
|
|
17
56
|
|
|
18
57
|
### Added
|
|
@@ -24,7 +24,9 @@ module DeadBro
|
|
|
24
24
|
next unless Thread.current[THREAD_LOCAL_KEY]
|
|
25
25
|
|
|
26
26
|
duration_ms = ((finished - started) * 1000.0).round(2)
|
|
27
|
-
|
|
27
|
+
tracking_start = Thread.current[DeadBro::TRACKING_START_TIME_KEY]
|
|
28
|
+
start_offset_ms = tracking_start ? ((started - tracking_start) * 1000.0).round(2) : nil
|
|
29
|
+
event = build_event(name, data, duration_ms, start_offset_ms)
|
|
28
30
|
if event && should_continue_tracking?
|
|
29
31
|
Thread.current[THREAD_LOCAL_KEY] << event
|
|
30
32
|
end
|
|
@@ -63,12 +65,13 @@ module DeadBro
|
|
|
63
65
|
true
|
|
64
66
|
end
|
|
65
67
|
|
|
66
|
-
def self.build_event(name, data, duration_ms)
|
|
68
|
+
def self.build_event(name, data, duration_ms, start_offset_ms = nil)
|
|
67
69
|
return nil unless data.is_a?(Hash)
|
|
68
70
|
|
|
69
71
|
{
|
|
70
72
|
event: name,
|
|
71
73
|
duration_ms: duration_ms,
|
|
74
|
+
start_offset_ms: start_offset_ms,
|
|
72
75
|
key: safe_key(data[:key]),
|
|
73
76
|
keys_count: safe_keys_count(data[:keys]),
|
|
74
77
|
hit: infer_hit(name, data),
|
|
@@ -13,12 +13,12 @@ module DeadBro
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
# Called by HttpInstrumentation when it detects a Net::HTTP request to an ES host.
|
|
16
|
-
def self.record(method:, path:, status:, duration_ms:)
|
|
16
|
+
def self.record(method:, path:, status:, duration_ms:, start_offset_ms: nil)
|
|
17
17
|
events = Thread.current[THREAD_LOCAL_KEY]
|
|
18
18
|
return unless events
|
|
19
19
|
return unless should_continue_tracking?
|
|
20
20
|
|
|
21
|
-
events << build_event(method, path, status, duration_ms)
|
|
21
|
+
events << build_event(method, path, status, duration_ms, start_offset_ms)
|
|
22
22
|
rescue
|
|
23
23
|
end
|
|
24
24
|
|
|
@@ -120,20 +120,23 @@ module DeadBro
|
|
|
120
120
|
duration_ms = ((finished - started) * 1000.0).round(2)
|
|
121
121
|
method = payload[:method].to_s.upcase
|
|
122
122
|
path = payload[:path].to_s
|
|
123
|
-
|
|
123
|
+
tracking_start = Thread.current[DeadBro::TRACKING_START_TIME_KEY]
|
|
124
|
+
start_offset_ms = tracking_start ? ((started - tracking_start) * 1000.0).round(2) : nil
|
|
125
|
+
events << build_event(method, path, payload[:status], duration_ms, start_offset_ms)
|
|
124
126
|
rescue
|
|
125
127
|
end
|
|
126
128
|
end
|
|
127
129
|
rescue
|
|
128
130
|
end
|
|
129
131
|
|
|
130
|
-
def build_event(method, path, status, duration_ms)
|
|
132
|
+
def build_event(method, path, status, duration_ms, start_offset_ms = nil)
|
|
131
133
|
{
|
|
132
134
|
method: method.to_s.upcase,
|
|
133
135
|
path: sanitize_path(path),
|
|
134
136
|
operation: extract_operation(method, path),
|
|
135
137
|
status: status,
|
|
136
|
-
duration_ms: duration_ms
|
|
138
|
+
duration_ms: duration_ms,
|
|
139
|
+
start_offset_ms: start_offset_ms
|
|
137
140
|
}
|
|
138
141
|
end
|
|
139
142
|
end
|
|
@@ -21,6 +21,7 @@ module DeadBro
|
|
|
21
21
|
mod = Module.new do
|
|
22
22
|
define_method(:request) do |req, body = nil, &block|
|
|
23
23
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
24
|
+
wall_start = Time.now
|
|
24
25
|
response = nil
|
|
25
26
|
error = nil
|
|
26
27
|
begin
|
|
@@ -46,6 +47,9 @@ module DeadBro
|
|
|
46
47
|
# must still be tracked; only skip the deadbro backend itself.
|
|
47
48
|
skip_instrumentation = !is_es_host && uri && (uri.to_s.include?("localhost") || uri.to_s.include?("aberatii.com"))
|
|
48
49
|
|
|
50
|
+
tracking_start = Thread.current[DeadBro::TRACKING_START_TIME_KEY]
|
|
51
|
+
start_offset_ms = tracking_start ? ((wall_start - tracking_start) * 1000.0).round(2) : nil
|
|
52
|
+
|
|
49
53
|
if is_es_host
|
|
50
54
|
# Route to elasticsearch subscriber instead of http_outgoing
|
|
51
55
|
if Thread.current[DeadBro::ElasticsearchSubscriber::THREAD_LOCAL_KEY]
|
|
@@ -54,7 +58,8 @@ module DeadBro
|
|
|
54
58
|
method: req.method,
|
|
55
59
|
path: path,
|
|
56
60
|
status: response && response.code.to_i,
|
|
57
|
-
duration_ms: duration_ms
|
|
61
|
+
duration_ms: duration_ms,
|
|
62
|
+
start_offset_ms: start_offset_ms
|
|
58
63
|
)
|
|
59
64
|
end
|
|
60
65
|
elsif !skip_instrumentation
|
|
@@ -67,6 +72,7 @@ module DeadBro
|
|
|
67
72
|
path: (uri && uri.path) || req.path,
|
|
68
73
|
status: response && response.code.to_i,
|
|
69
74
|
duration_ms: duration_ms,
|
|
75
|
+
start_offset_ms: start_offset_ms,
|
|
70
76
|
exception: error && error.class.name
|
|
71
77
|
}
|
|
72
78
|
if Thread.current[THREAD_LOCAL_KEY] && DeadBro::HttpInstrumentation.should_continue_tracking?
|
|
@@ -63,6 +63,7 @@ module DeadBro
|
|
|
63
63
|
def record_redis_command(command)
|
|
64
64
|
return yield unless Thread.current[RedisSubscriber::THREAD_LOCAL_KEY]
|
|
65
65
|
|
|
66
|
+
wall_start = Time.now
|
|
66
67
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
67
68
|
error = nil
|
|
68
69
|
begin
|
|
@@ -77,12 +78,15 @@ module DeadBro
|
|
|
77
78
|
|
|
78
79
|
begin
|
|
79
80
|
cmd_info = extract_command_info(command)
|
|
81
|
+
tracking_start = Thread.current[DeadBro::TRACKING_START_TIME_KEY]
|
|
82
|
+
start_offset_ms = tracking_start ? ((wall_start - tracking_start) * 1000.0).round(2) : nil
|
|
80
83
|
event = {
|
|
81
84
|
event: "redis.command",
|
|
82
85
|
command: cmd_info[:command],
|
|
83
86
|
key: cmd_info[:key],
|
|
84
87
|
args_count: cmd_info[:args_count],
|
|
85
88
|
duration_ms: duration_ms,
|
|
89
|
+
start_offset_ms: start_offset_ms,
|
|
86
90
|
db: safe_db(@db),
|
|
87
91
|
error: error ? error.class.name : nil
|
|
88
92
|
}
|
|
@@ -98,6 +102,7 @@ module DeadBro
|
|
|
98
102
|
def record_redis_pipeline(pipeline)
|
|
99
103
|
return yield unless Thread.current[RedisSubscriber::THREAD_LOCAL_KEY]
|
|
100
104
|
|
|
105
|
+
wall_start = Time.now
|
|
101
106
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
102
107
|
begin
|
|
103
108
|
result = yield
|
|
@@ -108,10 +113,13 @@ module DeadBro
|
|
|
108
113
|
|
|
109
114
|
begin
|
|
110
115
|
commands_count = pipeline.commands&.length || 0
|
|
116
|
+
tracking_start = Thread.current[DeadBro::TRACKING_START_TIME_KEY]
|
|
117
|
+
start_offset_ms = tracking_start ? ((wall_start - tracking_start) * 1000.0).round(2) : nil
|
|
111
118
|
event = {
|
|
112
119
|
event: "redis.pipeline",
|
|
113
120
|
commands_count: commands_count,
|
|
114
121
|
duration_ms: duration_ms,
|
|
122
|
+
start_offset_ms: start_offset_ms,
|
|
115
123
|
db: safe_db(@db)
|
|
116
124
|
}
|
|
117
125
|
|
|
@@ -126,6 +134,7 @@ module DeadBro
|
|
|
126
134
|
def record_redis_multi(multi)
|
|
127
135
|
return yield unless Thread.current[RedisSubscriber::THREAD_LOCAL_KEY]
|
|
128
136
|
|
|
137
|
+
wall_start = Time.now
|
|
129
138
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
130
139
|
begin
|
|
131
140
|
result = yield
|
|
@@ -136,10 +145,13 @@ module DeadBro
|
|
|
136
145
|
|
|
137
146
|
begin
|
|
138
147
|
commands_count = multi.commands&.length || 0
|
|
148
|
+
tracking_start = Thread.current[DeadBro::TRACKING_START_TIME_KEY]
|
|
149
|
+
start_offset_ms = tracking_start ? ((wall_start - tracking_start) * 1000.0).round(2) : nil
|
|
139
150
|
event = {
|
|
140
151
|
event: "redis.multi",
|
|
141
152
|
commands_count: commands_count,
|
|
142
153
|
duration_ms: duration_ms,
|
|
154
|
+
start_offset_ms: start_offset_ms,
|
|
143
155
|
db: safe_db(@db)
|
|
144
156
|
}
|
|
145
157
|
|
|
@@ -201,7 +213,9 @@ module DeadBro
|
|
|
201
213
|
ActiveSupport::Notifications.subscribe(/\Aredis\..+\z/) do |name, started, finished, _unique_id, data|
|
|
202
214
|
next unless Thread.current[THREAD_LOCAL_KEY]
|
|
203
215
|
duration_ms = ((finished - started) * 1000.0).round(2)
|
|
204
|
-
|
|
216
|
+
tracking_start = Thread.current[DeadBro::TRACKING_START_TIME_KEY]
|
|
217
|
+
start_offset_ms = tracking_start ? ((started - tracking_start) * 1000.0).round(2) : nil
|
|
218
|
+
event = build_event(name, data, duration_ms, start_offset_ms)
|
|
205
219
|
if event && should_continue_tracking?
|
|
206
220
|
Thread.current[THREAD_LOCAL_KEY] << event
|
|
207
221
|
end
|
|
@@ -239,7 +253,7 @@ module DeadBro
|
|
|
239
253
|
true
|
|
240
254
|
end
|
|
241
255
|
|
|
242
|
-
def self.build_event(name, data, duration_ms)
|
|
256
|
+
def self.build_event(name, data, duration_ms, start_offset_ms = nil)
|
|
243
257
|
cmd = extract_command(data)
|
|
244
258
|
{
|
|
245
259
|
event: name.to_s,
|
|
@@ -247,6 +261,7 @@ module DeadBro
|
|
|
247
261
|
key: cmd[:key],
|
|
248
262
|
args_count: cmd[:args_count],
|
|
249
263
|
duration_ms: duration_ms,
|
|
264
|
+
start_offset_ms: start_offset_ms,
|
|
250
265
|
db: safe_db(data[:db])
|
|
251
266
|
}
|
|
252
267
|
rescue
|
|
@@ -106,6 +106,9 @@ module DeadBro
|
|
|
106
106
|
duration_ms = ((finished - started) * 1000.0).round(2)
|
|
107
107
|
original_sql = data[:sql]
|
|
108
108
|
|
|
109
|
+
tracking_start = Thread.current[DeadBro::TRACKING_START_TIME_KEY]
|
|
110
|
+
start_offset_ms = tracking_start ? ((started - tracking_start) * 1000.0).round(2) : nil
|
|
111
|
+
|
|
109
112
|
threshold = begin
|
|
110
113
|
DeadBro.configuration.slow_query_threshold_ms
|
|
111
114
|
rescue
|
|
@@ -133,6 +136,7 @@ module DeadBro
|
|
|
133
136
|
sql: sanitized_sql,
|
|
134
137
|
name: data[:name],
|
|
135
138
|
duration_ms: duration_ms,
|
|
139
|
+
start_offset_ms: start_offset_ms,
|
|
136
140
|
cached: data[:cached] || false,
|
|
137
141
|
connection_id: data[:connection_id],
|
|
138
142
|
trace: captured_trace,
|
|
@@ -167,6 +171,7 @@ module DeadBro
|
|
|
167
171
|
total_allocations: allocations || 0,
|
|
168
172
|
cached_count: (data[:cached] ? 1 : 0),
|
|
169
173
|
n_plus_one: false,
|
|
174
|
+
start_offset_ms: start_offset_ms,
|
|
170
175
|
backtrace: captured_trace,
|
|
171
176
|
explain_plan: nil
|
|
172
177
|
}
|
data/lib/dead_bro/version.rb
CHANGED
|
@@ -14,24 +14,30 @@ module DeadBro
|
|
|
14
14
|
|
|
15
15
|
def self.subscribe!(client: Client.new)
|
|
16
16
|
ActiveSupport::Notifications.subscribe(RENDER_TEMPLATE_EVENT) do |_name, started, finished, _uid, data|
|
|
17
|
+
tracking_start = Thread.current[DeadBro::TRACKING_START_TIME_KEY]
|
|
17
18
|
add_view_event(type: "template", identifier: safe_identifier(data[:identifier]),
|
|
18
19
|
duration_ms: ((finished - started) * 1000.0).round(2),
|
|
19
|
-
rendered_at: Time.now.utc.to_i
|
|
20
|
+
rendered_at: Time.now.utc.to_i,
|
|
21
|
+
start_offset_ms: tracking_start ? ((started - tracking_start) * 1000.0).round(2) : nil)
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
ActiveSupport::Notifications.subscribe(RENDER_PARTIAL_EVENT) do |_name, started, finished, _uid, data|
|
|
25
|
+
tracking_start = Thread.current[DeadBro::TRACKING_START_TIME_KEY]
|
|
23
26
|
add_view_event(type: "partial", identifier: safe_identifier(data[:identifier]),
|
|
24
27
|
duration_ms: ((finished - started) * 1000.0).round(2),
|
|
25
28
|
cache_key: data[:cache_key],
|
|
26
|
-
rendered_at: Time.now.utc.to_i
|
|
29
|
+
rendered_at: Time.now.utc.to_i,
|
|
30
|
+
start_offset_ms: tracking_start ? ((started - tracking_start) * 1000.0).round(2) : nil)
|
|
27
31
|
end
|
|
28
32
|
|
|
29
33
|
ActiveSupport::Notifications.subscribe(RENDER_COLLECTION_EVENT) do |_name, started, finished, _uid, data|
|
|
34
|
+
tracking_start = Thread.current[DeadBro::TRACKING_START_TIME_KEY]
|
|
30
35
|
add_view_event(type: "collection", identifier: safe_identifier(data[:identifier]),
|
|
31
36
|
duration_ms: ((finished - started) * 1000.0).round(2),
|
|
32
37
|
collection_count: (data[:count] || 0).to_i,
|
|
33
38
|
collection_cached_count: (data[:cached_count] || 0).to_i,
|
|
34
|
-
rendered_at: Time.now.utc.to_i
|
|
39
|
+
rendered_at: Time.now.utc.to_i,
|
|
40
|
+
start_offset_ms: tracking_start ? ((started - tracking_start) * 1000.0).round(2) : nil)
|
|
35
41
|
end
|
|
36
42
|
rescue
|
|
37
43
|
end
|
|
@@ -80,7 +86,8 @@ module DeadBro
|
|
|
80
86
|
rendered_at_max: rendered_at,
|
|
81
87
|
cache_hit_count: (view_info[:cache_key] ? 1 : 0),
|
|
82
88
|
collection_count: view_info[:collection_count].to_i,
|
|
83
|
-
collection_cached_count: view_info[:collection_cached_count].to_i
|
|
89
|
+
collection_cached_count: view_info[:collection_cached_count].to_i,
|
|
90
|
+
start_offset_ms: view_info[:start_offset_ms]
|
|
84
91
|
}
|
|
85
92
|
end
|
|
86
93
|
end
|