Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/codex_usage_tracker/diagnostic_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
DIAGNOSTIC_FACT_SORT_CHOICES = (
"uncached",
"tokens",
"cached",
"output",
"cache",
"largest",
"calls",
"occurrences",
"time",
Expand Down
148 changes: 126 additions & 22 deletions src/codex_usage_tracker/plugin_data/dashboard/dashboard_diagnostics.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
const factCallPageSize = 25;
const factCallPayloads = new Map();
const factCallSorts = new Map();
const factSorts = new Map();

function setActive(active) {
diagnosticsPanelEl.hidden = !active;
Expand All @@ -52,6 +53,7 @@
payloads = emptyPayloads();
factCallPayloads.clear();
factCallSorts.clear();
factSorts.clear();
}

function renderDiagnostics(dateRange) {
Expand Down Expand Up @@ -86,10 +88,13 @@
const generation = requestGeneration + 1;
requestGeneration = generation;
try {
const factsSort = factSortState('facts');
const toolsSort = factSortState('tools');
const compactionsSort = factSortState('compactions');
const [facts, tools, compactions] = await Promise.all([
fetchPayload('/api/diagnostics/facts', { ...filters, limit: '50', sort: 'uncached', direction: 'desc' }),
fetchPayload('/api/diagnostics/tools', { ...filters, limit: '25', sort: 'uncached', direction: 'desc' }),
fetchPayload('/api/diagnostics/compactions', { ...filters, limit: '25', sort: 'uncached', direction: 'desc' }),
fetchPayload('/api/diagnostics/facts', { ...filters, limit: '50', sort: factsSort.sort, direction: factsSort.direction }),
fetchPayload('/api/diagnostics/tools', { ...filters, limit: '25', sort: toolsSort.sort, direction: toolsSort.direction }),
fetchPayload('/api/diagnostics/compactions', { ...filters, limit: '25', sort: compactionsSort.sort, direction: compactionsSort.direction }),
]);
if (generation !== requestGeneration || signature !== activeSignature) return;
payloads = { facts, tools, compactions };
Expand Down Expand Up @@ -164,6 +169,20 @@
renderIfActive();
}

function sortFactRows(sectionKey, sortKey) {
if (!diagnosticFactSortLabels()[sortKey]) return;
const current = factSortState(sectionKey);
const next = current.sort === sortKey
? { sort: sortKey, direction: current.direction === 'asc' ? 'desc' : 'asc' }
: { sort: sortKey, direction: defaultFactSortDirection(sortKey) };
factSorts.set(sectionKey, next);
status = 'loading';
errorMessage = '';
const filters = getDiagnosticFilters();
void fetchDiagnostics(activeSignature, filters);
renderIfActive();
}

function sortFactCalls(sortKey) {
if (!selectedFactKey || !diagnosticCallSortLabels()[sortKey]) return;
const current = factCallSortState(selectedFactKey);
Expand Down Expand Up @@ -206,30 +225,30 @@
${readoutMetric('Compaction rows', payloads.compactions)}
<span class="diagnostics-note">Structured labels only. Raw context remains on-demand in the call investigator.</span>
</div>
${renderFactSection('Top Diagnostic Facts', 'Ranked by associated uncached input tokens.', payloads.facts, loading)}
${renderFactSection('Tool and Function Activity', 'Tool/function facts associated with model calls.', payloads.tools, loading)}
${renderFactSection('Compaction Activity', 'Compaction facts and post-compaction associated costs.', payloads.compactions, loading)}
${renderFactSection('facts', 'Top Diagnostic Facts', 'Structured facts associated with model calls.', payloads.facts, loading)}
${renderFactSection('tools', 'Tool and Function Activity', 'Tool/function facts associated with model calls.', payloads.tools, loading)}
${renderFactSection('compactions', 'Compaction Activity', 'Compaction facts and post-compaction associated costs.', payloads.compactions, loading)}
</div>
`;
}

function renderFactSection(title, caption, payload, loading) {
function renderFactSection(sectionKey, title, caption, payload, loading) {
const rows = Array.isArray(payload?.rows) ? payload.rows : [];
return `
<div class="diagnostics-section">
<div class="diagnostics-section-header">
<div>
<h3>${escapeHtml(title)}</h3>
<p>${escapeHtml(caption)}</p>
<p>${escapeHtml(`${caption} Sorted by ${diagnosticFactSortDescription(sectionKey)}.`)}</p>
</div>
<span>${escapeHtml(payload ? `${number.format(payload.total_matched_rows || rows.length)} matched` : loading ? 'Loading' : 'No payload')}</span>
</div>
${renderFactTable(rows, loading)}
${renderFactTable(sectionKey, rows, loading)}
</div>
`;
}

function renderFactTable(rows, loading) {
function renderFactTable(sectionKey, rows, loading) {
if (loading && !rows.length) return renderState('Loading diagnostics...');
if (!rows.length) return renderState('No diagnostic facts matched the current filters.');
const body = rows.map(row => {
Expand All @@ -240,7 +259,7 @@
: tokenText(row.largest_call_tokens);
return `
<tr class="${selected ? 'selected-row' : ''}">
<td>
<td class="diagnostics-fact-cell">
<div class="diagnostic-fact">
<strong>${escapeHtml(row.fact_type || 'unknown')}/${escapeHtml(row.fact_name || 'unknown')}</strong>
<span>${escapeHtml(row.fact_category || 'uncategorized')}</span>
Expand All @@ -266,18 +285,31 @@
}).join('');
return `
<div class="diagnostics-table-wrap">
<table class="diagnostics-table">
<table class="diagnostics-table diagnostics-facts-table">
<colgroup>
<col class="diagnostics-fact-col">
<col class="diagnostics-count-col">
<col class="diagnostics-count-col">
<col class="diagnostics-token-col">
<col class="diagnostics-token-col">
<col class="diagnostics-token-col">
<col class="diagnostics-token-col">
<col class="diagnostics-ratio-col">
<col class="diagnostics-token-col">
<col class="diagnostics-latest-col">
<col class="diagnostics-action-col">
</colgroup>
<thead><tr>
${columnHeader('Fact', 'Diagnostic fact type and name derived from structured local log metadata. Raw prompts, assistant text, and tool output are not persisted.')}
${columnHeader('Occ', 'Occurrences: count of matching diagnostic fact events. One model call can contribute more than one occurrence.', 'num')}
${columnHeader('Calls', 'Distinct model calls associated with this diagnostic fact.', 'num')}
${columnHeader('Assoc total', 'Associated total tokens for those calls. Totals are not additive across facts because one call can have multiple facts.', 'num')}
${columnHeader('Cached', 'Associated cached input tokens for those calls.', 'num')}
${columnHeader('Uncached', 'Associated uncached input tokens for those calls.', 'num')}
${columnHeader('Output', 'Associated output tokens for those calls.', 'num')}
${columnHeader('Cache %', 'Average cache ratio across associated calls.', 'num')}
${columnHeader('Largest', 'Largest associated call by total tokens.', 'num')}
${columnHeader('Latest', 'Latest associated call timestamp.')}
${diagnosticFactHeader(sectionKey, 'fact', 'Fact', false, 'Diagnostic fact type and name derived from structured local log metadata. Raw prompts, assistant text, and tool output are not persisted.')}
${diagnosticFactHeader(sectionKey, 'occurrences', 'Occ', true, 'Occurrences: count of matching diagnostic fact events. One model call can contribute more than one occurrence.')}
${diagnosticFactHeader(sectionKey, 'calls', 'Calls', true, 'Distinct model calls associated with this diagnostic fact.')}
${diagnosticFactHeader(sectionKey, 'tokens', 'Assoc total', true, 'Associated total tokens for those calls. Totals are not additive across facts because one call can have multiple facts.')}
${diagnosticFactHeader(sectionKey, 'cached', 'Cached', true, 'Associated cached input tokens for those calls.')}
${diagnosticFactHeader(sectionKey, 'uncached', 'Uncached', true, 'Associated uncached input tokens for those calls.')}
${diagnosticFactHeader(sectionKey, 'output', 'Output', true, 'Associated output tokens for those calls.')}
${diagnosticFactHeader(sectionKey, 'cache', 'Cache %', true, 'Average cache ratio across associated calls.')}
${diagnosticFactHeader(sectionKey, 'largest', 'Largest', true, 'Largest associated call by total tokens.')}
${diagnosticFactHeader(sectionKey, 'time', 'Latest', false, 'Latest associated call timestamp.')}
${columnHeader('Action', 'Expand or collapse the associated calls.')}
</tr></thead>
<tbody>${body}</tbody>
Expand Down Expand Up @@ -380,6 +412,22 @@
return `<th${classAttr}${tooltipAttr ? ` ${tooltipAttr}` : ''}>${escapeHtml(label)}</th>`;
}

function diagnosticFactHeader(sectionKey, sortKey, label, numeric = false, tooltip = '') {
const state = factSortState(sectionKey);
const active = state.sort === sortKey;
const indicator = active ? (state.direction === 'asc' ? '▲' : '▼') : '';
const ariaSort = active ? (state.direction === 'asc' ? 'ascending' : 'descending') : 'none';
const tooltipAttr = tooltipAttributes(tooltip);
return `
<th${numeric ? ' class="num"' : ''} data-diagnostics-fact-sort-active="${active ? 'true' : 'false'}" aria-sort="${ariaSort}"${tooltipAttr ? ` ${tooltipAttr}` : ''}>
<button class="sort-header child-sort-header" type="button" data-diagnostics-fact-section="${escapeHtml(sectionKey)}" data-diagnostics-fact-sort-key="${escapeHtml(sortKey)}">
<span>${escapeHtml(label)}</span>
<span class="sort-indicator">${escapeHtml(indicator)}</span>
</button>
</th>
`;
}

function diagnosticCallHeader(sortKey, label, numeric = false, tooltip = '') {
const state = factCallSortState(selectedFactKey);
const active = state.sort === sortKey;
Expand All @@ -396,6 +444,28 @@
`;
}

