Add windowed telemetry adoption snapshots

This commit is contained in:
rcourtman
2026-04-14 11:16:11 +01:00
parent 58e67c7b19
commit 53bf37a8b2
3 changed files with 149 additions and 62 deletions
@@ -96,6 +96,11 @@ evidence with no subsystem ownership.
That same governed home now also owns the single "usage data" vocabulary for
anonymous outbound telemetry and local-only upgrade events, so Pulse stops
describing those two privacy scopes as unrelated systems.
That same operator-reporting boundary now also owns reusable latest-install
adoption baselines. `scripts/telemetry_adoption_report.py` must emit
windowed 24h, 72h, and 7d latest-install snapshots that split published
versions from unpublished or development builds, so RC adoption reads stop
depending on ad hoc SQL or one-off local helper scripts.
Security-facing settings remain intentionally shared with `frontend-primitives`
because shell framing and presentation consistency still belong there, but the
+86 -62
View File
@@ -23,6 +23,11 @@ from urllib.request import Request, urlopen
DEFAULT_DB_PATH = "/var/lib/pulse-license/licenses.sqlite"
DEFAULT_GITHUB_REPO = "rcourtman/Pulse"
DEFAULT_LATEST_INSTALL_WINDOWS = (
("24h", timedelta(hours=24)),
("72h", timedelta(hours=72)),
("7d", timedelta(days=7)),
)
GIT_DESCRIBE_RE = re.compile(
r"^(?P<base>\d+\.\d+\.\d+(?:-[0-9A-Za-z\.-]+)?)-(?P<count>\d+)-g(?P<sha>[0-9a-fA-F]+)(?P<dirty>-dirty)?$"
)
@@ -213,6 +218,51 @@ finally:
return json.loads(result.stdout)
def counter_entries(counter: Counter[str], key_name: str) -> list[dict[str, Any]]:
return [
{key_name: value, "installs": installs}
for value, installs in sorted(counter.items(), key=lambda item: (-item[1], item[0]))
]
def summarize_latest_install_windows(
latest_by_install: dict[str, dict[str, Any]],
published_versions: set[str],
*,
now: datetime | None = None,
windows: tuple[tuple[str, timedelta], ...] = DEFAULT_LATEST_INSTALL_WINDOWS,
) -> dict[str, Any]:
current_time = now or datetime.now(timezone.utc)
summary: dict[str, Any] = {}
for label, limit in windows:
version_split: Counter[str] = Counter()
published_split: Counter[str] = Counter()
non_release_split: Counter[str] = Counter()
platform_split: Counter[str] = Counter()
for row in latest_by_install.values():
received_at = parse_received_at(str(row["received_at"]))
if current_time - received_at > limit:
continue
platform = str(row.get("platform") or "unknown").strip() or "unknown"
identity = classify_reported_version(str(row.get("version") or ""), published_versions)
version_split[identity.version] += 1
platform_split[platform] += 1
target = published_split if identity.is_published_release else non_release_split
target[identity.version] += 1
summary[label] = {
"active_installs": sum(version_split.values()),
"latest_versions": counter_entries(version_split, "version"),
"published_versions": counter_entries(published_split, "version"),
"non_release_versions": counter_entries(non_release_split, "version"),
"platforms": counter_entries(platform_split, "platform"),
}
return summary
def summarize_rows(
db_stats: dict[str, Any],
rows: Iterable[dict[str, Any]],
@@ -225,50 +275,20 @@ def summarize_rows(
if existing is None or str(row["received_at"]) > str(existing["received_at"]):
latest_by_install[install_id] = row
now = datetime.now(timezone.utc)
active_24h = 0
active_72h = 0
platform_split = Counter()
version_split = Counter()
published_split = Counter()
non_release_split = Counter()
for row in latest_by_install.values():
received_at = parse_received_at(str(row["received_at"]))
age = now - received_at
if age <= timedelta(hours=24):
active_24h += 1
if age <= timedelta(hours=72):
active_72h += 1
platform = str(row.get("platform") or "unknown").strip() or "unknown"
platform_split[platform] += 1
identity = classify_reported_version(str(row.get("version") or ""), published_versions)
version_split[identity.version] += 1
target = published_split if identity.is_published_release else non_release_split
target[identity.version] += 1
latest_install_windows = summarize_latest_install_windows(latest_by_install, published_versions)
summary_72h = latest_install_windows["72h"]
return {
"db_stats": db_stats,
"latest_install_windows": latest_install_windows,
"active_latest": {
"active_24h": active_24h,
"active_72h": active_72h,
"active_24h": latest_install_windows["24h"]["active_installs"],
"active_72h": summary_72h["active_installs"],
},
"latest_version_split_72h": [
{"version": version, "installs": installs}
for version, installs in sorted(version_split.items(), key=lambda item: (-item[1], item[0]))
],
"published_version_split_72h": [
{"version": version, "installs": installs}
for version, installs in sorted(published_split.items(), key=lambda item: (-item[1], item[0]))
],
"non_release_version_split_72h": [
{"version": version, "installs": installs}
for version, installs in sorted(non_release_split.items(), key=lambda item: (-item[1], item[0]))
],
"latest_platform_split_72h": [
{"platform": platform, "installs": installs}
for platform, installs in sorted(platform_split.items(), key=lambda item: (-item[1], item[0]))
],
"latest_version_split_72h": summary_72h["latest_versions"],
"published_version_split_72h": summary_72h["published_versions"],
"non_release_version_split_72h": summary_72h["non_release_versions"],
"latest_platform_split_72h": summary_72h["platforms"],
}
@@ -280,30 +300,34 @@ def format_text(summary: dict[str, Any], repo: str, since_days: int) -> str:
f"latest ping: {summary['db_stats'].get('latest_ping') or 'unknown'}",
f"total rows: {summary['db_stats'].get('total_rows', 0)}",
f"total distinct installs: {summary['db_stats'].get('total_distinct_installs', 0)}",
f"active installs (24h): {summary['active_latest']['active_24h']}",
f"active installs (72h): {summary['active_latest']['active_72h']}",
"",
"Published versions (latest install state in last 72h):",
]
published = summary["published_version_split_72h"]
if published:
lines.extend(f"- {entry['version']}: {entry['installs']}" for entry in published)
else:
lines.append("- none")
lines.append("")
lines.append("Non-release or unpublished versions (latest install state in last 72h):")
non_release = summary["non_release_version_split_72h"]
if non_release:
lines.extend(f"- {entry['version']}: {entry['installs']}" for entry in non_release)
else:
lines.append("- none")
lines.append("")
lines.append("Platforms (latest install state in last 72h):")
platform_split = summary["latest_platform_split_72h"]
if platform_split:
lines.extend(f"- {entry['platform']}: {entry['installs']}" for entry in platform_split)
else:
lines.append("- none")
for label, _ in DEFAULT_LATEST_INSTALL_WINDOWS:
window_summary = summary["latest_install_windows"][label]
lines.extend(
[
"",
f"Latest install state ({label}):",
f"- active installs: {window_summary['active_installs']}",
"- published versions:",
]
)
if window_summary["published_versions"]:
lines.extend(f" - {entry['version']}: {entry['installs']}" for entry in window_summary["published_versions"])
else:
lines.append(" - none")
lines.append("- non-release or unpublished versions:")
if window_summary["non_release_versions"]:
lines.extend(
f" - {entry['version']}: {entry['installs']}" for entry in window_summary["non_release_versions"]
)
else:
lines.append(" - none")
lines.append("- platforms:")
if window_summary["platforms"]:
lines.extend(f" - {entry['platform']}: {entry['installs']}" for entry in window_summary["platforms"])
else:
lines.append(" - none")
return "\n".join(lines)
@@ -79,6 +79,9 @@ class TelemetryAdoptionReportTest(unittest.TestCase):
self.assertEqual(summary["active_latest"]["active_24h"], 2)
self.assertEqual(summary["active_latest"]["active_72h"], 3)
self.assertEqual(summary["latest_install_windows"]["24h"]["active_installs"], 2)
self.assertEqual(summary["latest_install_windows"]["72h"]["active_installs"], 3)
self.assertEqual(summary["latest_install_windows"]["7d"]["active_installs"], 3)
self.assertEqual(
summary["published_version_split_72h"],
[{"version": "6.0.0-rc.1", "installs": 1}],
@@ -90,6 +93,61 @@ class TelemetryAdoptionReportTest(unittest.TestCase):
{"version": "6.0.0-rc.2", "installs": 1},
],
)
self.assertEqual(
summary["latest_install_windows"]["24h"]["published_versions"],
[{"version": "6.0.0-rc.1", "installs": 1}],
)
self.assertEqual(
summary["latest_install_windows"]["72h"]["non_release_versions"],
[
{"version": "0.0.0-feature-new-metric", "installs": 1},
{"version": "6.0.0-rc.2", "installs": 1},
],
)
self.assertEqual(
summary["latest_install_windows"]["7d"]["platforms"],
[
{"platform": "binary", "installs": 2},
{"platform": "docker", "installs": 1},
],
)
def test_format_text_includes_latest_install_windows(self) -> None:
summary = {
"db_stats": {
"latest_ping": "2026-04-14 10:04:08",
"total_rows": 3228,
"total_distinct_installs": 229,
},
"latest_install_windows": {
"24h": {
"active_installs": 127,
"published_versions": [{"version": "6.0.0-rc.1", "installs": 91}],
"non_release_versions": [{"version": "6.0.0-rc.2", "installs": 32}],
"platforms": [{"platform": "docker", "installs": 66}],
},
"72h": {
"active_installs": 153,
"published_versions": [{"version": "6.0.0-rc.1", "installs": 117}],
"non_release_versions": [{"version": "6.0.0-rc.2", "installs": 32}],
"platforms": [{"platform": "binary", "installs": 83}],
},
"7d": {
"active_installs": 157,
"published_versions": [{"version": "6.0.0-rc.1", "installs": 118}],
"non_release_versions": [{"version": "6.0.0-rc.2", "installs": 32}],
"platforms": [{"platform": "binary", "installs": 87}],
},
},
}
rendered = report.format_text(summary, "rcourtman/Pulse", 7)
self.assertIn("Latest install state (24h):", rendered)
self.assertIn("Latest install state (72h):", rendered)
self.assertIn("Latest install state (7d):", rendered)
self.assertIn(" - 6.0.0-rc.1: 118", rendered)
self.assertIn(" - 6.0.0-rc.2: 32", rendered)
if __name__ == "__main__":