new function usages endpoint
This commit is contained in:
parent
9fe6c0a3c0
commit
ff8e730b8a
|
@ -184,7 +184,7 @@ App::get('/v1/functions/:functionId/usage')
|
|||
->label('scope', 'functions.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'getUsage')
|
||||
->label('sdk.method', 'getFunctionUsage')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USAGE_FUNCTIONS)
|
||||
|
@ -273,6 +273,107 @@ App::get('/v1/functions/:functionId/usage')
|
|||
]);
|
||||
}
|
||||
|
||||
$response->dynamic($usage, Response::MODEL_USAGE_FUNCTION);
|
||||
});
|
||||
|
||||
App::get('/v1/functions/usage')
|
||||
->desc('Get Function Usage')
|
||||
->groups(['api', 'functions'])
|
||||
->label('scope', 'functions.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'getUsage')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USAGE_FUNCTIONS)
|
||||
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d']), 'Date range.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $functionId, string $range, Response $response, Database $dbForProject) {
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$usage = [];
|
||||
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
|
||||
$periods = [
|
||||
'24h' => [
|
||||
'period' => '30m',
|
||||
'limit' => 48,
|
||||
],
|
||||
'7d' => [
|
||||
'period' => '1d',
|
||||
'limit' => 7,
|
||||
],
|
||||
'30d' => [
|
||||
'period' => '1d',
|
||||
'limit' => 30,
|
||||
],
|
||||
'90d' => [
|
||||
'period' => '1d',
|
||||
'limit' => 90,
|
||||
],
|
||||
];
|
||||
|
||||
$metrics = [
|
||||
"functions.executions",
|
||||
"functions.failures",
|
||||
"functions.executionTime",
|
||||
"functions.buildTime",
|
||||
"functions.compute"
|
||||
];
|
||||
|
||||
$stats = [];
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $periods, $range, $metrics, &$stats) {
|
||||
foreach ($metrics as $metric) {
|
||||
$limit = $periods[$range]['limit'];
|
||||
$period = $periods[$range]['period'];
|
||||
|
||||
$requestDocs = $dbForProject->find('stats', [
|
||||
new Query('period', Query::TYPE_EQUAL, [$period]),
|
||||
new Query('metric', Query::TYPE_EQUAL, [$metric]),
|
||||
], $limit, 0, ['time'], [Database::ORDER_DESC]);
|
||||
|
||||
$stats[$metric] = [];
|
||||
foreach ($requestDocs as $requestDoc) {
|
||||
$stats[$metric][] = [
|
||||
'value' => $requestDoc->getAttribute('value'),
|
||||
'date' => $requestDoc->getAttribute('time'),
|
||||
];
|
||||
}
|
||||
|
||||
// backfill metrics with empty values for graphs
|
||||
$backfill = $limit - \count($requestDocs);
|
||||
while ($backfill > 0) {
|
||||
$last = $limit - $backfill - 1; // array index of last added metric
|
||||
$diff = match ($period) { // convert period to seconds for unix timestamp math
|
||||
'30m' => 1800,
|
||||
'1d' => 86400,
|
||||
};
|
||||
$stats[$metric][] = [
|
||||
'value' => 0,
|
||||
'date' => ($stats[$metric][$last]['date'] ?? \time()) - $diff, // time of last metric minus period
|
||||
];
|
||||
$backfill--;
|
||||
}
|
||||
$stats[$metric] = array_reverse($stats[$metric]);
|
||||
}
|
||||
});
|
||||
|
||||
$usage = new Document([
|
||||
'range' => $range,
|
||||
'functionsExecutions' => $stats["functions.executions"],
|
||||
'functionsFailures' => $stats["functions.failures"],
|
||||
'functionsCompute' => $stats["functions.compute"],
|
||||
'functionsExecutionTime' => $stats["functions.executionTime"],
|
||||
'functionsBuildTime' => $stats["functions.buildTime"]
|
||||
]);
|
||||
}
|
||||
|
||||
$response->dynamic($usage, Response::MODEL_USAGE_FUNCTIONS);
|
||||
});
|
||||
|
||||
|
|
|
@ -177,6 +177,18 @@ class Usage
|
|||
'users.sessions.delete' => [
|
||||
'table' => 'appwrite_usage_users_sessions_delete',
|
||||
],
|
||||
'functions.executions' => [
|
||||
'table' => 'appwrite_usage_functions_executions_all',
|
||||
],
|
||||
'functions.builds' => [
|
||||
'table' => 'appwrite_usage_functions_builds_all',
|
||||
],
|
||||
'functions.failures' => [
|
||||
'table' => 'appwrite_usage_functions_executions_all',
|
||||
'filters' => [
|
||||
'functionStatus' => 'failed',
|
||||
],
|
||||
],
|
||||
'functions.functionId.executions' => [
|
||||
'table' => 'appwrite_usage_functions_executions_all',
|
||||
'groupBy' => ['functionId'],
|
||||
|
|
|
@ -234,7 +234,7 @@ class UsageDB extends Usage
|
|||
$executionTotal = $this->sum($projectId, 'executions', 'time', 'functions.executionTime');
|
||||
$buildTotal = $this->sum($projectId, 'builds', 'duration', 'functions.buildTime');
|
||||
|
||||
$this->createOrUpdateMetric($projectId, 'functions.compute', $executionTotal + $buildTotal);
|
||||
$this->createOrUpdateMetric($projectId, 'functions.compute', ($executionTotal * 1000) + ($buildTotal * 1000)); //in ms
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -66,6 +66,7 @@ use Appwrite\Utopia\Response\Model\UsageBuckets;
|
|||
use Appwrite\Utopia\Response\Model\UsageCollection;
|
||||
use Appwrite\Utopia\Response\Model\UsageDatabase;
|
||||
use Appwrite\Utopia\Response\Model\UsageDatabases;
|
||||
use Appwrite\Utopia\Response\Model\UsageFunction;
|
||||
use Appwrite\Utopia\Response\Model\UsageFunctions;
|
||||
use Appwrite\Utopia\Response\Model\UsageProject;
|
||||
use Appwrite\Utopia\Response\Model\UsageStorage;
|
||||
|
@ -93,6 +94,7 @@ class Response extends SwooleResponse
|
|||
public const MODEL_USAGE_BUCKETS = 'usageBuckets';
|
||||
public const MODEL_USAGE_STORAGE = 'usageStorage';
|
||||
public const MODEL_USAGE_FUNCTIONS = 'usageFunctions';
|
||||
public const MODEL_USAGE_FUNCTION = 'usageFunction';
|
||||
public const MODEL_USAGE_PROJECT = 'usageProject';
|
||||
|
||||
// Database
|
||||
|
@ -296,6 +298,7 @@ class Response extends SwooleResponse
|
|||
->setModel(new UsageStorage())
|
||||
->setModel(new UsageBuckets())
|
||||
->setModel(new UsageFunctions())
|
||||
->setModel(new UsageFunction())
|
||||
->setModel(new UsageProject())
|
||||
// Verification
|
||||
// Recovery
|
||||
|
|
62
src/Appwrite/Utopia/Response/Model/UsageFunction.php
Normal file
62
src/Appwrite/Utopia/Response/Model/UsageFunction.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class UsageFunction extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('range', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The time range of the usage stats.',
|
||||
'default' => '',
|
||||
'example' => '30d',
|
||||
])
|
||||
->addRule('functionsExecutions', [
|
||||
'type' => Response::MODEL_METRIC_LIST,
|
||||
'description' => 'Aggregated stats for function executions.',
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
'array' => true
|
||||
])
|
||||
->addRule('functionsFailures', [
|
||||
'type' => Response::MODEL_METRIC_LIST,
|
||||
'description' => 'Aggregated stats for function execution failures.',
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
'array' => true
|
||||
])
|
||||
->addRule('functionsCompute', [
|
||||
'type' => Response::MODEL_METRIC_LIST,
|
||||
'description' => 'Aggregated stats for function execution duration.',
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
'array' => true
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'UsageFunction';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_USAGE_FUNCTION;
|
||||
}
|
||||
}
|
|
@ -37,6 +37,20 @@ class UsageFunctions extends Model
|
|||
'example' => new \stdClass(),
|
||||
'array' => true
|
||||
])
|
||||
->addRule('functionsExecutionTime', [
|
||||
'type' => Response::MODEL_METRIC_LIST,
|
||||
'description' => 'Aggregated stats for function execution duration.',
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
'array' => true
|
||||
])
|
||||
->addRule('functionsBuildTime', [
|
||||
'type' => Response::MODEL_METRIC_LIST,
|
||||
'description' => 'Aggregated stats for function execution duration.',
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
'array' => true
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
|
|
|
@ -414,15 +414,9 @@ class UsageTest extends Scope
|
|||
|
||||
public function testFunctionsStats(): void
|
||||
{
|
||||
// create some functions
|
||||
// create some successful deployments and some
|
||||
// failed deployments
|
||||
// execute some functions
|
||||
// some failed executions
|
||||
// test the stats
|
||||
$functionId = '';
|
||||
$requestsCount = 0;
|
||||
$deploymentsTotal = 0;
|
||||
$executionTime = 0;
|
||||
$executions = 0;
|
||||
$failures = 0;
|
||||
$compute = 0;
|
||||
|
@ -488,27 +482,28 @@ class UsageTest extends Scope
|
|||
$this->assertIsInt($response['body']['$createdAt']);
|
||||
$this->assertIsInt($response['body']['$updatedAt']);
|
||||
$this->assertEquals($deploymentId, $response['body']['deployment']);
|
||||
|
||||
|
||||
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'async' => false,
|
||||
]);
|
||||
|
||||
|
||||
$this->assertEquals(201, $execution['headers']['status-code']);
|
||||
$this->assertNotEmpty($execution['body']['$id']);
|
||||
$this->assertEquals($functionId, $execution['body']['functionId']);
|
||||
$compute += $execution['body']['time'] * 1000;
|
||||
$executionTime += $execution['body']['time'] * 1000;
|
||||
$executions++;
|
||||
|
||||
|
||||
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'async' => true,
|
||||
]);
|
||||
|
||||
|
||||
$this->assertEquals(201, $execution['headers']['status-code']);
|
||||
$this->assertNotEmpty($execution['body']['$id']);
|
||||
$this->assertEquals($functionId, $execution['body']['functionId']);
|
||||
|
@ -517,17 +512,18 @@ class UsageTest extends Scope
|
|||
|
||||
//wait for execution to complete
|
||||
sleep(10);
|
||||
|
||||
|
||||
$execution = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
|
||||
$this->assertEquals(200, $execution['headers']['status-code']);
|
||||
$this->assertEquals($executionId, $execution['body']['$id']);
|
||||
$this->assertEquals($functionId, $execution['body']['functionId']);
|
||||
|
||||
$compute += $execution['body']['time'] * 1000;
|
||||
$executionTime += $execution['body']['time'] * 1000;
|
||||
|
||||
sleep(45);
|
||||
|
||||
|
@ -538,9 +534,9 @@ class UsageTest extends Scope
|
|||
'range' => '30d'
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 200);
|
||||
$this->assertEquals(count($response['body']), 4);
|
||||
$this->assertEquals($response['body']['range'], '30d');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(4, count($response['body']));
|
||||
$this->assertEquals('30d', $response['body']['range']);
|
||||
$this->assertIsArray($response['body']['functionsExecutions']);
|
||||
$this->assertIsArray($response['body']['functionsFailures']);
|
||||
$this->assertIsArray($response['body']['functionsCompute']);
|
||||
|
@ -550,6 +546,28 @@ class UsageTest extends Scope
|
|||
$this->assertGreaterThan($compute, $response['functionsCompute'][array_key_last($response['functionsCompute'])]['value']);
|
||||
$this->assertEquals($failures, $response['functionsFailures'][array_key_last($response['functionsFailures'])]['value']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/functions/usage', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
], $this->getHeaders()), [
|
||||
'range' => '30d'
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(6, count($response['body']));
|
||||
$this->assertEquals($response['body']['range'], '30d');
|
||||
$this->assertIsArray($response['body']['functionsExecutions']);
|
||||
$this->assertIsArray($response['body']['functionsFailures']);
|
||||
$this->assertIsArray($response['body']['functionsCompute']);
|
||||
$this->assertIsArray($response['body']['functionsExecutionTime']);
|
||||
$this->assertIsArray($response['body']['functionsBuildTime']);
|
||||
$response = $response['body'];
|
||||
|
||||
$this->assertEquals($executions, $response['functionsExecutions'][array_key_last($response['functionsExecutions'])]['value']);
|
||||
$this->assertGreaterThan($compute, $response['functionsCompute'][array_key_last($response['functionsCompute'])]['value']);
|
||||
$this->assertEquals($executionTime, $response['functionsExecutionTime'][array_key_last($response['functionsCompute'])]['value']);
|
||||
$this->assertGreaterThan(0, $response['functionsBuildTime'][array_key_last($response['functionsCompute'])]['value']);
|
||||
$this->assertEquals($failures, $response['functionsFailures'][array_key_last($response['functionsFailures'])]['value']);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
|
|
Loading…
Reference in a new issue