function diagnosticFactSortDescription(sectionKey) {
const state = factSortState(sectionKey);
const labels = diagnosticFactSortLabels();
const label = labels[state.sort] || state.sort;
return `${label} ${state.direction === 'asc' ? 'ascending' : 'descending'}`;
}

function diagnosticFactSortLabels() {
return {
cache: 'cache ratio',
cached: 'cached input tokens',
calls: 'associated calls',
fact: 'fact name',
largest: 'largest call',
occurrences: 'occurrences',
output: 'output tokens',
time: 'latest call time',
tokens: 'total tokens',
uncached: 'uncached input tokens',
};
}

function diagnosticCallSortDescription() {
const state = factCallSortState(selectedFactKey);
const labels = diagnosticCallSortLabels();
Expand All @@ -419,6 +489,14 @@
};
}

function factSortState(sectionKey) {
return factSorts.get(sectionKey) || { sort: 'uncached', direction: 'desc' };
}

function defaultFactSortDirection(sortKey) {
return sortKey === 'fact' ? 'asc' : 'desc';
}

function factCallSortState(key) {
return factCallSorts.get(key) || { sort: 'tokens', direction: 'desc' };
}
Expand Down Expand Up @@ -483,10 +561,25 @@
};
}

function captureScrollPosition() {
pendingScrollAnchor = {
key: '',
type: 'scroll',
top: 0,
scrollY: window.scrollY,
};
}

