diff --git a/frigate/config/telemetry.py b/frigate/config/telemetry.py index 3c219d7460..4b2ed4277d 100644 --- a/frigate/config/telemetry.py +++ b/frigate/config/telemetry.py @@ -29,6 +29,11 @@ class StatsConfig(FrigateBaseModel): class TelemetryConfig(FrigateBaseModel): + analytics: bool = Field( + default=False, + title="Share anonymous analytics", + description="Send one anonymous usage report a day to help the Frigate maintainers decide what to support. Nothing is sent until this is on.", + ) network_interfaces: list[str] = Field( default=[], title="Network interfaces", diff --git a/frigate/notices/registry.py b/frigate/notices/registry.py index cb8f304f47..d494cef63f 100644 --- a/frigate/notices/registry.py +++ b/frigate/notices/registry.py @@ -319,6 +319,19 @@ class NoticeRegistry: if row.kind in NOTICE_KINDS ] + def mark_reported(self, snapshot: list[dict[str, Any]]) -> None: + """Move the analytics watermarks to the counts a sent report was built from. + + Using the snapshot rather than the current counts sends anything raised + while the report was in flight with the next one. + """ + with self._lock: + for row in snapshot: + NoticeStats.update( + reported_occurrences=row["occurrences"], + reported_dismissals=row["dismissals"], + ).where(NoticeStats.kind == row["kind"]).execute() + def _write_repeats( self, row: Notice, diff --git a/frigate/notices/types.py b/frigate/notices/types.py index 8594386d42..71e08e8e59 100644 --- a/frigate/notices/types.py +++ b/frigate/notices/types.py @@ -86,6 +86,16 @@ _KINDS = ( link="https://github.com/blakeblackshear/frigate/releases/tag/v{version}", counts_repeats=False, keep_latest=1, + reportable=False, + ), + # raised while analytics is off; the row keeps a dismissal across restarts + NoticeKind( + "analytics_prompt", + NoticeSeverity.info, + "system", + link="/settings?page=systemTelemetry", + counts_repeats=False, + reportable=False, ), ) diff --git a/frigate/test/test_config.py b/frigate/test/test_config.py index c1b9531a31..908c8dadd2 100644 --- a/frigate/test/test_config.py +++ b/frigate/test/test_config.py @@ -17,6 +17,11 @@ from frigate.util.builtin import deep_merge class TestConfig(unittest.TestCase): + def test_analytics_is_off_by_default(self): + frigate_config = FrigateConfig(**self.minimal) + + self.assertFalse(frigate_config.telemetry.analytics) + def setUp(self): self.minimal = { "mqtt": {"host": "mqtt"}, diff --git a/frigate/test/test_notice_registry.py b/frigate/test/test_notice_registry.py index c888bdda4a..c8c72fa35c 100644 --- a/frigate/test/test_notice_registry.py +++ b/frigate/test/test_notice_registry.py @@ -48,6 +48,16 @@ class TestNoticeKinds(unittest.TestCase): self.assertIsNone(kind.link_for({"version": "0.19.1"})) + def test_analytics_prompt_links_to_telemetry_settings(self): + kind = NOTICE_KINDS["analytics_prompt"] + + self.assertEqual(kind.link_for({}), "/settings?page=systemTelemetry") + self.assertFalse(kind.counts_repeats) + self.assertFalse(kind.reportable) + + def test_update_available_is_not_reported(self): + self.assertFalse(NOTICE_KINDS["update_available"].reportable) + class RegistryTestCase(unittest.TestCase): def setUp(self): @@ -278,6 +288,24 @@ class TestNoticeRegistry(RegistryTestCase): self.assertEqual(dismissals["detector_stuck"], 1) +class TestMarkReported(RegistryTestCase): + def test_moves_watermarks_to_the_snapshot_not_the_current_count(self): + self.registry.raise_notice( + "detector_stuck", scope="ov", params={"detector": "ov"} + ) + snapshot = self.registry.stats() + self.registry.raise_notice( + "detector_stuck", scope="ov", params={"detector": "ov"} + ) + + self.registry.mark_reported(snapshot) + + row = self.registry.stats()[0] + self.assertEqual(row["occurrences"], 2) + self.assertEqual(row["reported_occurrences"], 1) + self.assertEqual(row["reported_dismissals"], 0) + + class TestApply(RegistryTestCase): def test_each_action_reaches_its_method(self): self.registry.apply(