Line data Source code
1 1 : local M = {}
2 :
3 : -- Cap venv_detection_times at this many unique root directories.
4 : -- Projects accumulate across a session; without a bound this table grows forever.
5 1 : local MAX_VENV_ENTRIES = 50
6 1 : local venv_key_order = {} -- insertion-order list for FIFO eviction
7 :
8 1 : local metrics = {
9 1 : attach_times = {},
10 1 : venv_detection_times = {},
11 1 : restart_counts = {},
12 1 : config_times = {},
13 : }
14 :
15 1 : function M.track_lsp_attach(server_name, start_time)
16 : local elapsed = (vim.uv.hrtime() - start_time) / 1000000
17 :
18 : if not metrics.attach_times[server_name] then
19 : metrics.attach_times[server_name] = { total = 0, count = 0, max = 0, min = math.huge }
20 : end
21 :
22 : local metric = metrics.attach_times[server_name]
23 : metric.total = metric.total + elapsed
24 : metric.count = metric.count + 1
25 : metric.max = math.max(metric.max, elapsed)
26 : metric.min = math.min(metric.min, elapsed)
27 :
28 : if elapsed > 500 then
29 : vim.notify(string.format("Slow LSP attach: %s took %.2fms", server_name, elapsed), vim.log.levels.WARN)
30 : end
31 1 : end
32 :
33 1 : function M.track_venv_detection(root_dir, start_time, found)
34 17 : local elapsed = (vim.uv.hrtime() - start_time) / 1000000
35 :
36 17 : if not metrics.venv_detection_times[root_dir] then
37 : -- Evict the oldest root_dir entry when at capacity
38 8 : if #venv_key_order >= MAX_VENV_ENTRIES then
39 : local oldest = table.remove(venv_key_order, 1)
40 : metrics.venv_detection_times[oldest] = nil
41 : end
42 8 : metrics.venv_detection_times[root_dir] = { total = 0, count = 0, found = 0 }
43 8 : table.insert(venv_key_order, root_dir)
44 : end
45 :
46 17 : local metric = metrics.venv_detection_times[root_dir]
47 17 : metric.total = metric.total + elapsed
48 17 : metric.count = metric.count + 1
49 17 : if found then
50 : metric.found = metric.found + 1
51 : end
52 18 : end
53 :
54 1 : function M.track_lsp_restart(server_name)
55 : if not metrics.restart_counts[server_name] then
56 : metrics.restart_counts[server_name] = 0
57 : end
58 :
59 : metrics.restart_counts[server_name] = metrics.restart_counts[server_name] + 1
60 :
61 : if metrics.restart_counts[server_name] > 5 then
62 : vim.notify(string.format("LSP %s has restarted %d times this session", server_name, metrics.restart_counts[server_name]), vim.log.levels.WARN)
63 : end
64 1 : end
65 :
66 1 : function M.track_config_time(server_name, start_time)
67 : local elapsed = (vim.uv.hrtime() - start_time) / 1000000
68 :
69 : if not metrics.config_times[server_name] then
70 : metrics.config_times[server_name] = { total = 0, count = 0, max = 0 }
71 : end
72 :
73 : local metric = metrics.config_times[server_name]
74 : metric.total = metric.total + elapsed
75 : metric.count = metric.count + 1
76 : metric.max = math.max(metric.max, elapsed)
77 1 : end
78 :
79 1 : function M.get_report()
80 : return {
81 : attach_times = metrics.attach_times,
82 : venv_detection = metrics.venv_detection_times,
83 : restarts = metrics.restart_counts,
84 : config_times = metrics.config_times,
85 : }
86 1 : end
87 :
88 1 : function M.reset_metrics()
89 : metrics = {
90 : attach_times = {},
91 : venv_detection_times = {},
92 : restart_counts = {},
93 : config_times = {},
94 : }
95 : venv_key_order = {}
96 1 : end
97 :
98 1 : function M.setup_commands()
99 : vim.api.nvim_create_user_command("LSPPerfReport", function()
100 : local report = M.get_report()
101 : local lines = { "=== LSP Performance Report ===", "" }
102 :
103 : table.insert(lines, "--- Attach Times ---")
104 : if vim.tbl_count(report.attach_times) == 0 then
105 : table.insert(lines, " No LSP attaches recorded")
106 : else
107 : for server, data in pairs(report.attach_times) do
108 : table.insert(
109 : lines,
110 : string.format(" %s: avg=%.2fms, min=%.2fms, max=%.2fms, count=%d", server, data.total / data.count, data.min, data.max, data.count)
111 : )
112 : end
113 : end
114 :
115 : table.insert(lines, "")
116 : table.insert(lines, "--- Virtual Environment Detection ---")
117 : if vim.tbl_count(report.venv_detection) == 0 then
118 : table.insert(lines, " No venv detections recorded")
119 : else
120 : for root, data in pairs(report.venv_detection) do
121 : local avg_time = data.total / data.count
122 : local success_rate = (data.found / data.count) * 100
123 : table.insert(lines, string.format(" %s: avg=%.2fms, success=%.1f%%, count=%d", root, avg_time, success_rate, data.count))
124 : end
125 : end
126 :
127 : table.insert(lines, "")
128 : table.insert(lines, "--- LSP Restarts ---")
129 : if vim.tbl_count(report.restarts) == 0 then
130 : table.insert(lines, " No LSP restarts recorded")
131 : else
132 : for server, count in pairs(report.restarts) do
133 : local status = count > 5 and "warning" or "ok"
134 : table.insert(lines, string.format(" [%s] %s: %d restarts", status, server, count))
135 : end
136 : end
137 :
138 : table.insert(lines, "")
139 : table.insert(lines, "--- Configuration Times ---")
140 : if vim.tbl_count(report.config_times) == 0 then
141 : table.insert(lines, " No config times recorded")
142 : else
143 : for server, data in pairs(report.config_times) do
144 : table.insert(lines, string.format(" %s: avg=%.2fms, max=%.2fms, count=%d", server, data.total / data.count, data.max, data.count))
145 : end
146 : end
147 :
148 : table.insert(lines, "")
149 : table.insert(lines, "========================")
150 : vim.notify(table.concat(lines, "\n"), vim.log.levels.INFO)
151 : end, { desc = "Show LSP performance metrics" })
152 :
153 : vim.api.nvim_create_user_command("LSPPerfReset", function()
154 : M.reset_metrics()
155 : vim.notify("LSP performance metrics reset", vim.log.levels.INFO)
156 : end, { desc = "Reset LSP performance metrics" })
157 1 : end
158 :
159 1 : return M
|