function restoreScrollAnchor() {
if (!pendingScrollAnchor) return;
const anchor = pendingScrollAnchor;
pendingScrollAnchor = null;
if (anchor.type === 'scroll') {
window.requestAnimationFrame(() => {
window.scrollTo({ top: anchor.scrollY, behavior: 'auto' });
});
return;
}
window.requestAnimationFrame(() => {
const target = anchor.type === 'load-more'
? diagnosticsPanelEl.querySelector('[data-diagnostics-call-load-more]')
Expand Down Expand Up @@ -539,6 +632,17 @@
void fetchFactCalls(factType, factName, { append: true });
return;
}
const factSortButton = target.closest('[data-diagnostics-fact-sort-key]');
if (factSortButton && diagnosticsPanelEl.contains(factSortButton)) {
event.preventDefault();
event.stopPropagation();
captureScrollPosition();
sortFactRows(
factSortButton.dataset.diagnosticsFactSection || 'facts',
factSortButton.dataset.diagnosticsFactSortKey || '',
);
return;
}
const sortButton = target.closest('[data-diagnostics-call-sort-key]');
if (sortButton && diagnosticsPanelEl.contains(sortButton)) {
event.preventDefault();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,12 +323,46 @@
min-width: 1120px;
table-layout: fixed;
}
.diagnostics-facts-table {
min-width: 1320px;
}
.diagnostics-facts-table .diagnostics-fact-col { width: 26%; }
.diagnostics-facts-table .diagnostics-count-col { width: 5.5%; }
.diagnostics-facts-table .diagnostics-token-col { width: 7.5%; }
.diagnostics-facts-table .diagnostics-ratio-col { width: 5%; }
.diagnostics-facts-table .diagnostics-latest-col { width: 14.5%; }
.diagnostics-facts-table .diagnostics-action-col { width: 6%; }
.diagnostics-call-table {
min-width: 1060px;
}
.diagnostics-table th {
position: static;
}
.diagnostics-facts-table th {
position: sticky;
top: 0;
z-index: 12;
background: #fbfcfe;
}
.diagnostics-facts-table th:first-child,
.diagnostics-facts-table td.diagnostics-fact-cell {
position: sticky;
left: 0;
z-index: 3;
background: #ffffff;
box-shadow: 1px 0 0 var(--line);
}
.diagnostics-facts-table th:first-child {
z-index: 15;
background: #fbfcfe;
}
.diagnostics-facts-table tr.selected-row td.diagnostics-fact-cell {
background: #eff6ff;
}
.diagnostics-facts-table tbody tr:not(.diagnostics-drilldown-row):hover td,
.diagnostics-facts-table tbody tr:not(.diagnostics-drilldown-row):hover td.diagnostics-fact-cell {
background: #eff6ff;
}
.diagnostics-table td,
.diagnostics-table th {
padding: 8px 10px;
Expand All @@ -342,9 +376,10 @@
min-width: 0;
}
.diagnostic-fact strong {
overflow-wrap: anywhere;
overflow-wrap: break-word;
font-size: 12px;
line-height: 1.25;
word-break: normal;
}
.diagnostic-fact span {
color: var(--muted);
Expand Down
8 changes: 8 additions & 0 deletions src/codex_usage_tracker/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,10 @@ def query_diagnostic_facts(
sort_map = {
"uncached": "associated_uncached_input_tokens",
"tokens": "associated_total_tokens",
"cached": "associated_cached_input_tokens",
"output": "associated_output_tokens",
"cache": "avg_cache_ratio",
"largest": "largest_call_tokens",
"calls": "associated_calls",
"occurrences": "occurrences",
"time": "latest_event_timestamp",
Expand Down Expand Up @@ -681,6 +685,10 @@ def query_diagnostic_summary(
sort_map = {
"uncached": "associated_uncached_input_tokens",
"tokens": "associated_total_tokens",
"cached": "associated_cached_input_tokens",
"output": "associated_output_tokens",
"cache": "avg_cache_ratio",
"largest": "largest_call_tokens",
"calls": "associated_calls",
"occurrences": "occurrences",
"time": "latest_event_timestamp",
Expand Down
8 changes: 8 additions & 0 deletions tests/test_dashboard_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,14 @@ def test_dashboard_and_csv_are_aggregate_only(tmp_path: Path) -> None:
assert "Occurrences: count of matching diagnostic fact events" in dashboard_diagnostics_js
assert "Associated total tokens for those calls" in dashboard_diagnostics_js
assert "Average cache ratio across associated calls" in dashboard_diagnostics_js
assert "data-diagnostics-fact-sort-key" in dashboard_diagnostics_js
assert "data-diagnostics-fact-sort-active" in dashboard_diagnostics_js
assert "sortFactRows" in dashboard_diagnostics_js
assert "diagnosticFactHeader" in dashboard_diagnostics_js
assert "diagnostics-facts-table" in dashboard_surface
assert "diagnostics-fact-cell" in dashboard_surface
assert "diagnostics-facts-table th:first-child" in dashboard_css
assert "td.diagnostics-fact-cell" in dashboard_css
assert "captureScrollAnchor" in dashboard_diagnostics_js
assert "restoreScrollAnchor" in dashboard_diagnostics_js
assert "data-diagnostics-call-load-more" in dashboard_diagnostics_js
Expand Down
6 changes: 6 additions & 0 deletions tests/test_dashboard_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,9 @@ def test_dashboard_server_live_sql_api_slices_are_aggregate_only(tmp_path: Path)
recommendations_payload = _read_json(f"{base_url}/api/recommendations?limit=5")
diagnostics_summary_payload = _read_json(f"{base_url}/api/diagnostics/summary?limit=5")
diagnostics_facts_payload = _read_json(f"{base_url}/api/diagnostics/facts?limit=5")
diagnostics_sorted_facts_payload = _read_json(
f"{base_url}/api/diagnostics/facts?limit=5&sort=cached&direction=desc"
)
diagnostics_compactions_payload = _read_json(
f"{base_url}/api/diagnostics/compactions?limit=5"
)
Expand Down Expand Up @@ -526,6 +529,9 @@ def test_dashboard_server_live_sql_api_slices_are_aggregate_only(tmp_path: Path)
assert {row["fact_name"] for row in diagnostics_facts_payload["rows"]} >= {
"post_compaction"
}
assert diagnostics_sorted_facts_payload["filters"]["sort"] == "cached"
assert diagnostics_sorted_facts_payload["filters"]["direction"] == "desc"
_assert_contract(diagnostics_sorted_facts_payload)
assert diagnostics_compactions_payload["filters"]["fact_type"] == "compaction"
_assert_contract(diagnostics_compactions_payload)
assert {row["fact_type"] for row in diagnostics_compactions_payload["rows"]} == {
Expand Down
Loading