2#include <engine/engine_export.h>
15#include <hpp/string_view.hpp>
38 std::fill(values_.begin(), values_.end(), value);
45 smart_init_samples_ = smart_init_samples_count;
50 if(smart_init_samples_ > 0 && offset_ > smart_init_samples_)
53 smart_init_samples_ = -1;
57 const float old_value = values_[offset_];
58 const int32_t current_index = offset_;
60 sum_ = sum_ - old_value + value;
61 values_[offset_] = value;
62 offset_ = (offset_ + 1) %
static_cast<int32_t
>(
num_samples);
65 if(min_index_ == current_index)
69 for(int32_t i = 1; i < static_cast<int32_t>(
num_samples); ++i)
81 min_index_ = current_index;
84 if(max_index_ == current_index)
88 for(int32_t i = 1; i < static_cast<int32_t>(
num_samples); ++i)
100 max_index_ = current_index;
104 auto get_values() const -> const
float* {
return values_.data(); }
106 auto get_min() const ->
float {
return min_; }
107 auto get_max() const ->
float {
return max_; }
111 static constexpr int32_t smart_init_samples_count = 20;
114 std::array<float, num_samples> values_{};
117 float average_{0.0f};
119 int32_t min_index_{0};
120 int32_t max_index_{0};
121 int32_t smart_init_samples_{-1};
130 uint32_t hash = 2166136261u;
133 hash ^=
static_cast<uint32_t
>(*str);
153 [[nodiscard]]
auto name() const -> const
char*
164 std::array<profile_event, max_events>
events{};
181 std::array<thread_profile_buffer, 2>
buffers{};
189 uint32_t old_idx =
write_idx.load(std::memory_order_relaxed);
190 write_idx.store(1 - old_idx, std::memory_order_release);
293 std::unique_ptr<thread_profile_data>
data;
350 std::mutex registration_mutex_;
354 int64_t frame_start_ns_{0};
355 int64_t frame_end_ns_{0};
356 int64_t prev_frame_start_ns_{0};
357 int64_t prev_frame_end_ns_{0};
359 std::vector<frame_snapshot> frame_history_;
360 uint32_t history_write_idx_{0};
361 uint32_t history_count_{0};
364 int32_t selected_frame_{-1};
366 auto register_thread_unlocked(
const std::string&
name) -> thread_profile_data*;
369 void sync_capture_active_to_threads();
371 void capture_frame_snapshot();
384 static std::atomic<uint8_t> process_capture_gate{0};
385 return process_capture_gate;
400 using clock = std::chrono::high_resolution_clock;
401 return std::chrono::duration_cast<std::chrono::nanoseconds>(
402 clock::now().time_since_epoch())
438 if(!data) [[unlikely]]
443 auto& buf = data->write_buffer();
449 uint32_t idx = buf.count++;
450 auto& ev = buf.events[idx];
451 ev.name_literal =
name;
452 ev.name_owned.clear();
457 ev.depth = buf.depth++;
458 ev.thread_index = buf.thread_index;
472 if(!data) [[unlikely]]
477 auto& buf = data->write_buffer();
483 uint32_t idx = buf.count++;
484 auto& ev = buf.events[idx];
485 ev.name_literal =
name;
486 ev.name_owned.clear();
491 ev.depth = buf.depth++;
492 ev.thread_index = buf.thread_index;
505 if(!data) [[unlikely]]
510 auto& buf = data->write_buffer();
516 uint32_t idx = buf.count++;
517 auto& ev = buf.events[idx];
518 ev.name_literal =
nullptr;
519 ev.name_owned = std::string(
name);
524 ev.depth = buf.depth++;
525 ev.thread_index = buf.thread_index;
538 if(!data) [[unlikely]]
543 auto& buf = data->write_buffer();
549 uint32_t idx = buf.count++;
550 auto& ev = buf.events[idx];
551 ev.name_literal =
nullptr;
552 ev.name_owned = std::string(
name);
557 ev.depth = buf.depth++;
558 ev.thread_index = buf.thread_index;
566 if(idx == UINT32_MAX) [[likely]]
571 if(!data) [[unlikely]]
576 auto& buf = data->write_buffer();
590 std::is_same_v<std::remove_const_t<std::remove_extent_t<std::remove_reference_t<T>>>,
char>;
639template<
typename ScopeName,
typename ThreadName>
669#define APP_SCOPE_PERF_CONCATENATE_DETAIL(x, y) x##y
670#define APP_SCOPE_PERF_CONCATENATE(x, y) APP_SCOPE_PERF_CONCATENATE_DETAIL(x, y)
671#define APP_SCOPE_PERF_UNIQUE_VAR(prefix) APP_SCOPE_PERF_CONCATENATE(prefix, __LINE__)
675#define APP_SCOPE_PERF(name_literal) \
676 const ::unravel::scope_profile_timer APP_SCOPE_PERF_UNIQUE_VAR(timer)(name_literal)
680#define APP_SCOPE_PERF_OWNED(name) \
681 const ::unravel::scope_profile_timer_owned APP_SCOPE_PERF_UNIQUE_VAR(timer)(name)
684#define APP_SCOPE_PERF_THREAD(scope_name_literal, thread_name_literal) \
685 const ::unravel::scope_profile_timer_named_thread APP_SCOPE_PERF_UNIQUE_VAR(timer_thread)(scope_name_literal, \
auto get_offset() const -> int32_t
static constexpr uint32_t num_samples
auto reset(float value) -> void
auto get_min() const -> float
auto get_max() const -> float
auto get_values() const -> const float *
auto get_average() const -> float
auto push_sample(float value) -> void
Like scope_profile_timer but registers the current OS thread with ThreadName on first use.
~scope_profile_timer_named_thread()
scope_profile_timer_named_thread(scope_profile_timer_named_thread &&)=delete
auto operator=(const scope_profile_timer_named_thread &) -> scope_profile_timer_named_thread &=delete
scope_profile_timer_named_thread(const ScopeName &scope_name, const ThreadName &thread_name)
scope_profile_timer_named_thread(const scope_profile_timer_named_thread &)=delete
auto operator=(scope_profile_timer_named_thread &&) -> scope_profile_timer_named_thread &=delete
auto operator=(scope_profile_timer_owned &&) -> scope_profile_timer_owned &=delete
scope_profile_timer_owned(hpp::string_view name)
auto operator=(const scope_profile_timer_owned &) -> scope_profile_timer_owned &=delete
scope_profile_timer_owned(scope_profile_timer_owned &&)=delete
~scope_profile_timer_owned()
scope_profile_timer_owned(const scope_profile_timer_owned &)=delete
auto operator=(scope_profile_timer &&) -> scope_profile_timer &=delete
auto operator=(const scope_profile_timer &) -> scope_profile_timer &=delete
scope_profile_timer(const T &name)
scope_profile_timer(scope_profile_timer &&)=delete
scope_profile_timer(const scope_profile_timer &)=delete
Concept to ensure only string literals (char arrays) are accepted. With const T& parameter binding,...
Hash specialization for batch_key to enable use in std::unordered_map.
auto profile_begin(const char *name) -> uint32_t
Begin a profiling scope. Returns an event index for profile_end().
auto get_app_profiler() -> performance_profiler *
void profiler_process_capture_gate_store(uint8_t v)
void profile_end(uint32_t idx)
End a profiling scope started by profile_begin().
auto profiler_process_capture_gate_load() -> bool
auto get_time_ns() -> int64_t
auto ensure_thread_registered(const char *thread_name) -> thread_profile_data *
Register the current thread on first use. thread_name if non-null and non-empty is stored as the lane...
auto get_thread_profile_data() -> thread_profile_data *
constexpr auto fnv1a_hash(const char *str) -> uint32_t
auto profiler_process_capture_gate_ref() -> std::atomic< uint8_t > &
thread_local thread_profile_data * t_profile_data
auto profile_begin_owned(hpp::string_view name) -> uint32_t
Like profile_begin() but copies the label into name_owned (script / dynamic names).
std::vector< profile_event > events
Compacted copy of one frame's profiling data across all threads.
float frame_cpu_ratio
frame_busy_ms / frame_wall_ms — fraction of the frame the main thread was running.
float frame_busy_ms
Main-thread busy time in milliseconds (Frame Loop CPU, or longest main root).
std::vector< thread_snapshot > threads
int64_t gpu_memory_used_bytes
Total GPU memory reported by bgfx at capture; bytes.
int64_t cpu_heap_used_bytes
Managed heap used (e.g. Mono GC) sampled at capture; bytes.
int64_t process_resident_bytes
Process resident set (RSS / working set) at capture; bytes.
float frame_wall_ms
Wall-clock frame duration in milliseconds (cached at capture for UI).
A single profiling event captured during a frame.
const char * name_literal
String literal from native scopes; null if the label is stored in name_owned.
auto name() const -> const char *
Fixed-size buffer of profile events for one frame on one thread.
static constexpr uint32_t max_events
Double-buffered per-thread profiling state. One buffer is being written by the owning thread while th...
std::atomic< uint32_t > write_idx
auto read_buffer() -> thread_profile_buffer &
std::array< thread_profile_buffer, 2 > buffers
auto write_buffer() -> thread_profile_buffer &