rails-profiler 0.36.2 → 0.38.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 +4 -4
- data/CHANGELOG.md +26 -0
- data/app/assets/builds/profiler-toolbar.js +1 -1
- data/app/assets/builds/profiler.js +10 -10
- data/app/controllers/profiler/api/profiles_controller.rb +10 -0
- data/app/controllers/profiler/health_controller.rb +27 -0
- data/app/views/profiler/health/show.html.erb +26 -0
- data/config/routes.rb +1 -0
- data/lib/profiler/collectors/circuit_breaker.rb +102 -0
- data/lib/profiler/collectors/lifecycle.rb +30 -5
- data/lib/profiler/configuration.rb +19 -1
- data/lib/profiler/console_profiler.rb +1 -3
- data/lib/profiler/current_context.rb +10 -0
- data/lib/profiler/instrumentation/net_http_instrumentation.rb +20 -0
- data/lib/profiler/job_profiler.rb +1 -3
- data/lib/profiler/middleware/profiler_middleware.rb +24 -12
- data/lib/profiler/middleware/streamed_profile.rb +9 -6
- data/lib/profiler/models/profile.rb +7 -1
- data/lib/profiler/storage/base_store.rb +5 -0
- data/lib/profiler/storage/file_store.rb +25 -6
- data/lib/profiler/storage/memory_store.rb +15 -2
- data/lib/profiler/storage/redis_store.rb +23 -0
- data/lib/profiler/storage/sqlite_store.rb +20 -3
- data/lib/profiler/test_profiler.rb +1 -3
- data/lib/profiler/trace_context.rb +48 -0
- data/lib/profiler/version.rb +1 -1
- metadata +6 -2
|
@@ -12,7 +12,7 @@ module Profiler
|
|
|
12
12
|
# cap). Beside them, the start time, type and parent of each, and the children of each parent,
|
|
13
13
|
# so that listing, finding the children and evicting deserialize only what they return.
|
|
14
14
|
class MemoryStore < BaseStore
|
|
15
|
-
Entry = Struct.new(:at, :type, :parent, :sequence)
|
|
15
|
+
Entry = Struct.new(:at, :type, :parent, :sequence, :trace)
|
|
16
16
|
|
|
17
17
|
def initialize(options = {})
|
|
18
18
|
@profiles = Concurrent::Hash.new
|
|
@@ -20,6 +20,7 @@ module Profiler
|
|
|
20
20
|
@mutex = Mutex.new
|
|
21
21
|
@entries = {}
|
|
22
22
|
@children = {}
|
|
23
|
+
@traces = {}
|
|
23
24
|
@sequence = 0
|
|
24
25
|
@sorted = nil
|
|
25
26
|
end
|
|
@@ -30,8 +31,9 @@ module Profiler
|
|
|
30
31
|
cleanup_if_needed unless @profiles.key?(token)
|
|
31
32
|
remove_entry(token)
|
|
32
33
|
@profiles[token] = data
|
|
33
|
-
@entries[token] = Entry.new(profile.started_at.to_f, data[:profile_type].to_s, profile.parent_token, @sequence += 1)
|
|
34
|
+
@entries[token] = Entry.new(profile.started_at.to_f, data[:profile_type].to_s, profile.parent_token, @sequence += 1, profile.trace_id)
|
|
34
35
|
(@children[profile.parent_token] ||= {})[token] = true if profile.parent_token
|
|
36
|
+
(@traces[profile.trace_id] ||= {})[token] = true if profile.trace_id
|
|
35
37
|
@sorted = nil
|
|
36
38
|
end
|
|
37
39
|
token
|
|
@@ -70,6 +72,13 @@ module Profiler
|
|
|
70
72
|
children.map { |data| deserialize_profile(data) }.sort_by(&:started_at)
|
|
71
73
|
end
|
|
72
74
|
|
|
75
|
+
def find_by_trace(trace_id)
|
|
76
|
+
return [] unless Token.valid?(trace_id)
|
|
77
|
+
|
|
78
|
+
members = @mutex.synchronize { (@traces[trace_id] || {}).keys.filter_map { |token| @profiles[token] } }
|
|
79
|
+
members.map { |data| deserialize_profile(data) }.sort_by(&:started_at)
|
|
80
|
+
end
|
|
81
|
+
|
|
73
82
|
def delete(token)
|
|
74
83
|
return unless Token.valid?(token)
|
|
75
84
|
|
|
@@ -109,6 +118,10 @@ module Profiler
|
|
|
109
118
|
siblings.delete(token)
|
|
110
119
|
@children.delete(entry.parent) if siblings.empty?
|
|
111
120
|
end
|
|
121
|
+
if entry.trace && (members = @traces[entry.trace])
|
|
122
|
+
members.delete(token)
|
|
123
|
+
@traces.delete(entry.trace) if members.empty?
|
|
124
|
+
end
|
|
112
125
|
@sorted = nil
|
|
113
126
|
end
|
|
114
127
|
|
|
@@ -102,6 +102,16 @@ module Profiler
|
|
|
102
102
|
.sort_by(&:started_at)
|
|
103
103
|
end
|
|
104
104
|
|
|
105
|
+
def find_by_trace(trace_id)
|
|
106
|
+
return [] unless Token.valid?(trace_id)
|
|
107
|
+
|
|
108
|
+
ensure_index
|
|
109
|
+
@redis.smembers(traces_key(trace_id))
|
|
110
|
+
.filter_map { |token| load(token) if Token.valid?(token) }
|
|
111
|
+
.select { |profile| profile.trace_id == trace_id }
|
|
112
|
+
.sort_by(&:started_at)
|
|
113
|
+
end
|
|
114
|
+
|
|
105
115
|
def delete(token)
|
|
106
116
|
return unless Token.valid?(token)
|
|
107
117
|
|
|
@@ -172,6 +182,11 @@ module Profiler
|
|
|
172
182
|
client.zadd(saved_key, saved_at, token)
|
|
173
183
|
client.sadd(types_key, type)
|
|
174
184
|
[list_key, type_list_key(type), saved_key, types_key].each { |key| client.expire(key, @ttl) }
|
|
185
|
+
trace = summary[:trace_id] || summary["trace_id"]
|
|
186
|
+
if Token.valid?(trace)
|
|
187
|
+
client.sadd(traces_key(trace), token)
|
|
188
|
+
client.expire(traces_key(trace), @ttl)
|
|
189
|
+
end
|
|
175
190
|
return unless Token.valid?(parent)
|
|
176
191
|
|
|
177
192
|
client.sadd(children_key(parent), token)
|
|
@@ -185,6 +200,8 @@ module Profiler
|
|
|
185
200
|
@redis.zrem(type_list_key(summary["profile_type"] || "http"), token)
|
|
186
201
|
parent = summary["parent_token"]
|
|
187
202
|
@redis.srem(children_key(parent), token) if Token.valid?(parent)
|
|
203
|
+
trace = summary["trace_id"]
|
|
204
|
+
@redis.srem(traces_key(trace), token) if Token.valid?(trace)
|
|
188
205
|
end
|
|
189
206
|
|
|
190
207
|
def remove(token)
|
|
@@ -205,6 +222,8 @@ module Profiler
|
|
|
205
222
|
pipe.zrem(type_list_key(summary["profile_type"] || "http"), token)
|
|
206
223
|
parent = summary["parent_token"]
|
|
207
224
|
pipe.srem(children_key(parent), token) if Token.valid?(parent)
|
|
225
|
+
trace = summary["trace_id"]
|
|
226
|
+
pipe.srem(traces_key(trace), token) if Token.valid?(trace)
|
|
208
227
|
else
|
|
209
228
|
types.each { |type| pipe.zrem(type_list_key(type), token) }
|
|
210
229
|
end
|
|
@@ -349,6 +368,10 @@ module Profiler
|
|
|
349
368
|
"#{@key_prefix}:children:#{parent_token}"
|
|
350
369
|
end
|
|
351
370
|
|
|
371
|
+
def traces_key(trace_id)
|
|
372
|
+
"#{@key_prefix}:traces:#{trace_id}"
|
|
373
|
+
end
|
|
374
|
+
|
|
352
375
|
def index_version_key
|
|
353
376
|
"#{@key_prefix}:index_version"
|
|
354
377
|
end
|
|
@@ -66,11 +66,11 @@ module Profiler
|
|
|
66
66
|
<<~SQL,
|
|
67
67
|
INSERT OR REPLACE INTO profiler_profiles (
|
|
68
68
|
token, profile_type, gem_version, path, method, status, duration, memory,
|
|
69
|
-
started_at, finished_at, parent_token, is_ajax,
|
|
69
|
+
started_at, finished_at, parent_token, trace_id, span_id, parent_span_id, is_ajax,
|
|
70
70
|
tabs, params, headers, response_headers, collectors_meta, summary
|
|
71
71
|
) VALUES (
|
|
72
72
|
:token, :profile_type, :gem_version, :path, :method, :status, :duration, :memory,
|
|
73
|
-
:started_at, :finished_at, :parent_token, :is_ajax,
|
|
73
|
+
:started_at, :finished_at, :parent_token, :trace_id, :span_id, :parent_span_id, :is_ajax,
|
|
74
74
|
:tabs, :params, :headers, :response_headers, :collectors_meta, :summary
|
|
75
75
|
)
|
|
76
76
|
SQL
|
|
@@ -85,6 +85,9 @@ module Profiler
|
|
|
85
85
|
started_at: utc_iso8601(data[:started_at]),
|
|
86
86
|
finished_at: utc_iso8601(data[:finished_at]),
|
|
87
87
|
parent_token: data[:parent_token],
|
|
88
|
+
trace_id: data[:trace_id],
|
|
89
|
+
span_id: data[:span_id],
|
|
90
|
+
parent_span_id: data[:parent_span_id],
|
|
88
91
|
is_ajax: data[:is_ajax] ? 1 : 0,
|
|
89
92
|
tabs: JSON.generate(data[:tabs] || []),
|
|
90
93
|
params: JSON.generate(data[:params] || {}),
|
|
@@ -136,6 +139,16 @@ module Profiler
|
|
|
136
139
|
rows.map { |row| row_to_profile(row) }.compact
|
|
137
140
|
end
|
|
138
141
|
|
|
142
|
+
def find_by_trace(trace_id)
|
|
143
|
+
return [] unless Token.valid?(trace_id)
|
|
144
|
+
|
|
145
|
+
rows = @db.execute(
|
|
146
|
+
"SELECT * FROM profiler_profiles WHERE trace_id = :trace_id ORDER BY started_at ASC, rowid ASC",
|
|
147
|
+
trace_id: trace_id
|
|
148
|
+
)
|
|
149
|
+
rows.map { |row| row_to_profile(row) }.compact
|
|
150
|
+
end
|
|
151
|
+
|
|
139
152
|
def delete(token)
|
|
140
153
|
return unless Token.valid?(token)
|
|
141
154
|
|
|
@@ -276,12 +289,13 @@ module Profiler
|
|
|
276
289
|
ON profiler_profiles(profile_type);
|
|
277
290
|
SQL
|
|
278
291
|
|
|
279
|
-
%w[gem_version summary].each do |column|
|
|
292
|
+
%w[gem_version summary trace_id span_id parent_span_id].each do |column|
|
|
280
293
|
@db.execute("ALTER TABLE profiler_profiles ADD COLUMN #{column} TEXT")
|
|
281
294
|
rescue SQLite3::Exception
|
|
282
295
|
# column already exists
|
|
283
296
|
end
|
|
284
297
|
|
|
298
|
+
@db.execute("CREATE INDEX IF NOT EXISTS idx_profiler_trace_id ON profiler_profiles(trace_id)")
|
|
285
299
|
migrate_times_to_utc
|
|
286
300
|
end
|
|
287
301
|
|
|
@@ -331,6 +345,9 @@ module Profiler
|
|
|
331
345
|
started_at: local_iso8601(row["started_at"]),
|
|
332
346
|
finished_at: local_iso8601(row["finished_at"]),
|
|
333
347
|
parent_token: row["parent_token"],
|
|
348
|
+
trace_id: row["trace_id"],
|
|
349
|
+
span_id: row["span_id"],
|
|
350
|
+
parent_span_id: row["parent_span_id"],
|
|
334
351
|
is_ajax: row["is_ajax"] == 1,
|
|
335
352
|
tabs: parse_json(row["tabs"], []),
|
|
336
353
|
params: parse_json(row["params"], {}),
|
|
@@ -101,11 +101,9 @@ module Profiler
|
|
|
101
101
|
profile.finish(test_status == "passed" ? 200 : 500)
|
|
102
102
|
|
|
103
103
|
collectors.each do |collector|
|
|
104
|
-
|
|
104
|
+
Collectors::Lifecycle.guard(collector, "TestProfiler") do
|
|
105
105
|
collector.collect if collector.respond_to?(:collect)
|
|
106
106
|
profile.add_collector_metadata(collector)
|
|
107
|
-
rescue => e
|
|
108
|
-
Profiler.log_error("TestProfiler: collector #{collector.class} failed", e)
|
|
109
107
|
end
|
|
110
108
|
end
|
|
111
109
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module Profiler
|
|
6
|
+
# The W3C Trace Context `traceparent` header, version 00:
|
|
7
|
+
# `00-{trace-id:32 hex}-{parent-id:16 hex}-{trace-flags:2 hex}`.
|
|
8
|
+
module TraceContext
|
|
9
|
+
HEADER = "traceparent"
|
|
10
|
+
# Version ff is forbidden by the specification; a later version is read by its first four
|
|
11
|
+
# fields, as the specification asks of a parser that does not know it.
|
|
12
|
+
PATTERN = /\A(?<version>\h{2})-(?<trace_id>\h{32})-(?<parent_id>\h{16})-(?<flags>\h{2})(?:-.*)?\z/
|
|
13
|
+
ZERO_TRACE_ID = ("0" * 32).freeze
|
|
14
|
+
ZERO_SPAN_ID = ("0" * 16).freeze
|
|
15
|
+
|
|
16
|
+
Parsed = Struct.new(:trace_id, :parent_id, :flags)
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# The trace-id, parent-id and flags of a valid header value, nil for anything else.
|
|
21
|
+
def parse(value)
|
|
22
|
+
match = PATTERN.match(value.to_s.strip)
|
|
23
|
+
return nil unless match
|
|
24
|
+
|
|
25
|
+
version = match[:version].downcase
|
|
26
|
+
trace_id = match[:trace_id].downcase
|
|
27
|
+
parent_id = match[:parent_id].downcase
|
|
28
|
+
return nil if version == "ff"
|
|
29
|
+
# Version 00 has exactly four fields; the all-zero ids are invalid.
|
|
30
|
+
return nil if version == "00" && value.to_s.strip.count("-") != 3
|
|
31
|
+
return nil if trace_id == ZERO_TRACE_ID || parent_id == ZERO_SPAN_ID
|
|
32
|
+
|
|
33
|
+
Parsed.new(trace_id, parent_id, match[:flags].downcase)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def new_trace_id
|
|
37
|
+
SecureRandom.hex(16)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def new_span_id
|
|
41
|
+
SecureRandom.hex(8)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def format(trace_id, span_id, flags = "01")
|
|
45
|
+
"00-#{trace_id}-#{span_id}-#{flags}"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/profiler/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails-profiler
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.38.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sébastien Duplessy
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -133,10 +133,12 @@ files:
|
|
|
133
133
|
- app/controllers/profiler/api/toolbar_controller.rb
|
|
134
134
|
- app/controllers/profiler/application_controller.rb
|
|
135
135
|
- app/controllers/profiler/assets_controller.rb
|
|
136
|
+
- app/controllers/profiler/health_controller.rb
|
|
136
137
|
- app/controllers/profiler/profiles_controller.rb
|
|
137
138
|
- app/controllers/profiler/test_runner_controller.rb
|
|
138
139
|
- app/views/layouts/profiler/application.html.erb
|
|
139
140
|
- app/views/layouts/profiler/embedded.html.erb
|
|
141
|
+
- app/views/profiler/health/show.html.erb
|
|
140
142
|
- app/views/profiler/profiles/index.html.erb
|
|
141
143
|
- app/views/profiler/profiles/show.html.erb
|
|
142
144
|
- app/views/profiler/test_runner/index.html.erb
|
|
@@ -156,6 +158,7 @@ files:
|
|
|
156
158
|
- lib/profiler/collectors/ajax_collector.rb
|
|
157
159
|
- lib/profiler/collectors/base_collector.rb
|
|
158
160
|
- lib/profiler/collectors/cache_collector.rb
|
|
161
|
+
- lib/profiler/collectors/circuit_breaker.rb
|
|
159
162
|
- lib/profiler/collectors/console_collector.rb
|
|
160
163
|
- lib/profiler/collectors/database_collector.rb
|
|
161
164
|
- lib/profiler/collectors/dump_collector.rb
|
|
@@ -257,6 +260,7 @@ files:
|
|
|
257
260
|
- lib/profiler/test_runner/discovery.rb
|
|
258
261
|
- lib/profiler/test_runner/run_store.rb
|
|
259
262
|
- lib/profiler/test_runner/runner.rb
|
|
263
|
+
- lib/profiler/trace_context.rb
|
|
260
264
|
- lib/profiler/version.rb
|
|
261
265
|
homepage: https://github.com/Pepsit36/rails-profiler
|
|
262
266
|
licenses:
|