Skip to content

Commit ee1cec7

Browse files
authored
Merge pull request #512 from alanpoulain/feat-openmetrics
Add OpenMetrics report
2 parents c049e34 + 01f2f6a commit ee1cec7

File tree

14 files changed

+271
-9
lines changed

14 files changed

+271
-9
lines changed

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"nikic/php-parser": "^5.0"
3333
},
3434
"require-dev": {
35+
"openmetrics-php/exposition-text": "^0.4.1",
3536
"phake/phake": "^4.4.0",
3637
"phpstan/extension-installer": "^1.3",
3738
"phpstan/phpstan": "^1.10",
@@ -56,6 +57,7 @@
5657
},
5758
"suggest": {
5859
"ext-dom": "To allow XML parsing and report results.",
59-
"ext-yaml": "To allow yaml parsing of configuration files."
60+
"ext-yaml": "To allow yaml parsing of configuration files.",
61+
"openmetrics-php/exposition-text": "To allow OpenMetrics report."
6062
}
6163
}

src/Hal/Application/Config/Validator.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,9 @@ public function validate(ConfigBagInterface $config): void
100100
$keys = [
101101
'report-html' => static fn (mixed $value): bool => is_string($value) && '' !== $value,
102102
'report-csv' => static fn (mixed $value): bool => is_string($value) && '' !== $value,
103-
'report-violation' => static fn (mixed $value): bool => is_string($value) && '' !== $value,
103+
'report-violations' => static fn (mixed $value): bool => is_string($value) && '' !== $value,
104104
'report-summary-json' => static fn (mixed $value): bool => is_string($value) && '' !== $value,
105+
'report-openmetrics' => static fn (mixed $value): bool => is_string($value) && '' !== $value,
105106
'report-json' => static fn (mixed $value): bool => is_string($value) && '' !== $value,
106107
'config' => static fn (mixed $value): bool => is_string($value) && '' !== $value,
107108
];

src/Hal/Application/HelpApplication.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public function run(): int
3838
--report-csv=<file> File where report CSV will be generated
3939
--report-json=<file> File where report Json will be generated
4040
--report-summary-json=<file> File where the summary report Json will be generated
41+
--report-openmetrics=<file> File where the OpenMetrics report will be generated
4142
--report-violations=<file> File where XML violations report will be generated
4243
--quiet Enable the quiet mode
4344
--version Display current version

