1
0
Fork 0
mirror of synced 2024-07-18 12:56:00 +12:00
appwrite/app/tasks/usage.php

303 lines
12 KiB
PHP
Raw Normal View History

2021-08-10 20:44:31 +12:00
<?php
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;
use Utopia\Cache\Cache;
2021-08-10 20:44:31 +12:00
use Utopia\CLI\Console;
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-19 18:54:32 +12:00
* requests
* network
* executions
*
2021-08-30 19:19:29 +12:00
* Database
*
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 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
* 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 19:19:29 +12:00
* Users
*
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
* 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')
->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 = [];
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-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
*/
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-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
}
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);
}
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";
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';
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);
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-17 18:03:27 +12:00
Console::info("[{$now}] Aggregation took {$loopTook} seconds");
}, $interval);
2022-06-13 22:56:24 +12:00
});