diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..1f36c7474 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,16 @@ +services: + prometheus: + restart: always + image: bitnami/prometheus:latest + links: + - pushgateway + volumes: + - ./prom.yml:/opt/bitnami/prometheus/conf/prometheus.yml + ports: + - 9092:9090 + + pushgateway: + restart: always + image: bitnami/pushgateway:latest + ports: + - 9091:9091 diff --git a/prom.yml b/prom.yml new file mode 100644 index 000000000..48164fa04 --- /dev/null +++ b/prom.yml @@ -0,0 +1,11 @@ +global: + scrape_interval: 5s + scrape_timeout: 2s + evaluation_interval: 15s + +scrape_configs: + - job_name: pushgateway + honor_labels: true + static_configs: + - targets: + - 'pushgateway:9091' diff --git a/src/Application/Linter/Linter.php b/src/Application/Linter/Linter.php index d5e0de115..c9eea6b21 100644 --- a/src/Application/Linter/Linter.php +++ b/src/Application/Linter/Linter.php @@ -19,9 +19,9 @@ use ArtARTs36\MergeRequestLinter\Domain\Rule\Rule; use ArtARTs36\MergeRequestLinter\Domain\Rule\Rules; use ArtARTs36\MergeRequestLinter\Shared\DataStructure\Arrayee; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\IncCounter; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\MetricManager; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\MetricSubject; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\Counter; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\MetricSubject; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\CollectorRegisterer; use ArtARTs36\MergeRequestLinter\Shared\Time\Timer; use Psr\EventDispatcher\EventDispatcherInterface; @@ -31,7 +31,7 @@ public function __construct( private Rules $rules, private LinterOptions $options, private EventDispatcherInterface $events, - private MetricManager $metrics, + private CollectorRegisterer $metrics, ) { } @@ -39,7 +39,7 @@ public function run(MergeRequest $request): LintResult { $timer = Timer::start(); - $this->addMetricUsedRules(); + $this->addMetricUsedRules($request); $this->events->dispatch(new LintStartedEvent($request)); @@ -93,15 +93,17 @@ public function run(MergeRequest $request): LintResult return $result; } - private function addMetricUsedRules(): void + private function addMetricUsedRules(MergeRequest $request): void { - $this->metrics->add( - new MetricSubject( - 'linter_used_rules', - '[Linter] Used rules', - ), - IncCounter::create($this->rules), - ); + $this + ->metrics + ->register(new Counter( + new MetricSubject('linter', 'used_rules', 'Used rules'), + [ + 'request' => (string) $request->uri, + ], + $this->rules->count(), + )); } /** diff --git a/src/Application/Linter/LinterFactory.php b/src/Application/Linter/LinterFactory.php index 158045672..4b769c706 100644 --- a/src/Application/Linter/LinterFactory.php +++ b/src/Application/Linter/LinterFactory.php @@ -4,14 +4,14 @@ use ArtARTs36\MergeRequestLinter\Domain\Configuration\Config; use ArtARTs36\MergeRequestLinter\Domain\Linter\Linter; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\MetricManager; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\CollectorRegistry; use Psr\EventDispatcher\EventDispatcherInterface; class LinterFactory { public function __construct( private readonly EventDispatcherInterface $events, - private readonly MetricManager $metrics, + private readonly CollectorRegistry $metrics, ) { } diff --git a/src/Application/Linter/RunnerFactory.php b/src/Application/Linter/RunnerFactory.php index 3eeb92b8d..f6c024fbb 100644 --- a/src/Application/Linter/RunnerFactory.php +++ b/src/Application/Linter/RunnerFactory.php @@ -22,7 +22,7 @@ use ArtARTs36\MergeRequestLinter\Infrastructure\RequestFetcher\CiRequestFetcher; use ArtARTs36\MergeRequestLinter\Shared\DataStructure\ArrayMap; use ArtARTs36\MergeRequestLinter\Shared\DataStructure\Map; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\MetricManager; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\CollectorRegistry; use ArtARTs36\MergeRequestLinter\Shared\Time\Clock; class RunnerFactory implements LinterRunnerFactory @@ -31,13 +31,13 @@ class RunnerFactory implements LinterRunnerFactory * @param Map> $ciSystems */ public function __construct( - protected Environment $environment, - protected Map $ciSystems, - protected ContextLogger $logger, - protected MetricManager $metrics, - protected ClientFactory $clientFactory, - protected Clock $clock, - protected MapContainer $container = new MapContainer(), + protected Environment $environment, + protected Map $ciSystems, + protected ContextLogger $logger, + protected CollectorRegistry $metrics, + protected ClientFactory $clientFactory, + protected Clock $clock, + protected MapContainer $container = new MapContainer(), ) { } diff --git a/src/Application/Rule/Metrics/RuleLintStateMetricHandler.php b/src/Application/Rule/Metrics/RuleLintStateMetricHandler.php new file mode 100644 index 000000000..cdeccc4db --- /dev/null +++ b/src/Application/Rule/Metrics/RuleLintStateMetricHandler.php @@ -0,0 +1,53 @@ +register($counter); + + return new self($counter); + } + + public function handle(RuleWasSuccessfulEvent|RuleWasFailedEvent $event): void + { + if ($event instanceof RuleWasSuccessfulEvent) { + $this + ->counter + ->add([ + 'rule' => $event->ruleName, + 'state' => 'true', + ]) + ->inc(); + + return; + } + + $this + ->counter + ->add([ + 'rule' => $event->ruleName, + 'state' => 'fail', + ]) + ->inc(); + } +} diff --git a/src/Application/Rule/Rules/ConditionRule.php b/src/Application/Rule/Rules/ConditionRule.php index 1809bb872..f7730ef9c 100644 --- a/src/Application/Rule/Rules/ConditionRule.php +++ b/src/Application/Rule/Rules/ConditionRule.php @@ -5,15 +5,14 @@ use ArtARTs36\MergeRequestLinter\Domain\Condition\ConditionOperator; use ArtARTs36\MergeRequestLinter\Domain\Request\MergeRequest; use ArtARTs36\MergeRequestLinter\Domain\Rule\Rule; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\Counter; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\NullCounter; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\CounterVector; class ConditionRule extends OneRuleDecoratorRule { public function __construct( Rule $rule, private readonly ConditionOperator $operator, - private readonly Counter $skippedRules = new NullCounter(), + private readonly CounterVector $skippedRules, ) { parent::__construct($rule); } @@ -21,7 +20,12 @@ public function __construct( public function lint(MergeRequest $request): array { if (! $this->operator->check($request)) { - $this->skippedRules->inc(); + $this + ->skippedRules + ->add([ + 'rule' => $this->getName(), + ]) + ->inc(); return []; } diff --git a/src/Domain/Configuration/Config.php b/src/Domain/Configuration/Config.php index 60a5a39d5..aeb320cb5 100644 --- a/src/Domain/Configuration/Config.php +++ b/src/Domain/Configuration/Config.php @@ -17,12 +17,14 @@ public const SUBJECT_NOTIFICATIONS = 4; public const SUBJECT_LINTER = 5; public const SUBJECT_COMMENTS = 6; + public const SUBJECT_METRICS = 7; public const SUBJECT_ALL = self::SUBJECT_RULES | self::SUBJECT_CI_SETTINGS | self::SUBJECT_HTTP_CLIENT | self::SUBJECT_NOTIFICATIONS | self::SUBJECT_LINTER | - self::SUBJECT_COMMENTS; + self::SUBJECT_COMMENTS | + self::SUBJECT_METRICS; /** * @param Map $settings @@ -34,6 +36,7 @@ public function __construct( public NotificationsConfig $notifications, public LinterConfig $linter, public CommentsConfig $comments, + public MetricsConfig $metrics, ) { } } diff --git a/src/Domain/Configuration/MetricsConfig.php b/src/Domain/Configuration/MetricsConfig.php new file mode 100644 index 000000000..2a8403620 --- /dev/null +++ b/src/Domain/Configuration/MetricsConfig.php @@ -0,0 +1,14 @@ + $name + */ + public function __construct( + public string $name, + public string $address, + ) { + } +} diff --git a/src/Infrastructure/Configuration/Loader/ArrayConfigLoaderFactory.php b/src/Infrastructure/Configuration/Loader/ArrayConfigLoaderFactory.php index 82e1706e9..ab59bb44c 100644 --- a/src/Infrastructure/Configuration/Loader/ArrayConfigLoaderFactory.php +++ b/src/Infrastructure/Configuration/Loader/ArrayConfigLoaderFactory.php @@ -35,9 +35,9 @@ use ArtARTs36\MergeRequestLinter\Infrastructure\Rule\Factories\RuleFactory; use ArtARTs36\MergeRequestLinter\Infrastructure\Rule\Resolver; use ArtARTs36\MergeRequestLinter\Infrastructure\Text\Decoder\DecoderFactory; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\MetricManager; -use ArtARTs36\MergeRequestLinter\Shared\Reflection\ParameterMapBuilder; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\CollectorRegistry; use ArtARTs36\MergeRequestLinter\Shared\Reflection\Instantiator\Finder; +use ArtARTs36\MergeRequestLinter\Shared\Reflection\ParameterMapBuilder; use ArtARTs36\MergeRequestLinter\Shared\Reflection\TypeResolver\ResolverFactory; class ArrayConfigLoaderFactory @@ -48,12 +48,12 @@ class ArrayConfigLoaderFactory ]; public function __construct( - private readonly FileSystem $fileSystem, - private readonly Environment $environment, - private readonly MetricManager $metrics, - private readonly ResolverFactory $argumentResolverFactory, - private readonly MapContainer $container, - private readonly DecoderFactory $decoderFactory = new DecoderFactory(), + private readonly FileSystem $fileSystem, + private readonly Environment $environment, + private readonly CollectorRegistry $metrics, + private readonly ResolverFactory $argumentResolverFactory, + private readonly MapContainer $container, + private readonly DecoderFactory $decoderFactory = new DecoderFactory(), ) { } diff --git a/src/Infrastructure/Configuration/Loader/Mapper/ArrayConfigHydrator.php b/src/Infrastructure/Configuration/Loader/Mapper/ArrayConfigHydrator.php index 85437fdab..638208e37 100644 --- a/src/Infrastructure/Configuration/Loader/Mapper/ArrayConfigHydrator.php +++ b/src/Infrastructure/Configuration/Loader/Mapper/ArrayConfigHydrator.php @@ -8,6 +8,8 @@ use ArtARTs36\MergeRequestLinter\Domain\Configuration\Config; use ArtARTs36\MergeRequestLinter\Domain\Configuration\HttpClientConfig; use ArtARTs36\MergeRequestLinter\Domain\Configuration\LinterConfig; +use ArtARTs36\MergeRequestLinter\Domain\Configuration\MetricsConfig; +use ArtARTs36\MergeRequestLinter\Domain\Configuration\MetricsStorageConfig; use ArtARTs36\MergeRequestLinter\Domain\Configuration\NotificationsConfig; use ArtARTs36\MergeRequestLinter\Domain\Linter\LinterOptions; use ArtARTs36\MergeRequestLinter\Domain\Rule\Rules; @@ -82,6 +84,7 @@ public function hydrate(array $data, int $subjects = Config::SUBJECT_ALL): Confi $notifications, $this->createLinterConfig($data['linter'] ?? []), $this->createCommentsConfig($data['comments'] ?? []), + $this->createMetricsConfig($data), ); } @@ -155,4 +158,58 @@ private function createCommentsConfig(array $config): CommentsConfig $messages, ); } + + /** + * @param array $config + */ + private function createMetricsConfig(array $config): MetricsConfig + { + if (! array_key_exists('metrics', $config)) { + return new MetricsConfig(new MetricsStorageConfig('null', 'null')); + } + + if (! is_array($config['metrics'])) { + throw ConfigInvalidException::invalidType('metrics', 'array', gettype($config['metrics'])); + } + + if (! array_key_exists('storage', $config['metrics'])) { + throw ConfigInvalidException::keyNotSet('metrics.storage'); + } + + if (! is_array($config['metrics']['storage'])) { + throw ConfigInvalidException::invalidType( + 'metrics.storage', + 'array', + gettype($config['metrics']['storage']), + ); + } + + if (count($config['metrics']['storage']) === 0) { + throw new ConfigInvalidException('Config[metrics.storage] must be not empty'); + } + + $storageName = array_key_first($config['metrics']['storage']); + + if (! in_array($storageName, MetricsStorageConfig::NAMES, true)) { + throw new ConfigInvalidException(sprintf( + 'Config[metrics.storage] name must be of [%s]', + implode(', ', MetricsStorageConfig::NAMES), + )); + } + + $storage = $config['metrics']['storage'][$storageName]; + + if (empty($storage['address'])) { + throw new ConfigInvalidException('Config[metrics.storage.address] must be not empty'); + } + + if (! is_string($storage['address'])) { + throw new ConfigInvalidException('Config[metrics.storage.address] must be string'); + } + + return new MetricsConfig(new MetricsStorageConfig( + $storageName, + $storage['address'], + )); + } } diff --git a/src/Infrastructure/Configuration/Resolver/MetricableConfigResolver.php b/src/Infrastructure/Configuration/Resolver/MetricableConfigResolver.php index 7beb76995..97c8cd620 100644 --- a/src/Infrastructure/Configuration/Resolver/MetricableConfigResolver.php +++ b/src/Infrastructure/Configuration/Resolver/MetricableConfigResolver.php @@ -4,15 +4,15 @@ use ArtARTs36\MergeRequestLinter\Domain\Configuration\Config; use ArtARTs36\MergeRequestLinter\Infrastructure\Configuration\User; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\MetricManager; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\MetricSubject; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\MetricSubject; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\CollectorRegistry; use ArtARTs36\MergeRequestLinter\Shared\Time\Timer; class MetricableConfigResolver implements \ArtARTs36\MergeRequestLinter\Infrastructure\Contracts\Configuration\ConfigResolver { public function __construct( private readonly \ArtARTs36\MergeRequestLinter\Infrastructure\Contracts\Configuration\ConfigResolver $resolver, - private readonly MetricManager $metrics, + private readonly CollectorRegistry $metrics, ) { } @@ -22,9 +22,12 @@ public function resolve(User $user, int $configSubjects = Config::SUBJECT_ALL): $config = $this->resolver->resolve($user, $configSubjects); - $this->metrics->add( - new MetricSubject('config_resolving_time', '[Config] Duration of config resolving'), - $timer->finish(), + $this->metrics->register( + new \ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\Gauge( + new MetricSubject('config', 'resolving_time', 'Duration of config resolving'), + [], + $timer->finish()->seconds, + ), ); return $config; diff --git a/src/Infrastructure/Http/Client/ClientFactory.php b/src/Infrastructure/Http/Client/ClientFactory.php index 05daf9eb1..cd38470ea 100644 --- a/src/Infrastructure/Http/Client/ClientFactory.php +++ b/src/Infrastructure/Http/Client/ClientFactory.php @@ -6,15 +6,15 @@ use ArtARTs36\MergeRequestLinter\Infrastructure\Contracts\Http\Client; use ArtARTs36\MergeRequestLinter\Infrastructure\Contracts\Http\HttpClientFactory; use ArtARTs36\MergeRequestLinter\Infrastructure\Http\Exceptions\HttpClientTypeNotSupported; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\MetricManager; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\CollectorRegistry; use GuzzleHttp\Client as GuzzleClient; use Psr\Log\LoggerInterface; class ClientFactory implements HttpClientFactory { public function __construct( - private readonly MetricManager $metrics, - private readonly LoggerInterface $logger, + private readonly CollectorRegistry $metrics, + private readonly LoggerInterface $logger, ) { } @@ -23,7 +23,7 @@ public function create(HttpClientConfig $config): Client if ($config->type === HttpClientConfig::TYPE_GUZZLE) { $wrapper = new ClientGuzzleWrapper(new GuzzleClient($config->params), $this->logger); - return new MetricableClient($wrapper, $this->metrics); + return MetricableClient::make($wrapper, $this->metrics); } if ($config->type === HttpClientConfig::TYPE_NULL) { diff --git a/src/Infrastructure/Http/Client/MetricableClient.php b/src/Infrastructure/Http/Client/MetricableClient.php index 5015cb1f0..fed887239 100644 --- a/src/Infrastructure/Http/Client/MetricableClient.php +++ b/src/Infrastructure/Http/Client/MetricableClient.php @@ -4,30 +4,45 @@ use ArtARTs36\MergeRequestLinter\Infrastructure\Contracts\Http\Client; use ArtARTs36\MergeRequestLinter\Shared\DataStructure\Arrayee; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\MetricManager; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\MetricSubject; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\GaugeVector; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\MetricSubject; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\CollectorRegisterer; use ArtARTs36\MergeRequestLinter\Shared\Time\Timer; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -class MetricableClient implements Client +final class MetricableClient implements Client { public function __construct( - private readonly Client $client, - private readonly MetricManager $metrics, + private readonly Client $client, + private readonly GaugeVector $observer, ) { } + public static function make(Client $client, CollectorRegisterer $metrics): self + { + $observer = $metrics->getOrRegister(new GaugeVector(new MetricSubject( + 'http', + 'send_request', + 'Wait of response', + 'Wait of response :host:', + ))); + + return new self($client, $observer); + } + public function sendRequest(RequestInterface $request): ResponseInterface { $timer = Timer::start(); $response = $this->client->sendRequest($request); - $this->metrics->add(new MetricSubject( - 'http_send_request', - sprintf('[HTTP] Wait of response from %s', $request->getUri()->getHost()), - ), $timer->finish()); + $this + ->observer + ->add([ + 'host' => $request->getUri()->getHost(), + ]) + ->set($timer->finish()->seconds); return $response; } @@ -38,12 +53,14 @@ public function sendAsyncRequests(array $requests): array $responses = $this->client->sendAsyncRequests($requests); - $hosts = $this->getHosts($requests)->implode(', '); + $hosts = $this->getHosts($requests)->implode(','); - $this->metrics->add(new MetricSubject( - 'http_send_request', - sprintf('[HTTP] Wait of response from %s for %d async requests', $hosts, count($requests)), - ), $timer->finish()); + $this + ->observer + ->add([ + 'host' => $hosts, + ]) + ->set($timer->finish()->seconds); return $responses; } diff --git a/src/Infrastructure/Http/Exceptions/HttpRequestException.php b/src/Infrastructure/Http/Exceptions/HttpRequestException.php index 3e032b663..18737840a 100644 --- a/src/Infrastructure/Http/Exceptions/HttpRequestException.php +++ b/src/Infrastructure/Http/Exceptions/HttpRequestException.php @@ -24,7 +24,12 @@ public static function create(RequestInterface $request, ResponseInterface $resp return new static( $request, $response, - sprintf('%s returns response with status %d', $request->getUri()->getHost(), $response->getStatusCode()), + sprintf( + '%s returns response with status %d: %s', + $request->getUri()->getHost(), + $response->getStatusCode(), + $response->getBody()->getContents(), + ), ); } diff --git a/src/Infrastructure/Logger/MetricableLogger.php b/src/Infrastructure/Logger/MetricableLogger.php index bd07e8969..d21b44a9c 100644 --- a/src/Infrastructure/Logger/MetricableLogger.php +++ b/src/Infrastructure/Logger/MetricableLogger.php @@ -2,10 +2,9 @@ namespace ArtARTs36\MergeRequestLinter\Infrastructure\Logger; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\Counter; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\IncCounter; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\MetricManager; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\MetricSubject; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\MetricSubject; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\CollectorRegisterer; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\Counter; use Psr\Log\LoggerInterface; use Psr\Log\LoggerTrait; @@ -18,11 +17,11 @@ public function __construct( ) { } - public static function create(MetricManager $manager): self + public static function create(CollectorRegisterer $metrics): self { - $counter = new IncCounter(); + $counter = new Counter(new MetricSubject('logger', 'logs_count', 'Logs count')); - $manager->add(new MetricSubject('logger_logs_count', '[Logger] Logs count'), $counter); + $metrics->register($counter); return new self($counter); } diff --git a/src/Infrastructure/Metrics/MetricStorageFactory.php b/src/Infrastructure/Metrics/MetricStorageFactory.php new file mode 100644 index 000000000..2b5884c58 --- /dev/null +++ b/src/Infrastructure/Metrics/MetricStorageFactory.php @@ -0,0 +1,31 @@ +name === MetricsStorageConfig::NAME_PROMETHEUS_PUSH_GATEWAY) { + return new PushGateway( + new HttpClient($this->httpClient, $config->address), + new Renderer(), + ); + } + + return new NullStorage(); + } +} diff --git a/src/Infrastructure/Metrics/RequestMetricFlusher.php b/src/Infrastructure/Metrics/RequestMetricFlusher.php new file mode 100644 index 000000000..b41fb88d8 --- /dev/null +++ b/src/Infrastructure/Metrics/RequestMetricFlusher.php @@ -0,0 +1,23 @@ +uri, time())); + + $this->storage->commit($transactionId, $this->metrics->describe()->toArray()); + } +} diff --git a/src/Infrastructure/RequestFetcher/CiRequestFetcher.php b/src/Infrastructure/RequestFetcher/CiRequestFetcher.php index f993bd13c..e4639e1fe 100644 --- a/src/Infrastructure/RequestFetcher/CiRequestFetcher.php +++ b/src/Infrastructure/RequestFetcher/CiRequestFetcher.php @@ -8,15 +8,15 @@ use ArtARTs36\MergeRequestLinter\Domain\Request\MergeRequest; use ArtARTs36\MergeRequestLinter\Domain\Request\MergeRequestFetcher; use ArtARTs36\MergeRequestLinter\Infrastructure\Contracts\CI\CiSystemFactory; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\MetricManager; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\MetricSubject; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\StringMetric; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\CounterVector; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\MetricSubject; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\CollectorRegisterer; final readonly class CiRequestFetcher implements MergeRequestFetcher { public function __construct( - private CiSystemFactory $systems, - private MetricManager $metrics, + private CiSystemFactory $systems, + private CollectorRegisterer $metrics, ) { } @@ -24,11 +24,13 @@ public function fetch(): MergeRequest { $ci = $this->systems->createCurrently(); - $this->metrics->add( - new MetricSubject('used_ci_system', '[CI] Used CI System'), - new StringMetric($ci->getName()), + $metric = CounterVector::once( + new MetricSubject('ci', 'used_systems', 'Used CI systems', 'Used CI systems: :ci:'), + ['ci' => $ci->getName()], ); + $this->metrics->register($metric); + try { return $ci->getCurrentlyMergeRequest(); } catch (CurrentlyNotMergeRequestException $e) { diff --git a/src/Infrastructure/Rule/Factories/ConditionRuleFactory.php b/src/Infrastructure/Rule/Factories/ConditionRuleFactory.php index 5dc5872c9..d66f8c96d 100644 --- a/src/Infrastructure/Rule/Factories/ConditionRuleFactory.php +++ b/src/Infrastructure/Rule/Factories/ConditionRuleFactory.php @@ -5,10 +5,9 @@ use ArtARTs36\MergeRequestLinter\Application\Rule\Rules\ConditionRule; use ArtARTs36\MergeRequestLinter\Domain\Rule\Rule; use ArtARTs36\MergeRequestLinter\Infrastructure\Contracts\Condition\OperatorResolver; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\Counter; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\IncCounter; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\MetricManager; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\MetricSubject; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\CounterVector; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\MetricSubject; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\CollectorRegisterer; /** * @phpstan-import-type Conditions from OperatorResolver @@ -17,15 +16,19 @@ class ConditionRuleFactory { public function __construct( private readonly OperatorResolver $operatorResolver, - private readonly Counter $skippedRulesCounter, + private readonly CounterVector $skippedRulesCounter, ) { } - public static function new(OperatorResolver $operatorResolver, MetricManager $metrics): self + public static function new(OperatorResolver $operatorResolver, CollectorRegisterer $metrics): self { - $counter = new IncCounter(); + $counter = new CounterVector(new MetricSubject( + 'linter', + 'skipped_rules', + 'Skipped rules', + )); - $metrics->add(new MetricSubject('linter_skipped_rules', '[Linter] Skipped rules'), $counter); + $metrics->register($counter); return new self($operatorResolver, $counter); } diff --git a/src/Presentation/Console/Application/Application.php b/src/Presentation/Console/Application/Application.php index a5aeb432f..a06a67732 100644 --- a/src/Presentation/Console/Application/Application.php +++ b/src/Presentation/Console/Application/Application.php @@ -2,9 +2,9 @@ namespace ArtARTs36\MergeRequestLinter\Presentation\Console\Application; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\MetricManager; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\MetricProxy; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\MetricSubject; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\GaugeVector; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\MetricSubject; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\CollectorRegistry; use ArtARTs36\MergeRequestLinter\Shared\Time\Timer; use ArtARTs36\MergeRequestLinter\Version; use Symfony\Component\Console\Command\Command; @@ -14,25 +14,37 @@ class Application extends \Symfony\Component\Console\Application { public function __construct( - private readonly MetricManager $metrics, + private readonly GaugeVector $observer, ) { parent::__construct('Merge Request Linter', Version::VERSION); } + public static function make(CollectorRegistry $metrics): self + { + $observer = new GaugeVector(new MetricSubject( + 'console', + 'command_execution_time', + 'Command execution', + )); + + $metrics->register($observer); + + return new self( + $observer, + ); + } + protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) { $timer = Timer::start(); - $this->metrics->add( - new MetricSubject( - sprintf('command_time_execution_%s', $command->getName() ?? 'main'), - sprintf('[Console] Command "%s" execution', $command->getName()), - ), - new MetricProxy(function () use ($timer) { - return $timer->finish(); - }), - ); + $status = parent::doRunCommand($command, $input, $output); + + $this + ->observer + ->add(['command' => $command->getName() ?? 'main']) + ->set($timer->finish()->seconds); - return parent::doRunCommand($command, $input, $output); + return $status; } } diff --git a/src/Presentation/Console/Application/ApplicationFactory.php b/src/Presentation/Console/Application/ApplicationFactory.php index 273db526b..d43396833 100644 --- a/src/Presentation/Console/Application/ApplicationFactory.php +++ b/src/Presentation/Console/Application/ApplicationFactory.php @@ -41,12 +41,13 @@ use ArtARTs36\MergeRequestLinter\Providers\CommentProvider; use ArtARTs36\MergeRequestLinter\Providers\EventDispatcherProvider; use ArtARTs36\MergeRequestLinter\Providers\NotificationsProvider; +use ArtARTs36\MergeRequestLinter\Providers\MetricsProvider; use ArtARTs36\MergeRequestLinter\Providers\RuleProvider; use ArtARTs36\MergeRequestLinter\Providers\ServiceProvider; use ArtARTs36\MergeRequestLinter\Shared\Events\EventManager; use ArtARTs36\MergeRequestLinter\Shared\File\Directory; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Manager\MemoryMetricManager; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\MetricManager; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\MemoryRegistry; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\CollectorRegistry; use ArtARTs36\MergeRequestLinter\Shared\Reflection\TypeResolver\ResolverFactory; use ArtARTs36\MergeRequestLinter\Shared\Time\Clock; use ArtARTs36\MergeRequestLinter\Shared\Time\LocalClock; @@ -61,6 +62,7 @@ class ApplicationFactory NotificationsProvider::class, RuleProvider::class, CommentProvider::class, + MetricsProvider::class, ]; public function __construct( @@ -119,7 +121,7 @@ public function create(OutputInterface $output): Application $this->registerTextRenderer(); - $application = new Application($metrics); + $application = Application::make($metrics); $application->add(new LintCommand($metrics, $events, new LintTaskHandler( $configResolver, @@ -175,7 +177,7 @@ private function registerTextRenderer(): void private function registerHttpClientFactory(): ClientFactory { $factory = new ClientFactory( - $this->container->get(MetricManager::class), + $this->container->get(CollectorRegistry::class), $this->container->get(LoggerInterface::class), ); @@ -185,11 +187,11 @@ private function registerHttpClientFactory(): ClientFactory return $factory; } - private function registerMetricManager(): MetricManager + private function registerMetricManager(): CollectorRegistry { - $metrics = new MemoryMetricManager($this->container->get(ClockInterface::class)); + $metrics = new MemoryRegistry(); - $this->container->set(MetricManager::class, $metrics); + $this->container->set(CollectorRegistry::class, $metrics); return $metrics; } @@ -203,7 +205,7 @@ private function registerFileSystem(): FileSystem return $fs; } - private function createLogger(OutputInterface $output, MetricManager $metricManager): ContextLogger + private function createLogger(OutputInterface $output, CollectorRegistry $metricManager): ContextLogger { $loggers = [ MetricableLogger::create($metricManager), diff --git a/src/Presentation/Console/Command/LintCommand.php b/src/Presentation/Console/Command/LintCommand.php index d7c99a127..07de63b2c 100644 --- a/src/Presentation/Console/Command/LintCommand.php +++ b/src/Presentation/Console/Command/LintCommand.php @@ -18,8 +18,7 @@ use ArtARTs36\MergeRequestLinter\Shared\DataStructure\Arrayee; use ArtARTs36\MergeRequestLinter\Shared\Events\EventManager; use ArtARTs36\MergeRequestLinter\Shared\File\Bytes; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\MetricManager; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\Record; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\CollectorRegistry; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -35,10 +34,10 @@ final class LintCommand extends Command protected static $defaultDescription = 'Run lint to current merge request'; public function __construct( - protected MetricManager $metrics, - protected EventManager $events, + protected CollectorRegistry $metrics, + protected EventManager $events, private readonly LintTaskHandler $handler, - protected readonly NotePrinter $notePrinter = new NotePrinter(), + protected readonly NotePrinter $notePrinter = new NotePrinter(), protected readonly MetricPrinter $metricPrinter = new MetricPrinter(), ) { parent::__construct(); @@ -115,11 +114,28 @@ private function printMetrics(StyleInterface $style, LintResult $result, bool $f ]); if ($fullMetrics) { - $metrics = $metrics->merge( - $this->metrics->describe()->mapToArray( - static fn (Record $record) => new Metric($record->subject->name, $record->getValue()), - ) - ); + $readMetrics = []; + + foreach ($this->metrics->describe() as $collector) { + if ($collector->getSubject()->key === 'rule_lint_state') { + continue; + } + + foreach ($collector->getSamples() as $sample) { + $metric = new Metric( + $collector->getSubject()->readableTitle($sample->labels), + "$sample->value", + ); + + if ($collector->getSubject()->category === 'linter') { + array_unshift($readMetrics, $metric); + } else { + $readMetrics[] = $metric; + } + } + } + + $metrics = $metrics->merge($readMetrics); } $this->metricPrinter->print(new SymfonyTablePrinter($style), $metrics); diff --git a/src/Providers/MetricsProvider.php b/src/Providers/MetricsProvider.php new file mode 100644 index 000000000..cc998ffa7 --- /dev/null +++ b/src/Providers/MetricsProvider.php @@ -0,0 +1,65 @@ +container + ->get(EventManager::class) + ->listen(ConfigResolvedEvent::class, new CallbackListener('register metric storage', function (ConfigResolvedEvent $event) { + $this->container->bind(MetricStorage::class, static function (ContainerInterface $container) use ($event) { + $httpClient = $container->get(ClientFactory::class)->create($event->config->config->httpClient); + + return (new MetricStorageFactory($httpClient))->create($event->config->config->metrics->storage); + }); + })); + + $this + ->container + ->get(EventManager::class) + ->listen(LintFinishedEvent::class, new CallbackListener('metrics_flush', function (LintFinishedEvent $event) { + $flusher = new RequestMetricFlusher( + $this->container->get(CollectorRegistry::class), + $this->container->get(MetricStorage::class), + ); + + $flusher->flush($event->request); + })); + + $this->container->bind(RuleLintStateMetricHandler::class, static function (ContainerInterface $container) { + return RuleLintStateMetricHandler::make($container->get(CollectorRegistry::class)); + }); + + $this + ->container + ->get(EventManager::class) + ->listen(RuleWasSuccessfulEvent::class, new CallbackListener('rule_lint_successful_state_metric', function (RuleWasSuccessfulEvent $event) { + $this->container->get(RuleLintStateMetricHandler::class)->handle($event); + })); + + $this + ->container + ->get(EventManager::class) + ->listen(RuleWasFailedEvent::class, new CallbackListener('rule_lint_failed_state_metric', function (RuleWasFailedEvent $event) { + $this->container->get(RuleLintStateMetricHandler::class)->handle($event); + })); + } +} diff --git a/src/Shared/DataStructure/ArrayMap.php b/src/Shared/DataStructure/ArrayMap.php index 211ad033a..af7dfba14 100644 --- a/src/Shared/DataStructure/ArrayMap.php +++ b/src/Shared/DataStructure/ArrayMap.php @@ -149,6 +149,21 @@ public function toArray(): array return $this->items; } + /** + * @param callable(K, V): mixed $mapper + * @return array + */ + public function mapToArray(callable $mapper): array + { + $items = []; + + foreach ($this->items as $key => $value) { + $items[] = $mapper($key, $value); + } + + return $items; + } + public function __debugInfo(): array { return [ diff --git a/src/Shared/Metrics/Collector/AbstractCollector.php b/src/Shared/Metrics/Collector/AbstractCollector.php new file mode 100644 index 000000000..4ae76f316 --- /dev/null +++ b/src/Shared/Metrics/Collector/AbstractCollector.php @@ -0,0 +1,23 @@ +subject; + } + + public function getFirstSampleValue(): null|string|int|float + { + $samples = $this->getSamples(); + + return isset($samples[0]) ? $samples[0]->value : null; + } +} diff --git a/src/Shared/Metrics/Collector/AbstractVector.php b/src/Shared/Metrics/Collector/AbstractVector.php new file mode 100644 index 000000000..c7cb35ce7 --- /dev/null +++ b/src/Shared/Metrics/Collector/AbstractVector.php @@ -0,0 +1,45 @@ + */ + private array $collectors = []; + + /** + * @param callable(array $labels): C $factory + * @param array $labels + * @return C + */ + final protected function attach(callable $factory, array $labels): Collector + { + $labelsHash = LabelsHash::hash($labels); + + if (array_key_exists($labelsHash, $this->collectors)) { + return $this->collectors[$labelsHash]; + } + + $collector = $factory($labels); + + $this->collectors[$labelsHash] = $collector; + + return $collector; + } + + public function getSamples(): array + { + $samples = []; + + foreach ($this->collectors as $collector) { + foreach ($collector->getSamples() as $sample) { + $samples[] = $sample; + } + } + + return $samples; + } +} diff --git a/src/Shared/Metrics/Collector/Collector.php b/src/Shared/Metrics/Collector/Collector.php new file mode 100644 index 000000000..139f84b20 --- /dev/null +++ b/src/Shared/Metrics/Collector/Collector.php @@ -0,0 +1,25 @@ + + */ + public function getSamples(): array; +} diff --git a/src/Shared/Metrics/Collector/Counter.php b/src/Shared/Metrics/Collector/Counter.php new file mode 100644 index 000000000..9e118436e --- /dev/null +++ b/src/Shared/Metrics/Collector/Counter.php @@ -0,0 +1,37 @@ + $labels + */ + public function __construct( + MetricSubject $subject, + array $labels = [], + private int $value = 0, + ) { + parent::__construct($subject, $labels); + } + + public function inc(int $val = 1): void + { + $this->value += $val; + } + + public function getSamples(): array + { + return [ + new Sample($this->value, $this->labels), + ]; + } + + /** + * @codeCoverageIgnore + */ + public function getMetricType(): MetricType + { + return MetricType::Counter; + } +} diff --git a/src/Shared/Metrics/Collector/CounterVector.php b/src/Shared/Metrics/Collector/CounterVector.php new file mode 100644 index 000000000..d85529948 --- /dev/null +++ b/src/Shared/Metrics/Collector/CounterVector.php @@ -0,0 +1,44 @@ + + */ +final class CounterVector extends AbstractVector +{ + public static function null(): self + { + return new CounterVector(new MetricSubject('', '', '')); + } + + /** + * @param array $labels + */ + public static function once(MetricSubject $subject, array $labels): self + { + $vector = new CounterVector($subject); + + $vector->add($labels)->inc(); + + return $vector; + } + + /** + * @param array $labels + */ + public function add(array $labels): Counter + { + return $this->attach(function (array $labels) { + return new Counter($this->getSubject(), $labels); + }, $labels); + } + + /** + * @codeCoverageIgnore + */ + public function getMetricType(): MetricType + { + return MetricType::Counter; + } +} diff --git a/src/Shared/Metrics/Collector/Gauge.php b/src/Shared/Metrics/Collector/Gauge.php new file mode 100644 index 000000000..55cf5a0c3 --- /dev/null +++ b/src/Shared/Metrics/Collector/Gauge.php @@ -0,0 +1,34 @@ +value = $value; + } + + public function getSamples(): array + { + return [ + new Sample($this->value, $this->labels), + ]; + } + + /** + * @codeCoverageIgnore + */ + public function getMetricType(): MetricType + { + return MetricType::Gauge; + } +} diff --git a/src/Shared/Metrics/Collector/GaugeVector.php b/src/Shared/Metrics/Collector/GaugeVector.php new file mode 100644 index 000000000..2a9cdc6b2 --- /dev/null +++ b/src/Shared/Metrics/Collector/GaugeVector.php @@ -0,0 +1,27 @@ + + */ +final class GaugeVector extends AbstractVector +{ + /** + * @param array $labels + */ + public function add(array $labels): Gauge + { + return $this->attach(function (array $labels) { + return new Gauge($this->getSubject(), $labels); + }, $labels); + } + + /** + * @codeCoverageIgnore + */ + public function getMetricType(): MetricType + { + return MetricType::Gauge; + } +} diff --git a/src/Shared/Metrics/Collector/LabeledCollector.php b/src/Shared/Metrics/Collector/LabeledCollector.php new file mode 100644 index 000000000..126877ede --- /dev/null +++ b/src/Shared/Metrics/Collector/LabeledCollector.php @@ -0,0 +1,16 @@ + $labels + */ + public function __construct( + MetricSubject $subject, + protected readonly array $labels, + ) { + parent::__construct($subject); + } +} diff --git a/src/Shared/Metrics/Collector/LabelsHash.php b/src/Shared/Metrics/Collector/LabelsHash.php new file mode 100644 index 000000000..c2038f0ad --- /dev/null +++ b/src/Shared/Metrics/Collector/LabelsHash.php @@ -0,0 +1,20 @@ + $labels + */ + public static function hash(array $labels): string + { + $hash = ''; + + foreach ($labels as $key => $value) { + $hash .= $key . '-' . $value; + } + + return $hash; + } +} diff --git a/src/Shared/Metrics/Collector/MetricSubject.php b/src/Shared/Metrics/Collector/MetricSubject.php new file mode 100644 index 000000000..06a5207e0 --- /dev/null +++ b/src/Shared/Metrics/Collector/MetricSubject.php @@ -0,0 +1,48 @@ +category, $this->key); + } + + /** + * @param array $labels + */ + public function readableTitle(array $labels): string + { + if (Str::isEmpty($this->placeholder)) { + return sprintf('[%s] %s', Str::upFirstSymbol($this->category), $this->title); + } + + $replacesKeys = []; + $replacesValues = []; + + foreach ($labels as $key => $value) { + $replacesKeys[] = ':' . $key . ':'; + $replacesValues[] = $value; + } + + return sprintf( + '[%s] %s', + Str::upFirstSymbol($this->category), + str_replace($replacesKeys, $replacesValues, $this->placeholder), + ); + } +} diff --git a/src/Shared/Metrics/Collector/MetricType.php b/src/Shared/Metrics/Collector/MetricType.php new file mode 100644 index 000000000..a4f4aea0e --- /dev/null +++ b/src/Shared/Metrics/Collector/MetricType.php @@ -0,0 +1,12 @@ + $labels + */ + public function __construct( + public readonly string|int|float $value, + public readonly array $labels, + ) { + } +} diff --git a/src/Shared/Metrics/Manager/MemoryMetricManager.php b/src/Shared/Metrics/Manager/MemoryMetricManager.php deleted file mode 100644 index 1ecba7ce5..000000000 --- a/src/Shared/Metrics/Manager/MemoryMetricManager.php +++ /dev/null @@ -1,39 +0,0 @@ - - */ - private array $records = []; - - public function __construct( - private readonly ClockInterface $clock, - ) { - } - - public function add(MetricSubject $subject, Metric $value): self - { - $this->records[] = new Record( - $subject, - $value, - $this->clock->now(), - ); - - return $this; - } - - public function describe(): Arrayee - { - return new Arrayee($this->records); - } -} diff --git a/src/Shared/Metrics/Manager/NullMetricManager.php b/src/Shared/Metrics/Manager/NullMetricManager.php deleted file mode 100644 index efa63762a..000000000 --- a/src/Shared/Metrics/Manager/NullMetricManager.php +++ /dev/null @@ -1,24 +0,0 @@ - + */ + public function describe(): Map; +} diff --git a/src/Shared/Metrics/Registry/MemoryRegistry.php b/src/Shared/Metrics/Registry/MemoryRegistry.php new file mode 100644 index 000000000..81bee75ba --- /dev/null +++ b/src/Shared/Metrics/Registry/MemoryRegistry.php @@ -0,0 +1,56 @@ + + */ + private array $collectors = []; + + public function getOrRegister(Collector $collector): Collector + { + $key = $collector->getSubject()->identity(); + + if (array_key_exists($key, $this->collectors)) { + if (get_class($this->collectors[$key]) !== get_class($collector)) { + throw new CollectorAlreadyRegisteredException(sprintf( + 'Already registered collector "%s" with type "%s". Expected type: %s', + $key, + $this->collectors[$key]::class, + $collector::class, + )); + } + + return $this->collectors[$key]; + } + + $this->register($collector); + + return $collector; + } + + public function register(Collector $collector): void + { + $key = $collector->getSubject()->identity(); + + if (array_key_exists($key, $this->collectors)) { + throw new CollectorAlreadyRegisteredException(sprintf( + 'Collector for metric with key "%s" already registered', + $key, + )); + } + + $this->collectors[$key] = $collector; + } + + public function describe(): Map + { + return new ArrayMap($this->collectors); + } +} diff --git a/src/Shared/Metrics/Registry/NullRegistry.php b/src/Shared/Metrics/Registry/NullRegistry.php new file mode 100644 index 000000000..2ee238925 --- /dev/null +++ b/src/Shared/Metrics/Registry/NullRegistry.php @@ -0,0 +1,28 @@ + $collectors + */ + public function commit(string $id, array $collectors): void; +} diff --git a/src/Shared/Metrics/Storage/NullStorage.php b/src/Shared/Metrics/Storage/NullStorage.php new file mode 100644 index 000000000..629e29e9d --- /dev/null +++ b/src/Shared/Metrics/Storage/NullStorage.php @@ -0,0 +1,14 @@ +address, $job); + + $this->http->sendRequest(new Request('POST', $url, body: $data)); + } +} diff --git a/src/Shared/Metrics/Storage/PrometheusPushGateway/PushGateway.php b/src/Shared/Metrics/Storage/PrometheusPushGateway/PushGateway.php new file mode 100644 index 000000000..7f9e90d2c --- /dev/null +++ b/src/Shared/Metrics/Storage/PrometheusPushGateway/PushGateway.php @@ -0,0 +1,21 @@ +renderer->render($collectors); + + $this->client->replace($id, $data); + } +} diff --git a/src/Shared/Metrics/Storage/PrometheusPushGateway/Renderer.php b/src/Shared/Metrics/Storage/PrometheusPushGateway/Renderer.php new file mode 100644 index 000000000..670bfe26c --- /dev/null +++ b/src/Shared/Metrics/Storage/PrometheusPushGateway/Renderer.php @@ -0,0 +1,66 @@ + $collectors + */ + public function render(iterable $collectors): string + { + $content = []; + + foreach ($collectors as $collector) { + $samples = $collector->getSamples(); + + if (count($samples) === 0) { + continue; + } + + $key = $collector->getSubject()->identity(); + + $content[] = "# HELP $key {$collector->getSubject()->title}"; + $content[] = "# TYPE $key {$collector->getMetricType()->value}"; + $content[] = "\n"; + + foreach ($samples as $sample) { + $labelsString = $this->collectLabels($sample); + + $content[] = "$key$labelsString $sample->value"; + } + + $content[] = "\n"; + } + + return Str::implode("\n", $content); + } + + private function collectLabels(Sample $metric): string + { + if (count($metric->labels) === 0) { + return ''; + } + + $labelsString = '{'; + $labels = $metric->labels; + + foreach ($labels as $labelKey => $labelValue) { + $labelsString .= sprintf( + '%s=%s', + $labelKey, + is_numeric($labelValue) ? $labelValue : ('"'. $labelValue .'"'), + ); + + if (next($labels) !== false) { + $labelsString .= ','; + } + } + + return $labelsString . '}'; + } +} diff --git a/src/Shared/Metrics/Value/Counter.php b/src/Shared/Metrics/Value/Counter.php deleted file mode 100644 index 423144aca..000000000 --- a/src/Shared/Metrics/Value/Counter.php +++ /dev/null @@ -1,14 +0,0 @@ - $countable - */ - public static function create(\Countable|array $countable): self - { - return new self(count($countable)); - } - - public function inc(): void - { - ++$this->count; - } - - public function getMetricValue(): string - { - return "$this->count"; - } -} diff --git a/src/Shared/Metrics/Value/Metric.php b/src/Shared/Metrics/Value/Metric.php deleted file mode 100644 index 507fe83fa..000000000 --- a/src/Shared/Metrics/Value/Metric.php +++ /dev/null @@ -1,14 +0,0 @@ - - */ - public function describe(): Arrayee; -} diff --git a/src/Shared/Metrics/Value/MetricProxy.php b/src/Shared/Metrics/Value/MetricProxy.php deleted file mode 100644 index ce7419843..000000000 --- a/src/Shared/Metrics/Value/MetricProxy.php +++ /dev/null @@ -1,30 +0,0 @@ -retrieve()->getMetricValue(); - } - - private function retrieve(): Metric - { - if ($this->metric === null) { - $this->metric = ($this->callback)(); - } - - return $this->metric; - } -} diff --git a/src/Shared/Metrics/Value/MetricSubject.php b/src/Shared/Metrics/Value/MetricSubject.php deleted file mode 100644 index ab6148d09..000000000 --- a/src/Shared/Metrics/Value/MetricSubject.php +++ /dev/null @@ -1,15 +0,0 @@ -value->getMetricValue(); - } -} diff --git a/src/Shared/Metrics/Value/StringMetric.php b/src/Shared/Metrics/Value/StringMetric.php deleted file mode 100644 index 326277f33..000000000 --- a/src/Shared/Metrics/Value/StringMetric.php +++ /dev/null @@ -1,16 +0,0 @@ -value; - } -} diff --git a/src/Shared/Time/Duration.php b/src/Shared/Time/Duration.php index 17ff85fac..457059ce3 100644 --- a/src/Shared/Time/Duration.php +++ b/src/Shared/Time/Duration.php @@ -2,9 +2,7 @@ namespace ArtARTs36\MergeRequestLinter\Shared\Time; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\Metric; - -readonly class Duration implements Metric +readonly class Duration { public function __construct( public float $seconds, @@ -13,11 +11,6 @@ public function __construct( public function __toString(): string { - return "$this->seconds" . 's'; - } - - public function getMetricValue(): string - { - return (string) $this; + return "{$this->seconds}s"; } } diff --git a/tests/Feature/Console/Command/LintCommandTest.php b/tests/Feature/Console/Command/LintCommandTest.php index 3a56a2118..6d4aaac53 100644 --- a/tests/Feature/Console/Command/LintCommandTest.php +++ b/tests/Feature/Console/Command/LintCommandTest.php @@ -5,7 +5,7 @@ use ArtARTs36\MergeRequestLinter\Application\Linter\LinterFactory; use ArtARTs36\MergeRequestLinter\Application\Linter\TaskHandlers\LintTaskHandler; use ArtARTs36\MergeRequestLinter\Presentation\Console\Command\LintCommand; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Manager\NullMetricManager; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\NullRegistry; use ArtARTs36\MergeRequestLinter\Tests\Mocks\MockCi; use ArtARTs36\MergeRequestLinter\Tests\Mocks\MockCiSystemFactory; use ArtARTs36\MergeRequestLinter\Tests\Mocks\MockConfigResolver; @@ -27,7 +27,7 @@ public function testExecuteAllGood(): void { $tester = new CommandTester( new LintCommand( - $metrics = new NullMetricManager(), + $metrics = new NullRegistry(), $events = new NullEventDispatcher(), new LintTaskHandler( new MockConfigResolver($this->makeConfig([new SuccessRule()])), diff --git a/tests/Mocks/MockCollector.php b/tests/Mocks/MockCollector.php new file mode 100644 index 000000000..c34c9da51 --- /dev/null +++ b/tests/Mocks/MockCollector.php @@ -0,0 +1,34 @@ +metricSubject = $metricSubject ?? new MetricSubject('mock', 'collector', 'Mock title'); + } + + public function getSubject(): MetricSubject + { + return $this->metricSubject; + } + + public function getMetricType(): MetricType + { + // TODO: Implement getMetricType() method. + } + + public function getSamples(): array + { + return $this->samples; + } +} diff --git a/tests/Mocks/MockRunnerFactory.php b/tests/Mocks/MockRunnerFactory.php index f7512ae92..f3eec616a 100644 --- a/tests/Mocks/MockRunnerFactory.php +++ b/tests/Mocks/MockRunnerFactory.php @@ -8,7 +8,7 @@ use ArtARTs36\MergeRequestLinter\Infrastructure\Contracts\CI\CiSystemFactory; use ArtARTs36\MergeRequestLinter\Infrastructure\Contracts\Linter\LinterRunnerFactory; use ArtARTs36\MergeRequestLinter\Infrastructure\RequestFetcher\CiRequestFetcher; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Manager\NullMetricManager; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\NullRegistry; final class MockRunnerFactory implements LinterRunnerFactory { @@ -19,6 +19,6 @@ public function __construct(private CiSystemFactory $ciSystemFactory) public function create(Config $config): LinterRunner { - return new Runner(new CiRequestFetcher($this->ciSystemFactory, new NullMetricManager())); + return new Runner(new CiRequestFetcher($this->ciSystemFactory, new NullRegistry())); } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 3fb1520ec..571a51b03 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -7,6 +7,8 @@ use ArtARTs36\MergeRequestLinter\Domain\Configuration\Config; use ArtARTs36\MergeRequestLinter\Domain\Configuration\HttpClientConfig; use ArtARTs36\MergeRequestLinter\Domain\Configuration\LinterConfig; +use ArtARTs36\MergeRequestLinter\Domain\Configuration\MetricsConfig; +use ArtARTs36\MergeRequestLinter\Domain\Configuration\MetricsStorageConfig; use ArtARTs36\MergeRequestLinter\Domain\Configuration\NotificationsConfig; use ArtARTs36\MergeRequestLinter\Domain\Linter\LinterOptions; use ArtARTs36\MergeRequestLinter\Domain\Linter\LintResult; @@ -47,6 +49,9 @@ protected function makeConfig(array $rules): Config CommentsPostStrategy::Null, [], ), + new MetricsConfig( + new MetricsStorageConfig(MetricsStorageConfig::NAME_NULL, 'null'), + ), ); } diff --git a/tests/Unit/Application/Linter/LinterFactoryTest.php b/tests/Unit/Application/Linter/LinterFactoryTest.php index a1c19fa7a..568e4a4d6 100644 --- a/tests/Unit/Application/Linter/LinterFactoryTest.php +++ b/tests/Unit/Application/Linter/LinterFactoryTest.php @@ -4,16 +4,7 @@ use ArtARTs36\MergeRequestLinter\Application\Linter\Linter; use ArtARTs36\MergeRequestLinter\Application\Linter\LinterFactory; -use ArtARTs36\MergeRequestLinter\Domain\Configuration\CommentsConfig; -use ArtARTs36\MergeRequestLinter\Domain\Configuration\CommentsPostStrategy; -use ArtARTs36\MergeRequestLinter\Domain\Configuration\Config; -use ArtARTs36\MergeRequestLinter\Domain\Configuration\HttpClientConfig; -use ArtARTs36\MergeRequestLinter\Domain\Configuration\LinterConfig; -use ArtARTs36\MergeRequestLinter\Domain\Configuration\NotificationsConfig; -use ArtARTs36\MergeRequestLinter\Domain\Linter\LinterOptions; -use ArtARTs36\MergeRequestLinter\Domain\Rule\Rules; -use ArtARTs36\MergeRequestLinter\Shared\DataStructure\ArrayMap; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Manager\NullMetricManager; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\NullRegistry; use ArtARTs36\MergeRequestLinter\Tests\Mocks\NullEventDispatcher; use ArtARTs36\MergeRequestLinter\Tests\TestCase; @@ -27,17 +18,10 @@ public function testCreate(): void { $factory = new LinterFactory( new NullEventDispatcher(), - new NullMetricManager(), + new NullRegistry(), ); - $gotLinter = $factory->create(new Config( - new Rules([]), - new ArrayMap([]), - new HttpClientConfig(HttpClientConfig::TYPE_NULL, []), - new NotificationsConfig(new ArrayMap([]), new ArrayMap([])), - new LinterConfig(new LinterOptions()), - new CommentsConfig(CommentsPostStrategy::New, []), - )); + $gotLinter = $factory->create($this->makeConfig([])); self::assertInstanceOf(Linter::class, $gotLinter); } diff --git a/tests/Unit/Application/Linter/LinterTest.php b/tests/Unit/Application/Linter/LinterTest.php index 11900ec34..830412560 100644 --- a/tests/Unit/Application/Linter/LinterTest.php +++ b/tests/Unit/Application/Linter/LinterTest.php @@ -17,7 +17,7 @@ use ArtARTs36\MergeRequestLinter\Domain\Rule\RuleDefinition; use ArtARTs36\MergeRequestLinter\Domain\Rule\Rules; use ArtARTs36\MergeRequestLinter\Shared\DataStructure\Arrayee; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Manager\NullMetricManager; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\NullRegistry; use ArtARTs36\MergeRequestLinter\Shared\Time\Duration; use ArtARTs36\MergeRequestLinter\Tests\Mocks\EmptyNote; use ArtARTs36\MergeRequestLinter\Tests\Mocks\ExceptionRule; @@ -45,7 +45,7 @@ public function testRunOnException(): void ]), new LinterOptions(false), new NullEventDispatcher(), - new NullMetricManager(), + new NullRegistry(), ); $result = $linter->run($this->makeMergeRequest()); @@ -167,7 +167,7 @@ public function testRun(Rules $rules, LinterOptions $options, array $expectedEve $rules, $options, $eventDispatcher, - new NullMetricManager(), + new NullRegistry(), ); $result = $linter->run($this->makeMergeRequest()); @@ -209,7 +209,7 @@ public function testRunOnEvaluatorCrashedException(): void ]), new LinterOptions(), $eventDispatcher, - new NullMetricManager(), + new NullRegistry(), ); $gotLintResult = $linter->run($this->makeMergeRequest()); diff --git a/tests/Unit/Application/Linter/Runner/RunnerFactoryTest.php b/tests/Unit/Application/Linter/Runner/RunnerFactoryTest.php index c19eec6ed..6b6d53ae1 100644 --- a/tests/Unit/Application/Linter/Runner/RunnerFactoryTest.php +++ b/tests/Unit/Application/Linter/Runner/RunnerFactoryTest.php @@ -8,7 +8,7 @@ use ArtARTs36\MergeRequestLinter\Infrastructure\Environment\Environments\NullEnvironment; use ArtARTs36\MergeRequestLinter\Infrastructure\Http\Client\ClientFactory; use ArtARTs36\MergeRequestLinter\Shared\DataStructure\ArrayMap; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Manager\NullMetricManager; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\NullRegistry; use ArtARTs36\MergeRequestLinter\Shared\Time\LocalClock; use ArtARTs36\MergeRequestLinter\Tests\TestCase; @@ -25,8 +25,8 @@ public function testCreate(): void new NullEnvironment(), new ArrayMap([]), LoggerFactory::null(), - new NullMetricManager(), - new ClientFactory(new NullMetricManager(), LoggerFactory::null()), + new NullRegistry(), + new ClientFactory(new NullRegistry(), LoggerFactory::null()), LocalClock::utc(), ); diff --git a/tests/Unit/Application/Linter/Runner/RunnerTest.php b/tests/Unit/Application/Linter/Runner/RunnerTest.php index 568023432..ffb09fe6d 100644 --- a/tests/Unit/Application/Linter/Runner/RunnerTest.php +++ b/tests/Unit/Application/Linter/Runner/RunnerTest.php @@ -13,7 +13,7 @@ use ArtARTs36\MergeRequestLinter\Infrastructure\Ci\Exceptions\CiNotSupported; use ArtARTs36\MergeRequestLinter\Infrastructure\Contracts\CI\CiSystemFactory; use ArtARTs36\MergeRequestLinter\Infrastructure\RequestFetcher\CiRequestFetcher; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Manager\NullMetricManager; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\NullRegistry; use ArtARTs36\MergeRequestLinter\Tests\Mocks\MockCi; use ArtARTs36\MergeRequestLinter\Tests\Mocks\NullEventDispatcher; use ArtARTs36\MergeRequestLinter\Tests\Mocks\SuccessRule; @@ -32,7 +32,7 @@ public function createCurrently(): CiSystem { throw new CiNotSupported(); } - }, new NullMetricManager())); + }, new NullRegistry())); $result = $runner->run($this->createLinter()); @@ -51,7 +51,7 @@ public function createCurrently(): CiSystem { return new MockCi(); } - }, new NullMetricManager())); + }, new NullRegistry())); $result = $runner->run($this->createLinter()); @@ -73,7 +73,7 @@ public function createCurrently(): CiSystem { throw new \Exception(); } - }, new NullMetricManager())); + }, new NullRegistry())); $result = $runner->run($this->createLinter()); @@ -100,7 +100,7 @@ public function createCurrently(): CiSystem { return new MockCi($this->request); } - }, new NullMetricManager())); + }, new NullRegistry())); $result = $runner->run($this->createLinter([ new SuccessRule(), @@ -115,7 +115,7 @@ private function createLinter(array $rules = []): Linter new Rules($rules), new LinterOptions(false), new NullEventDispatcher(), - new NullMetricManager(), + new NullRegistry(), ); } } diff --git a/tests/Unit/Application/Linter/TaskHandlers/LintTaskHandlerTest.php b/tests/Unit/Application/Linter/TaskHandlers/LintTaskHandlerTest.php index 0adc64e9a..7f271c5a4 100644 --- a/tests/Unit/Application/Linter/TaskHandlers/LintTaskHandlerTest.php +++ b/tests/Unit/Application/Linter/TaskHandlers/LintTaskHandlerTest.php @@ -6,18 +6,9 @@ use ArtARTs36\MergeRequestLinter\Application\Linter\LinterFactory; use ArtARTs36\MergeRequestLinter\Application\Linter\TaskHandlers\LintTaskHandler; use ArtARTs36\MergeRequestLinter\Application\Linter\Tasks\LintTask; -use ArtARTs36\MergeRequestLinter\Domain\Configuration\CommentsConfig; -use ArtARTs36\MergeRequestLinter\Domain\Configuration\CommentsPostStrategy; -use ArtARTs36\MergeRequestLinter\Domain\Configuration\Config; -use ArtARTs36\MergeRequestLinter\Domain\Configuration\HttpClientConfig; -use ArtARTs36\MergeRequestLinter\Domain\Configuration\LinterConfig; -use ArtARTs36\MergeRequestLinter\Domain\Configuration\NotificationsConfig; -use ArtARTs36\MergeRequestLinter\Domain\Linter\LinterOptions; use ArtARTs36\MergeRequestLinter\Domain\Linter\LintState; -use ArtARTs36\MergeRequestLinter\Domain\Rule\Rules; use ArtARTs36\MergeRequestLinter\Infrastructure\Configuration\Resolver\ResolvedConfig; use ArtARTs36\MergeRequestLinter\Infrastructure\Contracts\Configuration\ConfigResolver; -use ArtARTs36\MergeRequestLinter\Shared\DataStructure\ArrayMap; use ArtARTs36\MergeRequestLinter\Tests\Mocks\MockCi; use ArtARTs36\MergeRequestLinter\Tests\Mocks\MockCiSystemFactory; use ArtARTs36\MergeRequestLinter\Tests\Mocks\MockEventDispatcher; @@ -34,14 +25,7 @@ public function testHandle(): void ->expects(new InvokedCount(1)) ->method('resolve') ->willReturn($config = new ResolvedConfig( - new Config( - new Rules([]), - new ArrayMap([]), - new HttpClientConfig('', []), - new NotificationsConfig(new ArrayMap([]), new ArrayMap([])), - new LinterConfig(new LinterOptions()), - new CommentsConfig(CommentsPostStrategy::New, []), - ), + $this->makeConfig([]), '', )); diff --git a/tests/Unit/Application/Rule/Rules/ConditionRuleTest.php b/tests/Unit/Application/Rule/Rules/ConditionRuleTest.php index e513f2a34..ffc008ce3 100644 --- a/tests/Unit/Application/Rule/Rules/ConditionRuleTest.php +++ b/tests/Unit/Application/Rule/Rules/ConditionRuleTest.php @@ -4,7 +4,7 @@ use ArtARTs36\MergeRequestLinter\Application\Rule\Rules\ConditionRule; use ArtARTs36\MergeRequestLinter\Domain\Condition\ConditionOperator; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\IncCounter; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\CounterVector; use ArtARTs36\MergeRequestLinter\Tests\Mocks\MockConditionOperator; use ArtARTs36\MergeRequestLinter\Tests\Mocks\SuccessRule; use ArtARTs36\MergeRequestLinter\Tests\TestCase; @@ -35,12 +35,12 @@ public function testLintIncSkipRulesMetric(ConditionOperator $operator, int $exp $rule = new ConditionRule( new SuccessRule(), $operator, - $counter = new IncCounter(), + $counter = CounterVector::null(), ); $rule->lint($this->makeMergeRequest()); - self::assertEquals($expected, $counter->getMetricValue()); + self::assertEquals($expected, $counter->getFirstSampleValue()); } /** @@ -52,6 +52,7 @@ public function testGetDefinition(): void $rule = new ConditionRule( $subRule = new SuccessRule(), MockConditionOperator::true(), + CounterVector::null(), ); self::assertEquals($subRule->getDefinition(), $rule->getDefinition()); @@ -66,6 +67,7 @@ public function testGetName(): void $rule = new ConditionRule( $subRule = new SuccessRule(), MockConditionOperator::true(), + CounterVector::null(), ); self::assertEquals($subRule->getName(), $rule->getName()); @@ -80,6 +82,7 @@ public function testGetDecoratedRules(): void $rule = new ConditionRule( $subRule = new SuccessRule(), MockConditionOperator::true(), + CounterVector::null(), ); self::assertEquals([$subRule], $rule->getDecoratedRules()); diff --git a/tests/Unit/Infrastructure/Http/ClientFactoryTest.php b/tests/Unit/Infrastructure/Http/ClientFactoryTest.php index 378568ce6..514c5f0a7 100644 --- a/tests/Unit/Infrastructure/Http/ClientFactoryTest.php +++ b/tests/Unit/Infrastructure/Http/ClientFactoryTest.php @@ -7,7 +7,7 @@ use ArtARTs36\MergeRequestLinter\Infrastructure\Http\Client\ClientFactory; use ArtARTs36\MergeRequestLinter\Infrastructure\Http\Client\MetricableClient; use ArtARTs36\MergeRequestLinter\Infrastructure\Http\Client\NullClient; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Manager\NullMetricManager; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\NullRegistry; use ArtARTs36\MergeRequestLinter\Tests\TestCase; final class ClientFactoryTest extends TestCase @@ -28,7 +28,7 @@ public function providerForTestCreate(): array */ public function testCreate(HttpClientConfig $config, string $expectedClass): void { - $factory = new ClientFactory(new NullMetricManager(), LoggerFactory::null()); + $factory = new ClientFactory(new NullRegistry(), LoggerFactory::null()); self::assertInstanceOf($expectedClass, $factory->create($config)); } @@ -42,7 +42,7 @@ public function testCreateOnNotSupported(): void { self::expectExceptionMessage('HTTP Client with type "non-exists-client-type" not supported'); - $factory = new ClientFactory(new NullMetricManager(), LoggerFactory::null()); + $factory = new ClientFactory(new NullRegistry(), LoggerFactory::null()); $factory->create(new HttpClientConfig('non-exists-client-type', [])); } diff --git a/tests/Unit/Infrastructure/Http/MetricableClientTest.php b/tests/Unit/Infrastructure/Http/MetricableClientTest.php index b700c70a0..64a3c94a3 100644 --- a/tests/Unit/Infrastructure/Http/MetricableClientTest.php +++ b/tests/Unit/Infrastructure/Http/MetricableClientTest.php @@ -4,8 +4,7 @@ use ArtARTs36\MergeRequestLinter\Infrastructure\Http\Client\MetricableClient; use ArtARTs36\MergeRequestLinter\Infrastructure\Http\Client\NullClient; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Manager\MemoryMetricManager; -use ArtARTs36\MergeRequestLinter\Shared\Time\LocalClock; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\MemoryRegistry; use ArtARTs36\MergeRequestLinter\Tests\TestCase; use GuzzleHttp\Psr7\Request; @@ -13,30 +12,32 @@ final class MetricableClientTest extends TestCase { /** * @covers \ArtARTs36\MergeRequestLinter\Infrastructure\Http\Client\MetricableClient::sendRequest + * @covers \ArtARTs36\MergeRequestLinter\Infrastructure\Http\Client\MetricableClient::make * @covers \ArtARTs36\MergeRequestLinter\Infrastructure\Http\Client\MetricableClient::__construct */ public function testSendRequest(): void { - $client = new MetricableClient(new NullClient(), $metrics = new MemoryMetricManager(LocalClock::utc())); + $client = MetricableClient::make(new NullClient(), $metrics = new MemoryRegistry()); $client->sendRequest(new Request('GET', 'http://site.ru')); self::assertCount(1, $metrics->describe()); - self::assertEquals('http_send_request', $metrics->describe()->first()->subject->key); + self::assertGreaterThan(0.0, $metrics->describe()->first()->getFirstSampleValue()); } /** * @covers \ArtARTs36\MergeRequestLinter\Infrastructure\Http\Client\MetricableClient::sendAsyncRequests + * @covers \ArtARTs36\MergeRequestLinter\Infrastructure\Http\Client\MetricableClient::make * @covers \ArtARTs36\MergeRequestLinter\Infrastructure\Http\Client\MetricableClient::__construct * @covers \ArtARTs36\MergeRequestLinter\Infrastructure\Http\Client\MetricableClient::getHosts */ public function testSendAsyncRequest(): void { - $client = new MetricableClient(new NullClient(), $metrics = new MemoryMetricManager(LocalClock::utc())); + $client = MetricableClient::make(new NullClient(), $metrics = new MemoryRegistry()); $client->sendAsyncRequests(['k' => new Request('GET', 'http://site.ru')]); self::assertCount(1, $metrics->describe()); - self::assertEquals('http_send_request', $metrics->describe()->first()->subject->key); + self::assertGreaterThan(0.0, $metrics->describe()->first()->getFirstSampleValue()); } } diff --git a/tests/Unit/Infrastructure/RequestFetcher/CiRequestFetcherTest.php b/tests/Unit/Infrastructure/RequestFetcher/CiRequestFetcherTest.php index 90a5b8e92..c6e7adf15 100644 --- a/tests/Unit/Infrastructure/RequestFetcher/CiRequestFetcherTest.php +++ b/tests/Unit/Infrastructure/RequestFetcher/CiRequestFetcherTest.php @@ -5,8 +5,9 @@ use ArtARTs36\MergeRequestLinter\Domain\CI\CiSystem; use ArtARTs36\MergeRequestLinter\Domain\CI\FetchMergeRequestException; use ArtARTs36\MergeRequestLinter\Infrastructure\RequestFetcher\CiRequestFetcher; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Manager\MemoryMetricManager; -use ArtARTs36\MergeRequestLinter\Shared\Time\LocalClock; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\CounterVector; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\Sample; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\MemoryRegistry; use ArtARTs36\MergeRequestLinter\Tests\Mocks\MockCi; use ArtARTs36\MergeRequestLinter\Tests\Mocks\MockCiSystemFactory; use ArtARTs36\MergeRequestLinter\Tests\TestCase; @@ -22,12 +23,17 @@ public function testFetchAddMetric(): void { $fetcher = new CiRequestFetcher( new MockCiSystemFactory(new MockCi($this->makeMergeRequest())), - $metrics = new MemoryMetricManager(LocalClock::utc()), + $metrics = new MemoryRegistry(), ); $fetcher->fetch(); - self::assertEquals('used_ci_system', $metrics->describe()->first()->subject->key); + self::assertInstanceOf(CounterVector::class, $firstMetric = $metrics->describe()->first()); + self::assertEquals([ + new Sample(1, [ + 'ci' => 'mock_ci', + ]), + ], $firstMetric->getSamples()); } /** @@ -37,7 +43,7 @@ public function testFetchOnCurrentlyNotMergeRequestException(): void { $fetcher = new CiRequestFetcher( new MockCiSystemFactory(new MockCi()), - new MemoryMetricManager(LocalClock::utc()), + new MemoryRegistry(), ); self::expectExceptionMessageMatches('/Fetch current merge request from (.*) was failed/i'); @@ -58,7 +64,7 @@ public function testFetchOnGettingMergeRequestException(): void $fetcher = new CiRequestFetcher( new MockCiSystemFactory($ci), - new MemoryMetricManager(LocalClock::utc()), + new MemoryRegistry(), ); self::expectExceptionMessageMatches('/Fetch current merge request from (.*) was failed/i'); diff --git a/tests/Unit/Infrastructure/Rule/Factories/ConditionRuleFactoryTest.php b/tests/Unit/Infrastructure/Rule/Factories/ConditionRuleFactoryTest.php index 954a6a792..4f4084b5d 100644 --- a/tests/Unit/Infrastructure/Rule/Factories/ConditionRuleFactoryTest.php +++ b/tests/Unit/Infrastructure/Rule/Factories/ConditionRuleFactoryTest.php @@ -4,8 +4,8 @@ use ArtARTs36\MergeRequestLinter\Application\Rule\Rules\ConditionRule; use ArtARTs36\MergeRequestLinter\Infrastructure\Rule\Factories\ConditionRuleFactory; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Manager\MemoryMetricManager; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Manager\NullMetricManager; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\MemoryRegistry; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\NullRegistry; use ArtARTs36\MergeRequestLinter\Shared\Time\LocalClock; use ArtARTs36\MergeRequestLinter\Tests\Mocks\MockConditionOperator; use ArtARTs36\MergeRequestLinter\Tests\Mocks\MockOperatorResolver; @@ -22,7 +22,7 @@ public function testNew(): void { ConditionRuleFactory::new( new MockOperatorResolver(), - $metrics = new MemoryMetricManager(LocalClock::utc()), + $metrics = new MemoryRegistry(LocalClock::utc()), ); self::assertCount(1, $metrics->describe()); @@ -34,7 +34,7 @@ public function testNew(): void */ public function testCreate(): void { - $factory = ConditionRuleFactory::new(new MockOperatorResolver(new MockConditionOperator(true)), new NullMetricManager()); + $factory = ConditionRuleFactory::new(new MockOperatorResolver(new MockConditionOperator(true)), new NullRegistry()); $rule = $factory->create(new SuccessRule(), []); diff --git a/tests/Unit/Infrastructure/Rule/ResolverTest.php b/tests/Unit/Infrastructure/Rule/ResolverTest.php index 76bd296e1..5cde72200 100644 --- a/tests/Unit/Infrastructure/Rule/ResolverTest.php +++ b/tests/Unit/Infrastructure/Rule/ResolverTest.php @@ -11,7 +11,7 @@ use ArtARTs36\MergeRequestLinter\Infrastructure\Rule\Factories\RuleFactory; use ArtARTs36\MergeRequestLinter\Infrastructure\Rule\Resolver; use ArtARTs36\MergeRequestLinter\Shared\DataStructure\ArrayMap; -use ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\NullCounter; +use ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\CounterVector; use ArtARTs36\MergeRequestLinter\Shared\Reflection\Instantiator\Finder; use ArtARTs36\MergeRequestLinter\Shared\Reflection\ParameterMapBuilder; use ArtARTs36\MergeRequestLinter\Tests\Mocks\MockConditionOperator; @@ -31,7 +31,7 @@ public function testResolveOnRuleNotFound(): void { $resolver = new Resolver(new ArrayMap([ 'success' => SuccessRule::class, - ]), new RuleFactory(new ParameterMapBuilder(new MockTypeResolver()), new Finder()), new ConditionRuleFactory(new MockOperatorResolver(), new NullCounter())); + ]), new RuleFactory(new ParameterMapBuilder(new MockTypeResolver()), new Finder()), new ConditionRuleFactory(new MockOperatorResolver(), CounterVector::null())); self::expectException(RuleNotFound::class); @@ -47,7 +47,7 @@ public function testResolveOnManyConfigurations(): void { $resolver = new Resolver(new ArrayMap([ 'success' => SuccessRule::class, - ]), new RuleFactory(new ParameterMapBuilder(new MockTypeResolver()), new Finder()), new ConditionRuleFactory(new MockOperatorResolver(), new NullCounter())); + ]), new RuleFactory(new ParameterMapBuilder(new MockTypeResolver()), new Finder()), new ConditionRuleFactory(new MockOperatorResolver(), CounterVector::null())); $gotRule = $resolver->resolve('success', [ [ @@ -69,7 +69,7 @@ public function testResolveNonCriticalRule(): void { $resolver = new Resolver(new ArrayMap([ 'success' => SuccessRule::class, - ]), new RuleFactory(new ParameterMapBuilder(new MockTypeResolver()), new Finder()), new ConditionRuleFactory(new MockOperatorResolver(), new NullCounter())); + ]), new RuleFactory(new ParameterMapBuilder(new MockTypeResolver()), new Finder()), new ConditionRuleFactory(new MockOperatorResolver(), CounterVector::null())); $gotRule = $resolver->resolve('success', ['critical' => false]); @@ -84,7 +84,7 @@ public function testResolveOnCriticalParamNonBoolean(): void { $resolver = new Resolver(new ArrayMap([ 'success' => SuccessRule::class, - ]), new RuleFactory(new ParameterMapBuilder(new MockTypeResolver()), new Finder()), new ConditionRuleFactory(new MockOperatorResolver(), new NullCounter())); + ]), new RuleFactory(new ParameterMapBuilder(new MockTypeResolver()), new Finder()), new ConditionRuleFactory(new MockOperatorResolver(), CounterVector::null())); self::expectExceptionMessage('Failed to create Rule with name "success": param "critical" must be boolean'); @@ -107,7 +107,7 @@ public function testResolveConditionRule(): void ), new Finder(), ), - new ConditionRuleFactory(new MockOperatorResolver(MockConditionOperator::true()), new NullCounter()), + new ConditionRuleFactory(new MockOperatorResolver(MockConditionOperator::true()), CounterVector::null()), ); $gotRule = $resolver->resolve('success', ['when' => [ @@ -135,7 +135,7 @@ public function testResolveOnParamWhenNonArray(): void ), new Finder(), ), - new ConditionRuleFactory(new MockOperatorResolver(MockConditionOperator::true()), new NullCounter()), + new ConditionRuleFactory(new MockOperatorResolver(MockConditionOperator::true()), CounterVector::null()), ); self::expectExceptionMessage('Config[rules.success.when] has invalid type. Expected type: array, actual: string'); @@ -160,7 +160,7 @@ public function testResolveOnFactoryWhenFailed(): void 'success' => SuccessRule::class, ]), $ruleFactory, - new ConditionRuleFactory(new MockOperatorResolver(MockConditionOperator::true()), new NullCounter()), + new ConditionRuleFactory(new MockOperatorResolver(MockConditionOperator::true()), CounterVector::null()), ); self::expectException(CreatingRuleException::class); diff --git a/tests/Unit/Shared/Metrics/Collector/AbstractCollectorTest.php b/tests/Unit/Shared/Metrics/Collector/AbstractCollectorTest.php new file mode 100644 index 000000000..22a49df61 --- /dev/null +++ b/tests/Unit/Shared/Metrics/Collector/AbstractCollectorTest.php @@ -0,0 +1,61 @@ + [ + 'existsSamples' => [], + 'expectedValue' => null, + ], + 'returns first sample value from array with length 1' => [ + 'existsSamples' => [ + new Sample('val', []), + ], + 'expectedValue' => 'val', + ], + 'returns first sample value from array with length 2' => [ + 'existsSamples' => [ + new Sample('val', []), + new Sample('val2', []), + ], + 'expectedValue' => 'val', + ], + ]; + } + + /** + * @covers \ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\AbstractCollector::getFirstSampleValue + * + * @dataProvider providerForTestGetFirstSampleValue + */ + public function testGetFirstSampleValue(array $existsSamples, int|float|string|null $expectedValue): void + { + $collector = new class ($existsSamples) extends AbstractCollector { + public function __construct(private array $samples) + { + parent::__construct(new MetricSubject('', '', '')); + } + + public function getSamples(): array + { + return $this->samples; + } + + public function getMetricType(): MetricType + { + } + }; + + self::assertEquals($expectedValue, $collector->getFirstSampleValue()); + } +} diff --git a/tests/Unit/Shared/Metrics/Collector/AbstractVectorTest.php b/tests/Unit/Shared/Metrics/Collector/AbstractVectorTest.php new file mode 100644 index 000000000..2db57efd9 --- /dev/null +++ b/tests/Unit/Shared/Metrics/Collector/AbstractVectorTest.php @@ -0,0 +1,81 @@ + [ + 'subCollectors' => [], + 'samples' => [], + ], + 'collectors with samples' => [ + 'subCollectors' => [ + new Counter(new MetricSubject('', '', '')), + ], + 'samples' => [ + new Sample(0, []), + ], + ], + ]; + } + + /** + * @covers \ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\AbstractVector::getSamples + * + * @dataProvider providerForTestSamples + */ + public function testGetSamples(array $subCollectors, array $expectedSamples): void + { + $vector = new class ($subCollectors) extends AbstractVector { + public function __construct(array $collectors) + { + parent::__construct(new MetricSubject('', '', '')); + + foreach ($collectors as $collector) { + $this->attach(fn () => $collector, []); + } + } + + public function getMetricType(): MetricType + { + return MetricType::Counter; + } + }; + + self::assertEquals($expectedSamples, $vector->getSamples()); + } + + /** + * @covers \ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\AbstractVector::attach + */ + public function testAttachExistsCollector(): void + { + $vector = new class (new MetricSubject('', '', '')) extends AbstractVector { + public function add(array $labels): Collector + { + return $this->attach(fn () => new MockCollector(), $labels); + } + + public function getMetricType(): MetricType + { + } + }; + + $collectorOne = $vector->add(['k' => 'v']); + $collectorTwo = $vector->add(['k' => 'v']); + + self::assertSame(spl_object_hash($collectorOne), spl_object_hash($collectorTwo)); + } +} diff --git a/tests/Unit/Shared/Metrics/Collector/CounterTest.php b/tests/Unit/Shared/Metrics/Collector/CounterTest.php new file mode 100644 index 000000000..415893b7b --- /dev/null +++ b/tests/Unit/Shared/Metrics/Collector/CounterTest.php @@ -0,0 +1,25 @@ +inc(); + + self::assertEquals([new Sample(1, [])], $counter->getSamples()); + } +} diff --git a/tests/Unit/Shared/Metrics/Collector/CounterVectorTest.php b/tests/Unit/Shared/Metrics/Collector/CounterVectorTest.php new file mode 100644 index 000000000..155e288c7 --- /dev/null +++ b/tests/Unit/Shared/Metrics/Collector/CounterVectorTest.php @@ -0,0 +1,32 @@ +add(['k' => 'v']); + + self::assertEquals($subject, $createdCounter->getSubject()); + } + + /** + * @covers \ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\CounterVector::null + */ + public function testNull(): void + { + $vector = CounterVector::null(); + + self::assertEquals(new MetricSubject('', '', ''), $vector->getSubject()); + } +} diff --git a/tests/Unit/Shared/Metrics/Collector/GaugeTest.php b/tests/Unit/Shared/Metrics/Collector/GaugeTest.php new file mode 100644 index 000000000..a7e643153 --- /dev/null +++ b/tests/Unit/Shared/Metrics/Collector/GaugeTest.php @@ -0,0 +1,26 @@ +getFirstSampleValue()); + + $gauge->set(1); + + self::assertEquals(1, $gauge->getFirstSampleValue()); + } +} diff --git a/tests/Unit/Shared/Metrics/Collector/GaugeVectorTest.php b/tests/Unit/Shared/Metrics/Collector/GaugeVectorTest.php new file mode 100644 index 000000000..41256d336 --- /dev/null +++ b/tests/Unit/Shared/Metrics/Collector/GaugeVectorTest.php @@ -0,0 +1,22 @@ +add([]); + + self::assertEquals($subject, $createdGaugeFirst->getSubject()); + } +} diff --git a/tests/Unit/Shared/Metrics/Manager/MemoryMetricManagerTest.php b/tests/Unit/Shared/Metrics/Manager/MemoryMetricManagerTest.php deleted file mode 100644 index f5e00b81f..000000000 --- a/tests/Unit/Shared/Metrics/Manager/MemoryMetricManagerTest.php +++ /dev/null @@ -1,61 +0,0 @@ -describe()); - - $manager->add(new MetricSubject('', ''), new IncCounter()); - - self::assertCount(1, $manager->describe()); - } - - public function providerForTestDescribe(): array - { - return [ - [ - [ - [$subject1 = new MetricSubject('k', 'n'), $metric1 = new IncCounter()], - ], - [ - new Record($subject1, $metric1, new \DateTimeImmutable()), - ], - ], - ]; - } - - /** - * @covers \ArtARTs36\MergeRequestLinter\Shared\Metrics\Manager\MemoryMetricManager::describe - * @covers \ArtARTs36\MergeRequestLinter\Shared\Metrics\Manager\MemoryMetricManager::__construct - * @dataProvider providerForTestDescribe - */ - public function testDescribe(array $adds, array $expected): void - { - $manager = new MemoryMetricManager( - new QueueClock(array_map(fn (Record $record) => $record->date, $expected)), - ); - - foreach ($adds as [$subject, $value]) { - $manager->add($subject, $value); - } - - self::assertEquals($expected, $manager->describe()->mapToArray(fn ($item) => $item)); - } -} diff --git a/tests/Unit/Shared/Metrics/Registry/MemoryRegistryTest.php b/tests/Unit/Shared/Metrics/Registry/MemoryRegistryTest.php new file mode 100644 index 000000000..fe32967f2 --- /dev/null +++ b/tests/Unit/Shared/Metrics/Registry/MemoryRegistryTest.php @@ -0,0 +1,89 @@ + $counter1, + ], + ], + ]; + } + + /** + * @covers \ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\MemoryRegistry::register + * @covers \ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\MemoryRegistry::describe + * @dataProvider providerForTestDescribe + */ + public function testDescribe(array $adds, array $expected): void + { + $manager = new MemoryRegistry(); + + foreach ($adds as $collector) { + $manager->register($collector); + } + + self::assertEquals($expected, $manager->describe()->toArray()); + } + + /** + * @covers \ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\MemoryRegistry::register + */ + public function testRegisterOnAlreadyRegistered(): void + { + $registry = new MemoryRegistry(); + + $registry->register(new Counter(new MetricSubject('test', 'collector', ''))); + + self::expectException(CollectorAlreadyRegisteredException::class); + + $registry->register(new Gauge(new MetricSubject('test', 'collector', ''))); + } + + /** + * @covers \ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\MemoryRegistry::getOrRegister + */ + public function testGetOrRegister(): void + { + $registry = new MemoryRegistry(); + + $registry->getOrRegister($first = new Counter(new MetricSubject('test', 'collector', ''))); + + $got = $registry->getOrRegister(new Counter(new MetricSubject('test', 'collector', ''))); + + self::assertEquals($first, $got); + } + + /** + * @covers \ArtARTs36\MergeRequestLinter\Shared\Metrics\Registry\MemoryRegistry::getOrRegister + */ + public function testGetOrRegisterOnAlreadyRegisteredWithDifferentType(): void + { + $registry = new MemoryRegistry(); + + $registry->getOrRegister(new Counter(new MetricSubject('test', 'collector', ''))); + + self::expectExceptionMessage('Already registered collector "test_collector" with type "ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\Counter". Expected type: ArtARTs36\MergeRequestLinter\Shared\Metrics\Collector\Gauge'); + + $registry->getOrRegister(new Gauge(new MetricSubject('test', 'collector', ''))); + } +} diff --git a/tests/Unit/Shared/Metrics/Storage/PrometheusPushGateway/HttpClientTest.php b/tests/Unit/Shared/Metrics/Storage/PrometheusPushGateway/HttpClientTest.php new file mode 100644 index 000000000..e84e669c8 --- /dev/null +++ b/tests/Unit/Shared/Metrics/Storage/PrometheusPushGateway/HttpClientTest.php @@ -0,0 +1,29 @@ +replace('job-123', 'content'); + + self::assertNotNull($http->lastRequest()); + self::assertEquals( + 'http://push-gateway/metrics/job/job-123', + $http->lastRequest()->getUri()->__toString(), + ); + } +} diff --git a/tests/Unit/Shared/Metrics/Storage/PrometheusPushGateway/PushGatewayTest.php b/tests/Unit/Shared/Metrics/Storage/PrometheusPushGateway/PushGatewayTest.php new file mode 100644 index 000000000..0faf8529c --- /dev/null +++ b/tests/Unit/Shared/Metrics/Storage/PrometheusPushGateway/PushGatewayTest.php @@ -0,0 +1,38 @@ +createMock(Client::class); + $client + ->expects(new InvokedCount(1)) + ->method('replace') + ->with($id, $renderedVal); + + $renderer = $this->createMock(Renderer::class); + $renderer + ->expects(new InvokedCount(1)) + ->method('render') + ->willReturn($renderedVal); + + $gateway = new PushGateway($client, $renderer); + + $gateway->commit($id, []); + } +} diff --git a/tests/Unit/Shared/Metrics/Storage/PrometheusPushGateway/RendererTest.php b/tests/Unit/Shared/Metrics/Storage/PrometheusPushGateway/RendererTest.php new file mode 100644 index 000000000..9bb9f4d65 --- /dev/null +++ b/tests/Unit/Shared/Metrics/Storage/PrometheusPushGateway/RendererTest.php @@ -0,0 +1,68 @@ + [], + 'expectedContent' => '', + ], + [ + 'collectors' => [ + new MockCollector(), + ], + 'expectedContent' => '', + ], + [ + 'collectors' => [ + new Counter(new MetricSubject('test', 'collector', 'Super title'), [], 2), + ], + 'expectedContent' => "# HELP test_collector Super title +# TYPE test_collector Counter +\n +test_collector 2\n\n", + ], + [ + 'collectors' => [ + new Counter(new MetricSubject('test', 'collector', 'Super title'), ['label1' => 'value1'], 2), + ], + 'expectedContent' => "# HELP test_collector Super title +# TYPE test_collector Counter +\n +test_collector{label1=\"value1\"} 2\n\n", + ], + [ + 'collectors' => [ + new Counter(new MetricSubject('test', 'collector', 'Super title'), ['label1' => 'value1', 'label2' => 'value2'], 2), + ], + 'expectedContent' => "# HELP test_collector Super title +# TYPE test_collector Counter +\n +test_collector{label1=\"value1\",label2=\"value2\"} 2\n\n", + ], + ]; + } + + /** + * @covers \ArtARTs36\MergeRequestLinter\Shared\Metrics\Storage\PrometheusPushGateway\Renderer::render + * @covers \ArtARTs36\MergeRequestLinter\Shared\Metrics\Storage\PrometheusPushGateway\Renderer::collectLabels + * + * @dataProvider providerForTestRender + */ + public function testRender(array $collectors, string $expectedContent): void + { + $renderer = new Renderer(); + + self::assertEquals($expectedContent, $renderer->render($collectors)); + } +} diff --git a/tests/Unit/Shared/Metrics/Value/MemoryCounterTest.php b/tests/Unit/Shared/Metrics/Value/MemoryCounterTest.php deleted file mode 100644 index 28729e91e..000000000 --- a/tests/Unit/Shared/Metrics/Value/MemoryCounterTest.php +++ /dev/null @@ -1,35 +0,0 @@ -getMetricValue()); - } - - /** - * @covers \ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\IncCounter::inc - * @covers \ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\IncCounter::getMetricValue - * @covers \ArtARTs36\MergeRequestLinter\Shared\Metrics\Value\IncCounter::__construct - */ - public function testInc(): void - { - $counter = new IncCounter(2); - - $counter->inc(); - - self::assertEquals('3', $counter->getMetricValue()); - } -} diff --git a/tests/Unit/Shared/Metrics/Value/MetricProxyTest.php b/tests/Unit/Shared/Metrics/Value/MetricProxyTest.php deleted file mode 100644 index 3a63e80ff..000000000 --- a/tests/Unit/Shared/Metrics/Value/MetricProxyTest.php +++ /dev/null @@ -1,29 +0,0 @@ -getMetricValue()); - } -} diff --git a/tests/Unit/Shared/Metrics/Value/RecordTest.php b/tests/Unit/Shared/Metrics/Value/RecordTest.php deleted file mode 100644 index 9222e14e1..000000000 --- a/tests/Unit/Shared/Metrics/Value/RecordTest.php +++ /dev/null @@ -1,31 +0,0 @@ -getValue()); - } -} diff --git a/tests/Unit/Shared/Time/DurationTest.php b/tests/Unit/Shared/Time/DurationTest.php index c6411e38c..e66f168da 100644 --- a/tests/Unit/Shared/Time/DurationTest.php +++ b/tests/Unit/Shared/Time/DurationTest.php @@ -17,14 +17,4 @@ public function testToString(): void self::assertEquals('0.12s', (string) $duration); } - /** - * @covers \ArtARTs36\MergeRequestLinter\Shared\Time\Duration::getMetricValue - * @covers \ArtARTs36\MergeRequestLinter\Shared\Time\Duration::__construct - */ - public function testToGetMetricValue(): void - { - $duration = new Duration(0.12); - - self::assertEquals('0.12s', $duration->getMetricValue()); - } }