rails-profiler 0.3.0 → 0.4.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/app/assets/builds/profiler-toolbar.js +57 -0
- data/app/assets/builds/profiler.js +62 -2
- data/lib/profiler/collectors/i18n_collector.rb +100 -0
- data/lib/profiler/railtie.rb +2 -1
- data/lib/profiler/version.rb +1 -1
- data/lib/profiler.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9e97a77a23dca55d709a1512c3c2ab3b95f0e6b15ed01b7769852587b962be8a
|
|
4
|
+
data.tar.gz: 131c40f561129e8835c41a0b3ba456431bb11fe791c8b0add6396fbbfe0be293
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7565ae06d41a7bd2d574610962e20fc3c4a6fcba5bda89482e840fb09af57c686eff9e8f4ccb02bfde05d53e0b5cc8019622ee7ea4db44fc85d6f5a0bc09c8c6
|
|
7
|
+
data.tar.gz: bea8aee55cc58b41fc6d01c2ad4dffcdd9ecfdfc4617a45ba16b019577ad5209a2bcae5c9d226d0d5e31af1fbb3e508db8a7e3eae25c5c87f920dcad3e48d1f8
|
|
@@ -946,6 +946,46 @@
|
|
|
946
946
|
] });
|
|
947
947
|
}
|
|
948
948
|
|
|
949
|
+
// app/assets/typescript/profiler/components/toolbar/panels/I18nPanel.tsx
|
|
950
|
+
function I18nPanel({ i18nData }) {
|
|
951
|
+
const missing = i18nData.lookups.filter((l3) => l3.missing);
|
|
952
|
+
const ok = i18nData.lookups.filter((l3) => !l3.missing);
|
|
953
|
+
const prioritized = [...missing, ...ok].slice(0, 5);
|
|
954
|
+
const remaining = i18nData.total - 5;
|
|
955
|
+
return /* @__PURE__ */ u3(k, { children: [
|
|
956
|
+
/* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-header", children: [
|
|
957
|
+
"I18n",
|
|
958
|
+
/* @__PURE__ */ u3("span", { style: "margin-left:6px;font-weight:400;color:var(--profiler-muted,#6b7280);", children: [
|
|
959
|
+
"[",
|
|
960
|
+
i18nData.locale,
|
|
961
|
+
"]"
|
|
962
|
+
] }),
|
|
963
|
+
i18nData.missing_count > 0 && /* @__PURE__ */ u3("span", { style: "color:var(--profiler-error,#ef4444);margin-left:8px;", children: [
|
|
964
|
+
i18nData.missing_count,
|
|
965
|
+
" missing"
|
|
966
|
+
] })
|
|
967
|
+
] }),
|
|
968
|
+
/* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-content", children: [
|
|
969
|
+
prioritized.map((entry, i3) => /* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-row", style: "align-items:flex-start;gap:6px;", children: [
|
|
970
|
+
/* @__PURE__ */ u3(
|
|
971
|
+
"span",
|
|
972
|
+
{
|
|
973
|
+
class: "profiler-text--xs profiler-text--truncate",
|
|
974
|
+
style: `flex:1;max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;${entry.missing ? "color:var(--profiler-error,#ef4444);" : ""}`,
|
|
975
|
+
children: entry.key
|
|
976
|
+
}
|
|
977
|
+
),
|
|
978
|
+
entry.missing && /* @__PURE__ */ u3("span", { class: "profiler-text--xs", style: "color:var(--profiler-error,#ef4444);font-weight:600;", children: "\u26A0" })
|
|
979
|
+
] }, i3)),
|
|
980
|
+
remaining > 0 && /* @__PURE__ */ u3("div", { class: "profiler-more", children: [
|
|
981
|
+
"+ ",
|
|
982
|
+
remaining,
|
|
983
|
+
" more keys"
|
|
984
|
+
] })
|
|
985
|
+
] })
|
|
986
|
+
] });
|
|
987
|
+
}
|
|
988
|
+
|
|
949
989
|
// app/assets/typescript/profiler/components/toolbar/ToolbarApp.tsx
|
|
950
990
|
function statusClass2(status) {
|
|
951
991
|
if (status >= 200 && status < 300) return "profiler-text--success";
|
|
@@ -979,6 +1019,7 @@
|
|
|
979
1019
|
const logData = cd["logs"];
|
|
980
1020
|
const exceptionData = cd["exception"];
|
|
981
1021
|
const routesData = cd["routes"];
|
|
1022
|
+
const i18nData = cd["i18n"];
|
|
982
1023
|
const reqClass = statusClass2(profile.status);
|
|
983
1024
|
const durClass = durationClass(profile.duration);
|
|
984
1025
|
const dbClass = (dbData?.slow_queries ?? 0) > 0 ? "profiler-text--error" : "profiler-text--success";
|
|
@@ -1186,6 +1227,22 @@
|
|
|
1186
1227
|
]
|
|
1187
1228
|
}
|
|
1188
1229
|
),
|
|
1230
|
+
i18nData && i18nData.total > 0 && /* @__PURE__ */ u3(
|
|
1231
|
+
ToolbarItem,
|
|
1232
|
+
{
|
|
1233
|
+
href: `/_profiler/profiles/${token}?tab=i18n`,
|
|
1234
|
+
className: i18nData.missing_count > 0 ? "profiler-text--error" : "profiler-text--muted",
|
|
1235
|
+
panel: /* @__PURE__ */ u3(I18nPanel, { i18nData }),
|
|
1236
|
+
children: [
|
|
1237
|
+
/* @__PURE__ */ u3("span", { class: "profiler-text--muted profiler-text--xs", children: "I18N" }),
|
|
1238
|
+
/* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--mono", children: i18nData.locale }),
|
|
1239
|
+
i18nData.missing_count > 0 && /* @__PURE__ */ u3("span", { class: "profiler-text--error profiler-text--xs", children: [
|
|
1240
|
+
"\u26A0 ",
|
|
1241
|
+
i18nData.missing_count
|
|
1242
|
+
] })
|
|
1243
|
+
]
|
|
1244
|
+
}
|
|
1245
|
+
),
|
|
1189
1246
|
/* @__PURE__ */ u3("a", { href: "/_profiler", class: "profiler-toolbar-item", children: "\u2B21 Profiler" })
|
|
1190
1247
|
] });
|
|
1191
1248
|
}
|
|
@@ -2419,6 +2419,63 @@
|
|
|
2419
2419
|
] });
|
|
2420
2420
|
}
|
|
2421
2421
|
|
|
2422
|
+
// app/assets/typescript/profiler/components/dashboard/tabs/I18nTab.tsx
|
|
2423
|
+
function I18nTab({ i18nData }) {
|
|
2424
|
+
const [filter, setFilter] = d2("ALL");
|
|
2425
|
+
if (!i18nData?.lookups?.length) {
|
|
2426
|
+
return /* @__PURE__ */ u3("div", { class: "profiler-empty", children: [
|
|
2427
|
+
/* @__PURE__ */ u3("div", { class: "profiler-empty__icon", children: "\u{1F310}" }),
|
|
2428
|
+
/* @__PURE__ */ u3("h3", { class: "profiler-empty__title", children: "No translation lookups captured" }),
|
|
2429
|
+
/* @__PURE__ */ u3("div", { class: "profiler-empty__description", children: /* @__PURE__ */ u3("p", { children: [
|
|
2430
|
+
"Calls to ",
|
|
2431
|
+
/* @__PURE__ */ u3("code", { children: "I18n.t" }),
|
|
2432
|
+
" during this request will appear here."
|
|
2433
|
+
] }) })
|
|
2434
|
+
] });
|
|
2435
|
+
}
|
|
2436
|
+
const filtered = filter === "MISSING" ? i18nData.lookups.filter((l3) => l3.missing) : i18nData.lookups;
|
|
2437
|
+
return /* @__PURE__ */ u3(k, { children: [
|
|
2438
|
+
/* @__PURE__ */ u3("h2", { class: "profiler-section__header", children: [
|
|
2439
|
+
"I18n Lookups (",
|
|
2440
|
+
i18nData.total,
|
|
2441
|
+
")"
|
|
2442
|
+
] }),
|
|
2443
|
+
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-4 profiler-mb-4 profiler-text--sm", children: [
|
|
2444
|
+
/* @__PURE__ */ u3("span", { children: [
|
|
2445
|
+
"Locale: ",
|
|
2446
|
+
/* @__PURE__ */ u3("strong", { children: i18nData.locale })
|
|
2447
|
+
] }),
|
|
2448
|
+
i18nData.missing_count > 0 && /* @__PURE__ */ u3("span", { children: [
|
|
2449
|
+
"Missing: ",
|
|
2450
|
+
/* @__PURE__ */ u3("strong", { class: "profiler-text--error", children: i18nData.missing_count })
|
|
2451
|
+
] })
|
|
2452
|
+
] }),
|
|
2453
|
+
/* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-2 profiler-mb-4", children: ["ALL", "MISSING"].map((f4) => /* @__PURE__ */ u3(
|
|
2454
|
+
"button",
|
|
2455
|
+
{
|
|
2456
|
+
onClick: () => setFilter(f4),
|
|
2457
|
+
class: `btn btn-sm ${filter === f4 ? "btn-primary" : "btn-secondary"}`,
|
|
2458
|
+
children: f4
|
|
2459
|
+
},
|
|
2460
|
+
f4
|
|
2461
|
+
)) }),
|
|
2462
|
+
filtered.length === 0 ? /* @__PURE__ */ u3("div", { class: "profiler-text--muted profiler-text--sm", children: "No missing translations." }) : /* @__PURE__ */ u3("table", { class: "profiler-table", children: [
|
|
2463
|
+
/* @__PURE__ */ u3("thead", { children: /* @__PURE__ */ u3("tr", { children: [
|
|
2464
|
+
/* @__PURE__ */ u3("th", { children: "Key" }),
|
|
2465
|
+
/* @__PURE__ */ u3("th", { children: "Locale" }),
|
|
2466
|
+
/* @__PURE__ */ u3("th", { children: "Value" }),
|
|
2467
|
+
/* @__PURE__ */ u3("th", { children: "Status" })
|
|
2468
|
+
] }) }),
|
|
2469
|
+
/* @__PURE__ */ u3("tbody", { children: filtered.map((entry, index) => /* @__PURE__ */ u3("tr", { style: entry.missing ? "background:var(--profiler-error-bg,rgba(239,68,68,0.08));" : "", children: [
|
|
2470
|
+
/* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("code", { class: `profiler-text--xs profiler-text--mono${entry.missing ? " profiler-text--error" : ""}`, children: entry.key }) }),
|
|
2471
|
+
/* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--muted", children: entry.locale }) }),
|
|
2472
|
+
/* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("span", { class: `profiler-text--xs${entry.missing ? " profiler-text--error" : ""}`, children: entry.value }) }),
|
|
2473
|
+
/* @__PURE__ */ u3("td", { children: entry.missing ? /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--error", style: "font-weight:600;", children: "\u26A0 missing" }) : /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--success", children: "\u2713" }) })
|
|
2474
|
+
] }, index)) })
|
|
2475
|
+
] })
|
|
2476
|
+
] });
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2422
2479
|
// app/assets/typescript/profiler/components/dashboard/ProfileDashboard.tsx
|
|
2423
2480
|
function ProfileDashboard({ profile, initialTab, embedded }) {
|
|
2424
2481
|
const cd = profile.collectors_data || {};
|
|
@@ -2427,6 +2484,7 @@
|
|
|
2427
2484
|
const hasException = !!cd["exception"]?.exception_class;
|
|
2428
2485
|
const hasLogs = (cd["logs"]?.count ?? 0) > 0;
|
|
2429
2486
|
const hasRoutes = (cd["routes"]?.total ?? 0) > 0;
|
|
2487
|
+
const hasI18n = (cd["i18n"]?.total ?? 0) > 0;
|
|
2430
2488
|
const [activeTab, setActiveTab] = d2(hasException ? "exception" : initialTab);
|
|
2431
2489
|
const handleTabClick = (tab) => (e3) => {
|
|
2432
2490
|
e3.preventDefault();
|
|
@@ -2480,7 +2538,8 @@
|
|
|
2480
2538
|
/* @__PURE__ */ u3("a", { href: "#", class: tabClass("views"), onClick: handleTabClick("views"), children: "Views" }),
|
|
2481
2539
|
/* @__PURE__ */ u3("a", { href: "#", class: tabClass("cache"), onClick: handleTabClick("cache"), children: "Cache" }),
|
|
2482
2540
|
hasLogs && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("logs"), onClick: handleTabClick("logs"), children: "Logs" }),
|
|
2483
|
-
hasRoutes && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("routes"), onClick: handleTabClick("routes"), children: "Routes" })
|
|
2541
|
+
hasRoutes && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("routes"), onClick: handleTabClick("routes"), children: "Routes" }),
|
|
2542
|
+
hasI18n && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("i18n"), onClick: handleTabClick("i18n"), children: "I18n" })
|
|
2484
2543
|
] }),
|
|
2485
2544
|
/* @__PURE__ */ u3("div", { class: "profiler-p-4 tab-content active", children: [
|
|
2486
2545
|
activeTab === "exception" && /* @__PURE__ */ u3(ExceptionTab, { exceptionData: cd["exception"] }),
|
|
@@ -2493,7 +2552,8 @@
|
|
|
2493
2552
|
activeTab === "views" && /* @__PURE__ */ u3(ViewsTab, { viewData: cd["view"] }),
|
|
2494
2553
|
activeTab === "cache" && /* @__PURE__ */ u3(CacheTab, { cacheData: cd["cache"] }),
|
|
2495
2554
|
activeTab === "logs" && /* @__PURE__ */ u3(LogsTab, { logData: cd["logs"] }),
|
|
2496
|
-
activeTab === "routes" && /* @__PURE__ */ u3(RoutesTab, { routesData: cd["routes"] })
|
|
2555
|
+
activeTab === "routes" && /* @__PURE__ */ u3(RoutesTab, { routesData: cd["routes"] }),
|
|
2556
|
+
activeTab === "i18n" && /* @__PURE__ */ u3(I18nTab, { i18nData: cd["i18n"] })
|
|
2497
2557
|
] })
|
|
2498
2558
|
] }),
|
|
2499
2559
|
!embedded && /* @__PURE__ */ u3("div", { class: "profiler-mt-6", children: /* @__PURE__ */ u3("a", { href: "/_profiler", style: "color: var(--profiler-accent);", children: "\u2190 Back to profiles" }) })
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_collector"
|
|
4
|
+
|
|
5
|
+
module Profiler
|
|
6
|
+
module I18nLookupTracker
|
|
7
|
+
def translate(key, **options)
|
|
8
|
+
result = super
|
|
9
|
+
if (collector = Thread.current[:profiler_i18n_collector])
|
|
10
|
+
missing = result.is_a?(String) && result.downcase.include?("translation missing:")
|
|
11
|
+
collector.record_lookup(key, options[:locale] || I18n.locale, result, missing)
|
|
12
|
+
end
|
|
13
|
+
result
|
|
14
|
+
rescue I18n::MissingTranslationData => e
|
|
15
|
+
if (collector = Thread.current[:profiler_i18n_collector])
|
|
16
|
+
collector.record_lookup(key, options[:locale] || I18n.locale, nil, true)
|
|
17
|
+
end
|
|
18
|
+
raise
|
|
19
|
+
end
|
|
20
|
+
alias_method :t, :translate
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module Collectors
|
|
24
|
+
class I18nCollector < BaseCollector
|
|
25
|
+
def initialize(profile)
|
|
26
|
+
super
|
|
27
|
+
@lookups = []
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def icon
|
|
31
|
+
"🌐"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def priority
|
|
35
|
+
45
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def tab_config
|
|
39
|
+
{
|
|
40
|
+
key: "i18n",
|
|
41
|
+
label: "I18n",
|
|
42
|
+
icon: icon,
|
|
43
|
+
priority: priority,
|
|
44
|
+
enabled: true,
|
|
45
|
+
default_active: false
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def subscribe
|
|
50
|
+
return unless defined?(I18n)
|
|
51
|
+
|
|
52
|
+
unless I18n.singleton_class.ancestors.include?(Profiler::I18nLookupTracker)
|
|
53
|
+
I18n.singleton_class.prepend(Profiler::I18nLookupTracker)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
Thread.current[:profiler_i18n_collector] = self
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def collect
|
|
60
|
+
Thread.current[:profiler_i18n_collector] = nil
|
|
61
|
+
|
|
62
|
+
missing_count = @lookups.count { |l| l[:missing] }
|
|
63
|
+
|
|
64
|
+
store_data(
|
|
65
|
+
locale: I18n.locale.to_s,
|
|
66
|
+
total: @lookups.size,
|
|
67
|
+
missing_count: missing_count,
|
|
68
|
+
lookups: @lookups
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def record_lookup(key, locale, result, missing = false)
|
|
73
|
+
value = missing ? "[missing]" : truncate(result.to_s)
|
|
74
|
+
|
|
75
|
+
@lookups << {
|
|
76
|
+
key: key.to_s,
|
|
77
|
+
locale: locale.to_s,
|
|
78
|
+
value: value,
|
|
79
|
+
missing: missing
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def toolbar_summary
|
|
84
|
+
missing = @lookups.count { |l| l[:missing] }
|
|
85
|
+
locale = I18n.locale.to_s
|
|
86
|
+
total = @lookups.size
|
|
87
|
+
|
|
88
|
+
color = missing > 0 ? "red" : "green"
|
|
89
|
+
|
|
90
|
+
{ text: "#{locale} · #{total} keys", color: color }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def truncate(str, max = 100)
|
|
96
|
+
str.length > max ? "#{str[0, max]}…" : str
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
data/lib/profiler/railtie.rb
CHANGED
|
@@ -45,7 +45,8 @@ module Profiler
|
|
|
45
45
|
Profiler::Collectors::HttpCollector,
|
|
46
46
|
Profiler::Collectors::FlameGraphCollector,
|
|
47
47
|
Profiler::Collectors::LogCollector,
|
|
48
|
-
Profiler::Collectors::RoutesCollector
|
|
48
|
+
Profiler::Collectors::RoutesCollector,
|
|
49
|
+
Profiler::Collectors::I18nCollector
|
|
49
50
|
]
|
|
50
51
|
end
|
|
51
52
|
end
|
data/lib/profiler/version.rb
CHANGED
data/lib/profiler.rb
CHANGED
|
@@ -66,6 +66,7 @@ require_relative "profiler/collectors/flamegraph_collector"
|
|
|
66
66
|
require_relative "profiler/collectors/log_collector"
|
|
67
67
|
require_relative "profiler/collectors/exception_collector"
|
|
68
68
|
require_relative "profiler/collectors/routes_collector"
|
|
69
|
+
require_relative "profiler/collectors/i18n_collector"
|
|
69
70
|
|
|
70
71
|
require_relative "profiler/railtie" if defined?(Rails::Railtie)
|
|
71
72
|
require_relative "profiler/engine" if defined?(Rails::Engine)
|
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.4.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-04-
|
|
11
|
+
date: 2026-04-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -129,6 +129,7 @@ files:
|
|
|
129
129
|
- lib/profiler/collectors/exception_collector.rb
|
|
130
130
|
- lib/profiler/collectors/flamegraph_collector.rb
|
|
131
131
|
- lib/profiler/collectors/http_collector.rb
|
|
132
|
+
- lib/profiler/collectors/i18n_collector.rb
|
|
132
133
|
- lib/profiler/collectors/job_collector.rb
|
|
133
134
|
- lib/profiler/collectors/log_collector.rb
|
|
134
135
|
- lib/profiler/collectors/performance_collector.rb
|