rails-profiler 0.37.0 → 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 +12 -0
- data/app/assets/builds/profiler.js +10 -10
- data/app/controllers/profiler/api/profiles_controller.rb +10 -0
- data/lib/profiler/current_context.rb +10 -0
- data/lib/profiler/instrumentation/net_http_instrumentation.rb +20 -0
- data/lib/profiler/middleware/profiler_middleware.rb +12 -0
- data/lib/profiler/middleware/streamed_profile.rb +5 -2
- 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/trace_context.rb +48 -0
- data/lib/profiler/version.rb +1 -1
- metadata +3 -2
|
@@ -9,6 +9,8 @@ module Profiler
|
|
|
9
9
|
# dashboard relies on the default: http profiles, as summaries.
|
|
10
10
|
if params[:parent_token].present?
|
|
11
11
|
render_children_page
|
|
12
|
+
elsif params[:trace_id].present?
|
|
13
|
+
render_trace_page
|
|
12
14
|
elsif all_types?
|
|
13
15
|
render_profile_page(type: profile_type_param, summary: false)
|
|
14
16
|
else
|
|
@@ -57,6 +59,14 @@ module Profiler
|
|
|
57
59
|
params[:type].to_s.match?(/\A[a-z_]{1,32}\z/) ? params[:type].to_s : nil
|
|
58
60
|
end
|
|
59
61
|
|
|
62
|
+
# The profiles of a trace, oldest last, read through the store's trace index.
|
|
63
|
+
def render_trace_page
|
|
64
|
+
limit, offset = page_params
|
|
65
|
+
members = Profiler.storage.find_by_trace(params[:trace_id].to_s).reverse
|
|
66
|
+
members = members.select { |p| p.profile_type == "http" } unless all_types?
|
|
67
|
+
render_page(members.drop(offset).first(limit + 1), limit, offset)
|
|
68
|
+
end
|
|
69
|
+
|
|
60
70
|
# The children of a page, oldest last, read through the store's parent index.
|
|
61
71
|
def render_children_page
|
|
62
72
|
limit, offset = page_params
|
|
@@ -10,8 +10,18 @@ module Profiler
|
|
|
10
10
|
Thread.current[:profiler_token] = value
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
# The trace id of the profile in progress, for the outgoing calls to carry.
|
|
14
|
+
def self.trace_id
|
|
15
|
+
Thread.current[:profiler_trace_id]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.trace_id=(value)
|
|
19
|
+
Thread.current[:profiler_trace_id] = value
|
|
20
|
+
end
|
|
21
|
+
|
|
13
22
|
def self.clear
|
|
14
23
|
Thread.current[:profiler_token] = nil
|
|
24
|
+
Thread.current[:profiler_trace_id] = nil
|
|
15
25
|
end
|
|
16
26
|
end
|
|
17
27
|
end
|
|
@@ -8,12 +8,18 @@ require "securerandom"
|
|
|
8
8
|
require_relative "../redaction"
|
|
9
9
|
require_relative "../body_content"
|
|
10
10
|
require_relative "../configuration"
|
|
11
|
+
require_relative "../current_context"
|
|
12
|
+
require_relative "../trace_context"
|
|
11
13
|
|
|
12
14
|
module Profiler
|
|
13
15
|
module Instrumentation
|
|
14
16
|
module NetHttpInstrumentation
|
|
15
17
|
module RequestPatch
|
|
16
18
|
def request(req, body = nil, &block)
|
|
19
|
+
# Before the guards below: the profiler's own calls (the master's requests to its
|
|
20
|
+
# slaves) carry the trace too, so that the slave's profile joins the request's trace.
|
|
21
|
+
NetHttpInstrumentation.inject_trace_context(req, address.to_s, port)
|
|
22
|
+
|
|
17
23
|
collector = Thread.current[:profiler_http_collector]
|
|
18
24
|
return super unless collector
|
|
19
25
|
# Re-entrancy guard: Net::HTTP#request calls itself recursively when
|
|
@@ -128,6 +134,20 @@ module Profiler
|
|
|
128
134
|
@installed = true
|
|
129
135
|
end
|
|
130
136
|
|
|
137
|
+
# Adds a traceparent to a call made while a profiled request runs on this thread: the trace
|
|
138
|
+
# id of that request and a span id of its own. A traceparent the application set is kept,
|
|
139
|
+
# and a host excluded from the instrumentation gets none.
|
|
140
|
+
def self.inject_trace_context(req, host, port)
|
|
141
|
+
trace_id = CurrentContext.trace_id
|
|
142
|
+
return unless trace_id
|
|
143
|
+
return if req[TraceContext::HEADER]
|
|
144
|
+
return if skip_host?(host, port)
|
|
145
|
+
|
|
146
|
+
req[TraceContext::HEADER] = TraceContext.format(trace_id, TraceContext.new_span_id)
|
|
147
|
+
rescue => e
|
|
148
|
+
Profiler.log_error("NetHttpInstrumentation: could not add the traceparent header", e)
|
|
149
|
+
end
|
|
150
|
+
|
|
131
151
|
# The hosts of http_skip_hosts, and the cluster's master (host and port): a slave's calls to
|
|
132
152
|
# it are the profiler's, whichever code makes them.
|
|
133
153
|
def self.skip_host?(host, port = nil)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "rack"
|
|
4
4
|
require_relative "../models/profile"
|
|
5
5
|
require_relative "../current_context"
|
|
6
|
+
require_relative "../trace_context"
|
|
6
7
|
require_relative "../collectors/lifecycle"
|
|
7
8
|
require_relative "toolbar_injector"
|
|
8
9
|
require_relative "capturing_body"
|
|
@@ -31,6 +32,7 @@ module Profiler
|
|
|
31
32
|
profile = Models::Profile.new(build_request(env), params: false)
|
|
32
33
|
profile.gem_version = Profiler.version
|
|
33
34
|
Profiler::CurrentContext.token = profile.token
|
|
35
|
+
assign_trace(env, profile)
|
|
34
36
|
|
|
35
37
|
# Capture request body before app processes it
|
|
36
38
|
request_body = read_request_body(env)
|
|
@@ -76,6 +78,16 @@ module Profiler
|
|
|
76
78
|
|
|
77
79
|
private
|
|
78
80
|
|
|
81
|
+
# Continues the trace of a valid incoming traceparent (its parent-id becomes this profile's
|
|
82
|
+
# parent_span_id), or starts a new one. Either way the profile gets a span of its own.
|
|
83
|
+
def assign_trace(env, profile)
|
|
84
|
+
incoming = TraceContext.parse(env["HTTP_TRACEPARENT"])
|
|
85
|
+
profile.trace_id = incoming ? incoming.trace_id : TraceContext.new_trace_id
|
|
86
|
+
profile.parent_span_id = incoming&.parent_id
|
|
87
|
+
profile.span_id = TraceContext.new_span_id
|
|
88
|
+
Profiler::CurrentContext.trace_id = profile.trace_id
|
|
89
|
+
end
|
|
90
|
+
|
|
79
91
|
# Read after the application has run, so after the routing: the route parameters are there,
|
|
80
92
|
# and the params carry the encoding the route declared, as the controller saw them.
|
|
81
93
|
def capture_params(env, profile)
|
|
@@ -114,7 +114,9 @@ module Profiler
|
|
|
114
114
|
# once the collectors are released.
|
|
115
115
|
def enter
|
|
116
116
|
token = CurrentContext.token
|
|
117
|
+
trace_id = CurrentContext.trace_id
|
|
117
118
|
CurrentContext.token = @profile.token
|
|
119
|
+
CurrentContext.trace_id = @profile.trace_id
|
|
118
120
|
lent = @mutex.synchronize do
|
|
119
121
|
next [] if @released
|
|
120
122
|
|
|
@@ -122,13 +124,14 @@ module Profiler
|
|
|
122
124
|
[collector, collector.lend_thread_slots] if collector.respond_to?(:lend_thread_slots)
|
|
123
125
|
end
|
|
124
126
|
end
|
|
125
|
-
[token, lent]
|
|
127
|
+
[token, lent, trace_id]
|
|
126
128
|
end
|
|
127
129
|
|
|
128
130
|
def leave(state)
|
|
129
|
-
token, lent = state
|
|
131
|
+
token, lent, trace_id = state
|
|
130
132
|
lent.reverse_each { |collector, previous| collector.return_thread_slots(previous) }
|
|
131
133
|
CurrentContext.token = token
|
|
134
|
+
CurrentContext.trace_id = trace_id
|
|
132
135
|
end
|
|
133
136
|
end
|
|
134
137
|
end
|
|
@@ -21,7 +21,7 @@ module Profiler
|
|
|
21
21
|
:request_body_size, :request_body_truncated, :request_body_size_is_minimum,
|
|
22
22
|
:response_body_size, :response_body_truncated, :response_body_size_is_minimum,
|
|
23
23
|
:collectors_released_after_seconds, :params_truncated,
|
|
24
|
-
:gem_version
|
|
24
|
+
:gem_version, :trace_id, :span_id, :parent_span_id
|
|
25
25
|
|
|
26
26
|
# +params+: false leaves the params to capture_params, for a caller that builds the profile
|
|
27
27
|
# before the router has run.
|
|
@@ -167,6 +167,9 @@ module Profiler
|
|
|
167
167
|
collectors_data: @collectors_data,
|
|
168
168
|
tabs: @collectors_metadata,
|
|
169
169
|
parent_token: @parent_token,
|
|
170
|
+
trace_id: @trace_id,
|
|
171
|
+
span_id: @span_id,
|
|
172
|
+
parent_span_id: @parent_span_id,
|
|
170
173
|
is_ajax: @is_ajax
|
|
171
174
|
}
|
|
172
175
|
end
|
|
@@ -217,6 +220,9 @@ module Profiler
|
|
|
217
220
|
profile.is_ajax = data[:is_ajax] || false
|
|
218
221
|
profile.profile_type = data[:profile_type] || "http"
|
|
219
222
|
profile.gem_version = data[:gem_version]
|
|
223
|
+
profile.trace_id = data[:trace_id]
|
|
224
|
+
profile.span_id = data[:span_id]
|
|
225
|
+
profile.parent_span_id = data[:parent_span_id]
|
|
220
226
|
|
|
221
227
|
# Convert collectors_data keys to strings recursively for consistency
|
|
222
228
|
profile.collectors_data = (data[:collectors_data] || {}).transform_keys(&:to_s).transform_values do |value|
|
|
@@ -45,6 +45,11 @@ module Profiler
|
|
|
45
45
|
raise NotImplementedError, "#{self.class} must implement #find_by_parent"
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
# The profiles sharing a trace id, oldest first.
|
|
49
|
+
def find_by_trace(trace_id)
|
|
50
|
+
raise NotImplementedError, "#{self.class} must implement #find_by_trace"
|
|
51
|
+
end
|
|
52
|
+
|
|
48
53
|
def delete(token)
|
|
49
54
|
raise NotImplementedError, "#{self.class} must implement #delete"
|
|
50
55
|
end
|
|
@@ -41,7 +41,7 @@ module Profiler
|
|
|
41
41
|
# Past a cap, the oldest profiles are evicted down to this share of it.
|
|
42
42
|
LOW_WATER = 0.8
|
|
43
43
|
|
|
44
|
-
Entry = Struct.new(:token, :at, :type, :parent, :bytes, :summary, :sequence)
|
|
44
|
+
Entry = Struct.new(:token, :at, :type, :parent, :bytes, :summary, :sequence, :trace)
|
|
45
45
|
|
|
46
46
|
def initialize(options = {})
|
|
47
47
|
super()
|
|
@@ -138,6 +138,16 @@ module Profiler
|
|
|
138
138
|
children.filter_map { |entry| load(entry.token) }.sort_by(&:started_at)
|
|
139
139
|
end
|
|
140
140
|
|
|
141
|
+
def find_by_trace(trace_id)
|
|
142
|
+
return [] unless Token.valid?(trace_id)
|
|
143
|
+
|
|
144
|
+
members = @mutex.synchronize do
|
|
145
|
+
refresh
|
|
146
|
+
(@traces[trace_id] || {}).keys.filter_map { |token| @entries[token] }
|
|
147
|
+
end
|
|
148
|
+
members.filter_map { |entry| load(entry.token) }.sort_by(&:started_at)
|
|
149
|
+
end
|
|
150
|
+
|
|
141
151
|
def delete(token)
|
|
142
152
|
return unless Token.valid?(token)
|
|
143
153
|
|
|
@@ -173,13 +183,14 @@ module Profiler
|
|
|
173
183
|
def put_record(token, data, bytes, started_at: nil)
|
|
174
184
|
started_at = started_at&.to_f || (data[:started_at] ? Time.parse(data[:started_at]).to_f : Time.now.to_f)
|
|
175
185
|
{ "op" => "put", "token" => token, "at" => started_at, "type" => (data[:profile_type] || "http").to_s,
|
|
176
|
-
"parent" => data[:parent_token], "bytes" => bytes, "summary" => Summary.build(data) }
|
|
186
|
+
"parent" => data[:parent_token], "trace" => data[:trace_id], "bytes" => bytes, "summary" => Summary.build(data) }
|
|
177
187
|
end
|
|
178
188
|
|
|
179
189
|
def forget_index
|
|
180
190
|
@entries = {}
|
|
181
191
|
@sequence = 0
|
|
182
192
|
@children = {}
|
|
193
|
+
@traces = {}
|
|
183
194
|
@bytes = 0
|
|
184
195
|
@records = 0
|
|
185
196
|
@offset = 0
|
|
@@ -243,8 +254,9 @@ module Profiler
|
|
|
243
254
|
when "put"
|
|
244
255
|
remove_entry(token)
|
|
245
256
|
parent = Token.valid?(record["parent"]) ? record["parent"] : nil
|
|
257
|
+
trace = Token.valid?(record["trace"]) ? record["trace"] : nil
|
|
246
258
|
add_entry(Entry.new(token, record["at"].to_f, record["type"], parent, record["bytes"].to_i,
|
|
247
|
-
record["summary"].is_a?(Hash) ? record["summary"] : nil))
|
|
259
|
+
record["summary"].is_a?(Hash) ? record["summary"] : nil, nil, trace))
|
|
248
260
|
when "del"
|
|
249
261
|
remove_entry(token)
|
|
250
262
|
end
|
|
@@ -255,6 +267,7 @@ module Profiler
|
|
|
255
267
|
@entries[entry.token] = entry
|
|
256
268
|
@bytes += entry.bytes
|
|
257
269
|
(@children[entry.parent] ||= {})[entry.token] = true if entry.parent
|
|
270
|
+
(@traces[entry.trace] ||= {})[entry.token] = true if entry.trace
|
|
258
271
|
@sorted = nil
|
|
259
272
|
end
|
|
260
273
|
|
|
@@ -267,6 +280,10 @@ module Profiler
|
|
|
267
280
|
siblings.delete(token)
|
|
268
281
|
@children.delete(entry.parent) if siblings.empty?
|
|
269
282
|
end
|
|
283
|
+
if entry.trace && (members = @traces[entry.trace])
|
|
284
|
+
members.delete(token)
|
|
285
|
+
@traces.delete(entry.trace) if members.empty?
|
|
286
|
+
end
|
|
270
287
|
@sorted = nil
|
|
271
288
|
end
|
|
272
289
|
|
|
@@ -344,7 +361,8 @@ module Profiler
|
|
|
344
361
|
def entry_from_file(token)
|
|
345
362
|
json = PrivateFiles.open_for_reading(profile_file_path(token), &:read)
|
|
346
363
|
record = put_record(token, Models::Profile.from_json(json).to_h, json.bytesize)
|
|
347
|
-
|
|
364
|
+
trace = Token.valid?(record["trace"]) ? record["trace"] : nil
|
|
365
|
+
Entry.new(token, record["at"], record["type"], record["parent"], record["bytes"], record["summary"], nil, trace)
|
|
348
366
|
rescue StandardError => e
|
|
349
367
|
# Unreadable: kept in the count, so that it is evicted in turn; listed by no one.
|
|
350
368
|
Profiler.log_error_once(:file_store_entry, "FileStore: could not read profile #{token} to index it", e)
|
|
@@ -353,7 +371,7 @@ module Profiler
|
|
|
353
371
|
rescue SystemCallError
|
|
354
372
|
nil
|
|
355
373
|
end
|
|
356
|
-
Entry.new(token, stat ? stat.mtime.to_f : 0.0, nil, nil, stat ? stat.size : 0, nil)
|
|
374
|
+
Entry.new(token, stat ? stat.mtime.to_f : 0.0, nil, nil, stat ? stat.size : 0, nil, nil, nil)
|
|
357
375
|
end
|
|
358
376
|
|
|
359
377
|
def modified_at(token)
|
|
@@ -390,7 +408,8 @@ module Profiler
|
|
|
390
408
|
"compacted_at" => Time.now.to_f)]
|
|
391
409
|
@entries.each_value do |entry|
|
|
392
410
|
lines << JSON.generate("op" => "put", "token" => entry.token, "at" => entry.at, "type" => entry.type,
|
|
393
|
-
"parent" => entry.parent, "
|
|
411
|
+
"parent" => entry.parent, "trace" => entry.trace, "bytes" => entry.bytes,
|
|
412
|
+
"summary" => entry.summary)
|
|
394
413
|
end
|
|
395
414
|
data = "#{lines.join("\n")}\n"
|
|
396
415
|
PrivateFiles.write(@index_path, data)
|
|
@@ -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"], {}),
|
|
@@ -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
|
|
@@ -260,6 +260,7 @@ files:
|
|
|
260
260
|
- lib/profiler/test_runner/discovery.rb
|
|
261
261
|
- lib/profiler/test_runner/run_store.rb
|
|
262
262
|
- lib/profiler/test_runner/runner.rb
|
|
263
|
+
- lib/profiler/trace_context.rb
|
|
263
264
|
- lib/profiler/version.rb
|
|
264
265
|
homepage: https://github.com/Pepsit36/rails-profiler
|
|
265
266
|
licenses:
|