Skip to content

Commit 73be451

Browse files
authored
Add stats panel (silva96#67)
* Add stats panel * Fix standard
1 parent 007acc2 commit 73be451

5 files changed

Lines changed: 190 additions & 20 deletions

File tree

‎lib/log_bench/app/main.rb‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ def setup_components
6868

6969
def load_initial_data
7070
self.log_file = Log::File.new(log_file_path)
71-
state.requests = log_file.requests
71+
initial_requests = log_file.requests
72+
state.requests = initial_requests
73+
state.set_initial_query_count(initial_requests)
7274
log_file.mark_as_read!
7375
end
7476

‎lib/log_bench/app/monitor.rb‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def monitor_loop
4343
def add_new_requests(new_requests)
4444
return if new_requests.empty?
4545

46+
state.track_new_requests(new_requests)
4647
state.requests.concat(new_requests)
4748
keep_recent_requests
4849
end

‎lib/log_bench/app/renderer/header.rb‎

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,30 +50,76 @@ def draw_file_name
5050
end
5151

5252
def draw_stats
53-
filtered_requests = state.filtered_requests
54-
total_requests = state.requests.size
55-
5653
if state.main_filter.present?
57-
# Filter active - show "X found (Y total)"
58-
stats_text = "#{filtered_requests.size} found (#{total_requests} total)"
59-
header_win.setpos(1, screen.width - stats_text.length - 2)
60-
header_win.attron(color_pair(3)) { header_win.addstr(filtered_requests.size.to_s) }
61-
header_win.addstr(" found (")
62-
header_win.attron(color_pair(3)) { header_win.addstr(total_requests.to_s) }
63-
header_win.addstr(" total)")
54+
draw_filtered_stats
6455
else
65-
# No filter active - show simple count
66-
stats_text = "Requests: #{total_requests}"
67-
header_win.setpos(1, screen.width - stats_text.length - 2)
68-
header_win.addstr("Requests: ")
69-
header_win.attron(color_pair(3)) { header_win.addstr(total_requests.to_s) }
56+
draw_stats_panel
7057
end
7158
end
7259

60+
def draw_filtered_stats
61+
# When filter is active, show compact "X found (Y total)" on line 1
62+
filtered_requests = state.filtered_requests
63+
total_requests = state.requests.size
64+
stats_text = "#{filtered_requests.size} found (#{total_requests} total)"
65+
header_win.setpos(1, screen.width - stats_text.length - 2)
66+
header_win.attron(color_pair(3)) { header_win.addstr(filtered_requests.size.to_s) }
67+
header_win.addstr(" found (")
68+
header_win.attron(color_pair(3)) { header_win.addstr(total_requests.to_s) }
69+
header_win.addstr(" total)")
70+
end
71+
72+
def draw_stats_panel
73+
# Calculate stats
74+
total_requests = state.requests.size
75+
total_queries = state.total_queries
76+
req_per_sec = state.requests_per_second
77+
req_per_min = state.requests_per_minute
78+
queries_per_sec = state.queries_per_second
79+
queries_per_min = state.queries_per_minute
80+
81+
# Calculate width for alignment
82+
header_text = " Total | sec | min"
83+
max_width = header_text.length
84+
start_x = screen.width - max_width - 2
85+
86+
# Draw header line (dimmed)
87+
header_win.setpos(1, start_x)
88+
header_win.attron(A_DIM) { header_win.addstr(header_text) }
89+
90+
# Draw requests line
91+
header_win.setpos(2, start_x)
92+
draw_stats_line("Req: ", total_requests, req_per_sec, req_per_min)
93+
94+
# Draw queries line
95+
header_win.setpos(3, start_x)
96+
draw_stats_line("Query: ", total_queries, queries_per_sec, queries_per_min)
97+
end
98+
99+
def draw_stats_line(label, total, per_sec, per_min)
100+
# Draw label
101+
header_win.addstr(label)
102+
103+
# Draw total (highlighted, right-aligned in 6 chars)
104+
total_str = format("%6d", total)
105+
header_win.attron(color_pair(SUCCESS_GREEN)) { header_win.addstr(total_str) }
106+
header_win.addstr(" | ")
107+
108+
# Draw per second (highlighted, right-aligned in 3 chars)
109+
per_sec_str = format("%3.0f", per_sec)
110+
header_win.attron(color_pair(SUCCESS_GREEN)) { header_win.addstr(per_sec_str) }
111+
header_win.addstr(" | ")
112+
113+
# Draw per minute (highlighted, right-aligned in 3 chars)
114+
per_min_str = format("%3.0f", per_min)
115+
header_win.attron(color_pair(SUCCESS_GREEN)) { header_win.addstr(per_min_str) }
116+
end
117+
73118
def draw_help_text
74119
header_win.setpos(2, 2)
75120
header_win.attron(A_DIM) do
76-
header_win.addstr("a:Auto-scroll(")
121+
help_line_1 = "a:Auto-scroll("
122+
header_win.addstr(help_line_1)
77123
header_win.attron(color_pair(3)) { header_win.addstr(state.auto_scroll ? "ON" : "OFF") }
78124
header_win.addstr(") | f:Filter | c:Clear filter | s:Sort(")
79125
header_win.attron(color_pair(3)) { header_win.addstr(state.sort.display_name) }

‎lib/log_bench/app/state.rb‎

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module App
77
class State
88
include Singleton
99

10-
attr_reader :main_filter, :sort, :detail_filter, :cleared_requests
10+
attr_reader :main_filter, :sort, :detail_filter, :cleared_requests, :start_time, :stats, :total_queries
1111
attr_accessor :requests, :orphan_requests, :auto_scroll, :scroll_offset, :selected, :detail_scroll_offset, :detail_selected_entry, :text_selection_mode, :update_available, :update_version
1212

1313
def initialize
@@ -32,6 +32,9 @@ def reset!
3232
self.update_version = nil
3333
self.cleared_requests = nil
3434
self.job_ids_map = {}
35+
self.start_time = Time.now
36+
self.stats = Stats.new
37+
self.total_queries = 0
3538
end
3639

3740
def running?
@@ -91,17 +94,20 @@ def clear_detail_filter
9194
def clear_requests
9295
if cleared_requests
9396
cleared_requests[:requests] += requests
97+
cleared_requests[:total_queries] += total_queries
9498
else
9599
self.cleared_requests = {
96100
requests: requests,
97101
selected: selected,
98102
scroll_offset: scroll_offset,
99103
detail_scroll_offset: detail_scroll_offset,
100-
detail_selected_entry: detail_selected_entry
104+
detail_selected_entry: detail_selected_entry,
105+
total_queries: total_queries
101106
}
102107
end
103108

104109
self.requests = []
110+
self.total_queries = 0
105111
self.selected = 0
106112
self.scroll_offset = 0
107113
self.detail_scroll_offset = 0
@@ -115,6 +121,7 @@ def undo_clear_requests
115121
restored_requests = cleared_requests[:requests] + requests
116122

117123
self.requests = restored_requests
124+
self.total_queries = cleared_requests[:total_queries] + total_queries
118125
self.selected = cleared_requests[:selected]
119126
self.scroll_offset = cleared_requests[:scroll_offset]
120127
self.detail_scroll_offset = cleared_requests[:detail_scroll_offset]
@@ -297,10 +304,40 @@ def request_id_for_job(job_id)
297304
job_ids_map[job_id]
298305
end
299306

307+
def track_new_requests(new_requests)
308+
stats.track_requests(new_requests)
309+
# Update total queries counter
310+
self.total_queries += new_requests.sum(&:query_count)
311+
end
312+
313+
def set_initial_query_count(requests)
314+
self.total_queries = requests.sum(&:query_count)
315+
end
316+
317+
def elapsed_time
318+
Time.now - start_time
319+
end
320+
321+
def requests_per_second
322+
stats.requests_per_second
323+
end
324+
325+
def requests_per_minute
326+
stats.requests_per_minute
327+
end
328+
329+
def queries_per_second
330+
stats.queries_per_second
331+
end
332+
333+
def queries_per_minute
334+
stats.queries_per_minute
335+
end
336+
300337
private
301338

302339
attr_accessor :focused_pane, :running, :job_ids_map
303-
attr_writer :main_filter, :detail_filter, :sort, :cleared_requests
340+
attr_writer :main_filter, :detail_filter, :sort, :cleared_requests, :start_time, :stats, :total_queries
304341
end
305342
end
306343
end

‎lib/log_bench/app/stats.rb‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# frozen_string_literal: true
2+
3+
module LogBench
4+
module App
5+
class Stats
6+
def initialize
7+
self.request_history = []
8+
self.last_calculation_time = Time.now
9+
self.cached_requests_per_sec = 0.0
10+
self.cached_requests_per_min = 0.0
11+
self.cached_queries_per_sec = 0.0
12+
self.cached_queries_per_min = 0.0
13+
end
14+
15+
def track_requests(new_requests)
16+
return if new_requests.empty?
17+
18+
now = Time.now
19+
new_requests.each do |req|
20+
request_history << {time: now, queries: req.query_count}
21+
end
22+
end
23+
24+
def requests_per_second
25+
update_calculations
26+
cached_requests_per_sec
27+
end
28+
29+
def requests_per_minute
30+
update_calculations
31+
cached_requests_per_min
32+
end
33+
34+
def queries_per_second
35+
update_calculations
36+
cached_queries_per_sec
37+
end
38+
39+
def queries_per_minute
40+
update_calculations
41+
cached_queries_per_min
42+
end
43+
44+
private
45+
46+
attr_accessor :request_history, :last_calculation_time
47+
attr_accessor :cached_requests_per_sec, :cached_requests_per_min
48+
attr_accessor :cached_queries_per_sec, :cached_queries_per_min
49+
50+
# Update rate calculations based on recent activity using rolling buffer
51+
# We calculate per-second rate from last 1 second, per-minute rate from last 60 seconds
52+
def update_calculations
53+
now = Time.now
54+
55+
# Only recalculate every 0.5 seconds to avoid excessive computation
56+
return if now - last_calculation_time < 0.5
57+
58+
self.last_calculation_time = now
59+
60+
# Remove entries older than 60 seconds (we only need last 60 seconds)
61+
cutoff_time = now - 60.0
62+
request_history.reject! { |entry| entry[:time] < cutoff_time }
63+
64+
# Count requests and queries in the last 1 second
65+
one_sec_cutoff = now - 1.0
66+
last_sec_entries = request_history.select { |entry| entry[:time] > one_sec_cutoff }
67+
last_sec_count = last_sec_entries.size
68+
last_sec_queries = last_sec_entries.sum { |entry| entry[:queries] }
69+
70+
# Count requests and queries in the last 60 seconds (all remaining entries)
71+
last_min_count = request_history.size
72+
last_min_queries = request_history.sum { |entry| entry[:queries] }
73+
74+
# Per-second rate: count from last 1 second
75+
self.cached_requests_per_sec = last_sec_count.to_f
76+
self.cached_queries_per_sec = last_sec_queries.to_f
77+
78+
# Per-minute rate: count from last 60 seconds
79+
self.cached_requests_per_min = last_min_count.to_f
80+
self.cached_queries_per_min = last_min_queries.to_f
81+
end
82+
end
83+
end
84+
end

0 commit comments

Comments
 (0)