perf(dashboard): skip full InsightsEngine on /api/analytics/usage (#18511)
This commit is contained in:
parent
c4ac62a7ee
commit
c1639322c2
|
|
@ -196,6 +196,18 @@ class InsightsEngine:
|
|||
"top_sessions": top_sessions,
|
||||
}
|
||||
|
||||
def get_skill_breakdown(self, days: int = 30, source: str = None) -> Dict[str, Any]:
|
||||
"""Return only the skills section without running a full generate().
|
||||
|
||||
Uses the instr()-prefiltered _get_skill_usage query so only messages
|
||||
that reference skill_view or skill_manage are loaded from SQLite,
|
||||
avoiding the cost of _get_sessions, _get_tool_usage, and the other
|
||||
compute passes that generate() performs.
|
||||
"""
|
||||
cutoff = time.time() - (days * 86400)
|
||||
skill_usage = self._get_skill_usage(cutoff, source)
|
||||
return self._compute_skill_breakdown(skill_usage)
|
||||
|
||||
# =========================================================================
|
||||
# Data gathering (SQL queries)
|
||||
# =========================================================================
|
||||
|
|
@ -254,6 +266,8 @@ class InsightsEngine:
|
|||
" JOIN sessions s ON s.id = m.session_id"
|
||||
" WHERE s.started_at >= ? AND s.source = ?"
|
||||
" AND m.role = 'assistant' AND m.tool_calls IS NOT NULL"
|
||||
" AND (instr(m.tool_calls, 'skill_view') > 0"
|
||||
" OR instr(m.tool_calls, 'skill_manage') > 0)"
|
||||
)
|
||||
_GET_SKILL_CALLS_ALL = (
|
||||
"SELECT m.tool_calls, m.timestamp"
|
||||
|
|
@ -261,6 +275,8 @@ class InsightsEngine:
|
|||
" JOIN sessions s ON s.id = m.session_id"
|
||||
" WHERE s.started_at >= ?"
|
||||
" AND m.role = 'assistant' AND m.tool_calls IS NOT NULL"
|
||||
" AND (instr(m.tool_calls, 'skill_view') > 0"
|
||||
" OR instr(m.tool_calls, 'skill_manage') > 0)"
|
||||
)
|
||||
|
||||
def _get_sessions(self, cutoff: float, source: str = None) -> List[Dict]:
|
||||
|
|
|
|||
|
|
@ -14073,16 +14073,7 @@ def _get_usage_analytics(days: int = 30, profile: Optional[str] = None):
|
|||
FROM sessions WHERE started_at > ?
|
||||
""", (cutoff,))
|
||||
totals = dict(cur3.fetchone())
|
||||
insights_report = InsightsEngine(db).generate(days=days)
|
||||
skills = insights_report.get("skills", {
|
||||
"summary": {
|
||||
"total_skill_loads": 0,
|
||||
"total_skill_edits": 0,
|
||||
"total_skill_actions": 0,
|
||||
"distinct_skills_used": 0,
|
||||
},
|
||||
"top_skills": [],
|
||||
})
|
||||
skills = InsightsEngine(db).get_skill_breakdown(days=days)
|
||||
|
||||
return {
|
||||
"daily": daily,
|
||||
|
|
|
|||
|
|
@ -473,6 +473,49 @@ class TestInsightsPopulated:
|
|||
# And with the index present, the pin stays.
|
||||
assert "INDEXED BY" in InsightsEngine._GET_TOOL_CALLS_ALL
|
||||
|
||||
def test_get_skill_breakdown_matches_full_generate(self, populated_db):
|
||||
engine = InsightsEngine(populated_db)
|
||||
full = engine.generate(days=30)
|
||||
focused = engine.get_skill_breakdown(days=30)
|
||||
assert focused == full["skills"]
|
||||
|
||||
def test_get_skill_breakdown_respects_source_filter(self, populated_db):
|
||||
engine = InsightsEngine(populated_db)
|
||||
# Only s1 (cli) has skill_view "github-pr-workflow"
|
||||
focused = engine.get_skill_breakdown(days=30, source="cli")
|
||||
skill_names = [s["skill"] for s in focused["top_skills"]]
|
||||
assert "github-pr-workflow" in skill_names
|
||||
# github-code-review was in discord (s4), not cli
|
||||
assert "github-code-review" not in skill_names
|
||||
|
||||
def test_get_skill_breakdown_empty_db(self, db):
|
||||
focused = InsightsEngine(db).get_skill_breakdown(days=30)
|
||||
assert focused == {
|
||||
"summary": {
|
||||
"total_skill_loads": 0,
|
||||
"total_skill_edits": 0,
|
||||
"total_skill_actions": 0,
|
||||
"distinct_skills_used": 0,
|
||||
},
|
||||
"top_skills": [],
|
||||
}
|
||||
|
||||
def test_get_skill_usage_prefilter_ignores_non_skill_substring(self, db):
|
||||
# "my_skill_view_helper" contains "skill_view" as a substring; instr()
|
||||
# will match but the Python-side name check keeps the set clean.
|
||||
# More importantly, messages with no skill_* tools must be excluded.
|
||||
db.create_session(session_id="sx", source="cli", model="gpt-4o")
|
||||
db.append_message(
|
||||
"sx",
|
||||
role="assistant",
|
||||
content="Just using read_file.",
|
||||
tool_calls=[{"function": {"name": "read_file", "arguments": '{"path":"/tmp/x"}'}}],
|
||||
)
|
||||
db._conn.commit()
|
||||
focused = InsightsEngine(db).get_skill_breakdown(days=30)
|
||||
assert focused["summary"]["total_skill_actions"] == 0
|
||||
assert focused["top_skills"] == []
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# Formatting
|
||||
|
|
|
|||
|
|
@ -2222,6 +2222,15 @@ class TestNewEndpoints:
|
|||
assert top_skill["total_count"] == 1
|
||||
assert top_skill["last_used_at"] is not None
|
||||
|
||||
def test_analytics_usage_skips_full_insights_generate(self):
|
||||
"""get_usage_analytics must call get_skill_breakdown, not generate()."""
|
||||
from unittest.mock import patch
|
||||
from agent.insights import InsightsEngine
|
||||
|
||||
with patch.object(InsightsEngine, "generate") as mock_generate:
|
||||
resp = self.client.get("/api/analytics/usage?days=7")
|
||||
assert resp.status_code == 200
|
||||
mock_generate.assert_not_called()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Model context length: normalize/denormalize + /api/model/info
|
||||
|
|
|
|||
Loading…
Reference in New Issue