src/Hal/DependencyInjection/DependencyInjectionProcessor.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ public function __construct()
196196
new Report\Json\SummaryWriter($config, $fileWriter),
197197
$fileWriter
198198
),
199+
new Report\OpenMetrics\Reporter(
200+
new Report\OpenMetrics\SummaryWriter($config, $fileWriter),
201+
$fileWriter
202+
),
199203
new Report\Violations\Xml\Reporter($config, $output, $fileWriter),
200204
),
201205
new NoCriticalViolationsAllowed($metrics),
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Hal\Exception;
5+
6+
use RuntimeException;
7+
8+
/**
9+
* Exception thrown when OpenMetrics-PHP Exposition Text is not installed.
10+
*/
11+
final class NotInstalledOpenMetricsPhpExpositionTextException extends RuntimeException
12+
{
13+
/**
14+
* @return NotInstalledOpenMetricsPhpExpositionTextException
15+
*/
16+
public static function notInstalled(): NotInstalledOpenMetricsPhpExpositionTextException
17+
{
18+
return new self('Please install OpenMetrics-PHP Exposition Text to use --report-openmetrics. Try running "composer require openmetrics-php/exposition-text".');
19+
}
20+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Hal\Exception;
5+
6+
use RuntimeException;
7+
use function sprintf;
8+
9+
/**
10+
* Exception thrown when trying to write an OpenMetrics report but user has no permissions to do so.
11+
*/
12+
final class NotWritableOpenMetricsReportException extends RuntimeException
13+
{
14+
/**
15+
* @param string $logFile
16+
* @return NotWritableOpenMetricsReportException
17+
*/
18+
public static function noPermission(string $logFile): NotWritableOpenMetricsReportException
19+
{
20+
return new self(sprintf('You do not have permissions to write OpenMetrics report in %s', $logFile));
21+
}
22+
}

src/Hal/Report/Json/SummaryWriter.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public function getReport(): array
3535
'commentLinesOfCode' => $this->sum->cloc,
3636
'avgVolume' => $this->avg->volume,
3737
'avgCommentWeight' => $this->avg->commentWeight,
38-
'avgIntelligentContent' => $this->avg->commentWeight,
38+
'avgIntelligentContent' => $this->avg->intelligentContent,
3939
'logicalLinesByClass' => $this->locByClass,
4040
'logicalLinesByMethod' => $this->locByMethod,
4141
],
@@ -54,7 +54,7 @@ public function getReport(): array
5454
],
5555
'Package' => [
5656
'packages' => $this->sum->nbPackages,
57-
'acgClassesPerPackage' => $this->avg->classesPerPackage,
57+
'avgClassesPerPackage' => $this->avg->classesPerPackage,
5858
'avgDistance' => $this->avg->distance,
5959
'avgIncomingClassDependencies' => $this->avg->incomingCDep,
6060
'avgOutgoingClassDependencies' => $this->avg->outgoingCDep,
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Hal\Report\OpenMetrics;
5+
6+
use Hal\Component\File\WriterInterface;
7+
use Hal\Metric\Metrics;
8+
use Hal\Report\ReporterInterface;
9+
use Hal\Report\SummaryProviderInterface;
10+
11+
/**
12+
* This class is responsible for the OpenMetrics report on a file.
13+
*/
14+
final class Reporter implements ReporterInterface
15+
{
16+
public function __construct(
17+
private readonly SummaryProviderInterface $summary,
18+
private readonly WriterInterface $fileWriter,
19+
) {
20+
}
21+
22+
/**
23+
* {@inheritDoc}
24+
*/
25+
public function generate(Metrics $metrics): void
26+
{
27+
/** @var string|false $logFile */
28+
$logFile = $this->summary->getReportFile();
29+
if (false === $logFile) {
30+
return;
31+
}
32+
33+
$this->summary->summarize($metrics);
34+
/** @var string $report */
35+
$report = $this->summary->getReport();
36+
$this->fileWriter->write($logFile, $report);
37+
}
38+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Hal\Report\OpenMetrics;
5+
6+
use Hal\Application\Config\ConfigBagInterface;
7+
use Hal\Component\File\WriterInterface;
8+
use Hal\Exception\NotInstalledOpenMetricsPhpExpositionTextException;
9+
use Hal\Exception\NotWritableOpenMetricsReportException;
10+
use Hal\Report\SummaryProvider;
11+
use OpenMetricsPhp\Exposition\Text\Collections\GaugeCollection;
12+
use OpenMetricsPhp\Exposition\Text\Metrics\Gauge;
13+
use OpenMetricsPhp\Exposition\Text\Types\MetricName;
14+
15+
/**
16+
* Dedicated writer that defines the content to write in a file when exporting the OpenMetrics summary of the metrics.
17+
*/
18+
final class SummaryWriter extends SummaryProvider
19+
{
20+
public function __construct(
21+
ConfigBagInterface $config,
22+
private readonly WriterInterface $fileWriter
23+
) {
24+
parent::__construct($config);
25+
}
26+
27+
/**
28+
* Return the report of the summary, as a string for the OpenMetrics report format.
29+
*
30+
* @return string
31+
*/
32+
public function getReport(): string
33+
{
34+
if (!class_exists(GaugeCollection::class)) {
35+
throw NotInstalledOpenMetricsPhpExpositionTextException::notInstalled();
36+
}
37+
38+
$reportMetrics = [
39+
// LOC
40+
'lines_of_code' => [$this->sum->loc, 'Lines of code'],
41+
'logical_lines_of_code' => [$this->sum->lloc, 'Logical lines of code'],
42+
'comment_lines_of_code' => [$this->sum->cloc, 'Comment lines of code'],
43+
'average_volume' => [$this->avg->volume, 'Average volume'],
44+
'average_comment_weight' => [$this->avg->commentWeight, 'Average comment weight'],
45+
'average_intelligent_content' => [$this->avg->intelligentContent, 'Average intelligent content'],
46+
'loc_by_class' => [$this->locByClass, 'Logical lines of code by class'],
47+
'loc_by_method' => [$this->locByMethod, 'Logical lines of code by method'],
48+
// Object oriented programming
49+
'number_of_classes' => [$this->sum->nbClasses, 'Number of classes'],
50+
'number_of_interfaces' => [$this->sum->nbInterfaces, 'Number of interfaces'],
51+
'number_of_methods' => [$this->sum->nbMethods, 'Number of methods'],
52+
'methods_by_class' => [$this->methodsByClass, 'Methods by class'],
53+
'lack_cohesion_methods' => [$this->avg->lcom, 'Lack of cohesion of methods'],
54+
// Coupling
55+
'afferent_coupling' => [$this->avg->afferentCoupling, 'Average afferent coupling'],
56+
'efferent_coupling' => [$this->avg->efferentCoupling, 'Average efferent coupling'],
57+
'instability' => [$this->avg->instability, 'Average instability'],
58+
'tree_inheritance_depth' => [$this->treeInheritanceDepth, 'Depth of inheritance tree'],
59+
// Package
60+
'number_of_packages' => [$this->sum->nbPackages, 'Number of packages'],
61+
'classes_by_package' => [$this->avg->classesPerPackage, 'Average classes by package'],
62+
'distance' => [$this->avg->distance, 'Average distance'],
63+
'incoming_class_dependencies' => [$this->avg->incomingCDep, 'Average incoming class dependencies'],
64+
'outgoing_class_dependencies' => [$this->avg->outgoingCDep, 'Average outgoing class dependencies'],
65+
'incoming_package_dependencies' => [$this->avg->incomingPDep, 'Average incoming package dependencies'],
66+
'outgoing_package_dependencies' => [$this->avg->outgoingPDep, 'Average outgoing package dependencies'],
67+
// Complexity
68+
'cyclomatic_complexity_by_class' => [$this->avg->ccn, 'Average cyclomatic complexity by class'],
69+
'weighted_method_count_by_class' => [$this->avg->wmc, 'Average weighted method count by class'],
70+
'relative_system_complexity' => [
71+
$this->avg->relativeSystemComplexity,
72+
'Average relative system complexity'
73+
],
74+
'difficulty' => [$this->avg->difficulty, 'Average difficulty'],
75+
// Bugs
76+
'bugs_by_class' => [$this->avg->bugs, 'Average bugs by class'],
77+
'defects_by_class' => [$this->avg->kanDefect, 'Average defects by class (Kan)'],
78+
// Violations
79+
'critical_violations' => [$this->sum->violations->critical, 'Critical violations'],
80+
'error_violations' => [$this->sum->violations->error, 'Error violations'],
81+
'warning_violations' => [$this->sum->violations->warning, 'Warning violations'],
82+
'information_violations' => [$this->sum->violations->information, 'Information violations']
83+
];
84+
85+
$reportStrings = [];
86+
87+
foreach ($reportMetrics as $metricName => $reportMetric) {
88+
$reportStrings[] = GaugeCollection::fromGauges(
89+
MetricName::fromString($metricName),
90+
Gauge::fromValue($reportMetric[0])
91+
)->withHelp($reportMetric[1])->getMetricsString();
92+
}
93+
94+
return implode("\n", $reportStrings) . "\n# EOF\n";
95+
}
96+
97+
/**
98+
* {@inheritDoc}
99+
*/
100+
public function getReportFile(): string|false
101+
{
102+
if ($this->config->has('quiet')) {
103+
return false;
104+
}
105+
106+
/** @var null|string $logFile */
107+
$logFile = $this->config->get('report-openmetrics');
108+
if (null === $logFile) {
109+
return false;
110+
}
111+
112+
if (!$this->fileWriter->exists(dirname($logFile)) || !$this->fileWriter->isWritable(dirname($logFile))) {
113+
throw NotWritableOpenMetricsReportException::noPermission($logFile);
114+
}
115+
116+
return $logFile;
117+
}
118+
}

src/Hal/Report/SummaryProviderInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public function summarize(Metrics $metrics): void;
2121

2222
/**
2323
* Return the report of the summary, into a type adapted for the report format:
24-
* - returns "string" for CLI
24+
* - returns "string" for CLI or OpenMetrics
2525
* - returns "array" for JSON
2626
*
2727
* @return string|array<string, mixed>

0 commit comments

Comments
 (0)