HIPO4 C++ Library 4.4.1
Columnar I/O library for CLAS12 physics data
Loading...
Searching...
No Matches
progresstracker.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <fmt/core.h>
4#include <fmt/format.h>
5#include <algorithm>
6#include <atomic>
7#include <chrono>
8#include <cstddef>
9#include <iostream>
10#include <mutex>
11#include <stdexcept>
12#include <string>
13#include <string_view>
14#include <thread>
15
16namespace detail {
19inline std::recursive_mutex& get_stdout_mutex() {
20 static std::recursive_mutex s_stdout_mutex;
21 return s_stdout_mutex;
22}
23} // namespace detail
24
29 public:
30 struct Config {
31 std::size_t bar_width;
36 std::string label;
37 std::chrono::milliseconds update_interval;
38
40 : bar_width(50), show_eta(true), show_rate(true), use_colors(true), use_gradient(true), label("Processing"), update_interval(100) {}
41 };
42
49 explicit ProgressTracker(std::size_t total, Config config = {});
50
51 ~ProgressTracker() noexcept;
52
53 // Non-copyable, move-only
55 ProgressTracker& operator=(const ProgressTracker&) = delete;
57 ProgressTracker& operator=(ProgressTracker&&) noexcept;
58
62 auto increment() noexcept -> void;
63
67 auto add(std::size_t count) noexcept -> void;
68
72 [[nodiscard]] auto get_processed() const noexcept -> std::size_t {
73 return m_processed.load(std::memory_order_acquire);
74 }
75
79 [[nodiscard]] auto get_total() const noexcept -> std::size_t {
80 return m_total;
81 }
82
86 [[nodiscard]] auto get_progress() const noexcept -> double;
87
91 auto start() noexcept -> void;
92
96 auto finish() noexcept -> void;
97
101 static auto clear_line() noexcept -> void;
102
114 [[nodiscard]] static auto format_duration(std::chrono::seconds duration) noexcept -> std::string;
115
116 private:
117 // ANSI escape codes
118 struct AnsiCodes {
119 static constexpr std::string_view CLEAR_LINE = "\r\033[K";
120 static constexpr std::string_view RESET = "\033[0m";
121 static constexpr std::string_view BOLD = "\033[1m";
122 static constexpr std::string_view DIM = "\033[2m";
123
124 // Standard colors
125 static constexpr std::string_view GREEN = "\033[32m";
126 static constexpr std::string_view CYAN = "\033[36m";
127 static constexpr std::string_view YELLOW = "\033[33m";
128 static constexpr std::string_view MAGENTA = "\033[35m";
129 static constexpr std::string_view WHITE = "\033[37m";
130 static constexpr std::string_view BLUE = "\033[34m";
131 static constexpr std::string_view RED = "\033[31m";
132
133 // Bright colors
134 static constexpr std::string_view BRIGHT_GREEN = "\033[92m";
135 static constexpr std::string_view BRIGHT_CYAN = "\033[96m";
136 static constexpr std::string_view BRIGHT_YELLOW = "\033[93m";
137 static constexpr std::string_view BRIGHT_MAGENTA = "\033[95m";
138 static constexpr std::string_view BRIGHT_WHITE = "\033[97m";
139
140 // Background colors for gradient effect
141 static constexpr std::string_view BG_CYAN = "\033[46m";
142 static constexpr std::string_view BG_BLUE = "\033[44m";
143 static constexpr std::string_view BG_MAGENTA = "\033[45m";
144 };
145
146 // Progress bar block characters (8 levels of granularity)
147 static constexpr std::array<std::string_view, 9> BLOCK_CHARS = {
148 " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"};
149
150 // Spinner animation characters
151 static constexpr std::array<std::string_view, 10> SPINNER_CHARS = {
152 "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"};
153
154 auto update_display() noexcept -> void;
155 auto render_bar(double progress) const noexcept -> void;
156 auto update_loop() noexcept -> void;
157 auto stop_update_thread() noexcept -> void;
158
159 [[nodiscard]] static auto format_number(std::size_t num) noexcept -> std::string;
160 [[nodiscard]] auto get_elapsed() const noexcept -> std::chrono::seconds;
161 [[nodiscard]] auto calculate_eta() const noexcept -> std::chrono::seconds;
162 [[nodiscard]] auto calculate_rate() const noexcept -> double;
163 [[nodiscard]] auto get_progress_color() const noexcept -> std::string_view;
164
165 const std::size_t m_total;
166 std::atomic<std::size_t> m_processed{0};
167 std::chrono::steady_clock::time_point m_start_time;
168 Config m_config;
169 mutable std::mutex m_display_mutex;
170 bool m_finished{false};
171 mutable std::size_t m_spinner_index{0};
172
173 // Background update thread
174 std::atomic<bool> m_should_stop{false};
175 std::thread m_update_thread;
176};
177
178inline ProgressTracker::ProgressTracker(std::size_t total, Config config)
179 : m_total{total}, m_start_time{std::chrono::steady_clock::now()}, m_config{config} {
180 if (total == 0) throw std::invalid_argument("ProgressTracker: total must be greater than 0");
181}
182
184 stop_update_thread();
185 if (!m_finished) clear_line();
186}
187
189 : m_total{other.m_total}, m_processed{other.m_processed.load(std::memory_order_acquire)}, m_start_time{other.m_start_time}, m_config{other.m_config}, m_finished{other.m_finished} {
190 other.stop_update_thread();
191 other.m_finished = true;
192}
193
195 if (this != &other) {
196 stop_update_thread();
197 if (!m_finished) clear_line();
198 other.stop_update_thread();
199
200 const_cast<std::size_t&>(m_total) = other.m_total;
201 m_processed.store(other.m_processed.load(std::memory_order_acquire), std::memory_order_release);
202 m_start_time = other.m_start_time;
203 m_config = other.m_config;
204 m_finished = other.m_finished;
205
206 other.m_finished = true;
207 }
208 return *this;
209}
210
211inline auto ProgressTracker::increment() noexcept -> void {
212 m_processed.fetch_add(1, std::memory_order_acq_rel);
213}
214
215inline auto ProgressTracker::add(std::size_t count) noexcept -> void {
216 m_processed.fetch_add(count, std::memory_order_acq_rel);
217}
218
219inline auto ProgressTracker::get_progress() const noexcept -> double {
220 const auto current = m_processed.load(std::memory_order_acquire);
221 return m_total > 0 ? static_cast<double>(current) / static_cast<double>(m_total) : 0.0;
222}
223
224inline auto ProgressTracker::get_elapsed() const noexcept -> std::chrono::seconds {
225 const auto elapsed = std::chrono::steady_clock::now() - m_start_time;
226 return std::chrono::duration_cast<std::chrono::seconds>(elapsed);
227}
228
229inline auto ProgressTracker::calculate_rate() const noexcept -> double {
230 const auto elapsed = get_elapsed().count();
231 const auto current = m_processed.load(std::memory_order_acquire);
232 return elapsed > 0 ? static_cast<double>(current) / static_cast<double>(elapsed) : 0.0;
233}
234
235inline auto ProgressTracker::calculate_eta() const noexcept -> std::chrono::seconds {
236 const auto current = m_processed.load(std::memory_order_acquire);
237 const auto rate = calculate_rate();
238
239 if (rate > 0.0 && current < m_total) {
240 const auto remaining = m_total - current;
241 return std::chrono::seconds{static_cast<long>(remaining / rate)};
242 }
243 return std::chrono::seconds{0};
244}
245
246inline auto ProgressTracker::format_duration(std::chrono::seconds duration) noexcept -> std::string {
247 const auto m_totalseconds = duration.count();
248
249 if (m_totalseconds < 60) return fmt::format("{}s", m_totalseconds);
250 if (m_totalseconds < 3600) {
251 const auto minutes = m_totalseconds / 60;
252 const auto seconds = m_totalseconds % 60;
253 return fmt::format("{}m {:02d}s", minutes, seconds);
254 }
255
256 const auto hours = m_totalseconds / 3600;
257 const auto minutes = (m_totalseconds % 3600) / 60;
258 return fmt::format("{}h {:02d}m", hours, minutes);
259}
260
261inline auto ProgressTracker::format_number(std::size_t num) noexcept -> std::string {
262 if (num < 1000) return fmt::format("{}", num);
263 if (num < 1000000) return fmt::format("{:.1f}k", num / 1000.0);
264 return fmt::format("{:.1f}M", num / 1000000.0);
265}
266
267inline auto ProgressTracker::clear_line() noexcept -> void {
268 std::lock_guard lock{detail::get_stdout_mutex()};
269 try {
270 fmt::print("{}", AnsiCodes::CLEAR_LINE);
271 std::cout.flush();
272 } catch (...) {
273 }
274}
275
276inline auto ProgressTracker::get_progress_color() const noexcept -> std::string_view {
277 if (!m_config.use_colors || !m_config.use_gradient) return AnsiCodes::GREEN;
278 return AnsiCodes::BRIGHT_GREEN;
279}
280
281inline auto ProgressTracker::render_bar(double progress) const noexcept -> void {
282 std::lock_guard lock{detail::get_stdout_mutex()};
283 try {
284 const auto& colors = m_config.use_colors;
285 progress = std::clamp(progress, 0.0, 1.0);
286
287 const auto filled_width = m_config.bar_width * progress;
288 const auto full_blocks = static_cast<std::size_t>(filled_width);
289 const auto remainder = filled_width - static_cast<double>(full_blocks);
290 const auto partial_block_idx = static_cast<std::size_t>(remainder * 8.0);
291
292 // Left bracket with color
293 if (colors) fmt::print("{}", AnsiCodes::DIM);
294 fmt::print("╢");
295 if (colors) fmt::print("{}", AnsiCodes::RESET);
296
297 // Render progress bar with gradient
298 for (std::size_t i = 0; i < m_config.bar_width; ++i) {
299 if (i < full_blocks) {
300 if (colors) {
301 fmt::print("{}", get_progress_color());
302 }
303 fmt::print("{}", BLOCK_CHARS[8]);
304 } else if (i == full_blocks) {
305 if (colors) {
306 fmt::print("{}", get_progress_color());
307 }
308 fmt::print("{}", BLOCK_CHARS[partial_block_idx]);
309 } else {
310 if (colors) fmt::print("{}", AnsiCodes::DIM);
311 fmt::print("░");
312 }
313 }
314
315 if (colors) fmt::print("{}", AnsiCodes::RESET);
316
317 // Right bracket
318 if (colors) fmt::print("{}", AnsiCodes::DIM);
319 fmt::print("╟");
320 if (colors) fmt::print("{}", AnsiCodes::RESET);
321 fmt::print(" ");
322
323 } catch (...) {
324 }
325}
326
327inline auto ProgressTracker::update_display() noexcept -> void {
328 std::lock_guard lock{m_display_mutex};
329
330 if (m_finished) return;
331
332 try {
333 const auto current = m_processed.load(std::memory_order_acquire);
334 const auto progress = get_progress();
335 const auto elapsed = get_elapsed();
336 const auto& colors = m_config.use_colors;
337
338 // Lock stdout mutex for all print operations
339 std::lock_guard stdout_lock{detail::get_stdout_mutex()};
340
341 clear_line();
342
343 // Spinner animation
344 if (progress < 1.0) {
345 if (colors) fmt::print("{}", AnsiCodes::BRIGHT_CYAN);
346 fmt::print("{}", SPINNER_CHARS[m_spinner_index % SPINNER_CHARS.size()]);
347 m_spinner_index++;
348 if (colors) fmt::print("{}", AnsiCodes::RESET);
349 fmt::print(" ");
350 } else {
351 if (colors) fmt::print("{}", AnsiCodes::BRIGHT_GREEN);
352 fmt::print("✓");
353 if (colors) fmt::print("{}", AnsiCodes::RESET);
354 fmt::print(" ");
355 }
356
357 // Label
358 if (!m_config.label.empty()) {
359 if (colors) fmt::print("{}", AnsiCodes::BOLD);
360 fmt::print("{}", m_config.label);
361 if (colors) fmt::print("{}", AnsiCodes::RESET);
362 fmt::print(" ");
363 }
364
365 render_bar(progress);
366
367 // Percentage with enhanced styling
368 if (progress >= 1.0) {
369 if (colors) fmt::print("{}{}", AnsiCodes::BRIGHT_GREEN, AnsiCodes::BOLD);
370 fmt::print("100%");
371 if (colors) fmt::print("{}", AnsiCodes::RESET);
372 } else {
373 if (colors) fmt::print("{}", get_progress_color());
374 fmt::print("{:>3.0f}%", progress * 100.0);
375 if (colors) fmt::print("{}", AnsiCodes::RESET);
376 }
377
378 // Count with dividers
379 fmt::print(" ");
380 if (colors) fmt::print("{}", AnsiCodes::DIM);
381 fmt::print("│");
382 if (colors) fmt::print("{}", AnsiCodes::RESET);
383 fmt::print(" ");
384
385 if (colors) fmt::print("{}", AnsiCodes::BRIGHT_WHITE);
386 fmt::print("{}", format_number(current));
387 if (colors) fmt::print("{}", AnsiCodes::RESET);
388
389 if (colors) fmt::print("{}", AnsiCodes::DIM);
390 fmt::print("/");
391 if (colors) fmt::print("{}", AnsiCodes::RESET);
392
393 if (colors) fmt::print("{}", AnsiCodes::BRIGHT_CYAN);
394 fmt::print("{}", format_number(m_total));
395 if (colors) fmt::print("{}", AnsiCodes::RESET);
396
397 // Elapsed time
398 fmt::print(" ");
399 if (colors) fmt::print("{}", AnsiCodes::DIM);
400 fmt::print("│ ⏱");
401 if (colors) fmt::print("{}", AnsiCodes::RESET);
402 fmt::print(" ");
403 if (colors) fmt::print("{}", AnsiCodes::MAGENTA);
404 fmt::print("{}", format_duration(elapsed));
405 if (colors) fmt::print("{}", AnsiCodes::RESET);
406
407 // ETA
408 if (m_config.show_eta && current < m_total) {
409 const auto eta = calculate_eta();
410 if (eta.count() > 0) {
411 fmt::print(" ");
412 if (colors) fmt::print("{}", AnsiCodes::DIM);
413 fmt::print("│ ⏳");
414 if (colors) fmt::print("{}", AnsiCodes::RESET);
415 fmt::print(" ");
416 if (colors) fmt::print("{}", AnsiCodes::YELLOW);
417 fmt::print("{}", format_duration(eta));
418 if (colors) fmt::print("{}", AnsiCodes::RESET);
419 }
420 }
421
422 // Rate
423 if (m_config.show_rate) {
424 const auto rate = calculate_rate();
425 if (rate > 0.0) {
426 fmt::print(" ");
427 if (colors) fmt::print("{}", AnsiCodes::DIM);
428 fmt::print("│ ⚡");
429 if (colors) fmt::print("{}", AnsiCodes::RESET);
430 fmt::print(" ");
431 if (colors) fmt::print("{}", AnsiCodes::BRIGHT_MAGENTA);
432 fmt::print("{:.1f}/s", rate);
433 if (colors) fmt::print("{}", AnsiCodes::RESET);
434 }
435 }
436
437 std::cout.flush();
438
439 } catch (...) {
440 }
441}
442
443inline auto ProgressTracker::update_loop() noexcept -> void {
444 while (!m_should_stop.load(std::memory_order_acquire)) {
445 update_display();
446 if (m_processed.load(std::memory_order_acquire) >= m_total) break;
447 std::this_thread::sleep_for(m_config.update_interval);
448 }
449
450 update_display();
451}
452
453inline auto ProgressTracker::start() noexcept -> void {
454 if (!m_update_thread.joinable() && !m_finished) {
455 m_should_stop.store(false, std::memory_order_release);
456 update_display();
457 m_update_thread = std::thread(&ProgressTracker::update_loop, this);
458 }
459}
460
461inline auto ProgressTracker::stop_update_thread() noexcept -> void {
462 if (m_update_thread.joinable()) {
463 m_should_stop.store(true, std::memory_order_release);
464 m_update_thread.join();
465 }
466}
467
468inline auto ProgressTracker::finish() noexcept -> void {
469 stop_update_thread();
470
471 std::lock_guard lock{m_display_mutex};
472
473 if (m_finished) return;
474 m_finished = true;
475
476 try {
477 const auto elapsed = get_elapsed();
478 const auto& colors = m_config.use_colors;
479
480 // Lock stdout mutex for all print operations
481 std::lock_guard stdout_lock{detail::get_stdout_mutex()};
482
483 clear_line();
484
485 // Completion line
486 if (colors) fmt::print("{}", AnsiCodes::BRIGHT_GREEN);
487 fmt::print(" {} Complete! │ ", m_config.label);
488 if (colors) fmt::print("{}", AnsiCodes::RESET);
489
490 // Files processed
491 fmt::print(" Items: ");
492 if (colors) fmt::print("{}", AnsiCodes::BRIGHT_CYAN);
493 fmt::print("{}", format_number(m_total));
494 if (colors) fmt::print("{}", AnsiCodes::RESET);
495 fmt::print(" │ ");
496
497 // Time elapsed
498 fmt::print("⏱ Time: ");
499 if (colors) fmt::print("{}", AnsiCodes::MAGENTA);
500 fmt::print("{}", format_duration(elapsed));
501 if (colors) fmt::print("{}", AnsiCodes::RESET);
502
503 fmt::print("\n");
504 std::cout.flush();
505
506 } catch (...) {
507 }
508}
Thread-safe progress tracker with visual progress bar.
Definition progresstracker.hpp:28
ProgressTracker & operator=(const ProgressTracker &)=delete
auto get_total() const noexcept -> std::size_t
Gets total item count (thread-safe).
Definition progresstracker.hpp:79
static auto clear_line() noexcept -> void
Clears the current progress line.
Definition progresstracker.hpp:267
auto increment() noexcept -> void
Increments the progress counter (thread-safe, no immediate display update).
Definition progresstracker.hpp:211
auto get_processed() const noexcept -> std::size_t
Gets current progress count (thread-safe).
Definition progresstracker.hpp:72
~ProgressTracker() noexcept
Definition progresstracker.hpp:183
auto add(std::size_t count) noexcept -> void
Adds multiple items to progress (thread-safe, no immediate display update).
Definition progresstracker.hpp:215
static auto format_duration(std::chrono::seconds duration) noexcept -> std::string
Formats a duration given in seconds into a human-readable string.
Definition progresstracker.hpp:246
auto start() noexcept -> void
Starts the background update thread and shows initial display.
Definition progresstracker.hpp:453
auto get_progress() const noexcept -> double
Gets current progress ratio [0.0, 1.0] (thread-safe).
Definition progresstracker.hpp:219
auto finish() noexcept -> void
Stops the update thread and displays final completion message.
Definition progresstracker.hpp:468
ProgressTracker(std::size_t total, Config config={})
Constructs a progress tracker.
Definition progresstracker.hpp:178
Definition progresstracker.hpp:16
std::recursive_mutex & get_stdout_mutex()
Returns the global recursive mutex used to synchronize stdout access.
Definition progresstracker.hpp:19
Definition progresstracker.hpp:30
bool use_gradient
Definition progresstracker.hpp:35
Config()
Definition progresstracker.hpp:39
std::size_t bar_width
Definition progresstracker.hpp:31
std::string label
Definition progresstracker.hpp:36
bool show_rate
Definition progresstracker.hpp:33
std::chrono::milliseconds update_interval
Definition progresstracker.hpp:37
bool show_eta
Definition progresstracker.hpp:32
bool use_colors
Definition progresstracker.hpp:34