mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-13 23:51:16 -07:00
Change detailed model stats to use a new shared Table class to resolve robustness issues. (#15208)
This commit is contained in:
@@ -13,42 +13,17 @@ import {
|
||||
calculateCacheHitRate,
|
||||
calculateErrorRate,
|
||||
} from '../utils/computeStats.js';
|
||||
import type { ModelMetrics } from '../contexts/SessionContext.js';
|
||||
import { useSessionStats } from '../contexts/SessionContext.js';
|
||||
import { Table, type Column } from './Table.js';
|
||||
|
||||
const METRIC_COL_WIDTH = 28;
|
||||
const MODEL_COL_WIDTH = 22;
|
||||
|
||||
interface StatRowProps {
|
||||
title: string;
|
||||
values: Array<string | React.ReactElement>;
|
||||
isSubtle?: boolean;
|
||||
interface StatRowData {
|
||||
metric: string;
|
||||
isSection?: boolean;
|
||||
isSubtle?: boolean;
|
||||
// Dynamic keys for model values
|
||||
[key: string]: string | React.ReactNode | boolean | undefined;
|
||||
}
|
||||
|
||||
const StatRow: React.FC<StatRowProps> = ({
|
||||
title,
|
||||
values,
|
||||
isSubtle = false,
|
||||
isSection = false,
|
||||
}) => (
|
||||
<Box>
|
||||
<Box width={METRIC_COL_WIDTH}>
|
||||
<Text
|
||||
bold={isSection}
|
||||
color={isSection ? theme.text.primary : theme.text.link}
|
||||
>
|
||||
{isSubtle ? ` ↳ ${title}` : title}
|
||||
</Text>
|
||||
</Box>
|
||||
{values.map((value, index) => (
|
||||
<Box width={MODEL_COL_WIDTH} key={index}>
|
||||
<Text color={theme.text.primary}>{value}</Text>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
|
||||
export const ModelStatsDisplay: React.FC = () => {
|
||||
const { stats } = useSessionStats();
|
||||
const { models } = stats.metrics;
|
||||
@@ -73,10 +48,6 @@ export const ModelStatsDisplay: React.FC = () => {
|
||||
|
||||
const modelNames = activeModels.map(([name]) => name);
|
||||
|
||||
const getModelValues = (
|
||||
getter: (metrics: ModelMetrics) => string | React.ReactElement,
|
||||
) => activeModels.map(([, metrics]) => getter(metrics));
|
||||
|
||||
const hasThoughts = activeModels.some(
|
||||
([, metrics]) => metrics.tokens.thoughts > 0,
|
||||
);
|
||||
@@ -85,6 +56,152 @@ export const ModelStatsDisplay: React.FC = () => {
|
||||
([, metrics]) => metrics.tokens.cached > 0,
|
||||
);
|
||||
|
||||
// Helper to create a row with values for each model
|
||||
const createRow = (
|
||||
metric: string,
|
||||
getValue: (
|
||||
metrics: (typeof activeModels)[0][1],
|
||||
) => string | React.ReactNode,
|
||||
options: { isSection?: boolean; isSubtle?: boolean } = {},
|
||||
): StatRowData => {
|
||||
const row: StatRowData = {
|
||||
metric,
|
||||
isSection: options.isSection,
|
||||
isSubtle: options.isSubtle,
|
||||
};
|
||||
activeModels.forEach(([name, metrics]) => {
|
||||
row[name] = getValue(metrics);
|
||||
});
|
||||
return row;
|
||||
};
|
||||
|
||||
const rows: StatRowData[] = [
|
||||
// API Section
|
||||
{ metric: 'API', isSection: true },
|
||||
createRow('Requests', (m) => m.api.totalRequests.toLocaleString()),
|
||||
createRow('Errors', (m) => {
|
||||
const errorRate = calculateErrorRate(m);
|
||||
return (
|
||||
<Text
|
||||
color={
|
||||
m.api.totalErrors > 0 ? theme.status.error : theme.text.primary
|
||||
}
|
||||
>
|
||||
{m.api.totalErrors.toLocaleString()} ({errorRate.toFixed(1)}%)
|
||||
</Text>
|
||||
);
|
||||
}),
|
||||
createRow('Avg Latency', (m) => formatDuration(calculateAverageLatency(m))),
|
||||
|
||||
// Spacer
|
||||
{ metric: '' },
|
||||
|
||||
// Tokens Section
|
||||
{ metric: 'Tokens', isSection: true },
|
||||
createRow('Total', (m) => (
|
||||
<Text color={theme.text.secondary}>
|
||||
{m.tokens.total.toLocaleString()}
|
||||
</Text>
|
||||
)),
|
||||
createRow(
|
||||
'Input',
|
||||
(m) => (
|
||||
<Text color={theme.text.primary}>
|
||||
{m.tokens.input.toLocaleString()}
|
||||
</Text>
|
||||
),
|
||||
{ isSubtle: true },
|
||||
),
|
||||
];
|
||||
|
||||
if (hasCached) {
|
||||
rows.push(
|
||||
createRow(
|
||||
'Cache Reads',
|
||||
(m) => {
|
||||
const cacheHitRate = calculateCacheHitRate(m);
|
||||
return (
|
||||
<Text color={theme.text.secondary}>
|
||||
{m.tokens.cached.toLocaleString()} ({cacheHitRate.toFixed(1)}%)
|
||||
</Text>
|
||||
);
|
||||
},
|
||||
{ isSubtle: true },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (hasThoughts) {
|
||||
rows.push(
|
||||
createRow(
|
||||
'Thoughts',
|
||||
(m) => (
|
||||
<Text color={theme.text.primary}>
|
||||
{m.tokens.thoughts.toLocaleString()}
|
||||
</Text>
|
||||
),
|
||||
{ isSubtle: true },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (hasTool) {
|
||||
rows.push(
|
||||
createRow(
|
||||
'Tool',
|
||||
(m) => (
|
||||
<Text color={theme.text.primary}>
|
||||
{m.tokens.tool.toLocaleString()}
|
||||
</Text>
|
||||
),
|
||||
{ isSubtle: true },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
rows.push(
|
||||
createRow(
|
||||
'Output',
|
||||
(m) => (
|
||||
<Text color={theme.text.primary}>
|
||||
{m.tokens.candidates.toLocaleString()}
|
||||
</Text>
|
||||
),
|
||||
{ isSubtle: true },
|
||||
),
|
||||
);
|
||||
|
||||
const columns: Array<Column<StatRowData>> = [
|
||||
{
|
||||
key: 'metric',
|
||||
header: 'Metric',
|
||||
width: 28,
|
||||
renderCell: (row) => (
|
||||
<Text
|
||||
bold={row.isSection}
|
||||
color={row.isSection ? theme.text.primary : theme.text.link}
|
||||
>
|
||||
{row.isSubtle ? ` ↳ ${row.metric}` : row.metric}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
...modelNames.map((name) => ({
|
||||
key: name,
|
||||
header: name,
|
||||
flexGrow: 1,
|
||||
renderCell: (row: StatRowData) => {
|
||||
// Don't render anything for section headers in model columns
|
||||
if (row.isSection) return null;
|
||||
const val = row[name];
|
||||
if (val === undefined || val === null) return null;
|
||||
if (typeof val === 'string' || typeof val === 'number') {
|
||||
return <Text color={theme.text.primary}>{val}</Text>;
|
||||
}
|
||||
return val as React.ReactNode;
|
||||
},
|
||||
})),
|
||||
];
|
||||
|
||||
return (
|
||||
<Box
|
||||
borderStyle="round"
|
||||
@@ -97,128 +214,7 @@ export const ModelStatsDisplay: React.FC = () => {
|
||||
Model Stats For Nerds
|
||||
</Text>
|
||||
<Box height={1} />
|
||||
|
||||
{/* Header */}
|
||||
<Box>
|
||||
<Box width={METRIC_COL_WIDTH}>
|
||||
<Text bold color={theme.text.primary}>
|
||||
Metric
|
||||
</Text>
|
||||
</Box>
|
||||
{modelNames.map((name) => (
|
||||
<Box width={MODEL_COL_WIDTH} key={name}>
|
||||
<Text bold color={theme.text.primary}>
|
||||
{name}
|
||||
</Text>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{/* Divider */}
|
||||
<Box
|
||||
borderStyle="single"
|
||||
borderBottom={true}
|
||||
borderTop={false}
|
||||
borderLeft={false}
|
||||
borderRight={false}
|
||||
borderColor={theme.border.default}
|
||||
/>
|
||||
|
||||
{/* API Section */}
|
||||
<StatRow title="API" values={[]} isSection />
|
||||
<StatRow
|
||||
title="Requests"
|
||||
values={getModelValues((m) => m.api.totalRequests.toLocaleString())}
|
||||
/>
|
||||
<StatRow
|
||||
title="Errors"
|
||||
values={getModelValues((m) => {
|
||||
const errorRate = calculateErrorRate(m);
|
||||
return (
|
||||
<Text
|
||||
color={
|
||||
m.api.totalErrors > 0 ? theme.status.error : theme.text.primary
|
||||
}
|
||||
>
|
||||
{m.api.totalErrors.toLocaleString()} ({errorRate.toFixed(1)}%)
|
||||
</Text>
|
||||
);
|
||||
})}
|
||||
/>
|
||||
<StatRow
|
||||
title="Avg Latency"
|
||||
values={getModelValues((m) => {
|
||||
const avgLatency = calculateAverageLatency(m);
|
||||
return formatDuration(avgLatency);
|
||||
})}
|
||||
/>
|
||||
|
||||
<Box height={1} />
|
||||
|
||||
{/* Tokens Section */}
|
||||
<StatRow title="Tokens" values={[]} isSection />
|
||||
<StatRow
|
||||
title="Total"
|
||||
values={getModelValues((m) => (
|
||||
<Text color={theme.text.secondary}>
|
||||
{m.tokens.total.toLocaleString()}
|
||||
</Text>
|
||||
))}
|
||||
/>
|
||||
<StatRow
|
||||
title="Input"
|
||||
isSubtle
|
||||
values={getModelValues((m) => (
|
||||
<Text color={theme.text.primary}>
|
||||
{m.tokens.input.toLocaleString()}
|
||||
</Text>
|
||||
))}
|
||||
/>
|
||||
{hasCached && (
|
||||
<StatRow
|
||||
title="Cache Reads"
|
||||
isSubtle
|
||||
values={getModelValues((m) => {
|
||||
const cacheHitRate = calculateCacheHitRate(m);
|
||||
return (
|
||||
<Text color={theme.text.secondary}>
|
||||
{m.tokens.cached.toLocaleString()} ({cacheHitRate.toFixed(1)}%)
|
||||
</Text>
|
||||
);
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{hasThoughts && (
|
||||
<StatRow
|
||||
title="Thoughts"
|
||||
isSubtle
|
||||
values={getModelValues((m) => (
|
||||
<Text color={theme.text.primary}>
|
||||
{m.tokens.thoughts.toLocaleString()}
|
||||
</Text>
|
||||
))}
|
||||
/>
|
||||
)}
|
||||
{hasTool && (
|
||||
<StatRow
|
||||
title="Tool"
|
||||
isSubtle
|
||||
values={getModelValues((m) => (
|
||||
<Text color={theme.text.primary}>
|
||||
{m.tokens.tool.toLocaleString()}
|
||||
</Text>
|
||||
))}
|
||||
/>
|
||||
)}
|
||||
<StatRow
|
||||
title="Output"
|
||||
isSubtle
|
||||
values={getModelValues((m) => (
|
||||
<Text color={theme.text.primary}>
|
||||
{m.tokens.candidates.toLocaleString()}
|
||||
</Text>
|
||||
))}
|
||||
/>
|
||||
<Table data={rows} columns={columns} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user