Break down monthly active users by appservice_id (#7030)
* Break down monthly active users by appservice_id and emit via prometheus. Co-authored-by: Brendan Abolivier <babolivier@matrix.org>pull/7048/head
							parent
							
								
									87972f07e5
								
							
						
					
					
						commit
						1d66dce83e
					
				|  | @ -0,0 +1 @@ | |||
| Break down monthly active users by `appservice_id` and emit via Prometheus. | ||||
|  | @ -298,6 +298,11 @@ class SynapseHomeServer(HomeServer): | |||
| 
 | ||||
| # Gauges to expose monthly active user control metrics | ||||
| current_mau_gauge = Gauge("synapse_admin_mau:current", "Current MAU") | ||||
| current_mau_by_service_gauge = Gauge( | ||||
|     "synapse_admin_mau_current_mau_by_service", | ||||
|     "Current MAU by service", | ||||
|     ["app_service"], | ||||
| ) | ||||
| max_mau_gauge = Gauge("synapse_admin_mau:max", "MAU Limit") | ||||
| registered_reserved_users_mau_gauge = Gauge( | ||||
|     "synapse_admin_mau:registered_reserved_users", | ||||
|  | @ -585,12 +590,20 @@ def run(hs): | |||
|     @defer.inlineCallbacks | ||||
|     def generate_monthly_active_users(): | ||||
|         current_mau_count = 0 | ||||
|         current_mau_count_by_service = {} | ||||
|         reserved_users = () | ||||
|         store = hs.get_datastore() | ||||
|         if hs.config.limit_usage_by_mau or hs.config.mau_stats_only: | ||||
|             current_mau_count = yield store.get_monthly_active_count() | ||||
|             current_mau_count_by_service = ( | ||||
|                 yield store.get_monthly_active_count_by_service() | ||||
|             ) | ||||
|             reserved_users = yield store.get_registered_reserved_users() | ||||
|         current_mau_gauge.set(float(current_mau_count)) | ||||
| 
 | ||||
|         for app_service, count in current_mau_count_by_service.items(): | ||||
|             current_mau_by_service_gauge.labels(app_service).set(float(count)) | ||||
| 
 | ||||
|         registered_reserved_users_mau_gauge.set(float(len(reserved_users))) | ||||
|         max_mau_gauge.set(float(hs.config.max_mau_value)) | ||||
| 
 | ||||
|  |  | |||
|  | @ -43,13 +43,40 @@ class MonthlyActiveUsersWorkerStore(SQLBaseStore): | |||
| 
 | ||||
|         def _count_users(txn): | ||||
|             sql = "SELECT COALESCE(count(*), 0) FROM monthly_active_users" | ||||
| 
 | ||||
|             txn.execute(sql) | ||||
|             (count,) = txn.fetchone() | ||||
|             return count | ||||
| 
 | ||||
|         return self.db.runInteraction("count_users", _count_users) | ||||
| 
 | ||||
|     @cached(num_args=0) | ||||
|     def get_monthly_active_count_by_service(self): | ||||
|         """Generates current count of monthly active users broken down by service. | ||||
|         A service is typically an appservice but also includes native matrix users. | ||||
|         Since the `monthly_active_users` table is populated from the `user_ips` table | ||||
|         `config.track_appservice_user_ips` must be set to `true` for this | ||||
|         method to return anything other than native matrix users. | ||||
| 
 | ||||
|         Returns: | ||||
|             Deferred[dict]: dict that includes a mapping between app_service_id | ||||
|                 and the number of occurrences. | ||||
| 
 | ||||
|         """ | ||||
| 
 | ||||
|         def _count_users_by_service(txn): | ||||
|             sql = """ | ||||
|                 SELECT COALESCE(appservice_id, 'native'), COALESCE(count(*), 0) | ||||
|                 FROM monthly_active_users | ||||
|                 LEFT JOIN users ON monthly_active_users.user_id=users.name | ||||
|                 GROUP BY appservice_id; | ||||
|             """ | ||||
| 
 | ||||
|             txn.execute(sql) | ||||
|             result = txn.fetchall() | ||||
|             return dict(result) | ||||
| 
 | ||||
|         return self.db.runInteraction("count_users_by_service", _count_users_by_service) | ||||
| 
 | ||||
|     @defer.inlineCallbacks | ||||
|     def get_registered_reserved_users(self): | ||||
|         """Of the reserved threepids defined in config, which are associated | ||||
|  | @ -291,6 +318,9 @@ class MonthlyActiveUsersStore(MonthlyActiveUsersWorkerStore): | |||
|         ) | ||||
| 
 | ||||
|         self._invalidate_cache_and_stream(txn, self.get_monthly_active_count, ()) | ||||
|         self._invalidate_cache_and_stream( | ||||
|             txn, self.get_monthly_active_count_by_service, () | ||||
|         ) | ||||
|         self._invalidate_cache_and_stream( | ||||
|             txn, self.user_last_seen_monthly_active, (user_id,) | ||||
|         ) | ||||
|  |  | |||
|  | @ -303,3 +303,45 @@ class MonthlyActiveUsersTestCase(unittest.HomeserverTestCase): | |||
|         self.pump() | ||||
| 
 | ||||
|         self.store.upsert_monthly_active_user.assert_not_called() | ||||
| 
 | ||||
|     def test_get_monthly_active_count_by_service(self): | ||||
|         appservice1_user1 = "@appservice1_user1:example.com" | ||||
|         appservice1_user2 = "@appservice1_user2:example.com" | ||||
| 
 | ||||
|         appservice2_user1 = "@appservice2_user1:example.com" | ||||
|         native_user1 = "@native_user1:example.com" | ||||
| 
 | ||||
|         service1 = "service1" | ||||
|         service2 = "service2" | ||||
|         native = "native" | ||||
| 
 | ||||
|         self.store.register_user( | ||||
|             user_id=appservice1_user1, password_hash=None, appservice_id=service1 | ||||
|         ) | ||||
|         self.store.register_user( | ||||
|             user_id=appservice1_user2, password_hash=None, appservice_id=service1 | ||||
|         ) | ||||
|         self.store.register_user( | ||||
|             user_id=appservice2_user1, password_hash=None, appservice_id=service2 | ||||
|         ) | ||||
|         self.store.register_user(user_id=native_user1, password_hash=None) | ||||
|         self.pump() | ||||
| 
 | ||||
|         count = self.store.get_monthly_active_count_by_service() | ||||
|         self.assertEqual({}, self.get_success(count)) | ||||
| 
 | ||||
|         self.store.upsert_monthly_active_user(native_user1) | ||||
|         self.store.upsert_monthly_active_user(appservice1_user1) | ||||
|         self.store.upsert_monthly_active_user(appservice1_user2) | ||||
|         self.store.upsert_monthly_active_user(appservice2_user1) | ||||
|         self.pump() | ||||
| 
 | ||||
|         count = self.store.get_monthly_active_count() | ||||
|         self.assertEqual(4, self.get_success(count)) | ||||
| 
 | ||||
|         count = self.store.get_monthly_active_count_by_service() | ||||
|         result = self.get_success(count) | ||||
| 
 | ||||
|         self.assertEqual(2, result[service1]) | ||||
|         self.assertEqual(1, result[service2]) | ||||
|         self.assertEqual(1, result[native]) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Neil Johnson
						Neil Johnson