diff --git a/docs/usage/administration/monitoring/reporting_homeserver_usage_statistics.md b/docs/usage/administration/monitoring/reporting_homeserver_usage_statistics.md index a8a717e2a2..b91cabae9b 100644 --- a/docs/usage/administration/monitoring/reporting_homeserver_usage_statistics.md +++ b/docs/usage/administration/monitoring/reporting_homeserver_usage_statistics.md @@ -19,39 +19,44 @@ homeserver configuration options that exist to tweak it. The following statistics are sent to the configured reporting endpoint: -| Statistic Name | Type | Description | -|----------------------------|--------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `homeserver` | string | The homeserver's server name. | -| `memory_rss` | int | The memory usage of the process (in kilobytes on Unix-based systems, bytes on MacOS). | -| `cpu_average` | int | CPU time in % of a single core (not % of all cores). | -| `server_context` | string | An arbitrary string used to group statistics from a set of homeservers. | -| `timestamp` | int | The current time, represented as the number of seconds since the epoch. | -| `uptime_seconds` | int | The number of seconds since the homeserver was last started. | -| `python_version` | string | The Python version number in use (e.g "3.7.1"). Taken from `sys.version_info`. | -| `total_users` | int | The number of registered users on the homeserver. | -| `total_nonbridged_users` | int | The number of users, excluding those created by an Application Service. | -| `daily_user_type_native` | int | The number of native, non-guest users created in the last 24 hours. | -| `daily_user_type_guest` | int | The number of guest users created in the last 24 hours. | -| `daily_user_type_bridged` | int | The number of users created by Application Services in the last 24 hours. | -| `total_room_count` | int | The total number of rooms present on the homeserver. | -| `daily_active_users` | int | The number of unique users[^1] that have used the homeserver in the last 24 hours. | -| `monthly_active_users` | int | The number of unique users[^1] that have used the homeserver in the last 30 days. | -| `daily_active_rooms` | int | The number of rooms that have had a (state) event with the type `m.room.message` sent in them in the last 24 hours. | -| `daily_active_e2ee_rooms` | int | The number of rooms that have had a (state) event with the type `m.room.encrypted` sent in them in the last 24 hours. | -| `daily_messages` | int | The number of (state) events with the type `m.room.message` seen in the last 24 hours. | -| `daily_e2ee_messages` | int | The number of (state) events with the type `m.room.encrypted` seen in the last 24 hours. | -| `daily_sent_messages` | int | The number of (state) events sent by a local user with the type `m.room.message` seen in the last 24 hours. | -| `daily_sent_e2ee_messages` | int | The number of (state) events sent by a local user with the type `m.room.encrypted` seen in the last 24 hours. | -| `r30v2_users_all` | int | The number of 30 day retained users, with a revised algorithm. Defined as users that appear more than once in the past 60 days, and have more than 30 days between the most and least recent appearances in the past 60 days. Includes clients that do not fit into the below r30 client types. | -| `r30v2_users_android` | int | The number of 30 day retained users, as defined above. Filtered only to clients with ("riot" or "element") and "android" (case-insensitive) in the user agent string. | -| `r30v2_users_ios` | int | The number of 30 day retained users, as defined above. Filtered only to clients with ("riot" or "element") and "ios" (case-insensitive) in the user agent string. | -| `r30v2_users_electron` | int | The number of 30 day retained users, as defined above. Filtered only to clients with ("riot" or "element") and "electron" (case-insensitive) in the user agent string. | -| `r30v2_users_web` | int | The number of 30 day retained users, as defined above. Filtered only to clients with "mozilla" or "gecko" (case-insensitive) in the user agent string. | -| `cache_factor` | int | The configured [`global factor`](../../configuration/config_documentation.md#caching) value for caching. | -| `event_cache_size` | int | The configured [`event_cache_size`](../../configuration/config_documentation.md#caching) value for caching. | -| `database_engine` | string | The database engine that is in use. Either "psycopg2" meaning PostgreSQL is in use, or "sqlite3" for SQLite3. | -| `database_server_version` | string | The version of the database server. Examples being "10.10" for PostgreSQL server version 10.0, and "3.38.5" for SQLite 3.38.5 installed on the system. | -| `log_level` | string | The log level in use. Examples are "INFO", "WARNING", "ERROR", "DEBUG", etc. | +| Statistic Name | Type | Description | +|-------------------------------|--------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `homeserver` | string | The homeserver's server name. | +| `memory_rss` | int | The memory usage of the process (in kilobytes on Unix-based systems, bytes on MacOS). | +| `cpu_average` | int | CPU time in % of a single core (not % of all cores). | +| `server_context` | string | An arbitrary string used to group statistics from a set of homeservers. | +| `timestamp` | int | The current time, represented as the number of seconds since the epoch. | +| `uptime_seconds` | int | The number of seconds since the homeserver was last started. | +| `python_version` | string | The Python version number in use (e.g "3.7.1"). Taken from `sys.version_info`. | +| `total_users` | int | The number of registered users on the homeserver. | +| `total_nonbridged_users` | int | The number of users, excluding those created by an Application Service. | +| `daily_user_type_native` | int | The number of native, non-guest users created in the last 24 hours. | +| `daily_user_type_guest` | int | The number of guest users created in the last 24 hours. | +| `daily_user_type_bridged` | int | The number of users created by Application Services in the last 24 hours. | +| `total_room_count` | int | The total number of rooms present on the homeserver. | +| `daily_active_users` | int | The number of unique users[^1] that have used the homeserver in the last 24 hours. | +| `monthly_active_users` | int | The number of unique users[^1] that have used the homeserver in the last 30 days. | +| `daily_active_rooms` | int | The number of rooms that have had a (state) event with the type `m.room.message` sent in them in the last 24 hours. | +| `daily_active_e2ee_rooms` | int | The number of rooms that have had a (state) event with the type `m.room.encrypted` sent in them in the last 24 hours. | +| `daily_messages` | int | The number of (state) events with the type `m.room.message` seen in the last 24 hours. | +| `daily_e2ee_messages` | int | The number of (state) events with the type `m.room.encrypted` seen in the last 24 hours. | +| `daily_sent_messages` | int | The number of (state) events sent by a local user with the type `m.room.message` seen in the last 24 hours. | +| `daily_sent_e2ee_messages` | int | The number of (state) events sent by a local user with the type `m.room.encrypted` seen in the last 24 hours. | +| `r30v2_users_all` | int | The number of 30 day retained users, with a revised algorithm. Defined as users that appear more than once in the past 60 days, and have more than 30 days between the most and least recent appearances in the past 60 days. Includes clients that do not fit into the below r30 client types. | +| `r30v2_users_element_android` | int | The number of 30 day retained users, as defined above. Filtered only to clients with ("riot" or "element") and "android" (case-insensitive) in the user agent string. | +| `r30v2_users_element_ios` | int | The number of 30 day retained users, as defined above. Filtered only to clients with ("riot" or "element") and "ios" (case-insensitive) in the user agent string. | +| `r30v2_users_element_electron`| int | The number of 30 day retained users, as defined above. Filtered only to clients with ("riot" or "element") and "electron" (case-insensitive) in the user agent string. | +| `r30v2_users_web` | int | The number of 30 day retained users, as defined above. Filtered only to clients with "mozilla" or "gecko" (case-insensitive) in the user agent string. This is checked before bare android/ios matching so mobile browsers are counted as web. | +| `r30v2_users_famedly_android` | int | The number of 30 day retained users, as defined above. Filtered only to clients with "famedly" and "android" (case-insensitive) in the user agent string. | +| `r30v2_users_famedly_ios` | int | The number of 30 day retained users, as defined above. Filtered only to clients with "famedly" and "ios" (case-insensitive) in the user agent string. | +| `r30v2_users_unknown_android` | int | The number of 30 day retained users, as defined above. Filtered only to clients with "android" but neither branded (Element/Riot/Famedly) nor browser (mozilla/gecko) tokens (case-insensitive) in the user agent string. | +| `r30v2_users_unknown_ios` | int | The number of 30 day retained users, as defined above. Filtered only to clients with "ios" but neither branded (Element/Riot/Famedly) nor browser (mozilla/gecko) tokens (case-insensitive) in the user agent string. | +| `r30v2_users_unknown` | int | The number of 30 day retained users, as defined above, that do not match any of the other client types. | +| `cache_factor` | int | The configured [`global factor`](../../configuration/config_documentation.md#caching) value for caching. | +| `event_cache_size` | int | The configured [`event_cache_size`](../../configuration/config_documentation.md#caching) value for caching. | +| `database_engine` | string | The database engine that is in use. Either "psycopg2" meaning PostgreSQL is in use, or "sqlite3" for SQLite3. | +| `database_server_version` | string | The version of the database server. Examples being "10.10" for PostgreSQL server version 10.0, and "3.38.5" for SQLite 3.38.5 installed on the system. | +| `log_level` | string | The log level in use. Examples are "INFO", "WARNING", "ERROR", "DEBUG", etc. | [^1]: Native matrix users and guests are always counted. If the diff --git a/synapse/metrics/common_usage_metrics.py b/synapse/metrics/common_usage_metrics.py index ea2cdecf51..b67a015dfa 100644 --- a/synapse/metrics/common_usage_metrics.py +++ b/synapse/metrics/common_usage_metrics.py @@ -58,6 +58,12 @@ ["time_range", SERVER_NAME_LABEL], ) +r30v2_clients_gauge = Gauge( + "synapse_r30v2_clients", + "Number of R30v2 retained users by client type", + ["client", SERVER_NAME_LABEL], +) + @dataclass class UserMetrics: @@ -171,3 +177,9 @@ async def _update_gauges(self) -> None: retained_users_gauge.labels( time_range="30d", **{SERVER_NAME_LABEL: self.server_name} ).set(float(metrics.monthly_retained_users)) + + r30v2_results = await self._store.count_r30v2_users() + for client, count in r30v2_results.items(): + r30v2_clients_gauge.labels( + client=client, **{SERVER_NAME_LABEL: self.server_name} + ).set(float(count)) diff --git a/synapse/storage/databases/main/metrics.py b/synapse/storage/databases/main/metrics.py index 895c79f5da..a5a2bc044b 100644 --- a/synapse/storage/databases/main/metrics.py +++ b/synapse/storage/databases/main/metrics.py @@ -299,10 +299,15 @@ async def count_r30v2_users(self) -> dict[str, int]: The dict keys are: - "all" (a combined number of users across any and all clients) - - "android" (Element Android) - - "ios" (Element iOS) - - "electron" (Element Desktop) + - "element_android" (Element Android) + - "element_ios" (Element iOS) + - "element_electron" (Element Desktop) - "web" (any web application -- it's not possible to distinguish Element Web here) + - "famedly_android" (Famedly Android) + - "famedly_ios" (Famedly iOS) + - "unknown_android" (Android clients that are neither Element nor Famedly) + - "unknown_ios" (iOS clients that are neither Element nor Famedly) + - "unknown" (any other client) """ def _count_r30v2_users(txn: LoggingTransaction) -> dict[str, int]: @@ -311,99 +316,124 @@ def _count_r30v2_users(txn: LoggingTransaction) -> dict[str, int]: sixty_days_ago_in_secs = now - 2 * thirty_days_in_secs one_day_from_now_in_secs = now + 86400 - # This is the 'per-platform' count. + # Single scan of user_daily_visits: lower the user-agent once, then + # derive both per-client and overall R30v2 counts from that set. + # + # Classification order matters: + # 1. Branded native clients (Famedly / Element-Riot) + # 2. Web browsers (mozilla/gecko) — before bare android/ios, because + # mobile browser user agents also contain those platform tokens + # 3. Unbranded android/ios native clients + # 4. unknown sql = """ - SELECT - client_type, - count(client_type) - FROM - ( - SELECT - user_id, - CASE - WHEN - LOWER(user_agent) LIKE '%%riot%%' OR - LOWER(user_agent) LIKE '%%element%%' - THEN CASE - WHEN - LOWER(user_agent) LIKE '%%electron%%' - THEN 'electron' - WHEN - LOWER(user_agent) LIKE '%%android%%' - THEN 'android' - WHEN - LOWER(user_agent) LIKE '%%ios%%' - THEN 'ios' - ELSE 'unknown' - END - WHEN - LOWER(user_agent) LIKE '%%mozilla%%' OR - LOWER(user_agent) LIKE '%%gecko%%' - THEN 'web' - ELSE 'unknown' - END as client_type - FROM - user_daily_visits - WHERE - timestamp > ? - AND - timestamp < ? - GROUP BY - user_id, - client_type - HAVING - max(timestamp) - min(timestamp) > ? - ) AS temp - GROUP BY - client_type - ; - """ - - # We initialise all the client types to zero, so we get an explicit - # zero if they don't appear in the query results - results = {"ios": 0, "android": 0, "web": 0, "electron": 0} - txn.execute( - sql, - ( - sixty_days_ago_in_secs * 1000, - one_day_from_now_in_secs * 1000, - thirty_days_in_secs * 1000, - ), - ) - - for row in txn: - if row[0] == "unknown": - continue - results[row[0]] = row[1] - - # This is the 'all users' count. - sql = """ - SELECT COUNT(*) FROM ( + -- `last_60_days_visits`: selects rows within 60 days and normalizes + -- the user_agent to lowercase as `ua`. + WITH last_60_days_visits AS ( SELECT - 1 + user_id, + timestamp, + LOWER(COALESCE(user_agent, '')) AS ua FROM user_daily_visits WHERE timestamp > ? AND timestamp < ? + ), + -- `last_60_days_classified`: map user_agent to client type from `last_60_days_visits`. + last_60_days_classified AS ( + SELECT + user_id, + timestamp, + CASE + WHEN ua LIKE '%%famedly%%' + THEN CASE + WHEN ua LIKE '%%android%%' THEN 'famedly_android' + WHEN ua LIKE '%%ios%%' THEN 'famedly_ios' + ELSE 'unknown' + END + WHEN (ua LIKE '%%element%%' OR ua LIKE '%%riot%%') + THEN CASE + WHEN ua LIKE '%%electron%%' THEN 'element_electron' + WHEN ua LIKE '%%android%%' THEN 'element_android' + WHEN ua LIKE '%%ios%%' THEN 'element_ios' + ELSE 'unknown' + END + WHEN + ua LIKE '%%mozilla%%' OR ua LIKE '%%gecko%%' THEN 'web' + WHEN + ua LIKE '%%android%%' THEN 'unknown_android' + WHEN + ua LIKE '%%ios%%' THEN 'unknown_ios' + ELSE 'unknown' + END AS client_type + FROM + last_60_days_visits + ) + -- get counts per client type from `last_60_days_classified` for users + -- who have been active for more than 30 days in the last 60 days. + SELECT + client_type, + COUNT(*) + FROM ( + SELECT + user_id, + client_type + FROM + last_60_days_classified + GROUP BY + user_id, + client_type + HAVING + MAX(timestamp) - MIN(timestamp) > ? + ) AS retained_by_client + GROUP BY + client_type + + UNION ALL + -- get count of all users from `last_60_days_visits` who have been + -- active for more than 30 days in the last 60 days. + SELECT + 'all', + COUNT(*) + FROM ( + SELECT + user_id + FROM + last_60_days_visits GROUP BY user_id HAVING - max(timestamp) - min(timestamp) > ? - ) AS r30_users + MAX(timestamp) - MIN(timestamp) > ? + ) AS retained_all """ + # We initialise all the client types to zero, so we get an explicit + # zero if they don't appear in the query results + results = { + "element_electron": 0, + "element_android": 0, + "element_ios": 0, + "famedly_android": 0, + "famedly_ios": 0, + "unknown_android": 0, + "unknown_ios": 0, + "web": 0, + "unknown": 0, + "all": 0, + } txn.execute( sql, ( sixty_days_ago_in_secs * 1000, one_day_from_now_in_secs * 1000, thirty_days_in_secs * 1000, + thirty_days_in_secs * 1000, ), ) - (count,) = cast(tuple[int], txn.fetchone()) - results["all"] = count + + for row in txn: + results[row[0]] = row[1] return results diff --git a/tests/app/test_phone_stats_home.py b/tests/app/test_phone_stats_home.py index 73c3a9fd98..8b9eca97e6 100644 --- a/tests/app/test_phone_stats_home.py +++ b/tests/app/test_phone_stats_home.py @@ -10,6 +10,19 @@ FIVE_MINUTES_IN_SECONDS = 300 ONE_DAY_IN_SECONDS = 86400 +EMPTY_R30V2_RESULTS = { + "all": 0, + "element_electron": 0, + "element_android": 0, + "element_ios": 0, + "famedly_android": 0, + "famedly_ios": 0, + "unknown_android": 0, + "unknown_ios": 0, + "web": 0, + "unknown": 0, +} + class PhoneHomeR30V2TestCase(HomeserverTestCase): servlets = [ @@ -58,9 +71,7 @@ def test_r30v2_minimum_usage(self) -> None: # Check the R30 results do not count that user. r30_results = self.get_success(store.count_r30v2_users()) - self.assertEqual( - r30_results, {"all": 0, "android": 0, "electron": 0, "ios": 0, "web": 0} - ) + self.assertEqual(r30_results, EMPTY_R30V2_RESULTS) # Advance 31 days. # (R30v2 includes users with **more** than 30 days between the two visits, @@ -71,9 +82,7 @@ def test_r30v2_minimum_usage(self) -> None: # (Make sure the user isn't somehow counted by this point.) r30_results = self.get_success(store.count_r30v2_users()) - self.assertEqual( - r30_results, {"all": 0, "android": 0, "electron": 0, "ios": 0, "web": 0} - ) + self.assertEqual(r30_results, EMPTY_R30V2_RESULTS) # Send a message (this counts as activity) self.helper.send(room_id, "message2", tok=access_token) @@ -84,27 +93,21 @@ def test_r30v2_minimum_usage(self) -> None: # *Now* the user is counted. r30_results = self.get_success(store.count_r30v2_users()) - self.assertEqual( - r30_results, {"all": 1, "android": 0, "electron": 0, "ios": 0, "web": 0} - ) + self.assertEqual(r30_results, {**EMPTY_R30V2_RESULTS, "all": 1, "unknown": 1}) # Advance to JUST under 60 days after the user's first post self._advance_to(first_post_at + 60 * ONE_DAY_IN_SECONDS - 5) # Check the user is still counted. r30_results = self.get_success(store.count_r30v2_users()) - self.assertEqual( - r30_results, {"all": 1, "android": 0, "electron": 0, "ios": 0, "web": 0} - ) + self.assertEqual(r30_results, {**EMPTY_R30V2_RESULTS, "all": 1, "unknown": 1}) # Advance into the next day. The user's first activity is now more than 60 days old. self._advance_to(first_post_at + 60 * ONE_DAY_IN_SECONDS + 5) # Check the user is now no longer counted in R30. r30_results = self.get_success(store.count_r30v2_users()) - self.assertEqual( - r30_results, {"all": 0, "android": 0, "electron": 0, "ios": 0, "web": 0} - ) + self.assertEqual(r30_results, EMPTY_R30V2_RESULTS) def test_r30v2_user_must_be_retained_for_at_least_a_month(self) -> None: """ @@ -137,9 +140,7 @@ def test_r30v2_user_must_be_retained_for_at_least_a_month(self) -> None: # Check the user does not contribute to R30 yet. r30_results = self.get_success(store.count_r30v2_users()) - self.assertEqual( - r30_results, {"all": 0, "android": 0, "electron": 0, "ios": 0, "web": 0} - ) + self.assertEqual(r30_results, EMPTY_R30V2_RESULTS) for _ in range(30): # This loop posts a message every day for 30 days @@ -153,9 +154,7 @@ def test_r30v2_user_must_be_retained_for_at_least_a_month(self) -> None: # Notice that the user *still* does not contribute to R30! r30_results = self.get_success(store.count_r30v2_users()) - self.assertEqual( - r30_results, {"all": 0, "android": 0, "electron": 0, "ios": 0, "web": 0} - ) + self.assertEqual(r30_results, EMPTY_R30V2_RESULTS) # advance yet another day with more activity self.reactor.advance(ONE_DAY_IN_SECONDS) @@ -169,7 +168,7 @@ def test_r30v2_user_must_be_retained_for_at_least_a_month(self) -> None: # *Now* the user appears in R30. r30_results = self.get_success(store.count_r30v2_users()) self.assertEqual( - r30_results, {"all": 1, "android": 1, "electron": 0, "ios": 0, "web": 0} + r30_results, {**EMPTY_R30V2_RESULTS, "all": 1, "element_android": 1} ) def test_r30v2_returning_dormant_users_not_counted(self) -> None: @@ -210,9 +209,7 @@ def test_r30v2_returning_dormant_users_not_counted(self) -> None: # Check that the user does not contribute to R30v2, even though it's been # more than 30 days since registration. r30_results = self.get_success(store.count_r30v2_users()) - self.assertEqual( - r30_results, {"all": 0, "android": 0, "electron": 0, "ios": 0, "web": 0} - ) + self.assertEqual(r30_results, EMPTY_R30V2_RESULTS) # Now we want to check that the user will still be able to appear in # R30v2 as long as the user performs some other activity between @@ -226,7 +223,7 @@ def test_r30v2_returning_dormant_users_not_counted(self) -> None: # Check the user now satisfies the requirements to appear in R30v2. r30_results = self.get_success(store.count_r30v2_users()) self.assertEqual( - r30_results, {"all": 1, "ios": 1, "android": 0, "electron": 0, "web": 0} + r30_results, {**EMPTY_R30V2_RESULTS, "all": 1, "element_ios": 1} ) # Advance to 59.5 days after the user's first R30v2-eligible activity. @@ -235,14 +232,106 @@ def test_r30v2_returning_dormant_users_not_counted(self) -> None: # Check the user still appears in R30v2. r30_results = self.get_success(store.count_r30v2_users()) self.assertEqual( - r30_results, {"all": 1, "ios": 1, "android": 0, "electron": 0, "web": 0} + r30_results, {**EMPTY_R30V2_RESULTS, "all": 1, "element_ios": 1} ) # Advance to 60.5 days after the user's first R30v2-eligible activity. self.reactor.advance(ONE_DAY_IN_SECONDS) # Check the user no longer appears in R30v2. + r30_results = self.get_success(store.count_r30v2_users()) + self.assertEqual(r30_results, EMPTY_R30V2_RESULTS) + + def test_r30v2_client_classification(self) -> None: + """Check user-agents map to the expected R30v2 client labels.""" + store = self.hs.get_datastores().main + + clients = [ + # element_electron + ( + "element_electron_user", + "Element/1.11.0 (Macintosh; Intel Mac OS X; Electron)", + ), + # element_android + ( + "element_android_user", + "Element/1.1 (Linux; U; Android 9; MatrixAndroidSDK_X 0.0.1)", + ), + # element_ios + ( + "element_ios_user", + "Riot/1.4 (iPhone; iOS 13; Scale/4.00)", + ), + # famedly_android + ("famedly_android_user", "Famedly/1.0 (Linux; U; Android 13)"), + # famedly_ios + ("famedly_ios_user", "Famedly/1.0 (iPhone; iOS 17; Scale/3.00)"), + # unknown_android + ("unknown_android_user", "SomeClient/1.0 (Linux; Android 12)"), + # unknown_ios + ("unknown_ios_user", "SomeClient/1.0 (iPhone; iOS 16)"), + # web + ( + "web_user", + "Mozilla/5.0 (X11; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0", + ), + ( + "android_chrome_user", + "Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 " + "(KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36", + ), + ( + "ios_safari_user", + "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) " + "AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 " + "Mobile/15E148 Safari/604.1", + ), + # unknown + ( + "non_element_electron_user", + "SomeApp/1.0.0 (Macintosh; Intel Mac OS X; Electron)", + ), + ("unknown_user", "SomeClient/1.0 (Unknown; Unknown)"), + ] + + # Create all users and record an initial visit first, so they share the + # same retention window and none fall outside the 60-day lookback. + sessions = [] + for localpart, user_agent in clients: + headers = (("User-Agent", user_agent),) + user_id = self.register_user(localpart, "secret!") + access_token = self.login(localpart, "secret!", custom_headers=headers) + room_id = self.helper.create_room_as( + room_creator=user_id, tok=access_token, custom_headers=headers + ) + self.helper.send( + room_id, "message", tok=access_token, custom_headers=headers + ) + sessions.append((room_id, access_token, headers)) + + self.reactor.advance(FIVE_MINUTES_IN_SECONDS) + self.reactor.advance(31 * ONE_DAY_IN_SECONDS) + + for room_id, access_token, headers in sessions: + self.helper.send( + room_id, "message2", tok=access_token, custom_headers=headers + ) + self.reactor.advance(FIVE_MINUTES_IN_SECONDS) + r30_results = self.get_success(store.count_r30v2_users()) self.assertEqual( - r30_results, {"all": 0, "android": 0, "electron": 0, "ios": 0, "web": 0} + r30_results, + { + **EMPTY_R30V2_RESULTS, + "all": 12, + "element_electron": 1, + "element_android": 1, + "element_ios": 1, + "famedly_android": 1, + "famedly_ios": 1, + "unknown_android": 1, + "unknown_ios": 1, + "web": 3, + "unknown": 2, + }, ) diff --git a/tests/metrics/test_phone_home_stats.py b/tests/metrics/test_phone_home_stats.py index a1e0c978a1..2cadf0d95c 100644 --- a/tests/metrics/test_phone_home_stats.py +++ b/tests/metrics/test_phone_home_stats.py @@ -242,10 +242,15 @@ def test_phone_home_stats(self) -> None: # Our users have not been around for >30 days, hence these are all 0. self.assertEqual(phone_home_stats["r30v2_users_all"], 0) - self.assertEqual(phone_home_stats["r30v2_users_android"], 0) - self.assertEqual(phone_home_stats["r30v2_users_ios"], 0) - self.assertEqual(phone_home_stats["r30v2_users_electron"], 0) + self.assertEqual(phone_home_stats["r30v2_users_element_android"], 0) + self.assertEqual(phone_home_stats["r30v2_users_element_ios"], 0) + self.assertEqual(phone_home_stats["r30v2_users_element_electron"], 0) + self.assertEqual(phone_home_stats["r30v2_users_famedly_android"], 0) + self.assertEqual(phone_home_stats["r30v2_users_famedly_ios"], 0) + self.assertEqual(phone_home_stats["r30v2_users_unknown_android"], 0) + self.assertEqual(phone_home_stats["r30v2_users_unknown_ios"], 0) self.assertEqual(phone_home_stats["r30v2_users_web"], 0) + self.assertEqual(phone_home_stats["r30v2_users_unknown"], 0) self.assertEqual( phone_home_stats["cache_factor"], self.hs.config.caches.global_factor )