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.
@@ -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
- begin
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
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Profiler
4
- VERSION = "0.36.2"
4
+ VERSION = "0.38.0"
5
5
 
6
6
  GEM_ROOT = File.expand_path("../..", __dir__)
7
7
 
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.36.2
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-18 00:00:00.000000000 Z
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: