From 49b0b0b4dffaef46af63d3f8c9f61db725778b0e Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 18 Dec 2025 14:30:36 +0200 Subject: [PATCH 1/4] Comma separate thousands, fix plurals --- .../sampling/_heatmap_assets/heatmap.js | 4 +++- Lib/profiling/sampling/heatmap_collector.py | 18 ++++++++++-------- Lib/profiling/sampling/sample.py | 4 ++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Lib/profiling/sampling/_heatmap_assets/heatmap.js b/Lib/profiling/sampling/_heatmap_assets/heatmap.js index 8ac4ef43e53b37..3869a07bd2586e 100644 --- a/Lib/profiling/sampling/_heatmap_assets/heatmap.js +++ b/Lib/profiling/sampling/_heatmap_assets/heatmap.js @@ -598,10 +598,12 @@ function populateBytecodePanel(panel, button) { else if (specPct >= 33) specClass = 'medium'; // Build specialization summary + const instruction_word = instructions.length === 1 ? 'instruction' : 'instructions'; + const sample_word = totalSamples === 1 ? 'sample' : 'samples'; let html = `
${specPct}% specialized - (${specializedCount}/${instructions.length} instructions, ${specializedSamples.toLocaleString()}/${totalSamples.toLocaleString()} samples) + (${specializedCount}/${instructions.length} ${instruction_word}, ${specializedSamples.toLocaleString()}/${totalSamples.toLocaleString()} ${sample_word})
`; html += '
' + diff --git a/Lib/profiling/sampling/heatmap_collector.py b/Lib/profiling/sampling/heatmap_collector.py index 5b4c89283be08c..0c6020feb4ead5 100644 --- a/Lib/profiling/sampling/heatmap_collector.py +++ b/Lib/profiling/sampling/heatmap_collector.py @@ -431,10 +431,11 @@ def _render_file_item(self, stat: FileStats, indent: str = '') -> str: bar_width = min(stat.percentage, 100) html_file = self.file_index[stat.filename] + s = "" if stat.total_samples == 1 else "s" return (f'{indent}
\n' f'{indent} ๐Ÿ“„ {module_name}\n' - f'{indent} {stat.total_samples:,} samples\n' + f'{indent} {stat.total_samples:,} sample{s}\n' f'{indent}
\n' f'{indent}
\n') @@ -761,7 +762,8 @@ def _print_export_summary(self, output_dir, file_stats: List[FileStats]): """Print summary of exported heatmap.""" print(f"Heatmap output written to {output_dir}/") print(f" - Index: {output_dir / 'index.html'}") - print(f" - {len(file_stats)} source file(s) analyzed") + s = "" if len(file_stats) == 1 else "s" + print(f" - {len(file_stats)} source file{s} analyzed") def _calculate_file_stats(self) -> List[FileStats]: """Calculate statistics for each file. @@ -859,10 +861,10 @@ def _generate_index_html(self, index_path: Path, file_stats: List[FileStats]): "": f"", "": self._template_loader.logo_html, "": f"{sys.version_info.major}.{sys.version_info.minor}", - "": str(len(file_stats)), + "": f"{len(file_stats):,}", "": f"{self._total_samples:,}", - "": f"{self.stats.get('duration_sec', 0):.1f}s", - "": f"{self.stats.get('sample_rate', 0):.1f}", + "": f"{self.stats.get('duration_sec', 0):,.1f}s", + "": f"{self.stats.get('sample_rate', 0):,.1f}", "": error_rate_str, "": str(error_rate_width), "": error_rate_class, @@ -908,10 +910,10 @@ def _generate_file_html(self, output_path: Path, filename: str, "": html.escape(filename), "": f"{file_stat.total_samples:,}", "": f"{file_stat.total_self_samples:,}", - "": str(file_stat.num_lines), + "": f"{file_stat.num_lines:,}", "": f"{file_stat.percentage:.2f}", - "": str(file_stat.max_samples), - "": str(file_stat.max_self_samples), + "": f"{file_stat.max_samples:,}", + "": f"{file_stat.max_self_samples:,}", "": ''.join(code_lines_html), "": f"", "": f"", diff --git a/Lib/profiling/sampling/sample.py b/Lib/profiling/sampling/sample.py index d4c3b577a17c7b..390cdbd1df042f 100644 --- a/Lib/profiling/sampling/sample.py +++ b/Lib/profiling/sampling/sample.py @@ -135,8 +135,8 @@ def sample(self, collector, duration_sec=10, *, async_aware=False): # Don't print stats for live mode (curses is handling display) is_live_mode = LiveStatsCollector is not None and isinstance(collector, LiveStatsCollector) if not is_live_mode: - print(f"Captured {num_samples} samples in {running_time:.2f} seconds") - print(f"Sample rate: {sample_rate:.2f} samples/sec") + print(f"Captured {num_samples:,} samples in {running_time:.2f} seconds") + print(f"Sample rate: {sample_rate:,.2f} samples/sec") print(f"Error rate: {error_rate:.2f}%") # Print unwinder stats if stats collection is enabled From 9cfd9efb70d50a986b8d0cb5a3ecbb83b68740cd Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:37:09 +0200 Subject: [PATCH 2/4] Format numbers with current locale --- Lib/profiling/sampling/_format_utils.py | 7 +++ Lib/profiling/sampling/heatmap_collector.py | 42 +++++++++--------- Lib/profiling/sampling/sample.py | 47 +++++++++------------ 3 files changed, 49 insertions(+), 47 deletions(-) create mode 100644 Lib/profiling/sampling/_format_utils.py diff --git a/Lib/profiling/sampling/_format_utils.py b/Lib/profiling/sampling/_format_utils.py new file mode 100644 index 00000000000000..d031ec2fdcb3d9 --- /dev/null +++ b/Lib/profiling/sampling/_format_utils.py @@ -0,0 +1,7 @@ +import locale + +locale.setlocale(locale.LC_ALL, "") + + +def fmt(value: int | float, decimals: int = 1) -> str: + return locale.format_string(f'%.{decimals}f', value, grouping=True) diff --git a/Lib/profiling/sampling/heatmap_collector.py b/Lib/profiling/sampling/heatmap_collector.py index 0c6020feb4ead5..076b4f35d4ad64 100644 --- a/Lib/profiling/sampling/heatmap_collector.py +++ b/Lib/profiling/sampling/heatmap_collector.py @@ -5,6 +5,7 @@ import html import importlib.resources import json +import locale import math import os import platform @@ -15,6 +16,7 @@ from typing import Dict, List, Tuple from ._css_utils import get_combined_css +from ._format_utils import fmt from .collector import normalize_location, extract_lineno from .stack_collector import StackTraceCollector @@ -343,7 +345,7 @@ def render_hierarchical_html(self, trees: Dict[str, TreeNode]) -> str:
{icon} {type_names[module_type]} - ({tree.count} {file_word}, {tree.samples:,} {sample_word}) + ({tree.count} {file_word}, {tree.samples:n} {sample_word})
''' @@ -390,7 +392,7 @@ def _render_folder(self, node: TreeNode, name: str, level: int = 1) -> str: parts.append(f'{indent} โ–ถ') parts.append(f'{indent} ๐Ÿ“ {html.escape(name)}') parts.append(f'{indent} ' - f'({node.count} {file_word}, {node.samples:,} {sample_word})') + f'({node.count} {file_word}, {node.samples:n} {sample_word})') parts.append(f'{indent}
') parts.append(f'{indent}