1
0
Fork 0
mirror of synced 2024-06-14 08:44:49 +12:00

new function usages endpoint

This commit is contained in:
Damodar Lohani 2022-07-17 10:30:58 +00:00
parent 9fe6c0a3c0
commit ff8e730b8a
7 changed files with 228 additions and 18 deletions

View file

@ -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);
});

View file

@ -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'],

View file

@ -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
}
/**

View file

@ -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

View 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;
}
}

View file

@ -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
])
;
}

View file

@ -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