2023-05-30 15:06:51 +00:00
< ? php
namespace Appwrite\Platform\Tasks ;
use Exception ;
2023-05-30 17:11:25 +00:00
use League\Csv\CannotInsertRecord ;
2023-05-30 15:06:51 +00:00
use Utopia\App ;
use Utopia\Database\Validator\Authorization ;
use Utopia\Platform\Action ;
use Utopia\Cache\Cache ;
use Utopia\CLI\Console ;
use Utopia\Database\Database ;
use Utopia\Database\Query ;
use League\Csv\Writer ;
use PHPMailer\PHPMailer\PHPMailer ;
use Utopia\Pools\Group ;
use Utopia\Registry\Registry ;
class CalcTierStats extends Action
{
2023-06-20 10:28:26 +00:00
/*
* Csv cols headers
*/
2023-05-30 15:06:51 +00:00
private array $columns = [
'Project ID' ,
'Organization ID' ,
2023-05-30 17:11:25 +00:00
'Organization Members' ,
2023-05-30 15:06:51 +00:00
'Teams' ,
2023-06-20 10:24:26 +00:00
'Users' ,
2023-05-30 15:06:51 +00:00
'Requests' ,
'Bandwidth' ,
'Domains' ,
2023-05-30 17:11:25 +00:00
'Api keys' ,
2023-05-30 15:06:51 +00:00
'Webhooks' ,
'Platforms' ,
'Buckets' ,
'Files' ,
2023-05-30 17:11:25 +00:00
'Storage (bytes)' ,
'Max File Size (bytes)' ,
2023-05-30 15:06:51 +00:00
'Databases' ,
'Functions' ,
'Deployments' ,
'Executions' ,
];
protected string $directory = '/usr/local' ;
protected string $path ;
protected string $date ;
private array $usageStats = [
'project.$all.network.requests' => 'Requests' ,
'project.$all.network.bandwidth' => 'Bandwidth' ,
];
public static function getName () : string
{
return 'calc-tier-stats' ;
}
public function __construct ()
{
$this
-> desc ( 'Get stats for projects' )
-> inject ( 'pools' )
-> inject ( 'cache' )
-> inject ( 'dbForConsole' )
-> inject ( 'register' )
-> callback ( function ( Group $pools , Cache $cache , Database $dbForConsole , Registry $register ) {
$this -> action ( $pools , $cache , $dbForConsole , $register );
});
}
2023-05-30 17:11:25 +00:00
/**
* @ throws \Utopia\Exception
* @ throws CannotInsertRecord
*/
2023-05-30 15:06:51 +00:00
public function action ( Group $pools , Cache $cache , Database $dbForConsole , Registry $register ) : void
{
//docker compose exec -t appwrite calc-tier-stats
Console :: title ( 'Cloud free tier stats calculation V1' );
Console :: success ( APP_NAME . ' cloud free tier stats calculation has started' );
/* Initialise new Utopia app */
$app = new App ( 'UTC' );
$console = $app -> getResource ( 'console' );
/** CSV stuff */
$this -> date = date ( 'Y-m-d' );
$this -> path = " { $this -> directory } /tier_stats_ { $this -> date } .csv " ;
$csv = Writer :: createFromPath ( $this -> path , 'w' );
$csv -> insertOne ( $this -> columns );
/** Database connections */
$totalProjects = $dbForConsole -> count ( 'projects' );
Console :: success ( " Found a total of: { $totalProjects } projects " );
$projects = [ $console ];
$count = 0 ;
$limit = 30 ;
$sum = 30 ;
$offset = 0 ;
while ( ! empty ( $projects )) {
foreach ( $projects as $project ) {
/**
* Skip user projects with id 'console'
*/
2023-06-19 10:55:16 +00:00
if ( $project -> getId () === 'console' ) {
2023-05-30 15:06:51 +00:00
continue ;
}
Console :: info ( " Getting stats for { $project -> getId () } " );
try {
$db = $project -> getAttribute ( 'database' );
$adapter = $pools
-> get ( $db )
-> pop ()
-> getResource ();
$dbForProject = new Database ( $adapter , $cache );
$dbForProject -> setDefaultDatabase ( 'appwrite' );
$dbForProject -> setNamespace ( '_' . $project -> getInternalId ());
/** Get Project ID */
$stats [ 'Project ID' ] = $project -> getId ();
2023-05-30 17:11:25 +00:00
$stats [ 'Organization ID' ] = $project -> getAttribute ( 'teamId' , null );
2023-05-30 15:06:51 +00:00
/** Get Total Members */
$teamInternalId = $project -> getAttribute ( 'teamInternalId' , null );
if ( $teamInternalId ) {
2023-05-30 17:11:25 +00:00
$stats [ 'Organization Members' ] = $dbForConsole -> count ( 'memberships' , [
2023-05-30 15:06:51 +00:00
Query :: equal ( 'teamInternalId' , [ $teamInternalId ])
]);
} else {
2023-06-20 10:24:26 +00:00
$stats [ 'Organization Members' ] = 0 ;
2023-05-30 15:06:51 +00:00
}
/** Get Total internal Teams */
2023-06-20 10:24:26 +00:00
try {
$stats [ 'Teams' ] = $dbForProject -> count ( 'teams' , []);
} catch ( \Throwable ) {
$stats [ 'Teams' ] = 0 ;
}
/** Get Total users */
try {
$stats [ 'Users' ] = $dbForProject -> count ( 'users' , []);
} catch ( \Throwable ) {
$stats [ 'Users' ] = 0 ;
}
2023-05-30 15:06:51 +00:00
/** Get Usage stats */
$range = '90d' ;
$periods = [
'90d' => [
'period' => '1d' ,
'limit' => 90 ,
],
];
$tmp = [];
$metrics = $this -> usageStats ;
Authorization :: skip ( function () use ( $dbForProject , $periods , $range , $metrics , & $tmp ) {
foreach ( $metrics as $metric => $name ) {
$limit = $periods [ $range ][ 'limit' ];
$period = $periods [ $range ][ 'period' ];
$requestDocs = $dbForProject -> find ( 'stats' , [
Query :: equal ( 'period' , [ $period ]),
Query :: equal ( 'metric' , [ $metric ]),
Query :: limit ( $limit ),
Query :: orderDesc ( 'time' ),
]);
$tmp [ $metric ] = [];
foreach ( $requestDocs as $requestDoc ) {
2023-06-19 10:37:16 +00:00
if ( empty ( $requestDoc )) {
continue ;
}
2023-05-30 15:06:51 +00:00
$tmp [ $metric ][] = [
'value' => $requestDoc -> getAttribute ( 'value' ),
'date' => $requestDoc -> getAttribute ( 'time' ),
];
}
$tmp [ $metric ] = array_reverse ( $tmp [ $metric ]);
$tmp [ $metric ] = array_sum ( array_column ( $tmp [ $metric ], 'value' ));
}
});
foreach ( $tmp as $key => $value ) {
$stats [ $metrics [ $key ]] = $value ;
}
2023-06-20 10:24:26 +00:00
try {
/** Get Domains */
$stats [ 'Domains' ] = $dbForConsole -> count ( 'domains' , [
Query :: equal ( 'projectInternalId' , [ $project -> getInternalId ()]),
]);
} catch ( \Throwable ) {
$stats [ 'Domains' ] = 0 ;
}
2023-05-30 15:06:51 +00:00
2023-06-20 10:24:26 +00:00
try {
2023-05-30 17:11:25 +00:00
/** Get Api keys */
2023-06-20 10:24:26 +00:00
$stats [ 'Api keys' ] = $dbForConsole -> count ( 'keys' , [
2023-05-30 17:11:25 +00:00
Query :: equal ( 'projectInternalId' , [ $project -> getInternalId ()]),
2023-06-20 10:24:26 +00:00
]);
} catch ( \Throwable ) {
$stats [ 'Api keys' ] = 0 ;
}
2023-05-30 17:11:25 +00:00
2023-06-20 10:24:26 +00:00
try {
2023-05-30 15:06:51 +00:00
/** Get Webhooks */
2023-06-20 10:24:26 +00:00
$stats [ 'Webhooks' ] = $dbForConsole -> count ( 'webhooks' , [
2023-05-30 15:06:51 +00:00
Query :: equal ( 'projectInternalId' , [ $project -> getInternalId ()]),
2023-06-20 10:24:26 +00:00
]);
} catch ( \Throwable ) {
$stats [ 'Webhooks' ] = 0 ;
}
2023-05-30 15:06:51 +00:00
2023-06-20 10:24:26 +00:00
try {
2023-05-30 15:06:51 +00:00
/** Get Platforms */
2023-06-20 10:24:26 +00:00
$stats [ 'Platforms' ] = $dbForConsole -> count ( 'platforms' , [
2023-05-30 15:06:51 +00:00
Query :: equal ( 'projectInternalId' , [ $project -> getInternalId ()]),
2023-06-20 10:24:26 +00:00
]);
} catch ( \Throwable ) {
$stats [ 'Platforms' ] = 0 ;
}
2023-05-30 15:06:51 +00:00
/** Get Files & Buckets */
$filesCount = 0 ;
$filesSum = 0 ;
$maxFileSize = 0 ;
$counter = 0 ;
2023-06-20 10:24:26 +00:00
try {
$buckets = $dbForProject -> find ( 'buckets' , []);
foreach ( $buckets as $bucket ) {
$file = $dbForProject -> findOne ( 'bucket_' . $bucket -> getInternalId (), [ Query :: orderDesc ( 'sizeOriginal' ),]);
if ( empty ( $file )) {
continue ;
}
2023-10-11 14:07:06 +00:00
$filesSum += $dbForProject -> sum ( 'bucket_' . $bucket -> getInternalId (), 'sizeOriginal' , []);
2023-06-20 10:24:26 +00:00
$filesCount += $dbForProject -> count ( 'bucket_' . $bucket -> getInternalId (), []);
if ( $file -> getAttribute ( 'sizeOriginal' ) > $maxFileSize ) {
$maxFileSize = $file -> getAttribute ( 'sizeOriginal' );
}
$counter ++ ;
2023-05-30 15:06:51 +00:00
}
2023-06-20 10:24:26 +00:00
} catch ( \Throwable ) {
;
2023-05-30 15:06:51 +00:00
}
$stats [ 'Buckets' ] = $counter ;
$stats [ 'Files' ] = $filesCount ;
2023-05-30 17:11:25 +00:00
$stats [ 'Storage (bytes)' ] = $filesSum ;
$stats [ 'Max File Size (bytes)' ] = $maxFileSize ;
2023-05-30 15:06:51 +00:00
2023-06-20 10:24:26 +00:00
try {
2023-05-30 15:06:51 +00:00
/** Get Total Functions */
2023-06-20 10:24:26 +00:00
$stats [ 'Databases' ] = $dbForProject -> count ( 'databases' , []);
} catch ( \Throwable ) {
$stats [ 'Databases' ] = 0 ;
}
2023-05-30 15:06:51 +00:00
/** Get Total Functions */
2023-06-20 10:24:26 +00:00
try {
$stats [ 'Functions' ] = $dbForProject -> count ( 'functions' , []);
} catch ( \Throwable ) {
$stats [ 'Functions' ] = 0 ;
}
2023-05-30 15:06:51 +00:00
/** Get Total Deployments */
2023-06-20 10:24:26 +00:00
try {
$stats [ 'Deployments' ] = $dbForProject -> count ( 'deployments' , []);
} catch ( \Throwable ) {
$stats [ 'Deployments' ] = 0 ;
}
2023-05-30 15:06:51 +00:00
/** Get Total Executions */
2023-06-20 10:24:26 +00:00
try {
$stats [ 'Executions' ] = $dbForProject -> count ( 'executions' , []);
} catch ( \Throwable ) {
$stats [ 'Executions' ] = 0 ;
}
2023-05-30 15:06:51 +00:00
$csv -> insertOne ( array_values ( $stats ));
} catch ( \Throwable $th ) {
2023-06-20 10:24:26 +00:00
Console :: error ( 'Failed on project ("' . $project -> getId () . '") version with error on File: ' . $th -> getFile () . ' line no: ' . $th -> getline () . ' with message: ' . $th -> getMessage ());
2023-05-30 15:06:51 +00:00
} finally {
$pools
-> get ( $db )
-> reclaim ();
}
}
$sum = \count ( $projects );
$projects = $dbForConsole -> find ( 'projects' , [
Query :: limit ( $limit ),
Query :: offset ( $offset ),
]);
$offset = $offset + $limit ;
$count = $count + $sum ;
}
2023-05-30 15:46:36 +00:00
2023-05-30 15:06:51 +00:00
Console :: log ( 'Iterated through ' . $count - 1 . '/' . $totalProjects . ' projects...' );
2023-05-30 15:46:36 +00:00
2023-05-30 15:06:51 +00:00
$pools
-> get ( 'console' )
-> reclaim ();
2023-05-30 15:13:30 +00:00
/** @var PHPMailer $mail */
$mail = $register -> get ( 'smtp' );
$mail -> clearAddresses ();
$mail -> clearAllRecipients ();
$mail -> clearReplyTos ();
$mail -> clearAttachments ();
$mail -> clearBCCs ();
$mail -> clearCCs ();
try {
/** Addresses */
$mail -> setFrom ( App :: getEnv ( '_APP_SYSTEM_EMAIL_ADDRESS' , APP_EMAIL_TEAM ), 'Appwrite Cloud Hamster' );
$recipients = explode ( ',' , App :: getEnv ( '_APP_USERS_STATS_RECIPIENTS' , '' ));
foreach ( $recipients as $recipient ) {
$mail -> addAddress ( $recipient );
}
/** Attachments */
$mail -> addAttachment ( $this -> path );
/** Content */
$mail -> Subject = " Cloud Report for { $this -> date } " ;
$mail -> Body = " Please find the daily cloud report atttached " ;
$mail -> send ();
Console :: success ( 'Email has been sent!' );
} catch ( Exception $e ) {
Console :: error ( " Message could not be sent. Mailer Error: { $mail -> ErrorInfo } " );
}
2023-05-30 15:06:51 +00:00
}
}