From 4cd8f49f829aa2612ccfff40884f744f4fa2e078 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 4 May 2025 18:09:39 +0530 Subject: [PATCH] add: usage. --- .../Databases/Http/Databases/Usage/Get.php | 138 ++++++++++++++++++ .../Databases/Http/Databases/Usage/XList.php | 132 +++++++++++++++++ .../Databases/Http/Tables/Usage/Get.php | 131 +++++++++++++++++ .../Modules/Databases/Services/Http.php | 6 + 4 files changed, 407 insertions(+) create mode 100644 src/Appwrite/Platform/Modules/Databases/Http/Databases/Usage/Get.php create mode 100644 src/Appwrite/Platform/Modules/Databases/Http/Databases/Usage/XList.php create mode 100644 src/Appwrite/Platform/Modules/Databases/Http/Tables/Usage/Get.php diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Usage/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Usage/Get.php new file mode 100644 index 0000000000..c4105effa0 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Usage/Get.php @@ -0,0 +1,138 @@ +setHttpMethod(self::HTTP_REQUEST_METHOD_GET) + ->setHttpPath('/v1/databases/:databaseId/usage') + ->desc('Get database usage stats') + ->groups(['api', 'database', 'usage']) + ->label('scope', 'collections.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) + ->label('sdk', new Method( + namespace: 'databases', + group: null, + name: 'getUsage', + description: '/docs/references/databases/get-database-usage.md', + auth: [AuthType::ADMIN], + responses: [ + new SDKResponse( + code: SwooleResponse::STATUS_CODE_OK, + model: UtopiaResponse::MODEL_USAGE_DATABASE, + ) + ], + contentType: ContentType::JSON, + )) + ->param('databaseId', '', new UID(), 'Database ID.') + ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true) + ->inject('response') + ->inject('dbForProject') + ->callback([$this, 'action']); + } + + public function action(string $databaseId, string $range, UtopiaResponse $response, Database $dbForProject): void + { + $database = $dbForProject->getDocument('databases', $databaseId); + + if ($database->isEmpty()) { + throw new Exception(Exception::DATABASE_NOT_FOUND); + } + + $periods = Config::getParam('usage', []); + $stats = $usage = []; + $days = $periods[$range]; + $metrics = [ + str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_COLLECTIONS), + str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_DOCUMENTS), + str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_STORAGE), + str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASES_OPERATIONS_READS), + str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASES_OPERATIONS_WRITES) + ]; + + Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) { + foreach ($metrics as $metric) { + $result = $dbForProject->findOne('stats', [ + Query::equal('metric', [$metric]), + Query::equal('period', ['inf']) + ]); + + $stats[$metric]['total'] = $result['value'] ?? 0; + $limit = $days['limit']; + $period = $days['period']; + $results = $dbForProject->find('stats', [ + Query::equal('metric', [$metric]), + Query::equal('period', [$period]), + Query::limit($limit), + Query::orderDesc('time'), + ]); + $stats[$metric]['data'] = []; + foreach ($results as $result) { + $stats[$metric]['data'][$result->getAttribute('time')] = [ + 'value' => $result->getAttribute('value'), + ]; + } + } + }); + + $format = match ($days['period']) { + '1h' => 'Y-m-d\TH:00:00.000P', + '1d' => 'Y-m-d\T00:00:00.000P', + }; + + foreach ($metrics as $metric) { + $usage[$metric]['total'] = $stats[$metric]['total']; + $usage[$metric]['data'] = []; + $leap = time() - ($days['limit'] * $days['factor']); + while ($leap < time()) { + $leap += $days['factor']; + $formatDate = date($format, $leap); + $usage[$metric]['data'][] = [ + 'value' => $stats[$metric]['data'][$formatDate]['value'] ?? 0, + 'date' => $formatDate, + ]; + } + } + + $response->dynamic(new Document([ + 'range' => $range, + 'collectionsTotal' => $usage[$metrics[0]]['total'], + 'documentsTotal' => $usage[$metrics[1]]['total'], + 'storageTotal' => $usage[$metrics[2]]['total'], + 'databaseReadsTotal' => $usage[$metrics[3]]['total'], + 'databaseWritesTotal' => $usage[$metrics[4]]['total'], + 'collections' => $usage[$metrics[0]]['data'], + 'documents' => $usage[$metrics[1]]['data'], + 'storage' => $usage[$metrics[2]]['data'], + 'databaseReads' => $usage[$metrics[3]]['data'], + 'databaseWrites' => $usage[$metrics[4]]['data'], + ]), UtopiaResponse::MODEL_USAGE_DATABASE); + } +} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Usage/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Usage/XList.php new file mode 100644 index 0000000000..0078d54a4b --- /dev/null +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Usage/XList.php @@ -0,0 +1,132 @@ +setHttpMethod(self::HTTP_REQUEST_METHOD_GET) + ->setHttpPath('/v1/databases/usage') + ->desc('Get databases usage stats') + ->groups(['api', 'database', 'usage']) + ->label('scope', 'collections.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) + ->label('sdk', new Method( + namespace: 'databases', + group: null, + name: 'listUsages', + description: '/docs/references/databases/get-usage.md', + auth: [AuthType::ADMIN], + responses: [ + new SDKResponse( + code: SwooleResponse::STATUS_CODE_OK, + model: UtopiaResponse::MODEL_USAGE_DATABASES, + ) + ], + contentType: ContentType::JSON + )) + ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true) + ->inject('response') + ->inject('dbForProject') + ->callback([$this, 'action']); + } + + public function action(string $range, UtopiaResponse $response, Database $dbForProject): void + { + + $periods = Config::getParam('usage', []); + $stats = $usage = []; + $days = $periods[$range]; + $metrics = [ + METRIC_DATABASES, + METRIC_COLLECTIONS, + METRIC_DOCUMENTS, + METRIC_DATABASES_STORAGE, + METRIC_DATABASES_OPERATIONS_READS, + METRIC_DATABASES_OPERATIONS_WRITES, + ]; + + Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) { + foreach ($metrics as $metric) { + $result = $dbForProject->findOne('stats', [ + Query::equal('metric', [$metric]), + Query::equal('period', ['inf']) + ]); + + $stats[$metric]['total'] = $result['value'] ?? 0; + $limit = $days['limit']; + $period = $days['period']; + $results = $dbForProject->find('stats', [ + Query::equal('metric', [$metric]), + Query::equal('period', [$period]), + Query::limit($limit), + Query::orderDesc('time'), + ]); + $stats[$metric]['data'] = []; + foreach ($results as $result) { + $stats[$metric]['data'][$result->getAttribute('time')] = [ + 'value' => $result->getAttribute('value'), + ]; + } + } + }); + + $format = match ($days['period']) { + '1h' => 'Y-m-d\TH:00:00.000P', + '1d' => 'Y-m-d\T00:00:00.000P', + }; + + foreach ($metrics as $metric) { + $usage[$metric]['total'] = $stats[$metric]['total']; + $usage[$metric]['data'] = []; + $leap = time() - ($days['limit'] * $days['factor']); + while ($leap < time()) { + $leap += $days['factor']; + $formatDate = date($format, $leap); + $usage[$metric]['data'][] = [ + 'value' => $stats[$metric]['data'][$formatDate]['value'] ?? 0, + 'date' => $formatDate, + ]; + } + } + $response->dynamic(new Document([ + 'range' => $range, + 'databasesTotal' => $usage[$metrics[0]]['total'], + 'collectionsTotal' => $usage[$metrics[1]]['total'], + 'documentsTotal' => $usage[$metrics[2]]['total'], + 'storageTotal' => $usage[$metrics[3]]['total'], + 'databasesReadsTotal' => $usage[$metrics[4]]['total'], + 'databasesWritesTotal' => $usage[$metrics[5]]['total'], + 'databases' => $usage[$metrics[0]]['data'], + 'collections' => $usage[$metrics[1]]['data'], + 'documents' => $usage[$metrics[2]]['data'], + 'storage' => $usage[$metrics[3]]['data'], + 'databasesReads' => $usage[$metrics[4]]['data'], + 'databasesWrites' => $usage[$metrics[5]]['data'], + ]), UtopiaResponse::MODEL_USAGE_DATABASES); + } +} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Tables/Usage/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/Tables/Usage/Get.php new file mode 100644 index 0000000000..3943c27618 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Databases/Http/Tables/Usage/Get.php @@ -0,0 +1,131 @@ +setHttpMethod(self::HTTP_REQUEST_METHOD_GET) + ->setHttpPath('/v1/databases/:databaseId/tables/:tableId/usage') + ->httpAlias('/v1/databases/:databaseId/collections/:tableId/usage') + ->desc('Get table usage stats') + ->groups(['api', 'database', 'usage']) + ->label('scope', 'collections.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) + ->label('sdk', new Method( + namespace: 'databases', + group: null, + name: 'getTableUsage', + description: '/docs/references/databases/get-collection-usage.md', + auth: [AuthType::ADMIN], + responses: [ + new SDKResponse( + code: SwooleResponse::STATUS_CODE_OK, + model: UtopiaResponse::MODEL_USAGE_COLLECTION, + ) + ], + contentType: ContentType::JSON, + )) + ->param('databaseId', '', new UID(), 'Database ID.') + ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true) + ->param('tableId', '', new UID(), 'Collection ID.') + ->inject('response') + ->inject('dbForProject') + ->callback([$this, 'action']); + } + + public function action(string $databaseId, string $range, string $tableId, UtopiaResponse $response, Database $dbForProject): void + { + + $database = $dbForProject->getDocument('databases', $databaseId); + $tableDocument = $dbForProject->getDocument('database_' . $database->getInternalId(), $tableId); + $table = $dbForProject->getCollection('database_' . $database->getInternalId() . '_collection_' . $tableDocument->getInternalId()); + + if ($table->isEmpty()) { + throw new Exception(Exception::COLLECTION_NOT_FOUND); + } + + $periods = Config::getParam('usage', []); + $stats = $usage = []; + $days = $periods[$range]; + $metrics = [ + str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $tableDocument->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS), + ]; + + Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) { + foreach ($metrics as $metric) { + $result = $dbForProject->findOne('stats', [ + Query::equal('metric', [$metric]), + Query::equal('period', ['inf']) + ]); + + $stats[$metric]['total'] = $result['value'] ?? 0; + $limit = $days['limit']; + $period = $days['period']; + $results = $dbForProject->find('stats', [ + Query::equal('metric', [$metric]), + Query::equal('period', [$period]), + Query::limit($limit), + Query::orderDesc('time'), + ]); + $stats[$metric]['data'] = []; + foreach ($results as $result) { + $stats[$metric]['data'][$result->getAttribute('time')] = [ + 'value' => $result->getAttribute('value'), + ]; + } + } + }); + + $format = match ($days['period']) { + '1h' => 'Y-m-d\TH:00:00.000P', + '1d' => 'Y-m-d\T00:00:00.000P', + }; + + foreach ($metrics as $metric) { + $usage[$metric]['total'] = $stats[$metric]['total']; + $usage[$metric]['data'] = []; + $leap = time() - ($days['limit'] * $days['factor']); + while ($leap < time()) { + $leap += $days['factor']; + $formatDate = date($format, $leap); + $usage[$metric]['data'][] = [ + 'value' => $stats[$metric]['data'][$formatDate]['value'] ?? 0, + 'date' => $formatDate, + ]; + } + } + + $response->dynamic(new Document([ + 'range' => $range, + 'documentsTotal' => $usage[$metrics[0]]['total'], + 'documents' => $usage[$metrics[0]]['data'], + ]), UtopiaResponse::MODEL_USAGE_COLLECTION); + } +} diff --git a/src/Appwrite/Platform/Modules/Databases/Services/Http.php b/src/Appwrite/Platform/Modules/Databases/Services/Http.php index 17dd715906..ffdd4f1903 100644 --- a/src/Appwrite/Platform/Modules/Databases/Services/Http.php +++ b/src/Appwrite/Platform/Modules/Databases/Services/Http.php @@ -30,6 +30,8 @@ use Appwrite\Platform\Modules\Databases\Http\Databases\Delete as DeleteDatabase; use Appwrite\Platform\Modules\Databases\Http\Databases\Get as GetDatabase; use Appwrite\Platform\Modules\Databases\Http\Databases\Logs\XList as ListDatabaseLogs; use Appwrite\Platform\Modules\Databases\Http\Databases\Update as UpdateDatabase; +use Appwrite\Platform\Modules\Databases\Http\Databases\Usage\Get as GetDatabaseUsage; +use Appwrite\Platform\Modules\Databases\Http\Databases\Usage\XList as ListDatabaseUsage; use Appwrite\Platform\Modules\Databases\Http\Databases\XList as ListDatabases; use Appwrite\Platform\Modules\Databases\Http\Indexes\Create as CreateIndex; use Appwrite\Platform\Modules\Databases\Http\Indexes\Delete as DeleteIndex; @@ -46,6 +48,7 @@ use Appwrite\Platform\Modules\Databases\Http\Tables\Delete as DeleteTable; use Appwrite\Platform\Modules\Databases\Http\Tables\Get as GetTable; use Appwrite\Platform\Modules\Databases\Http\Tables\Logs\XList as ListTableLogs; use Appwrite\Platform\Modules\Databases\Http\Tables\Update as UpdateTable; +use Appwrite\Platform\Modules\Databases\Http\Tables\Usage\Get as GetTableUsage; use Appwrite\Platform\Modules\Databases\Http\Tables\XList as ListTables; use Utopia\Platform\Service; @@ -70,6 +73,8 @@ class Http extends Service $this->addAction(DeleteDatabase::getName(), new DeleteDatabase()); $this->addAction(ListDatabases::getName(), new ListDatabases()); $this->addAction(ListDatabaseLogs::getName(), new ListDatabaseLogs()); + $this->addAction(GetDatabaseUsage::getName(), new GetDatabaseUsage()); + $this->addAction(ListDatabaseUsage::getName(), new ListDatabaseUsage()); } private function registerTableActions(): void @@ -80,6 +85,7 @@ class Http extends Service $this->addAction(DeleteTable::getName(), new DeleteTable()); $this->addAction(ListTables::getName(), new ListTables()); $this->addAction(ListTableLogs::getName(), new ListTableLogs()); + $this->addAction(GetTableUsage::getName(), new GetTableUsage()); } private function registerColumnActions(): void