rails-profiler 0.4.0 → 0.6.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/lib/profiler/instrumentation/active_job_instrumentation.rb +1 -1
- data/lib/profiler/instrumentation/net_http_instrumentation.rb +7 -0
- data/lib/profiler/mcp/body_formatter.rb +50 -0
- data/lib/profiler/mcp/file_cache.rb +31 -0
- data/lib/profiler/mcp/path_extractor.rb +30 -0
- data/lib/profiler/mcp/server.rb +30 -12
- data/lib/profiler/mcp/tools/analyze_queries.rb +46 -45
- data/lib/profiler/mcp/tools/get_profile_ajax.rb +5 -1
- data/lib/profiler/mcp/tools/get_profile_detail.rb +276 -194
- data/lib/profiler/mcp/tools/get_profile_dumps.rb +5 -1
- data/lib/profiler/mcp/tools/get_profile_http.rb +69 -21
- data/lib/profiler/mcp/tools/query_jobs.rb +37 -14
- data/lib/profiler/mcp/tools/query_profiles.rb +42 -27
- data/lib/profiler/models/profile.rb +8 -0
- data/lib/profiler/version.rb +1 -1
- metadata +5 -2
|
@@ -18,7 +18,11 @@ module Profiler
|
|
|
18
18
|
]
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
profile =
|
|
21
|
+
profile = if token == "latest"
|
|
22
|
+
Profiler.storage.list(limit: 1).first
|
|
23
|
+
else
|
|
24
|
+
Profiler.storage.load(token)
|
|
25
|
+
end
|
|
22
26
|
unless profile
|
|
23
27
|
return [
|
|
24
28
|
{
|
|
@@ -28,7 +32,7 @@ module Profiler
|
|
|
28
32
|
]
|
|
29
33
|
end
|
|
30
34
|
|
|
31
|
-
text = format_profile_detail(profile)
|
|
35
|
+
text = format_profile_detail(profile, params)
|
|
32
36
|
|
|
33
37
|
[
|
|
34
38
|
{
|
|
@@ -40,7 +44,29 @@ module Profiler
|
|
|
40
44
|
|
|
41
45
|
private
|
|
42
46
|
|
|
43
|
-
def self.format_profile_detail(profile)
|
|
47
|
+
def self.format_profile_detail(profile, params = {})
|
|
48
|
+
requested = params["sections"]&.map(&:to_s)
|
|
49
|
+
want = ->(name) { requested.nil? || requested.include?(name) }
|
|
50
|
+
|
|
51
|
+
lines = []
|
|
52
|
+
lines += section_overview(profile) if want.("overview")
|
|
53
|
+
lines += section_exception(profile) if want.("exception")
|
|
54
|
+
lines += section_job(profile) if want.("job")
|
|
55
|
+
lines += section_request(profile, params) if want.("request")
|
|
56
|
+
lines += section_response(profile, params) if want.("response")
|
|
57
|
+
lines += section_curl(profile) if want.("curl")
|
|
58
|
+
lines += section_database(profile) if want.("database")
|
|
59
|
+
lines += section_performance(profile) if want.("performance")
|
|
60
|
+
lines += section_views(profile) if want.("views")
|
|
61
|
+
lines += section_cache(profile) if want.("cache")
|
|
62
|
+
lines += section_ajax(profile) if want.("ajax")
|
|
63
|
+
lines += section_http(profile) if want.("http")
|
|
64
|
+
lines += section_routes(profile) if want.("routes")
|
|
65
|
+
lines += section_dumps(profile) if want.("dumps")
|
|
66
|
+
lines.join("\n")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def self.section_overview(profile)
|
|
44
70
|
lines = []
|
|
45
71
|
lines << "# Profile Details: #{profile.token}\n"
|
|
46
72
|
lines << "**Request:** #{profile.method} #{profile.path}"
|
|
@@ -48,260 +74,316 @@ module Profiler
|
|
|
48
74
|
lines << "**Duration:** #{profile.duration.round(2)} ms"
|
|
49
75
|
lines << "**Memory:** #{(profile.memory / 1024.0 / 1024.0).round(2)} MB" if profile.memory
|
|
50
76
|
lines << "**Time:** #{profile.started_at}\n"
|
|
77
|
+
lines
|
|
78
|
+
end
|
|
51
79
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
80
|
+
def self.section_exception(profile)
|
|
81
|
+
lines = []
|
|
82
|
+
exception_data = profile.collector_data("exception")
|
|
83
|
+
return lines unless exception_data && exception_data["exception_class"]
|
|
84
|
+
|
|
85
|
+
lines << "## Exception"
|
|
86
|
+
lines << "**Class:** #{exception_data['exception_class']}"
|
|
87
|
+
lines << "**Message:** #{exception_data['message']}\n"
|
|
88
|
+
|
|
89
|
+
backtrace = exception_data["backtrace"]
|
|
90
|
+
if backtrace && !backtrace.empty?
|
|
91
|
+
lines << "### Backtrace"
|
|
92
|
+
backtrace.first(20).each do |frame|
|
|
93
|
+
marker = frame["app_frame"] ? "★ " : " "
|
|
94
|
+
lines << "#{marker}#{frame['location']}"
|
|
64
95
|
end
|
|
65
96
|
lines << ""
|
|
66
97
|
end
|
|
98
|
+
lines
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def self.section_job(profile)
|
|
102
|
+
lines = []
|
|
103
|
+
job_data = profile.collector_data("job")
|
|
104
|
+
return lines unless job_data && job_data["job_class"]
|
|
105
|
+
|
|
106
|
+
lines << "## Job"
|
|
107
|
+
lines << "- Class: #{job_data['job_class']}"
|
|
108
|
+
lines << "- Job ID: #{job_data['job_id']}"
|
|
109
|
+
lines << "- Queue: #{job_data['queue']}"
|
|
110
|
+
lines << "- Executions: #{job_data['executions']}"
|
|
111
|
+
lines << "- Status: #{job_data['status']}"
|
|
112
|
+
lines << "- Error: #{job_data['error']}" if job_data["error"]
|
|
113
|
+
if job_data["arguments"] && !job_data["arguments"].empty?
|
|
114
|
+
lines << "- Arguments: #{job_data['arguments'].map(&:to_s).join(', ')}"
|
|
115
|
+
end
|
|
116
|
+
lines << ""
|
|
117
|
+
lines
|
|
118
|
+
end
|
|
67
119
|
|
|
68
|
-
|
|
120
|
+
def self.section_request(profile, params)
|
|
121
|
+
lines = []
|
|
69
122
|
req_data = profile.collector_data("request")
|
|
70
|
-
|
|
71
|
-
params = req_data["params"]
|
|
72
|
-
headers = req_data["headers"]
|
|
73
|
-
|
|
74
|
-
if params && !params.empty?
|
|
75
|
-
lines << "## Request Params"
|
|
76
|
-
params.each { |k, v| lines << "- **#{k}**: #{v}" }
|
|
77
|
-
lines << ""
|
|
78
|
-
end
|
|
123
|
+
return lines unless req_data
|
|
79
124
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
headers.each { |k, v| lines << "- **#{k}**: #{v}" }
|
|
83
|
-
lines << ""
|
|
84
|
-
end
|
|
125
|
+
request_params = req_data["params"]
|
|
126
|
+
headers = req_data["headers"]
|
|
85
127
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if enc == "base64"
|
|
91
|
-
lines << "_[binary, base64-encoded]_"
|
|
92
|
-
else
|
|
93
|
-
lines << "```"
|
|
94
|
-
lines << req_body
|
|
95
|
-
lines << "```"
|
|
96
|
-
end
|
|
97
|
-
lines << ""
|
|
98
|
-
end
|
|
128
|
+
if request_params && !request_params.empty?
|
|
129
|
+
lines << "## Request Params"
|
|
130
|
+
request_params.each { |k, v| lines << "- **#{k}**: #{v}" }
|
|
131
|
+
lines << ""
|
|
99
132
|
end
|
|
100
133
|
|
|
101
|
-
|
|
134
|
+
if headers && !headers.empty?
|
|
135
|
+
lines << "## Request Headers"
|
|
136
|
+
headers.each { |k, v| lines << "- **#{k}**: #{v}" }
|
|
137
|
+
lines << ""
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
req_body = req_data["request_body"]
|
|
141
|
+
if req_body && !req_body.empty?
|
|
142
|
+
lines << "## Request Body"
|
|
143
|
+
formatted = BodyFormatter.format_body(
|
|
144
|
+
profile.token,
|
|
145
|
+
"request_body",
|
|
146
|
+
req_body,
|
|
147
|
+
req_data["request_body_encoding"],
|
|
148
|
+
params
|
|
149
|
+
)
|
|
150
|
+
lines << formatted if formatted
|
|
151
|
+
lines << ""
|
|
152
|
+
end
|
|
153
|
+
lines
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def self.section_response(profile, params)
|
|
157
|
+
lines = []
|
|
158
|
+
|
|
102
159
|
if profile.response_headers&.any?
|
|
103
160
|
lines << "## Response Headers"
|
|
104
161
|
profile.response_headers.each { |k, v| lines << "- **#{k}**: #{v}" }
|
|
105
162
|
lines << ""
|
|
106
163
|
end
|
|
107
164
|
|
|
108
|
-
# Response Body section
|
|
109
165
|
resp_body = profile.response_body
|
|
110
166
|
if resp_body && !resp_body.empty?
|
|
111
|
-
enc = profile.response_body_encoding
|
|
112
167
|
lines << "## Response Body"
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
168
|
+
formatted = BodyFormatter.format_body(
|
|
169
|
+
profile.token,
|
|
170
|
+
"response_body",
|
|
171
|
+
resp_body,
|
|
172
|
+
profile.response_body_encoding,
|
|
173
|
+
params
|
|
174
|
+
)
|
|
175
|
+
lines << formatted if formatted
|
|
120
176
|
lines << ""
|
|
121
177
|
end
|
|
178
|
+
lines
|
|
179
|
+
end
|
|
122
180
|
|
|
123
|
-
|
|
124
|
-
|
|
181
|
+
def self.section_curl(profile)
|
|
182
|
+
req_data = profile.collector_data("request")
|
|
183
|
+
lines = []
|
|
125
184
|
lines << "## Curl Command"
|
|
126
185
|
lines << "```bash"
|
|
127
|
-
lines << generate_curl(profile,
|
|
186
|
+
lines << generate_curl(profile, req_data)
|
|
128
187
|
lines << "```"
|
|
129
188
|
lines << ""
|
|
189
|
+
lines
|
|
190
|
+
end
|
|
130
191
|
|
|
131
|
-
|
|
192
|
+
def self.section_database(profile)
|
|
193
|
+
lines = []
|
|
132
194
|
db_data = profile.collector_data("database")
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
195
|
+
return lines unless db_data && db_data["total_queries"]
|
|
196
|
+
|
|
197
|
+
lines << "## Database"
|
|
198
|
+
lines << "- Total Queries: #{db_data['total_queries']}"
|
|
199
|
+
lines << "- Total Duration: #{db_data['total_duration'].round(2)} ms"
|
|
200
|
+
lines << "- Slow Queries: #{db_data['slow_queries']}"
|
|
201
|
+
lines << "- Cached Queries: #{db_data['cached_queries']}\n"
|
|
202
|
+
|
|
203
|
+
if db_data["queries"] && !db_data["queries"].empty?
|
|
204
|
+
lines << "### Query Details"
|
|
205
|
+
db_data["queries"].each_with_index do |query, index|
|
|
206
|
+
lines << "\n**Query #{index + 1}** (#{query['duration'].round(2)}ms):"
|
|
207
|
+
lines << "```sql"
|
|
208
|
+
lines << query["sql"]
|
|
209
|
+
lines << "```"
|
|
210
|
+
if query["backtrace"] && !query["backtrace"].empty?
|
|
211
|
+
lines << "_Backtrace:_"
|
|
212
|
+
query["backtrace"].first(3).each { |frame| lines << " #{frame}" }
|
|
151
213
|
end
|
|
152
214
|
end
|
|
153
|
-
lines << ""
|
|
154
215
|
end
|
|
216
|
+
lines << ""
|
|
217
|
+
lines
|
|
218
|
+
end
|
|
155
219
|
|
|
156
|
-
|
|
220
|
+
def self.section_performance(profile)
|
|
221
|
+
lines = []
|
|
157
222
|
perf_data = profile.collector_data("performance")
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
223
|
+
return lines unless perf_data && perf_data["total_events"]
|
|
224
|
+
|
|
225
|
+
lines << "## Performance Timeline"
|
|
226
|
+
lines << "- Total Events: #{perf_data['total_events']}"
|
|
227
|
+
lines << "- Total Duration: #{perf_data['total_duration'].round(2)} ms\n"
|
|
228
|
+
|
|
229
|
+
if perf_data["events"] && !perf_data["events"].empty?
|
|
230
|
+
lines << "### Events"
|
|
231
|
+
perf_data["events"].each do |event|
|
|
232
|
+
lines << "- **#{event['name']}**: #{event['duration'].round(2)} ms"
|
|
168
233
|
end
|
|
169
|
-
lines << ""
|
|
170
234
|
end
|
|
235
|
+
lines << ""
|
|
236
|
+
lines
|
|
237
|
+
end
|
|
171
238
|
|
|
172
|
-
|
|
239
|
+
def self.section_views(profile)
|
|
240
|
+
lines = []
|
|
173
241
|
view_data = profile.collector_data("view")
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
lines << ""
|
|
242
|
+
return lines unless view_data && (view_data["total_views"] || view_data["total_partials"])
|
|
243
|
+
|
|
244
|
+
lines << "## View Rendering"
|
|
245
|
+
lines << "- Templates: #{view_data['total_views']}"
|
|
246
|
+
lines << "- Partials: #{view_data['total_partials']}"
|
|
247
|
+
lines << "- Total Duration: #{view_data['total_duration'].round(2)} ms\n"
|
|
248
|
+
|
|
249
|
+
if view_data["views"] && !view_data["views"].empty?
|
|
250
|
+
lines << "### Templates"
|
|
251
|
+
view_data["views"].each do |view|
|
|
252
|
+
lines << "- `#{view['identifier']}` — #{view['duration'].round(2)} ms"
|
|
186
253
|
end
|
|
254
|
+
lines << ""
|
|
255
|
+
end
|
|
187
256
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
end
|
|
193
|
-
lines << ""
|
|
257
|
+
if view_data["partials"] && !view_data["partials"].empty?
|
|
258
|
+
lines << "### Partials"
|
|
259
|
+
view_data["partials"].each do |partial|
|
|
260
|
+
lines << "- `#{partial['identifier']}` — #{partial['duration'].round(2)} ms"
|
|
194
261
|
end
|
|
262
|
+
lines << ""
|
|
195
263
|
end
|
|
264
|
+
lines
|
|
265
|
+
end
|
|
196
266
|
|
|
197
|
-
|
|
267
|
+
def self.section_cache(profile)
|
|
268
|
+
lines = []
|
|
198
269
|
cache_data = profile.collector_data("cache")
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
lines << ""
|
|
270
|
+
return lines unless cache_data && cache_data["total_reads"]
|
|
271
|
+
|
|
272
|
+
lines << "## Cache"
|
|
273
|
+
lines << "- Reads: #{cache_data['total_reads']}"
|
|
274
|
+
lines << "- Writes: #{cache_data['total_writes']}"
|
|
275
|
+
lines << "- Deletes: #{cache_data['total_deletes']}"
|
|
276
|
+
lines << "- Hit Rate: #{cache_data['hit_rate']}%\n"
|
|
277
|
+
|
|
278
|
+
if cache_data["reads"] && !cache_data["reads"].empty?
|
|
279
|
+
lines << "### Cache Reads"
|
|
280
|
+
cache_data["reads"].each do |op|
|
|
281
|
+
hit_label = op["hit"] ? "HIT" : "MISS"
|
|
282
|
+
lines << "- [#{hit_label}] `#{op['key']}` — #{op['duration'].round(2)} ms"
|
|
213
283
|
end
|
|
284
|
+
lines << ""
|
|
285
|
+
end
|
|
214
286
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
end
|
|
220
|
-
lines << ""
|
|
287
|
+
if cache_data["writes"] && !cache_data["writes"].empty?
|
|
288
|
+
lines << "### Cache Writes"
|
|
289
|
+
cache_data["writes"].each do |op|
|
|
290
|
+
lines << "- `#{op['key']}` — #{op['duration'].round(2)} ms"
|
|
221
291
|
end
|
|
292
|
+
lines << ""
|
|
293
|
+
end
|
|
222
294
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
end
|
|
228
|
-
lines << ""
|
|
295
|
+
if cache_data["deletes"] && !cache_data["deletes"].empty?
|
|
296
|
+
lines << "### Cache Deletes"
|
|
297
|
+
cache_data["deletes"].each do |op|
|
|
298
|
+
lines << "- `#{op['key']}` — #{op['duration'].round(2)} ms"
|
|
229
299
|
end
|
|
300
|
+
lines << ""
|
|
230
301
|
end
|
|
302
|
+
lines
|
|
303
|
+
end
|
|
231
304
|
|
|
232
|
-
|
|
305
|
+
def self.section_ajax(profile)
|
|
306
|
+
lines = []
|
|
233
307
|
ajax_data = profile.collector_data("ajax")
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
lines << ""
|
|
308
|
+
return lines unless ajax_data && ajax_data["total_requests"].to_i > 0
|
|
309
|
+
|
|
310
|
+
lines << "## AJAX Requests"
|
|
311
|
+
lines << "- Total: #{ajax_data['total_requests']}"
|
|
312
|
+
lines << "- Total Duration: #{ajax_data['total_duration'].round(2)} ms\n"
|
|
313
|
+
|
|
314
|
+
if ajax_data["requests"] && !ajax_data["requests"].empty?
|
|
315
|
+
lines << "### Request List"
|
|
316
|
+
ajax_data["requests"].each do |req|
|
|
317
|
+
lines << "- **#{req['method']} #{req['path']}** — #{req['status']} — #{req['duration'].round(2)} ms (token: #{req['token']})"
|
|
245
318
|
end
|
|
319
|
+
lines << ""
|
|
246
320
|
end
|
|
321
|
+
lines
|
|
322
|
+
end
|
|
247
323
|
|
|
248
|
-
|
|
324
|
+
def self.section_http(profile)
|
|
325
|
+
lines = []
|
|
249
326
|
http_data = profile.collector_data("http")
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
lines << ""
|
|
327
|
+
return lines unless http_data && http_data["total_requests"].to_i > 0
|
|
328
|
+
|
|
329
|
+
threshold = Profiler.configuration.slow_http_threshold
|
|
330
|
+
lines << "## Outbound HTTP"
|
|
331
|
+
lines << "- Total: #{http_data['total_requests']}"
|
|
332
|
+
lines << "- Total Duration: #{http_data['total_duration'].round(2)} ms"
|
|
333
|
+
lines << "- Slow (>#{threshold}ms): #{http_data['slow_requests']}"
|
|
334
|
+
lines << "- Errors: #{http_data['error_requests']}\n"
|
|
335
|
+
|
|
336
|
+
if http_data["requests"] && !http_data["requests"].empty?
|
|
337
|
+
lines << "### Request List"
|
|
338
|
+
http_data["requests"].each do |req|
|
|
339
|
+
flag = req["duration"] >= threshold ? " [SLOW]" : ""
|
|
340
|
+
err = req["status"] >= 400 || req["status"] == 0 ? " [ERROR]" : ""
|
|
341
|
+
lines << "- **#{req['method']} #{req['url']}** — #{req['status'] == 0 ? 'error' : req['status']} — #{req['duration'].round(2)} ms#{flag}#{err}"
|
|
266
342
|
end
|
|
343
|
+
lines << ""
|
|
267
344
|
end
|
|
345
|
+
lines
|
|
346
|
+
end
|
|
268
347
|
|
|
269
|
-
|
|
348
|
+
def self.section_routes(profile)
|
|
349
|
+
lines = []
|
|
270
350
|
routes_data = profile.collector_data("routes")
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
lines << ""
|
|
351
|
+
return lines unless routes_data && routes_data["total"].to_i > 0
|
|
352
|
+
|
|
353
|
+
lines << "## Routes"
|
|
354
|
+
lines << "- Total routes: #{routes_data['total']}"
|
|
355
|
+
|
|
356
|
+
matched = routes_data["matched"]
|
|
357
|
+
if matched
|
|
358
|
+
lines << "- **Matched:** `#{matched['verb']} #{matched['pattern']}`"
|
|
359
|
+
lines << " - Route name: #{matched['name']}_path" if matched["name"]
|
|
360
|
+
lines << " - Controller#Action: #{matched['controller_action']}" if matched["controller_action"]
|
|
361
|
+
else
|
|
362
|
+
lines << "- No route matched"
|
|
284
363
|
end
|
|
364
|
+
lines << ""
|
|
365
|
+
lines
|
|
366
|
+
end
|
|
285
367
|
|
|
286
|
-
|
|
368
|
+
def self.section_dumps(profile)
|
|
369
|
+
lines = []
|
|
287
370
|
dump_data = profile.collector_data("dump")
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
lines << ""
|
|
371
|
+
return lines unless dump_data && dump_data["count"].to_i > 0
|
|
372
|
+
|
|
373
|
+
lines << "## Variable Dumps"
|
|
374
|
+
lines << "- Count: #{dump_data['count']}\n"
|
|
375
|
+
|
|
376
|
+
dump_data["dumps"]&.each_with_index do |dump, index|
|
|
377
|
+
label = dump["label"] || "Dump #{index + 1}"
|
|
378
|
+
location = [dump["file"], dump["line"]].compact.join(":")
|
|
379
|
+
lines << "### #{label}"
|
|
380
|
+
lines << "_Source: #{location}_" unless location.empty?
|
|
381
|
+
lines << "```"
|
|
382
|
+
lines << (dump["formatted"] || dump["value"].inspect)
|
|
383
|
+
lines << "```"
|
|
302
384
|
end
|
|
303
|
-
|
|
304
|
-
lines
|
|
385
|
+
lines << ""
|
|
386
|
+
lines
|
|
305
387
|
end
|
|
306
388
|
|
|
307
389
|
def self.generate_curl(profile, req_data)
|
|
@@ -10,7 +10,11 @@ module Profiler
|
|
|
10
10
|
return [{ type: "text", text: "Error: token parameter is required" }]
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
profile =
|
|
13
|
+
profile = if token == "latest"
|
|
14
|
+
Profiler.storage.list(limit: 1).first
|
|
15
|
+
else
|
|
16
|
+
Profiler.storage.load(token)
|
|
17
|
+
end
|
|
14
18
|
unless profile
|
|
15
19
|
return [{ type: "text", text: "Profile not found: #{token}" }]
|
|
16
20
|
end
|