-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #441 from systemli/Add-Command-for-exporting-Metrics
Add Command for exporting Metrics
- Loading branch information
Showing
5 changed files
with
212 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
<?php | ||
|
||
namespace App\Command; | ||
|
||
use App\Entity\Alias; | ||
use App\Entity\Domain; | ||
use App\Entity\OpenPgpKey; | ||
use App\Entity\User; | ||
use App\Entity\Voucher; | ||
use Doctrine\ORM\EntityManagerInterface; | ||
use Symfony\Component\Console\Command\Command; | ||
use Symfony\Component\Console\Input\InputInterface; | ||
use Symfony\Component\Console\Output\OutputInterface; | ||
|
||
/** | ||
* Class MetricsCommand. | ||
* | ||
* This command exposes metrics for Prometheus. It is intended to be used as a | ||
* cronjob. It can be used together with the Prometheus Node Exporter (textfile | ||
* collector). | ||
* | ||
* Example for Cron: | ||
* * * * * * php /path/to/bin/console app:metrics | sponge /path/to/metrics/userli.prom | ||
*/ | ||
class MetricsCommand extends Command | ||
{ | ||
private EntityManagerInterface $manager; | ||
|
||
/** | ||
* MetricsCommand constructor. | ||
*/ | ||
public function __construct(EntityManagerInterface $manager) | ||
{ | ||
parent::__construct(); | ||
$this->manager = $manager; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function configure(): void | ||
{ | ||
# TODO: Option for exposing metrics in different formats (prometheus, openmetrics, etc.) | ||
$this | ||
->setName('app:metrics') | ||
->setDescription('Global Metrics for Userli'); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function execute(InputInterface $input, OutputInterface $output): int | ||
{ | ||
$usersTotal = $this->manager->getRepository(User::class)->countUsers(); | ||
$output->writeln('# HELP userli_users_total Total number of users'); | ||
$output->writeln('# TYPE userli_users_total gauge'); | ||
$output->writeln('userli_users_total ' . $usersTotal); | ||
|
||
$deletedUsersTotal = $this->manager->getRepository(User::class)->countDeletedUsers(); | ||
$output->writeln('# HELP userli_users_deleted_total Total number of deleted users'); | ||
$output->writeln('# TYPE userli_users_deleted_total gauge'); | ||
$output->writeln('userli_users_deleted_total ' . $deletedUsersTotal); | ||
|
||
$usersRecoveryTokenTotal = $this->manager->getRepository(User::class)->countUsersWithRecoveryToken(); | ||
$output->writeln('# HELP userli_users_recovery_token_total Total number of users with recovery token'); | ||
$output->writeln('# TYPE userli_users_recovery_token_total gauge'); | ||
$output->writeln('userli_users_recovery_token_total ' . $usersRecoveryTokenTotal); | ||
|
||
$usersMailCryptTotal = $this->manager->getRepository(User::class)->countUsersWithMailCrypt(); | ||
$output->writeln('# HELP userli_users_mailcrypt_total Total number of users with enabled mailcrypt'); | ||
$output->writeln('# TYPE userli_users_mailcrypt_total gauge'); | ||
$output->writeln('userli_users_mailcrypt_total ' . $usersMailCryptTotal); | ||
|
||
$usersTwofactorTotal = $this->manager->getRepository(User::class)->countUsersWithTwofactor(); | ||
$output->writeln('# HELP userli_users_twofactor_total Total number of users with enabled two factor authentication'); | ||
$output->writeln('# TYPE userli_users_twofactor_total gauge'); | ||
$output->writeln('userli_users_twofactor_total ' . $usersTwofactorTotal); | ||
|
||
$redeemedVouchersTotal = $this->manager->getRepository(Voucher::class)->countRedeemedVouchers(); | ||
$unredeemedVouchersTotal = $this->manager->getRepository(Voucher::class)->countUnredeemedVouchers(); | ||
$output->writeln('# HELP userli_vouchers_total Total number of vouchers'); | ||
$output->writeln('# TYPE userli_vouchers_total gauge'); | ||
$output->writeln('userli_vouchers_total{type="unredeemed"} ' . $unredeemedVouchersTotal); | ||
$output->writeln('userli_vouchers_total{type="redeemed"} ' . $redeemedVouchersTotal); | ||
|
||
$domainsTotal = $this->manager->getRepository(Domain::class)->count([]); | ||
$output->writeln('# HELP userli_domains_total Total number of domains'); | ||
$output->writeln('# TYPE userli_domains_total gauge'); | ||
$output->writeln('userli_domains_total ' . $domainsTotal); | ||
|
||
$aliasesTotal = $this->manager->getRepository(Alias::class)->count(['deleted' => false]); | ||
$output->writeln('# HELP userli_aliases_total Total number of aliases'); | ||
$output->writeln('# TYPE userli_aliases_total gauge'); | ||
$output->writeln('userli_aliases_total ' . $aliasesTotal); | ||
|
||
$openPgpKeysTotal = $this->manager->getRepository(OpenPgpKey::class)->countKeys(); | ||
$output->writeln('# HELP userli_openpgpkeys_total Total number of OpenPGP keys'); | ||
$output->writeln('# TYPE userli_openpgpkeys_total gauge'); | ||
$output->writeln('userli_openpgpkeys_total ' . $openPgpKeysTotal); | ||
|
||
return 0; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -98,7 +98,7 @@ private function loadStaticUsers(ObjectManager $manager): void | |
*/ | ||
private function loadRandomUsers(ObjectManager $manager): void | ||
{ | ||
$domainRepository = $manager->getRepository(Domain::classn); | ||
$domainRepository = $manager->getRepository(Domain::class); | ||
|
||
for ($i = 0; $i < 500; ++$i) { | ||
$email = sprintf('%[email protected]', uniqid('', true)); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
<?php | ||
|
||
namespace App\Tests\Command; | ||
|
||
use App\Command\MetricsCommand; | ||
use App\Entity\Alias; | ||
use App\Entity\Domain; | ||
use App\Entity\OpenPgpKey; | ||
use App\Entity\User; | ||
use App\Entity\Voucher; | ||
use App\Repository\AliasRepository; | ||
use App\Repository\DomainRepository; | ||
use App\Repository\OpenPgpKeyRepository; | ||
use App\Repository\UserRepository; | ||
use App\Repository\VoucherRepository; | ||
use Doctrine\ORM\EntityManagerInterface; | ||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Component\Console\Tester\CommandTester; | ||
|
||
class MetricsCommandTest extends TestCase | ||
{ | ||
public function testExecute(): void | ||
{ | ||
$userRepository = $this->getMockBuilder(UserRepository::class) | ||
->disableOriginalConstructor() | ||
->getMock(); | ||
$userRepository->method('countUsers')->willReturn(10); | ||
$userRepository->method('countDeletedUsers')->willReturn(3); | ||
$userRepository->method('countUsersWithRecoveryToken')->willReturn(5); | ||
$userRepository->method('countUsersWithMailCrypt')->willReturn(7); | ||
$userRepository->method('countUsersWithTwofactor')->willReturn(9); | ||
|
||
$openPgpKeyRepository = $this->getMockBuilder(OpenPgpKeyRepository::class) | ||
->disableOriginalConstructor() | ||
->getMock(); | ||
$openPgpKeyRepository->method('countKeys')->willReturn(2); | ||
|
||
$aliasRepository = $this->getMockBuilder(AliasRepository::class) | ||
->disableOriginalConstructor() | ||
->getMock(); | ||
$aliasRepository->method('count')->willReturn(4); | ||
|
||
$domainRepository = $this->getMockBuilder(DomainRepository::class) | ||
->disableOriginalConstructor() | ||
->getMock(); | ||
$domainRepository->method('count')->willReturn(6); | ||
|
||
$voucherRepository = $this->getMockBuilder(VoucherRepository::class) | ||
->disableOriginalConstructor() | ||
->getMock(); | ||
$voucherRepository->method('countRedeemedVouchers')->willReturn(1); | ||
$voucherRepository->method('countUnredeemedVouchers')->willReturn(7); | ||
|
||
$manager = $this->getMockBuilder(EntityManagerInterface::class) | ||
->disableOriginalConstructor() | ||
->getMock(); | ||
$manager->method('getRepository')->willReturnMap([ | ||
[User::class, $userRepository], | ||
[OpenPgpKey::class, $openPgpKeyRepository], | ||
[Alias::class, $aliasRepository], | ||
[Domain::class, $domainRepository], | ||
[Voucher::class, $voucherRepository], | ||
]); | ||
|
||
$command = new MetricsCommand($manager); | ||
|
||
$commandTester = new CommandTester($command); | ||
$commandTester->execute([]); | ||
|
||
$output = $commandTester->getDisplay(); | ||
|
||
self::assertStringContainsString("userli_users_total 10", $output); | ||
self::assertStringContainsString("userli_users_deleted_total 3", $output); | ||
self::assertStringContainsString("userli_users_recovery_token_total 5", $output); | ||
self::assertStringContainsString("userli_users_mailcrypt_total 7", $output); | ||
self::assertStringContainsString("userli_users_twofactor_total 9", $output); | ||
self::assertStringContainsString("userli_vouchers_total{type=\"unredeemed\"} 7", $output); | ||
self::assertStringContainsString("userli_vouchers_total{type=\"redeemed\"} 1", $output); | ||
self::assertStringContainsString("userli_domains_total 6", $output); | ||
self::assertStringContainsString("userli_aliases_total 4", $output); | ||
self::assertStringContainsString("userli_openpgpkeys_total 2", $output); | ||
} | ||
} |