diff --git a/Core/plottable/Performance_Overlay_p.h b/Core/plottable/Performance_Overlay_p.h index 40f216d..50e97ec 100644 --- a/Core/plottable/Performance_Overlay_p.h +++ b/Core/plottable/Performance_Overlay_p.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -11,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -367,6 +369,7 @@ struct Performance_Text_Span { Performance_Metric_Id field_id = Performance_Metric_Id::Generic_Number; Performance_Text_Role role = Performance_Text_Role::Value; std::string text; + std::string field_key; double measured_width{}; double reserved_width{}; double x_offset{}; @@ -405,6 +408,7 @@ struct Performance_Worker_State { std::atomic next_content_version{1}; Performance_Aggregate aggregate; std::array field_layout_states{}; + std::unordered_map field_max_value_widths; RectF last_scroll_track_rect; RectF last_scroll_thumb_rect; double last_content_viewport_height{}; @@ -459,6 +463,7 @@ struct Performance_Layout_Builder { std::vector render_lines; std::vector text_runs; std::array field_layout_states{}; + std::unordered_map field_max_value_widths; RectF panel_rect; RectF scroll_bar_rect; RectF scroll_thumb_rect; @@ -1021,38 +1026,155 @@ struct Performance_Layout_Builder { || (contains(line, "starvation_active:1")) || (contains(line, "confidence:low") && contains(line, "consumer_paced")); } + static bool whitespace(char value) { + return std::isspace(static_cast(value)) != 0; + } + static std::size_t metric_unit_offset(std::string_view value) { + std::size_t index = 0; + if (index < value.size() && (value[index] == '+' || value[index] == '-')) + ++index; + bool has_digit = false; + while (index < value.size()) { + char current = value[index]; + if (std::isdigit(static_cast(current))) { + has_digit = true; + ++index; + continue; + } + if (current == '.' || current == ',') { + ++index; + continue; + } + break; + } + if (!has_digit || index == value.size()) + return std::string_view::npos; + char unit = value[index]; + if (unit == '%' || std::isalpha(static_cast(unit))) + return index; + return std::string_view::npos; + } + static std::size_t first_metric_item(std::string_view text, std::size_t begin) { + std::size_t cursor = begin; + while (cursor < text.size()) { + while (cursor < text.size() && whitespace(text[cursor])) + ++cursor; + std::size_t token_end = cursor; + while (token_end < text.size() && !whitespace(text[token_end])) + ++token_end; + if (text.substr(cursor, token_end - cursor).find(':') != std::string_view::npos) + return cursor; + cursor = token_end; + } + return std::string_view::npos; + } std::vector plain_line_runs(const std::string& text, Canvas& canvas) { std::vector runs; - auto append = [&](std::string value, Performance_Text_Role role, double offset) { + std::unordered_map field_occurrences; + double offset = 0.0; + bool warning = warning_line(text); + double space_width = canvas.measure_text("0 0").width - canvas.measure_text("00").width; + if (space_width <= 0.0) + space_width = std::max(1.0, canvas.measure_text("0").width * 0.5); + auto trim = [](std::string_view value) { + while (!value.empty() && whitespace(value.front())) + value.remove_prefix(1); + while (!value.empty() && whitespace(value.back())) + value.remove_suffix(1); + return std::string(value); + }; + auto append = [&](std::string value, Performance_Text_Role role, bool is_value = false, std::string field_key = {}) { if (value.empty()) return; Performance_Text_Span run; run.text = std::move(value); + run.field_key = std::move(field_key); run.role = role; + run.value = is_value; run.column_index = 0; run.x_offset = offset; measure_run(canvas, run); + if (std::all_of(run.text.begin(), run.text.end(), whitespace)) { + run.measured_width = space_width * static_cast(run.text.size()); + run.reserved_width = run.measured_width; + } + offset += run_width(run); runs.push_back(std::move(run)); }; + auto make_field_key = [&](std::string_view section, std::string_view context, std::string_view label) { + std::string key; + key.reserve(section.size() + context.size() + label.size() + 3); + key.append(section); + key.push_back('|'); + key.append(context); + key.push_back('|'); + key.append(label); + std::size_t occurrence = field_occurrences[key]++; + key.push_back('|'); + key += std::to_string(occurrence); + return key; + }; + auto append_value = [&](std::string_view value, const std::string& field_key) { + Performance_Text_Role value_role = warning ? Performance_Text_Role::Warning : Performance_Text_Role::Value; + std::size_t unit_offset = metric_unit_offset(value); + if (unit_offset == std::string_view::npos) { + append(std::string(value), value_role, true, field_key); + return; + } + append(std::string(value.substr(0, unit_offset)), value_role, true, field_key); + append(std::string(value.substr(unit_offset)), warning ? Performance_Text_Role::Warning : Performance_Text_Role::Unit); + }; + std::size_t cursor = 0; + std::string section; std::size_t section_end = text.find(']'); if (!text.empty() && text.front() == '[' && section_end != std::string::npos) { - std::string section = text.substr(0, section_end + 1); - append(section, Performance_Text_Role::Section, 0.0); - double section_width = runs.empty() ? 0.0 : runs.back().measured_width + canvas.measure_text(" ").width; - Performance_Text_Role value_role = warning_line(text) ? Performance_Text_Role::Warning : Performance_Text_Role::Value; - append(text.substr(section_end + 1), value_role, section_width); + section = text.substr(0, section_end + 1); + append(section, Performance_Text_Role::Section); + cursor = section_end + 1; + } + std::size_t item_begin = first_metric_item(text, cursor); + if (item_begin == std::string_view::npos) { + append(text.substr(cursor), warning ? Performance_Text_Role::Warning : Performance_Text_Role::Value); return runs; } - std::size_t colon = text.find(':'); - if (colon != std::string::npos) { - std::string label = text.substr(0, colon + 1); - append(label, Performance_Text_Role::Label, 0.0); - double label_width = runs.empty() ? 0.0 : runs.back().measured_width + canvas.measure_text(" ").width; - Performance_Text_Role value_role = warning_line(text) ? Performance_Text_Role::Warning : Performance_Text_Role::Value; - append(text.substr(colon + 1), value_role, label_width); - return runs; + std::string context = trim(std::string_view(text).substr(cursor, item_begin - cursor)); + if (item_begin > cursor) + append(text.substr(cursor, item_begin - cursor), Performance_Text_Role::Label); + cursor = item_begin; + while (cursor < text.size()) { + std::size_t whitespace_end = cursor; + while (whitespace_end < text.size() && whitespace(text[whitespace_end])) + ++whitespace_end; + if (whitespace_end > cursor) + append(text.substr(cursor, whitespace_end - cursor), Performance_Text_Role::Muted); + cursor = whitespace_end; + if (cursor >= text.size()) + break; + std::size_t token_end = cursor; + while (token_end < text.size() && !whitespace(text[token_end])) + ++token_end; + std::size_t colon = text.find(':', cursor); + if (colon == std::string::npos || colon >= token_end) { + std::string token = text.substr(cursor, token_end - cursor); + append(token, warning ? Performance_Text_Role::Warning : Performance_Text_Role::Label); + std::size_t next = token_end; + while (next < text.size() && whitespace(text[next])) + ++next; + std::size_t next_end = next; + while (next_end < text.size() && !whitespace(text[next_end])) + ++next_end; + std::size_t next_colon = text.find(':', next); + if (next < text.size() && next_colon != std::string::npos && next_colon < next_end) + context = token; + cursor = token_end; + continue; + } + std::string_view label = std::string_view(text).substr(cursor, colon - cursor); + std::string field_key = make_field_key(section, context, label); + append(text.substr(cursor, colon - cursor + 1), Performance_Text_Role::Label); + append_value(std::string_view(text).substr(colon + 1, token_end - colon - 1), field_key); + cursor = token_end; } - append(text, Performance_Text_Role::Value, 0.0); return runs; } void build_render_lines(Canvas& canvas) { @@ -1062,7 +1184,7 @@ struct Performance_Layout_Builder { Performance_Line_Layout line; std::vector runs = plain_line_runs(text, canvas); for (auto& run : runs) { - line.width = std::max(line.width, run.x_offset + run.measured_width); + line.width = std::max(line.width, run.x_offset + run_width(run)); line.runs.push_back(std::move(run)); } if (!line.runs.empty()) @@ -1223,6 +1345,12 @@ struct Performance_Layout_Builder { run.reserved_width = run.measured_width; return; } + if (!run.field_key.empty()) { + double& max_width = field_max_value_widths[run.field_key]; + max_width = std::max(max_width, run.measured_width); + run.reserved_width = max_width; + return; + } auto index = static_cast(run.field_id); if (index >= field_layout_states.size()) index = static_cast(Performance_Metric_Id::Generic_Number); @@ -1390,6 +1518,7 @@ struct Performance_Overlay_Private { Performance_Layout_Builder layout; layout.aggregate = worker_state.aggregate; layout.field_layout_states = worker_state.field_layout_states; + layout.field_max_value_widths = worker_state.field_max_value_widths; layout.rebuild_lines(); auto snapshot = std::make_shared(); snapshot->content_version = worker_state.next_content_version.fetch_add(1, std::memory_order_acq_rel); @@ -1429,6 +1558,7 @@ struct Performance_Overlay_Private { layout.build_render_lines(measure_canvas); } worker_state.field_layout_states = layout.field_layout_states; + worker_state.field_max_value_widths = layout.field_max_value_widths; snapshot->lines = layout.display_lines(); layout.update_content_width(&state, worker_state.maximum_content_width, viewport.width); double line_height = std::max(1.0, metrics.line_height());