2021-08-10 20:44:31 +12:00
< ? php
2021-08-11 22:15:00 +12:00
global $cli , $register ;
2021-08-10 20:44:31 +12:00
2022-06-13 22:56:24 +12:00
use Appwrite\Stats\Usage ;
use Appwrite\Stats\UsageDB ;
use InfluxDB\Database as InfluxDatabase ;
2021-08-10 20:44:31 +12:00
use Utopia\App ;
2022-06-13 22:56:24 +12:00
use Utopia\Cache\Adapter\Redis as RedisCache ;
2021-08-11 22:15:00 +12:00
use Utopia\Cache\Cache ;
2021-08-10 20:44:31 +12:00
use Utopia\CLI\Console ;
2021-08-11 22:15:00 +12:00
use Utopia\Database\Adapter\MariaDB ;
use Utopia\Database\Database ;
use Utopia\Database\Document ;
use Utopia\Database\Validator\Authorization ;
2022-06-13 22:56:24 +12:00
use Utopia\Registry\Registry ;
function getDatabase ( Registry & $register , string $namespace ) : Database
{
$attempts = 0 ;
do {
try {
$attempts ++ ;
$db = $register -> get ( 'db' );
$redis = $register -> get ( 'cache' );
$cache = new Cache ( new RedisCache ( $redis ));
$database = new Database ( new MariaDB ( $db ), $cache );
$database -> setDefaultDatabase ( App :: getEnv ( '_APP_DB_SCHEMA' , 'appwrite' ));
$database -> setNamespace ( $namespace );
if ( ! $database -> exists ( $database -> getDefaultDatabase (), 'realtime' )) {
throw new Exception ( 'Collection not ready' );
}
break ; // leave loop if successful
} catch ( \Exception $e ) {
Console :: warning ( " Database not ready. Retrying connection ( { $attempts } )... " );
if ( $attempts >= DATABASE_RECONNECT_MAX_ATTEMPTS ) {
throw new \Exception ( 'Failed to connect to database: ' . $e -> getMessage ());
}
sleep ( DATABASE_RECONNECT_SLEEP );
}
} while ( $attempts < DATABASE_RECONNECT_MAX_ATTEMPTS );
return $database ;
}
function getInfluxDB ( Registry & $register ) : InfluxDatabase
{
/** @var InfluxDB\Client $client */
$client = $register -> get ( 'influxdb' );
$attempts = 0 ;
$max = 10 ;
$sleep = 1 ;
do { // check if telegraf database is ready
try {
$attempts ++ ;
$database = $client -> selectDB ( 'telegraf' );
if ( in_array ( 'telegraf' , $client -> listDatabases ())) {
break ; // leave the do-while if successful
}
} catch ( \Throwable $th ) {
Console :: warning ( " InfluxDB not ready. Retrying connection ( { $attempts } )... " );
if ( $attempts >= $max ) {
throw new \Exception ( 'InfluxDB database not ready yet' );
}
sleep ( $sleep );
}
} while ( $attempts < $max );
return $database ;
}
2021-08-10 20:44:31 +12:00
2021-08-19 18:54:32 +12:00
/**
* Metrics We collect
2021-08-19 20:01:22 +12:00
*
2021-08-30 19:19:29 +12:00
* General
2021-08-30 21:35:32 +12:00
*
2021-08-19 18:54:32 +12:00
* requests
* network
* executions
2021-08-30 21:35:32 +12:00
*
2021-08-30 19:19:29 +12:00
* Database
2021-08-30 21:35:32 +12:00
*
2021-08-19 18:54:32 +12:00
* database . collections . create
* database . collections . read
* database . collections . update
* database . collections . delete
* database . documents . create
* database . documents . read
* database . documents . update
* database . documents . delete
* database . collections . { collectionId } . documents . create
* database . collections . { collectionId } . documents . read
* database . collections . { collectionId } . documents . update
* database . collections . { collectionId } . documents . delete
2021-08-30 21:35:32 +12:00
*
2021-08-30 19:19:29 +12:00
* Storage
2022-06-13 22:56:24 +12:00
*
2021-09-10 20:49:16 +12:00
* storage . buckets . create
* storage . buckets . read
* storage . buckets . update
* storage . buckets . delete
2021-11-09 18:50:03 +13:00
* storage . files . create
* storage . files . read
* storage . files . update
* storage . files . delete
2021-08-19 18:54:32 +12:00
* storage . buckets . { bucketId } . files . create
* storage . buckets . { bucketId } . files . read
* storage . buckets . { bucketId } . files . update
* storage . buckets . { bucketId } . files . delete
2021-08-30 21:35:32 +12:00
*
2021-08-30 19:19:29 +12:00
* Users
2021-08-30 21:35:32 +12:00
*
2021-08-19 18:54:32 +12:00
* users . create
* users . read
* users . update
* users . delete
* users . sessions . create
2021-08-24 20:36:46 +12:00
* users . sessions . { provider } . create
2021-08-19 18:54:32 +12:00
* users . sessions . delete
2021-08-19 20:01:22 +12:00
*
2021-08-20 17:55:23 +12:00
* Functions
*
* functions . { functionId } . executions
* functions . { functionId } . failures
* functions . { functionId } . compute
*
2021-08-19 18:54:32 +12:00
* Counters
2021-08-19 20:01:22 +12:00
*
2021-08-19 18:54:32 +12:00
* users . count
2021-09-10 20:49:16 +12:00
* storage . buckets . count
2021-08-19 20:01:22 +12:00
* storage . files . count
2021-11-09 18:50:03 +13:00
* storage . buckets . { bucketId } . files . count
2021-08-19 20:01:22 +12:00
* database . collections . count
* database . documents . count
* database . collections . { collectionId } . documents . count
*
2021-08-19 20:14:23 +12:00
* Totals
*
* storage . total
*
2021-08-19 18:54:32 +12:00
*/
2021-08-10 20:44:31 +12:00
$cli
-> task ( 'usage' )
-> desc ( 'Schedules syncing data from influxdb to Appwrite console db' )
2021-08-11 22:15:00 +12:00
-> action ( function () use ( $register ) {
2021-08-17 00:31:49 +12:00
Console :: title ( 'Usage Aggregation V1' );
Console :: success ( APP_NAME . ' usage aggregation process v1 has started' );
2021-08-10 20:44:31 +12:00
2021-08-30 19:19:29 +12:00
$interval = ( int ) App :: getEnv ( '_APP_USAGE_AGGREGATION_INTERVAL' , '30' ); // 30 seconds (by default)
2021-08-10 20:44:31 +12:00
2022-06-13 22:56:24 +12:00
$database = getDatabase ( $register , '_console' );
$influxDB = getInfluxDB ( $register );
$usage = new Usage ( $database , $influxDB );
$usageDB = new UsageDB ( $database );
2021-08-10 20:44:31 +12:00
2021-08-16 21:02:35 +12:00
$latestTime = [];
2021-08-11 22:15:00 +12:00
Authorization :: disable ();
2021-08-10 20:44:31 +12:00
2021-08-17 18:03:27 +12:00
$iterations = 0 ;
2022-06-13 22:56:24 +12:00
Console :: loop ( function () use ( $interval , $database , $usage , $usageDB , & $latestTime , & $iterations ) {
2021-08-16 21:02:35 +12:00
$now = date ( 'd-m-Y H:i:s' , time ());
Console :: info ( " [ { $now } ] Aggregating usage data every { $interval } seconds " );
2021-08-11 22:15:00 +12:00
2021-08-16 20:53:34 +12:00
$loopStart = microtime ( true );
2021-08-30 19:19:29 +12:00
/**
2022-06-13 22:56:24 +12:00
* Aggregate InfluxDB every 30 seconds
*/
2021-08-30 21:35:32 +12:00
2022-02-20 23:03:19 +13:00
// sync data
2022-06-13 22:56:24 +12:00
foreach ( $usage -> getMetrics () as $metric => $options ) { //for each metrics
foreach ( $usage -> getPeriods () as $period ) { // aggregate data for each period
2022-02-20 23:03:19 +13:00
try {
2022-06-13 22:56:24 +12:00
$usage -> syncFromInfluxDB ( $metric , $options , $period , $latestTime );
} catch ( \Exception $e ) {
Console :: warning ( " Failed: { $e -> getMessage () } " );
2022-02-24 20:40:12 +13:00
Console :: warning ( $e -> getTraceAsString ());
2021-08-16 18:58:34 +12:00
}
2021-08-11 22:15:00 +12:00
}
}
2021-08-16 20:53:34 +12:00
2022-02-20 22:33:01 +13:00
if ( $iterations % 30 != 0 ) { // Aggregate aggregate number of objects in database only after 15 minutes
$iterations ++ ;
$loopTook = microtime ( true ) - $loopStart ;
$now = date ( 'd-m-Y H:i:s' , time ());
2022-02-20 22:38:52 +13:00
Console :: info ( " [ { $now } ] Aggregation took { $loopTook } seconds " );
2022-02-20 22:33:01 +13:00
return ;
2022-02-20 22:38:52 +13:00
}
2022-02-20 22:33:01 +13:00
2022-02-20 22:38:52 +13:00
/**
2022-06-13 22:56:24 +12:00
* Aggregate MariaDB every 15 minutes
* Some of the queries here might contain full - table scans .
*/
2022-02-20 22:38:52 +13:00
$now = date ( 'd-m-Y H:i:s' , time ());
Console :: info ( " [ { $now } ] Aggregating database counters. " );
2022-06-13 22:56:24 +12:00
$usageDB -> foreachDocument ( 'console' , 'projects' , [], function ( $project ) use ( $usageDB ) {
$projectId = $project -> getId ();
2021-08-30 19:19:29 +12:00
2022-06-13 22:56:24 +12:00
// Get total storage of deployments
try {
$deploymentsTotal = $usageDB -> sum ( $projectId , 'deployments' , 'size' , 'storage.deployments.total' );
} catch ( \Exception $e ) {
Console :: warning ( " Failed to save data for project { $projectId } and metric storage.deployments.total: { $e -> getMessage () } " );
Console :: warning ( $e -> getTraceAsString ());
2022-02-20 22:33:01 +13:00
}
2021-10-28 01:12:06 +13:00
2022-06-13 22:56:24 +12:00
foreach ( $usageDB -> getCollections () as $collection => $options ) {
try {
2022-02-20 22:33:01 +13:00
2022-06-13 22:56:24 +12:00
$metricPrefix = $options [ 'metricPrefix' ] ? ? '' ;
$metric = empty ( $metricPrefix ) ? " { $collection } .count " : " { $metricPrefix } . { $collection } .count " ;
$usageDB -> count ( $projectId , $collection , $metric );
2022-02-20 22:33:01 +13:00
2022-06-13 22:56:24 +12:00
$subCollections = $options [ 'subCollections' ] ? ? [];
2021-08-30 19:19:29 +12:00
2022-06-13 22:56:24 +12:00
if ( empty ( $subCollections )) {
continue ;
2022-02-24 20:40:12 +13:00
}
2022-02-20 22:33:01 +13:00
2022-06-13 22:56:24 +12:00
$subCollectionCounts = []; //total project level count of sub collections
$subCollectionTotals = []; //total project level sum of sub collections
2022-02-24 20:40:12 +13:00
2022-06-13 22:56:24 +12:00
$usageDB -> foreachDocument ( $projectId , $collection , [], function ( $parent ) use ( & $subCollectionCounts , & $subCollectionTotals , $subCollections , $projectId , $usageDB , $collection ) {
foreach ( $subCollections as $subCollection => $subOptions ) { // Sub collection counts, like database.collections.collectionId.documents.count
2022-02-20 22:33:01 +13:00
2022-06-13 22:56:24 +12:00
$metric = empty ( $metricPrefix ) ? " { $collection } . { $parent -> getId () } . { $subCollection } .count " : " { $metricPrefix } . { $collection } . { $parent -> getInternalId () } . { $subCollection } .count " ;
2022-02-20 22:33:01 +13:00
2022-06-13 22:56:24 +12:00
$count = $usageDB -> count ( $projectId , ( $subOptions [ 'collectionPrefix' ] ? ? '' ) . $parent -> getInternalId (), $metric );
2022-02-20 22:33:01 +13:00
2022-06-13 22:56:24 +12:00
$subCollectionCounts [ $subCollection ] = ( $subCollectionCounts [ $subCollection ] ? ? 0 ) + $count ; // Project level counts for sub collections like database.documents.count
2022-02-20 22:33:01 +13:00
2022-06-13 22:56:24 +12:00
// check if sum calculation is required
$total = $subOptions [ 'total' ] ? ? [];
if ( empty ( $total )) {
continue ;
}
2022-02-20 22:33:01 +13:00
2022-06-13 22:56:24 +12:00
$metric = empty ( $metricPrefix ) ? " { $collection } . { $parent -> getId () } . { $subCollection } .total " : " { $metricPrefix } . { $collection } . { $parent -> getInternalId () } . { $subCollection } .total " ;
$total = $usageDB -> sum ( $projectId , ( $subOptions [ 'collectionPrefix' ] ? ? '' ) . $parent -> getInternalId (), $total [ 'field' ], $metric );
2022-02-20 22:33:01 +13:00
2022-06-13 22:56:24 +12:00
$subCollectionTotals [ $subCollection ] = ( $subCollectionTotals [ $subCollection ] ? ? 0 ) + $total ; // Project level sum for sub collections like storage.total
}
});
2022-02-20 22:33:01 +13:00
2022-06-13 22:56:24 +12:00
/**
* Inserting project level counts for sub collections like database . documents . count
*/
foreach ( $subCollectionCounts as $subCollection => $count ) {
2022-02-20 22:33:01 +13:00
2022-06-13 22:56:24 +12:00
$metric = empty ( $metricPrefix ) ? " { $subCollection } .count " : " { $metricPrefix } . { $subCollection } .count " ;
2022-02-20 22:33:01 +13:00
2022-06-13 22:56:24 +12:00
$time = ( int ) ( floor ( time () / 1800 ) * 1800 ); // Time rounded to nearest 30 minutes
$usageDB -> createOrUpdateMetric ( $projectId , $time , '30m' , $metric , $count , 1 );
2022-02-20 22:33:01 +13:00
2022-06-13 22:56:24 +12:00
$time = ( int ) ( floor ( time () / 86400 ) * 86400 ); // Time rounded to nearest day
$usageDB -> createOrUpdateMetric ( $projectId , $time , '1d' , $metric , $count , 1 );
}
2021-08-30 21:35:32 +12:00
2022-06-13 22:56:24 +12:00
/**
* Inserting project level sums for sub collections like storage . files . total
*/
foreach ( $subCollectionTotals as $subCollection => $count ) {
$metric = empty ( $metricPrefix ) ? " { $subCollection } .total " : " { $metricPrefix } . { $subCollection } .total " ;
2021-08-30 21:35:32 +12:00
2022-06-13 22:56:24 +12:00
$time = ( int ) ( floor ( time () / 1800 ) * 1800 ); // Time rounded to nearest 30 minutes
$usageDB -> createOrUpdateMetric ( $projectId , $time , '30m' , $metric , $count , 1 );
2021-08-30 19:19:29 +12:00
2022-06-13 22:56:24 +12:00
$time = ( int ) ( floor ( time () / 86400 ) * 86400 ); // Time rounded to nearest day
$usageDB -> createOrUpdateMetric ( $projectId , $time , '1d' , $metric , $count , 1 );
2021-08-30 19:19:29 +12:00
2022-06-13 22:56:24 +12:00
// aggregate storage.total = storage.files.total + storage.deployments.total
if ( $metricPrefix === 'storage' && $subCollection === 'files' ) {
$metric = 'storage.total' ;
2021-11-09 18:50:03 +13:00
2022-02-20 22:33:01 +13:00
$time = ( int ) ( floor ( time () / 1800 ) * 1800 ); // Time rounded to nearest 30 minutes
2022-06-13 22:56:24 +12:00
$usageDB -> createOrUpdateMetric ( $projectId , $time , '30m' , $metric , $count + $deploymentsTotal , 1 );
2021-11-09 18:50:03 +13:00
2022-02-20 22:33:01 +13:00
$time = ( int ) ( floor ( time () / 86400 ) * 86400 ); // Time rounded to nearest day
2022-06-13 22:56:24 +12:00
$usageDB -> createOrUpdateMetric ( $projectId , $time , '1d' , $metric , $count + $deploymentsTotal , 1 );
2021-08-17 18:03:27 +12:00
}
2021-08-17 17:45:07 +12:00
}
2022-06-13 22:56:24 +12:00
} catch ( \Exception $e ) {
Console :: warning ( " Failed: { $e -> getMessage () } " );
Console :: warning ( $e -> getTraceAsString ());
2021-08-17 17:45:07 +12:00
}
2022-02-20 22:33:01 +13:00
}
2022-06-13 22:56:24 +12:00
});
2021-08-30 19:19:29 +12:00
2021-08-17 18:03:27 +12:00
$iterations ++ ;
2021-08-17 17:45:07 +12:00
$loopTook = microtime ( true ) - $loopStart ;
$now = date ( 'd-m-Y H:i:s' , time ());
2021-08-30 21:35:32 +12:00
2021-08-17 18:03:27 +12:00
Console :: info ( " [ { $now } ] Aggregation took { $loopTook } seconds " );
}, $interval );
2022-06-13 22:56:24 +12:00
});