29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
# File 'lib/profiler/mcp/tools/get_test_profile_detail.rb', line 29
def self.format_test_detail(profile)
test_data = profile.collector_data("test") || {}
db_data = profile.collector_data("database") || {}
cache_data = profile.collector_data("cache") || {}
exc_data = profile.collector_data("exception") || {}
queries = db_data["queries"] || []
n1_count = count_n1_patterns(queries)
lines = []
lines << "# Test Profile Detail\n"
lines << "## Overview"
lines << "| Field | Value |"
lines << "|-------|-------|"
lines << "| Token | `#{profile.token}` |"
lines << "| Test name | #{test_data["test_name"] || profile.path} |"
lines << "| Status | #{test_data["status"] || "unknown"} |"
lines << "| Framework | #{test_data["framework"]} |"
lines << "| File | #{test_data["test_file"]}:#{test_data["test_line"]} |"
lines << "| Duration | #{profile.duration&.round(2)}ms |"
lines << "| Assertions | #{test_data["assertions"] || "-"} |"
lines << "| Memory delta | #{profile.memory ? "#{(profile.memory.to_f / 1024 / 1024).round(2)} MB" : "-"} |"
lines << "| Time | #{profile.started_at&.strftime("%H:%M:%S")} |"
if test_data["exception_message"]
lines << "\n## Exception"
lines << "```\n#{test_data["exception_message"]}\n```"
elsif test_data["skip_reason"]
lines << "\n## Skip reason"
lines << test_data["skip_reason"].to_s
end
if exc_data["exception_class"]
lines << "\n## Unhandled Exception"
lines << "**#{exc_data["exception_class"]}**: #{exc_data["exception_message"]}"
if (backtrace = exc_data["backtrace"]).is_a?(Array) && backtrace.any?
lines << "\n```"
backtrace.first(5).each { |l| lines << l }
lines << "```"
end
end
lines << "\n## Database (#{db_data["total_queries"].to_i} queries · #{db_data["total_duration"].to_f.round(2)}ms · #{n1_count} N+1 patterns)"
if queries.any?
slow_threshold = Profiler.configuration.slow_query_threshold
slow = queries.select { |q| q["duration"].to_f >= slow_threshold }
show = slow.any? ? slow.first(10) : queries.first(10)
caption = slow.any? ? "Slowest queries:" : "First queries:"
lines << caption
lines << "| # | Duration | SQL |"
lines << "|---|----------|-----|"
show.each_with_index do |q, i|
sql = q["sql"].to_s.gsub("|", "\\|").then { |s| s.length > 100 ? s[0, 97] + "..." : s }
lines << "| #{i + 1} | #{q["duration"].to_f.round(2)}ms | `#{sql}` |"
end
if n1_count > 0
lines << "\n### N+1 Patterns Detected"
queries.group_by { |q| normalize_sql(q["sql"].to_s) }
.select { |_, qs| qs.size >= 3 }
.each do |pattern, qs|
lines << "- `#{pattern[0, 120]}` (×#{qs.size})"
end
end
else
lines << "_No SQL queries recorded._"
end
total_cache = cache_data["total_reads"].to_i + cache_data["total_writes"].to_i + cache_data["total_deletes"].to_i
if total_cache > 0
lines << "\n## Cache (#{total_cache} operations)"
lines << "- Reads: #{cache_data["total_reads"].to_i}"
lines << "- Writes: #{cache_data["total_writes"].to_i}"
lines << "- Deletes: #{cache_data["total_deletes"].to_i}"
lines << "- Misses: #{cache_data["total_misses"].to_i}"
end
lines.join("\n")
end
|