2022-06-13 10:56:24 +00:00
< ? php
namespace Appwrite\Stats ;
use Utopia\Database\Database ;
2022-06-13 11:11:26 +00:00
use Utopia\Database\Document ;
2022-06-13 10:56:24 +00:00
class UsageDB extends Usage
{
2022-06-13 11:11:26 +00:00
public function __construct ( Database $database , callable $errorHandler = null )
2022-06-13 10:56:24 +00:00
{
$this -> database = $database ;
2022-06-13 11:11:26 +00:00
$this -> errorHandler = $errorHandler ;
2022-06-13 10:56:24 +00:00
}
2022-06-13 11:11:26 +00:00
/**
* Create or Update Mertic
* Create or update each metric in the stats collection for the given project
*
* @ param string $projectId
* @ param string $metric
* @ param int $value
*
* @ return void
*/
private function createOrUpdateMetric ( string $projectId , string $metric , int $value ) : void
2022-06-13 10:56:24 +00:00
{
2022-06-13 11:11:26 +00:00
foreach ( $this -> periods as $options ) {
$period = $options [ 'key' ];
$time = ( int ) ( floor ( time () / $options [ 'multiplier' ]) * $options [ 'multiplier' ]);
$id = \md5 ( " { $time } _ { $period } _ { $metric } " );
$this -> database -> setNamespace ( '_' . $projectId );
try {
$document = $this -> database -> getDocument ( 'stats' , $id );
if ( $document -> isEmpty ()) {
$this -> database -> createDocument ( 'stats' , new Document ([
'$id' => $id ,
2022-06-14 00:40:33 +00:00
'period' => $period ,
2022-06-13 11:11:26 +00:00
'time' => $time ,
'metric' => $metric ,
'value' => $value ,
'type' => 1 ,
]));
} else {
$this -> database -> updateDocument (
'stats' ,
$document -> getId (),
$document -> setAttribute ( 'value' , $value )
);
2022-06-13 15:49:27 +00:00
}
2022-06-13 11:11:26 +00:00
} catch ( \Exception $e ) { // if projects are deleted this might fail
if ( is_callable ( $this -> errorHandler )) {
call_user_func ( $this -> errorHandler , " Unable to save data for project { $projectId } and metric { $metric } : { $e -> getMessage () } " , $e -> getTraceAsString ());
} else {
2022-06-13 15:49:27 +00:00
throw $e ;
2022-06-13 11:11:26 +00:00
}
}
}
2022-06-13 10:56:24 +00:00
}
2022-06-13 11:11:26 +00:00
/**
* Foreach Document
* Call provided callback for each document in the collection
2022-06-13 15:49:27 +00:00
*
2022-06-13 11:11:26 +00:00
* @ param string $projectId
* @ param string $collection
* @ param array $queries
* @ param callable $callback
2022-06-13 15:49:27 +00:00
*
2022-06-13 11:11:26 +00:00
* @ return void
*/
private function foreachDocument ( string $projectId , string $collection , array $queries , callable $callback ) : void
2022-06-13 10:56:24 +00:00
{
$limit = 50 ;
$results = [];
$sum = $limit ;
$latestDocument = null ;
$this -> database -> setNamespace ( '_' . $projectId );
while ( $sum === $limit ) {
2022-06-14 00:58:25 +00:00
try {
$results = $this -> database -> find ( $collection , $queries , $limit , cursor : $latestDocument );
} catch ( \Exception $e ) {
if ( is_callable ( $this -> errorHandler )) {
call_user_func ( $this -> errorHandler , " Unable to fetch documents for project { $projectId } and collection { $collection } : { $e -> getMessage () } " , $e -> getTraceAsString ());
return ;
} else {
throw $e ;
}
}
2022-06-13 15:49:27 +00:00
if ( empty ( $results )) {
return ;
}
2022-06-13 10:56:24 +00:00
$sum = count ( $results );
foreach ( $results as $document ) {
if ( is_callable ( $callback )) {
$callback ( $document );
}
}
$latestDocument = $results [ array_key_last ( $results )];
}
}
2022-06-13 11:11:26 +00:00
/**
* Sum
* Calculate sum of a attribute of documents in collection
2022-06-13 15:49:27 +00:00
*
2022-06-13 11:11:26 +00:00
* @ param string $projectId
* @ param string $collection
* @ param string $attribute
* @ param string $metric
2022-06-13 15:49:27 +00:00
*
2022-06-13 11:11:26 +00:00
* @ return int
*/
private function sum ( string $projectId , string $collection , string $attribute , string $metric ) : int
2022-06-13 10:56:24 +00:00
{
$this -> database -> setNamespace ( '_' . $projectId );
2022-06-14 00:58:25 +00:00
try {
$sum = ( int ) $this -> database -> sum ( $collection , $attribute );
$this -> createOrUpdateMetric ( $projectId , $metric , $sum );
return $sum ;
} catch ( \Exception $e ) {
if ( is_callable ( $this -> errorHandler )) {
call_user_func ( $this -> errorHandler , " Unable to fetch sum for project { $projectId } and metric { $metric } : { $e -> getMessage () } " , $e -> getTraceAsString ());
} else {
throw $e ;
}
}
2022-06-13 10:56:24 +00:00
}
2022-06-13 11:11:26 +00:00
/**
* Count
* Count number of documents in collection
2022-06-13 15:49:27 +00:00
*
2022-06-13 11:11:26 +00:00
* @ param string $projectId
* @ param string $collection
* @ param string $metric
2022-06-13 15:49:27 +00:00
*
2022-06-13 11:11:26 +00:00
* @ return int
*/
private function count ( string $projectId , string $collection , string $metric ) : int
2022-06-13 10:56:24 +00:00
{
$this -> database -> setNamespace ( " _ { $projectId } " );
2022-06-14 00:58:25 +00:00
try {
$count = $this -> database -> count ( $collection );
$this -> createOrUpdateMetric ( $projectId , $metric , $count );
return $count ;
} catch ( \Exception $e ) {
if ( is_callable ( $this -> errorHandler )) {
call_user_func ( $this -> errorHandler , " Unable to fetch count for project { $projectId } and metric { $metric } : { $e -> getMessage () } " , $e -> getTraceAsString ());
} else {
throw $e ;
}
}
2022-06-13 10:56:24 +00:00
}
2022-06-13 11:11:26 +00:00
/**
* Deployments Total
* Total sum of storage used by deployments
2022-06-13 15:49:27 +00:00
*
2022-06-13 11:11:26 +00:00
* @ param string $projectId
2022-06-13 15:49:27 +00:00
*
2022-06-13 11:11:26 +00:00
* @ return int
*/
private function deploymentsTotal ( string $projectId ) : int
{
return $this -> sum ( $projectId , 'deployments' , 'size' , 'stroage.deployments.total' );
}
/**
* Users Stats
* Metric : users . count
*
* @ param string $projectId
*
* @ return void
*/
private function usersStats ( string $projectId ) : void
{
$this -> count ( $projectId , 'users' , 'users.count' );
}
/**
* Storage Stats
* Metrics : storage . total , storage . files . total , storage . buckets . { bucketId } . files . total ,
* storage . buckets . count , storage . files . count , storage . buckets . { bucketId } . files . count
2022-06-13 15:49:27 +00:00
*
2022-06-13 11:11:26 +00:00
* @ param string $projectId
2022-06-13 15:49:27 +00:00
*
2022-06-13 11:11:26 +00:00
* @ return void
*/
private function storageStats ( string $projectId ) : void
{
$deploymentsTotal = $this -> deploymentsTotal ( $projectId );
$projectFilesTotal = 0 ;
$projectFilesCount = 0 ;
$metric = 'storage.buckets.count' ;
$this -> count ( $projectId , 'buckets' , $metric );
2022-06-13 15:49:27 +00:00
$this -> foreachDocument ( $projectId , 'buckets' , [], function ( $bucket ) use ( & $projectFilesCount , & $projectFilesTotal , $projectId ,) {
2022-06-13 11:11:26 +00:00
$metric = " storage.buckets. { $bucket -> getId () } .files.count " ;
2022-06-14 00:40:33 +00:00
$count = $this -> count ( $projectId , 'bucket_' . $bucket -> getInternalId (), $metric );
2022-06-13 11:11:26 +00:00
$projectFilesCount += $count ;
$metric = " storage.buckets. { $bucket -> getId () } .files.total " ;
$sum = $this -> sum ( $projectId , 'bucket_' . $bucket -> getInternalId (), 'sizeOriginal' , $metric );
$projectFilesTotal += $sum ;
});
$this -> createOrUpdateMetric ( $projectId , 'storage.files.count' , $projectFilesCount );
$this -> createOrUpdateMetric ( $projectId , 'storage.files.total' , $projectFilesTotal );
$this -> createOrUpdateMetric ( $projectId , 'storage.total' , $projectFilesTotal + $deploymentsTotal );
}
/**
* Database Stats
* Collect all database stats
* Metrics : database . collections . count , database . collections . { collectionId } . documents . count ,
* database . documents . count
2022-06-13 15:49:27 +00:00
*
2022-06-13 11:11:26 +00:00
* @ param string $projectId
2022-06-13 15:49:27 +00:00
*
2022-06-13 11:11:26 +00:00
* @ return void
*/
private function databaseStats ( string $projectId ) : void
{
$projectDocumentsCount = 0 ;
$metric = 'database.collections.count' ;
$this -> count ( $projectId , 'collections' , $metric );
2022-06-13 15:49:27 +00:00
$this -> foreachDocument ( $projectId , 'collections' , [], function ( $collection ) use ( & $projectDocumentsCount , $projectId ,) {
2022-06-13 11:11:26 +00:00
$metric = " database.collections. { $collection -> getId () } .documents.count " ;
$count = $this -> count ( $projectId , 'collection_' . $collection -> getInternalId (), $metric );
$projectDocumentsCount += $count ;
});
$this -> createOrUpdateMetric ( $projectId , 'database.documents.count' , $projectDocumentsCount );
}
/**
* Collect Stats
* Collect all database related stats
2022-06-13 15:49:27 +00:00
*
2022-06-13 11:11:26 +00:00
* @ return void
*/
public function collect () : void
{
$this -> foreachDocument ( 'console' , 'projects' , [], function ( $project ) {
$projectId = $project -> getId ();
$this -> usersStats ( $projectId );
$this -> databaseStats ( $projectId );
$this -> storageStats ( $projectId );
});
}
2022-06-13 10:56:24 +00:00
}