Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@
['name' => 'quotaRule#addRule', 'url' => '/quota/rule', 'verb' => 'POST'],
['name' => 'quotaRule#updateRule', 'url' => '/quota/rule', 'verb' => 'PUT'],
['name' => 'quotaRule#deleteRule', 'url' => '/quota/rule', 'verb' => 'DELETE'],
['name' => 'quotaRule#getQuotaUsage', 'url' => '/quota/download-usage', 'verb' => 'GET'],
],
];
34 changes: 34 additions & 0 deletions lib/Controller/QuotaRuleController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
use OCA\OpenAi\Service\QuotaRuleService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TextPlainResponse;
use OCP\IRequest;

class QuotaRuleController extends Controller {
Expand Down Expand Up @@ -79,4 +82,35 @@ public function deleteRule(int $id): DataResponse {
return new DataResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
}
}
/**
* Gets the quota usage between two dates
* @param int $startDate
* @param int $endDate
* @param int $type
* @return Http\StreamResponse|TextPlainResponse
*/
#[NoCSRFRequired]
public function getQuotaUsage(int $startDate, int $endDate, int $type): Response {
try {
$result = $this->quotaRuleService->getQuotaUsage($startDate, $endDate, $type);
$csv = fopen('php://memory', 'w');
try {
foreach ($result as $row) {
fputcsv($csv, $row);
}
rewind($csv);

$response = new Http\StreamResponse($csv, Http::STATUS_OK);
$response->setHeaders([
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="quota_usage.csv"'
]);
return $response;
} catch (Exception $e) {
return new TextPlainResponse('Failed to get quota usage:' . $e->getMessage(), Http::STATUS_INTERNAL_SERVER_ERROR);
}
} catch (Exception $e) {
return new TextPlainResponse('Failed to get quota usage:' . $e->getMessage(), Http::STATUS_NOT_FOUND);
}
}
}
7 changes: 4 additions & 3 deletions lib/Cron/CleanupQuotaDb.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ public function __construct(
protected function run($argument) {
$this->logger->debug('Run cleanup job for OpenAI quota db');
$quota = $this->openAiSettingsService->getQuotaPeriod();
$days = $quota['length'];
$quotaDays = $quota['length'];
if ($quota['unit'] == 'month') {
$days *= 30;
$quotaDays *= 30;
}
$days = $this->openAiSettingsService->getUsageStorageTime();
$this->quotaUsageMapper->cleanupQuotaUsages(
// The mimimum period is limited to DEFAULT_QUOTA_PERIOD to not lose
// the stored quota usage data below this limit.
max($days, Application::DEFAULT_QUOTA_PERIOD)
max($quotaDays, $days, Application::DEFAULT_QUOTA_PERIOD)
);

}
Expand Down
53 changes: 50 additions & 3 deletions lib/Db/QuotaUsageMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public function getQuotaUnitsInTimePeriod(int $type, int $periodStart): int {
$qb = $this->db->getQueryBuilder();

// Get the sum of the units used in the time period
$qb->select($qb->createFunction('SUM(units)'))
$qb->select($qb->func()->sum('units'))
->from($this->getTableName())
->where(
$qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT))
Expand Down Expand Up @@ -113,7 +113,7 @@ public function getQuotaUnitsOfUserInTimePeriod(string $userId, int $type, int $
$qb = $this->db->getQueryBuilder();

// Get the sum of the units used in the time period
$qb->select($qb->createFunction('SUM(units)'))
$qb->select($qb->func()->sum('units'))
->from($this->getTableName())
->where(
$qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT))
Expand Down Expand Up @@ -170,7 +170,7 @@ public function getQuotaUsagesOfUser(string $userId, int $type): array {
public function getQuotaUnitsOfUser(string $userId, int $type): int {
$qb = $this->db->getQueryBuilder();

$qb->select($qb->createFunction('SUM(units)'))
$qb->select($qb->func()->sum('units'))
->from($this->getTableName())
->where(
$qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
Expand Down Expand Up @@ -274,4 +274,51 @@ public function cleanupQuotaUsages(int $timePeriod): void {
);
$qb->executeStatement();
}

/**
* Gets quota usage of all users
* @param int $startTime
* @param int $endTime
* @return array
* @throws Exception
* @throws RuntimeException
*/
public function getUsersQuotaUsage(int $startTime, int $endTime, $type): array {
$qb = $this->db->getQueryBuilder();

$qb->select('user_id')
->selectAlias($qb->func()->sum('units'), 'usage')
->from($this->getTableName())
->where($qb->expr()->gte('timestamp', $qb->createNamedParameter($startTime, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->lte('timestamp', $qb->createNamedParameter($endTime, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT)))
->groupBy('user_id')
->orderBy('usage', 'DESC');

return $qb->executeQuery()->fetchAll();
}
/**
* Gets quota usage of all pools
* @param int $startTime
* @param int $endTime
* @param int $type
* @return array
* @throws Exception
* @throws RuntimeException
*/
public function getPoolsQuotaUsage(int $startTime, int $endTime, int $type): array {
$qb = $this->db->getQueryBuilder();

$qb->select('pool')
->selectAlias($qb->func()->sum('units'), 'usage')
->from($this->getTableName())
->where($qb->expr()->neq('pool', $qb->createNamedParameter(-1, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->gte('timestamp', $qb->createNamedParameter($startTime, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->lte('timestamp', $qb->createNamedParameter($endTime, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT)))
->groupBy('type', 'pool')
->orderBy('usage', 'DESC');

return $qb->executeQuery()->fetchAll();
}
}
22 changes: 21 additions & 1 deletion lib/Service/OpenAiSettingsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class OpenAiSettingsService {
'llm_extra_params' => 'string',
'quota_period' => 'array',
'quotas' => 'array',
'usage_storage_time' => 'integer',
'translation_provider_enabled' => 'boolean',
'llm_provider_enabled' => 'boolean',
't2i_provider_enabled' => 'boolean',
Expand Down Expand Up @@ -119,7 +120,9 @@ public function getQuotaEnd(): int {
$startDate = new DateTime('2000-01-' . $quotaPeriod['day']);
$months = $startDate->diff($periodEnd)->m + $startDate->diff($periodEnd)->y * 12;
$remainder = $months % $quotaPeriod['length'];
$periodEnd = $periodEnd->add(new DateInterval('P' . $quotaPeriod['length'] - $remainder . 'M'));
if ($remainder != 0) {
$periodEnd = $periodEnd->add(new DateInterval('P' . $quotaPeriod['length'] - $remainder . 'M'));
}
}
}
return $periodEnd->getTimestamp();
Expand Down Expand Up @@ -308,6 +311,10 @@ public function getQuotas(): array {
return $quotas;
}

public function getUsageStorageTime() : int {
return $this->appConfig->getValueInt(Application::APP_ID, 'usage_storage_time', Application::DEFAULT_QUOTA_PERIOD, lazy: true);
}

/**
* @return boolean
*/
Expand Down Expand Up @@ -395,6 +402,7 @@ public function getAdminConfig(): array {
// Updated to get quota period
'quotas' => $this->getQuotas(),
// Get quotas from the config value and return it
'usage_storage_time' => $this->getUsageStorageTime(),
'translation_provider_enabled' => $this->getTranslationProviderEnabled(),
'llm_provider_enabled' => $this->getLlmProviderEnabled(),
't2i_provider_enabled' => $this->getT2iProviderEnabled(),
Expand Down Expand Up @@ -522,6 +530,15 @@ public function setQuotas(array $quotas): void {
$cache->clear(Application::QUOTA_RULES_CACHE_PREFIX);
}

/**
* @param int $usageStorageTime
* @return void
*/
public function setUsageStorageTime(int $usageStorageTime): void {
$usageStorageTime = max(1, $usageStorageTime);
$this->appConfig->setValueInt(Application::APP_ID, 'usage_storage_time', $usageStorageTime, lazy: true);
}

/**
* @param string $apiKey
* @return void
Expand Down Expand Up @@ -831,6 +848,9 @@ public function setAdminConfig(array $adminConfig): void {
if (isset($adminConfig['quotas'])) {
$this->setQuotas($adminConfig['quotas']);
}
if (isset($adminConfig['usage_storage_time'])) {
$this->setUsageStorageTime(intval($adminConfig['usage_storage_time']));
}
if (isset($adminConfig['use_max_completion_tokens_param'])) {
$this->setUseMaxCompletionParam($adminConfig['use_max_completion_tokens_param']);
}
Expand Down
29 changes: 29 additions & 0 deletions lib/Service/QuotaRuleService.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
use OCA\OpenAi\AppInfo\Application;
use OCA\OpenAi\Db\EntityType;
use OCA\OpenAi\Db\QuotaRuleMapper;
use OCA\OpenAi\Db\QuotaUsageMapper;
use OCA\OpenAi\Db\QuotaUserMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\ICacheFactory;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\IUserManager;
use Psr\Log\LoggerInterface;

Expand All @@ -27,6 +29,8 @@ public function __construct(
private IGroupManager $groupManager,
private ICacheFactory $cacheFactory,
private IUserManager $userManager,
private QuotaUsageMapper $quotaUsageMapper,
private IL10N $l10n,
private LoggerInterface $logger,
) {
}
Expand Down Expand Up @@ -178,4 +182,29 @@ private function validateEntities(array $entities) {
}
}
}
public function getQuotaUsage(int $startDate, int $endDate, int $type): array {
$data = [[$this->l10n->t('Name'), $this->l10n->t('Usage')]];
$users = $this->quotaUsageMapper->getUsersQuotaUsage($startDate, $endDate, $type);
$pools = $this->quotaUsageMapper->getPoolsQuotaUsage($startDate, $endDate, $type);
$usersIdx = 0;
$poolsIdx = 0;
while ($usersIdx < count($users) && $poolsIdx < count($pools)) {
if ($users[$usersIdx]['usage'] > $pools[$poolsIdx]['usage']) {
$data[] = [$users[$usersIdx]['user_id'], $users[$usersIdx]['usage']];
$usersIdx++;
} else {
$data[] = [$this->l10n->t('Quota pool for rule %d', $pools[$poolsIdx]['pool']), $pools[$poolsIdx]['usage']];
$poolsIdx++;
}
}
while ($usersIdx < count($users)) {
$data[] = [$users[$usersIdx]['user_id'], $users[$usersIdx]['usage']];
$usersIdx++;
}
while ($poolsIdx < count($pools)) {
$data[] = [$this->l10n->t('Quota pool for rule %d', $pools[$poolsIdx]['pool']), $pools[$poolsIdx]['usage']];
$poolsIdx++;
}
return $data;
}
}
2 changes: 2 additions & 0 deletions lib/Settings/Admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public function getForm(): TemplateResponse {
$adminConfig['basic_password'] = $adminConfig['basic_password'] === '' ? '' : 'dummyPassword';
$isAssistantEnabled = $this->appManager->isEnabledForUser('assistant');
$adminConfig['assistant_enabled'] = $isAssistantEnabled;
$adminConfig['quota_start_date'] = $this->openAiSettingsService->getQuotaStart();
$adminConfig['quota_end_date'] = $this->openAiSettingsService->getQuotaEnd();
$this->initialStateService->provideInitialState('admin-config', $adminConfig);
$rules = $this->quotaRuleService->getRules();
$this->initialStateService->provideInitialState('rules', $rules);
Expand Down
Loading
Loading