diff --git a/webview-ui/src/diagnostics_panel/App.css b/webview-ui/src/diagnostics_panel/App.css index 7524b74..fbabd68 100644 --- a/webview-ui/src/diagnostics_panel/App.css +++ b/webview-ui/src/diagnostics_panel/App.css @@ -428,7 +428,7 @@ main { .minecraft-grouped-statistic-table-grid-select { text-align: center !important; - width: 72px; + width: 50px; padding: 6px 4px !important; } @@ -438,6 +438,12 @@ main { padding: 6px 4px !important; } +.minecraft-grouped-statistic-table-grid-numeric { + text-align: right !important; + white-space: nowrap; + width: 10%; +} + .minecraft-grouped-statistic-table-grid-sparkline { width: 240px; min-width: 240px; @@ -469,13 +475,19 @@ main { display: inline-block; margin-right: 0; font-weight: 600; - white-space: nowrap; + white-space: normal; + overflow-wrap: anywhere; + max-width: 100%; + vertical-align: middle; } .minecraft-grouped-statistic-group-meta { display: block; margin-left: 0; color: var(--vscode-descriptionForeground); + white-space: normal; + overflow-wrap: anywhere; + max-width: 100%; } .minecraft-grouped-statistic-toggle { @@ -492,6 +504,11 @@ main { .minecraft-grouped-statistic-child-key { display: inline-block; padding-left: 18px; + white-space: normal; + overflow: hidden; + overflow-wrap: anywhere; + max-width: 100%; + vertical-align: middle; } .minecraft-grouped-statistic-row-pinned { diff --git a/webview-ui/src/diagnostics_panel/controls/MinecraftGroupedStatisticTable.tsx b/webview-ui/src/diagnostics_panel/controls/MinecraftGroupedStatisticTable.tsx index 8852864..6684014 100644 --- a/webview-ui/src/diagnostics_panel/controls/MinecraftGroupedStatisticTable.tsx +++ b/webview-ui/src/diagnostics_panel/controls/MinecraftGroupedStatisticTable.tsx @@ -91,6 +91,7 @@ type MinecraftGroupedStatisticTableProps = { rowAction?: GroupedStatisticTableRowAction; nonConsolidatedColumnResolver?: NonConsolidatedColumnResolver; valueFormatter?: (value: string | number, columnIndex: number) => string; + keyFormatter?: (key: string) => string; prettifyNames?: boolean; selectionEnabled?: boolean; selectionHeaderLabel?: string; @@ -116,6 +117,27 @@ const SPARKLINE_BOUNDS_EXPAND_LERP = 0.35; const SPARKLINE_BOUNDS_CONTRACT_LERP = 0.12; const SPARKLINE_HISTORY_RETENTION_TICKS = 8; +const TICKS_PER_SECOND = 20; +const TICKS_PER_SPARKLINE_UPDATE = 12; // TODO I don't know if this is accurate or not, but it gives us the right values for now + +function sparklineTickRangeToSeconds(updatePoints: number): string { + const totalTicks = updatePoints * TICKS_PER_SPARKLINE_UPDATE; + const seconds = totalTicks / TICKS_PER_SECOND; + + if (seconds <= 0) { + return '0s'; + } + + if (seconds >= 60) { + const minutes = Math.floor(seconds / 60); + const remainingSeconds = Math.round(seconds % 60); + + return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`; + } + + // For short ranges, show one decimal place for precision + return seconds < 10 ? `${seconds.toFixed(1)}s` : `${Math.round(seconds)}s`; +} function getRowSparklineSeriesKey(category: string): string { return `row:${category}`; } @@ -400,6 +422,7 @@ const MinecraftGroupedStatisticTable = forwardRef< rowAction, nonConsolidatedColumnResolver, valueFormatter, + keyFormatter, prettifyNames = false, selectionEnabled = false, selectionHeaderLabel = 'Selected', @@ -1264,7 +1287,9 @@ const MinecraftGroupedStatisticTable = forwardRef< )} - {row.category} + + {keyFormatter ? keyFormatter(row.category) : row.category} + {row.values.map((value, valueIndex) => ( @@ -1363,7 +1388,9 @@ const MinecraftGroupedStatisticTable = forwardRef< ))} {sparklineColumnIndex !== undefined && ( - Trend + + Trend ({sparklineTickRangeToSeconds(sparklineTickRange)}) + )} {rowAction && ( @@ -1434,7 +1461,7 @@ const MinecraftGroupedStatisticTable = forwardRef< {isExpanded ? '▾' : '▸'} - {group.key} + {keyFormatter ? keyFormatter(group.key) : group.key} diff --git a/webview-ui/src/diagnostics_panel/controls/SparklineCell.tsx b/webview-ui/src/diagnostics_panel/controls/SparklineCell.tsx index bbe2676..a299874 100644 --- a/webview-ui/src/diagnostics_panel/controls/SparklineCell.tsx +++ b/webview-ui/src/diagnostics_panel/controls/SparklineCell.tsx @@ -13,6 +13,30 @@ function formatSparklineLabelValue(value: number, formatValue?: (value: number) return formatValue ? formatValue(value) : value.toFixed(1); } +function calculateMedian(values: number[]): number { + if (values.length === 0) { + return 0; + } + + // For median, we need to sort the values and find the middle one (or average of two middle ones) + const sorted = [...values].sort((a, b) => a - b); + const mid = Math.floor(sorted.length / 2); + return sorted.length % 2 !== 0 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2; +} + +function calculateStandardDeviation(values: number[]): number { + if (values.length === 0) { + return 0; + } + + // Calculate the mean by summing all values and dividing by the count + const mean = values.reduce((acc, v) => acc + v, 0) / values.length; + // Calculate the variance by averaging the squared differences from the mean + const variance = values.reduce((acc, v) => acc + Math.pow(v - mean, 2), 0) / values.length; + // Finally, take the square root of the variance to get the standard deviation + return Math.sqrt(variance); +} + // A simple sparkline component that renders // a basic line chart of the provided values along with min and max labels. // Implemented using a dynamic SVG polyline. @@ -30,6 +54,9 @@ export function SparklineCell({ const rawMax = Math.max(...values); const rawMin = Math.min(...values); + const median = calculateMedian(values); + const stddev = calculateStandardDeviation(values); + const max = displayedMax ?? rawMax; const min = displayedMin ?? rawMin; const range = max - min || 1; @@ -44,6 +71,8 @@ export function SparklineCell({ const maxLabel = formatSparklineLabelValue(rawMax, formatValue); const minLabel = formatSparklineLabelValue(rawMin, formatValue); + const medianLabel = formatSparklineLabelValue(median, formatValue); + const stddevLabel = formatSparklineLabelValue(stddev, formatValue); return (
@@ -67,6 +96,14 @@ export function SparklineCell({ Min {minLabel}
+
+ + Median {medianLabel} + + + Std Dev {stddevLabel} + +
); } diff --git a/webview-ui/src/diagnostics_panel/prefabs/tabs/ClientEntitySystems.tsx b/webview-ui/src/diagnostics_panel/prefabs/tabs/ClientEntitySystems.tsx index b965783..054abce 100644 --- a/webview-ui/src/diagnostics_panel/prefabs/tabs/ClientEntitySystems.tsx +++ b/webview-ui/src/diagnostics_panel/prefabs/tabs/ClientEntitySystems.tsx @@ -77,14 +77,14 @@ function ceilToDecimalPlace(value: number, decimalPlaces: number): string { function getTimingColumnLabel(unit: TimingUnit): string { if (unit === 'ms') { - return 'Time In Milliseconds'; + return 'Time (ms)'; } if (unit === 'us') { - return 'Time In Microseconds'; + return 'Time (µs)'; } - return 'Time In Nanoseconds'; + return 'Time (ns)'; } function formatTimingValue(value: number, unit: TimingUnit): string { @@ -97,7 +97,7 @@ function formatTimingValue(value: number, unit: TimingUnit): string { } if (unit === 'us') { - return `${ceilToDecimalPlace(value / 1_000, 1)} us`; + return `${ceilToDecimalPlace(value / 1_000, 1)} µs`; } return `${value} ns`; @@ -136,6 +136,10 @@ function extractEntityId(entityCategory: string): string | undefined { return match?.[1]; } +function formatStatKey(key: string): string { + return key.split('(')[0].trim(); +} + function resolveSystemId(fullName: string): string | undefined { // Example fullname: "System Name (4)" const indexAndBracket = fullName ? fullName.split('(')[1] : undefined; @@ -299,7 +303,7 @@ const StatsTab: TabPrefab = { [filterSelectionMode], ); - const entityValueLabels = [getTimingColumnLabel(entityTimingUnit), 'Percent Of Total']; + const entityValueLabels = [getTimingColumnLabel(entityTimingUnit), '% Of Total']; const filteredEntityLabel = (() => { if (filterSelectionMode === 'filter-by-single-entity') { @@ -604,7 +608,7 @@ const StatsTab: TabPrefab = { : undefined } keyLabel="System" - valueLabels={[getTimingColumnLabel(systemTimingUnit), 'Percent Of Total']} + valueLabels={[getTimingColumnLabel(systemTimingUnit), '% Of Total']} displayMode={ systemViewMode === 'grouped' ? MinecraftGroupedStatisticTableDisplayMode.Grouped @@ -641,6 +645,7 @@ const StatsTab: TabPrefab = { sparklineColumnIndex={0} sparklineTickRange={100} sparklineValueFormatter={value => formatTimingValue(value, systemTimingUnit)} + keyFormatter={formatStatKey} valueFormatter={(value, columnIndex) => { // Timing column if (columnIndex === 0) {