From 85fa6cdba79535645b77dbac25cc651c180978e5 Mon Sep 17 00:00:00 2001 From: John LeSueur Date: Wed, 27 Jan 2021 09:04:50 -0700 Subject: [PATCH 1/5] Barebones Metric implementation - A Metric interface describing data included in a metric - A Metric output interface for emitting metrics - Add Metrics to the socket protocol - Add a Json Metric file format (log file, each line is valid json, but file is not valid json) - hard code the metric file in the configuration - Add a metric to MethodCall to track calls to deprecated methods. --- src/Checks/BaseCheck.php | 2 + src/Checks/MethodCall.php | 18 +++++- src/Config.php | 4 ++ src/Metrics/JsonMetricOutput.php | 14 +++++ src/Metrics/Metric.php | 86 +++++++++++++++++++++++++++ src/Metrics/MetricInterface.php | 30 ++++++++++ src/Metrics/MetricOutputInterface.php | 5 ++ src/NodeVisitors/StaticAnalyzer.php | 8 ++- src/Output/SocketOutput.php | 8 ++- src/Phases/AnalyzingPhase.php | 8 ++- 10 files changed, 177 insertions(+), 6 deletions(-) create mode 100644 src/Metrics/JsonMetricOutput.php create mode 100644 src/Metrics/Metric.php create mode 100644 src/Metrics/MetricInterface.php create mode 100644 src/Metrics/MetricOutputInterface.php diff --git a/src/Checks/BaseCheck.php b/src/Checks/BaseCheck.php index a0c7a892..370752ca 100644 --- a/src/Checks/BaseCheck.php +++ b/src/Checks/BaseCheck.php @@ -5,6 +5,8 @@ * Apache 2.0 License */ +use BambooHR\Guardrail\Metrics\Metric; +use BambooHR\Guardrail\Metrics\MetricOutputInterface; use PhpParser\Node; use BambooHR\Guardrail\Scope; use BambooHR\Guardrail\SymbolTable\SymbolTable; diff --git a/src/Checks/MethodCall.php b/src/Checks/MethodCall.php index 38d34ee3..d9662ef3 100644 --- a/src/Checks/MethodCall.php +++ b/src/Checks/MethodCall.php @@ -8,6 +8,8 @@ use BambooHR\Guardrail\Abstractions\FunctionLikeParameter; use BambooHR\Guardrail\Abstractions\MethodInterface; use BambooHR\Guardrail\Attributes; +use BambooHR\Guardrail\Metrics\Metric; +use BambooHR\Guardrail\Metrics\MetricOutputInterface; use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Variable; @@ -34,18 +36,31 @@ class MethodCall extends BaseCheck { */ private $callableCheck; + /** @var MetricOutputInterface */ + protected $metricDoc; + /** * MethodCall constructor. * * @param SymbolTable $symbolTable Instance of the SymbolTable * @param OutputInterface $doc Instance of OutputInterface */ - public function __construct(SymbolTable $symbolTable, OutputInterface $doc) { + public function __construct(SymbolTable $symbolTable, OutputInterface $doc, MetricOutputInterface $metricOutputInterface) { + $this->metricDoc = $metricOutputInterface; parent::__construct($symbolTable, $doc); $this->inferenceEngine = new TypeInferrer($symbolTable); $this->callableCheck = new CallableCheck($symbolTable, $doc); } + public function emitMetric($fileName, Node $node, $errorType, $metricData) { + $lineNumber= $node->getLine(); + $metric = new Metric($fileName, $lineNumber, $errorType, $metricData); + if ($trait = $node->getAttribute('importedByTrait')) { + $metric->setCausedByTraitData($trait, $node->getAttribute('importedOnLine')); + } + $this->metricDoc->emitMetric($metric); + } + /** * getCheckNodeTypes * @@ -156,6 +171,7 @@ protected function checkMethod($fileName, $node, $className, $methodName, Scope if ($method->isDeprecated()) { $errorType = $method->isInternal() ? ErrorConstants::TYPE_DEPRECATED_INTERNAL : ErrorConstants::TYPE_DEPRECATED_USER; $this->emitError($fileName, $node, $errorType, "Call to deprecated function " . $method->getName()); + $this->emitMetric($fileName, $node, $errorType, ['class' => $className, 'method' => $methodName, 'line' => $method->getStartingLine()]); } $name = $className . "->" . $methodName; diff --git a/src/Config.php b/src/Config.php index 199a557f..f7b590c1 100644 --- a/src/Config.php +++ b/src/Config.php @@ -538,4 +538,8 @@ public function getOutputFile() { return $this->outputFile; } } + + public function getMetricOutputFile() { + return "metrics.json"; + } } \ No newline at end of file diff --git a/src/Metrics/JsonMetricOutput.php b/src/Metrics/JsonMetricOutput.php new file mode 100644 index 00000000..23753499 --- /dev/null +++ b/src/Metrics/JsonMetricOutput.php @@ -0,0 +1,14 @@ +fileHandle = fopen($filename, "w+"); + if (!$this->fileHandle) { + + } + } + + public function emitMetric(MetricInterface $metric) { + fwrite($this->fileHandle, json_encode($metric) . "\n"); + } +} \ No newline at end of file diff --git a/src/Metrics/Metric.php b/src/Metrics/Metric.php new file mode 100644 index 00000000..3123c208 --- /dev/null +++ b/src/Metrics/Metric.php @@ -0,0 +1,86 @@ +file = $file; + $this->lineNumber = $lineNumber; + $this->type = $type; + $this->data = $data; + } + + /** + * @param array $traitData + */ + public function setCausedByTraitData($traitData) { + $this->causedByTraitData = $traitData; + } + + /** + * @return string + */ + public function getFile() { + return $this->file; + } + + /** + * @return string + */ + public function getLineNumber() { + return $this->lineNumber; + } + + /** + * @return string + */ + public function getType() { + return $this->type; + } + + /** + * @return array - maybe becomes MetricDataInterface? + */ + public function getData() { + return $this->data; + } + + /** + * @return bool + */ + public function isCausedByTrait() { + return !is_null($this->causedByTraitData); + } + + /** + * @return array + */ + public function getCausingTraitData() { + return $this->causedByTraitData; + } + + public function jsonSerialize() { + $data = [ + 'file' => $this->file, + 'lineNumber' => $this->lineNumber, + 'type' => $this->type, + 'data' => $this->data + ]; + if ($this->isCausedByTrait()) { + $data['trait'] = $this->causedByTraitData; + } + return $data; + } +} \ No newline at end of file diff --git a/src/Metrics/MetricInterface.php b/src/Metrics/MetricInterface.php new file mode 100644 index 00000000..115d24b9 --- /dev/null +++ b/src/Metrics/MetricInterface.php @@ -0,0 +1,30 @@ +index = $index; $this->scopeStack = [new Scope(true, true)]; $this->typeInferrer = new TypeInferrer($index); $this->output = $output; + $this->metricOutput = new JsonMetricOutput($config->getMetricOutputFile()); /** @var \BambooHR\Guardrail\Checks\BaseCheck[] $checkers */ $checkers = [ @@ -139,12 +141,12 @@ function __construct($basePath, $index, OutputInterface $output, $config) { new InterfaceCheck($this->index, $output), new ParamTypesCheck($this->index, $output), new StaticCallCheck($this->index, $output), - new InstantiationCheck($this->index, $output), + new InstantiationCheck($this->index, $output, $this->metricOutput), new InstanceOfCheck($this->index, $output), new CatchCheck($this->index, $output), new ClassConstantCheck($this->index, $output), new FunctionCallCheck($this->index, $output), - new MethodCall($this->index, $output), + new MethodCall($this->index, $output, $this->metricOutput), new SwitchCheck($this->index, $output), new BreakCheck($this->index, $output), new ConstructorCheck($this->index, $output), diff --git a/src/Output/SocketOutput.php b/src/Output/SocketOutput.php index ed4c6ca0..9e7cfdcd 100644 --- a/src/Output/SocketOutput.php +++ b/src/Output/SocketOutput.php @@ -4,8 +4,10 @@ use BambooHR\Guardrail\Config; +use BambooHR\Guardrail\Metrics\MetricInterface; +use BambooHR\Guardrail\Metrics\MetricOutputInterface; -class SocketOutput extends XUnitOutput { +class SocketOutput extends XUnitOutput implements MetricOutputInterface { private $socket; /** @@ -39,6 +41,10 @@ function emitError($className, $file, $line, $type, $message = "") { } } + public function emitMetric(MetricInterface $metric) { + socket_write($this->socket, "METRIC " . base64_encode(serialize($metric)) . "\n"); + } + /** * @param string $verbose The text to show when -v * @param string $extraVerbose The text to show when -v -v diff --git a/src/Phases/AnalyzingPhase.php b/src/Phases/AnalyzingPhase.php index 912de0ed..4b0e7e63 100644 --- a/src/Phases/AnalyzingPhase.php +++ b/src/Phases/AnalyzingPhase.php @@ -28,6 +28,7 @@ use PhpParser\ParserFactory; use PhpParser\NodeTraverser; use BambooHR\Guardrail\Config; +use BambooHR\Guardrail\Metrics\JsonMetricOutput; use BambooHR\Guardrail\NodeVisitors\TraitImportingVisitor; use BambooHR\Guardrail\Util; use BambooHR\Guardrail\NodeVisitors\StaticAnalyzer; @@ -213,6 +214,7 @@ function analyzeFile($file, Config $config) { */ public function phase2(Config $config, OutputInterface $output, $toProcess) { $processingCount = 0; + $metricOutput = new JsonMetricOutput($config->getMetricOutputFile()); $pm = new ProcessManager(); @@ -250,7 +252,7 @@ function ($socket) use ($fileNumber, $config) { $processDied = false; $bytes = 0; $pm->loopWhileConnections( - function ($socket, $msg) use (&$processingCount, &$fileNumber, &$bytes, $output, $toProcess, $start, &$pm, &$processDied) { + function ($socket, $msg) use (&$processingCount, &$fileNumber, &$bytes, $output, $toProcess, $start, &$pm, &$processDied, $metricOutput) { if ($msg === false) { $processDied = true; echo "Error: Unexpected error reading from socket\n"; @@ -295,6 +297,10 @@ function ($socket, $msg) use (&$processingCount, &$fileNumber, &$bytes, $output, ); } break; + case 'METRIC': + $metric = unserialize(base64_decode($details)); + $metricOutput->emitMetric($metric); + break; case 'TIMINGS': $this->timingResults[] = json_decode(base64_decode($details), true); return ProcessManager::CLOSE_CONNECTION; From bf4e4bc76722a48ebb511e71b76ec7f710da1c90 Mon Sep 17 00:00:00 2001 From: John LeSueur Date: Thu, 28 Jan 2021 10:20:20 -0700 Subject: [PATCH 2/5] a couple bug fixes. --- src/Metrics/Metric.php | 4 ++-- src/NodeVisitors/StaticAnalyzer.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Metrics/Metric.php b/src/Metrics/Metric.php index 3123c208..306ca34d 100644 --- a/src/Metrics/Metric.php +++ b/src/Metrics/Metric.php @@ -25,8 +25,8 @@ public function __construct(string $file, string $lineNumber, string $type, arra /** * @param array $traitData */ - public function setCausedByTraitData($traitData) { - $this->causedByTraitData = $traitData; + public function setCausedByTraitData($traitName, $importLine) { + $this->causedByTraitData = ['name' => $traitName, 'importLine' => $importLine]; } /** diff --git a/src/NodeVisitors/StaticAnalyzer.php b/src/NodeVisitors/StaticAnalyzer.php index 7862f010..95624055 100644 --- a/src/NodeVisitors/StaticAnalyzer.php +++ b/src/NodeVisitors/StaticAnalyzer.php @@ -129,7 +129,7 @@ function __construct($basePath, $index, OutputInterface $output, Config $config) $this->scopeStack = [new Scope(true, true)]; $this->typeInferrer = new TypeInferrer($index); $this->output = $output; - $this->metricOutput = new JsonMetricOutput($config->getMetricOutputFile()); + $this->metricOutput = $output; /** @var \BambooHR\Guardrail\Checks\BaseCheck[] $checkers */ $checkers = [ From 05d8b167c5b49853aa2136a33315a44e165fdd95 Mon Sep 17 00:00:00 2001 From: John LeSueur Date: Thu, 28 Jan 2021 10:37:53 -0700 Subject: [PATCH 3/5] no need to double implement. --- src/Metrics/Metric.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Metrics/Metric.php b/src/Metrics/Metric.php index 306ca34d..85a1c9e2 100644 --- a/src/Metrics/Metric.php +++ b/src/Metrics/Metric.php @@ -2,7 +2,7 @@ use JsonSerializable; -class Metric implements MetricInterface, JsonSerializable { +class Metric implements MetricInterface { private $file; private $lineNumber; private $type; From 47a6dd67ae90acef8ac25508ae8f4dd239cb261c Mon Sep 17 00:00:00 2001 From: John LeSueur Date: Thu, 28 Jan 2021 15:26:29 -0700 Subject: [PATCH 4/5] WIP - really messy, does not work. --- src/Checks/MethodCall.php | 14 +++- src/Checks/MetricConstants.php | 28 +++++++ src/Config.php | 17 ++++ src/Filters/EmitFilterApplier.php | 78 +++++++++++++++++++ src/Metrics/JsonMetricOutput.php | 10 ++- src/NodeVisitors/StaticAnalyzer.php | 8 +- src/Output/SocketOutput.php | 6 +- src/Output/XUnitOutput.php | 53 +------------ src/Phases/AnalyzingPhase.php | 2 +- src/bin/Build.sh | 3 +- src/bin/aggregate_metrics.php | 61 +++++++++++++++ .../TestData/TestDefinedConstantCheck.2.inc | 2 +- tests/units/EmitFilterApplierTest.php | 16 ++++ 13 files changed, 239 insertions(+), 59 deletions(-) create mode 100644 src/Checks/MetricConstants.php create mode 100644 src/Filters/EmitFilterApplier.php create mode 100644 src/bin/aggregate_metrics.php create mode 100644 tests/units/EmitFilterApplierTest.php diff --git a/src/Checks/MethodCall.php b/src/Checks/MethodCall.php index d9662ef3..bb0ffdf7 100644 --- a/src/Checks/MethodCall.php +++ b/src/Checks/MethodCall.php @@ -171,13 +171,25 @@ protected function checkMethod($fileName, $node, $className, $methodName, Scope if ($method->isDeprecated()) { $errorType = $method->isInternal() ? ErrorConstants::TYPE_DEPRECATED_INTERNAL : ErrorConstants::TYPE_DEPRECATED_USER; $this->emitError($fileName, $node, $errorType, "Call to deprecated function " . $method->getName()); - $this->emitMetric($fileName, $node, $errorType, ['class' => $className, 'method' => $methodName, 'line' => $method->getStartingLine()]); + $metricType = $method->isInternal() ? MetricConstants::TYPE_DEPRECATED_INTERNAL : MetricConstants::TYPE_DEPRECATED_USER; + $this->emitMetric($fileName, $node, $metricType, ['class' => $className, 'method' => $methodName]); } $name = $className . "->" . $methodName; foreach ($node->args as $index => $arg) { $this->checkParam($fileName, $node, $name, $scope, $inside, $arg, $index, $params); } + $calledClassParts = explode('\\', $className); + if ($inside) { + $callingClassParts = $inside->namespacedName->parts; + for ($i = 0; $i < min(count($calledClassParts), count($callingClassParts)); $i++) { + if ($callingClassParts[$i] !== $callingClassParts[$i]) { + break; + } + } + $sharedPrefixParts = $i-1; + $this->emitMetric($fileName, $node, MetricConstants::TYPE_METHOD_CALL, ['callingClass' => implode('\\', $callingClassParts), 'calledClass' => $className, 'calledMethod' => $methodName, 'sharedNamespacePrefixParts' => $sharedPrefixParts]); + } } /** diff --git a/src/Checks/MetricConstants.php b/src/Checks/MetricConstants.php new file mode 100644 index 00000000..3be9238e --- /dev/null +++ b/src/Checks/MetricConstants.php @@ -0,0 +1,28 @@ +getConstants(); + sort($constants); + foreach ($constants as $name => $value) { + $ret[] = $value; + } + return $ret; + } +} \ No newline at end of file diff --git a/src/Config.php b/src/Config.php index f7b590c1..be4b5590 100644 --- a/src/Config.php +++ b/src/Config.php @@ -152,6 +152,10 @@ public function __construct($argv) { $this->emitList = $this->config['emit']; } + if (isset($this->config['emitMetrics']) && is_array($this->config['emitMetrics'])) { + $this->emitList = $this->config['emitMetrics']; + } + if ($this->processes > 1 && $this->preferredTable == self::MEMORY_SYMBOL_TABLE) { $this->preferredTable = self::SQLITE_SYMBOL_TABLE; } @@ -512,6 +516,19 @@ public function getEmitList() { return $this->emitList; } + public function getMetricEmitList() { + return [ + [ + 'emit' => 'Standard.Method.Call', + 'threshold' => [ + 'data.sharedNamespaceParts' => 3, + 'operator' => '<' + ] + ], + 'Standard.*' + ]; + } + /** * processCount * diff --git a/src/Filters/EmitFilterApplier.php b/src/Filters/EmitFilterApplier.php new file mode 100644 index 00000000..76377938 --- /dev/null +++ b/src/Filters/EmitFilterApplier.php @@ -0,0 +1,78 @@ + 0) { + return false; + } + $entry = static::findEmitEntry($emitList, $name); + if ($entry === false) { + return $entry; + } + if ( + isset($entry['when']) && + $entry['when'] == 'new' && + ( + !$filter || + !$filter->shouldEmit($fileName, $name, $lineNumber) + ) + ) { + return false; + } + return true; + } + + static public function findEmitEntry($emitList, $name) { + foreach ($emitList as $entry) { + if ( + is_array($entry) + ) { + if (isset($entry['emit']) && !static::emitPatternMatches($name, $entry['emit'])) { + continue; + } + if (isset($entry['glob']) && !Glob::match( "/" . $fileName, "/" . $entry['glob'])) { + continue; + } + if (isset($entry['ignore']) && Glob::match("/" . $fileName, "/" . $entry['ignore'])) { + continue; + } + return $entry; + + } else if (is_string($entry) && static::emitPatternMatches($name, $entry)) { + return $entry; + } + } + return false; + } +} \ No newline at end of file diff --git a/src/Metrics/JsonMetricOutput.php b/src/Metrics/JsonMetricOutput.php index 23753499..4d5a4595 100644 --- a/src/Metrics/JsonMetricOutput.php +++ b/src/Metrics/JsonMetricOutput.php @@ -1,11 +1,15 @@ fileHandle = fopen($filename, "w+"); + public function __construct(Config $config) { + $this->fileHandle = fopen($config->getMetricOutputFile(), "w+"); if (!$this->fileHandle) { - + throw new InvalidConfigException("Cannot write to the metric file: {$config->getMetricOutputFile()}"); } + $this->emitList = $config->getEmitList(); } public function emitMetric(MetricInterface $metric) { diff --git a/src/NodeVisitors/StaticAnalyzer.php b/src/NodeVisitors/StaticAnalyzer.php index 95624055..7d20fea4 100644 --- a/src/NodeVisitors/StaticAnalyzer.php +++ b/src/NodeVisitors/StaticAnalyzer.php @@ -62,6 +62,7 @@ use PhpParser\NodeVisitorAbstract; use BambooHR\Guardrail\Checks\ErrorConstants; use BambooHR\Guardrail\Metrics\JsonMetricOutput; +use BambooHR\Guardrail\Metrics\MetricOutputInterface; /** * Class StaticAnalyzer @@ -129,7 +130,12 @@ function __construct($basePath, $index, OutputInterface $output, Config $config) $this->scopeStack = [new Scope(true, true)]; $this->typeInferrer = new TypeInferrer($index); $this->output = $output; - $this->metricOutput = $output; + if ($this->output instanceof MetricOutputInterface) { + $this->metricOutput = $output; + } else { + $this->metricOutput = new JsonMetricOutput($config); + } + /** @var \BambooHR\Guardrail\Checks\BaseCheck[] $checkers */ $checkers = [ diff --git a/src/Output/SocketOutput.php b/src/Output/SocketOutput.php index 9e7cfdcd..dc26e6d9 100644 --- a/src/Output/SocketOutput.php +++ b/src/Output/SocketOutput.php @@ -4,6 +4,7 @@ use BambooHR\Guardrail\Config; +use BambooHR\Guardrail\Filters\EmitFilterApplier; use BambooHR\Guardrail\Metrics\MetricInterface; use BambooHR\Guardrail\Metrics\MetricOutputInterface; @@ -42,7 +43,10 @@ function emitError($className, $file, $line, $type, $message = "") { } public function emitMetric(MetricInterface $metric) { - socket_write($this->socket, "METRIC " . base64_encode(serialize($metric)) . "\n"); + if (EmitFilterApplier::shouldEmit($metric->getFile(), $metric->getType(), $metric->getLineNumber(), $this->config->getMetricEmitList(), [], $this->config->getFilter())) { + socket_write($this->socket, "METRIC " . base64_encode(serialize($metric)) . "\n"); + } + } /** diff --git a/src/Output/XUnitOutput.php b/src/Output/XUnitOutput.php index aaaa6a8b..ea7d5a3b 100644 --- a/src/Output/XUnitOutput.php +++ b/src/Output/XUnitOutput.php @@ -6,6 +6,7 @@ */ use BambooHR\Guardrail\Config; +use BambooHR\Guardrail\Filters\EmitFilterApplier; use N98\JUnitXml; use Webmozart\Glob\Glob; @@ -17,7 +18,7 @@ class XUnitOutput implements OutputInterface { /** @var Config */ - private $config; + protected $config; /** @var JUnitXml\TestSuiteElement[] */ protected $suites; @@ -106,23 +107,6 @@ public function getTypeCounts() { return $count; } - /** - * emitPatternMatches - * - * @param string $name The name - * @param string $pattern The pattern - * - * @return bool - */ - static public function emitPatternMatches($name, $pattern) { - if (substr($pattern, -2) == '.*') { - $start = substr($pattern, 0, -2); - return (strpos($name, $start) === 0); - } else { - return $name == $pattern; - } - } - /** * shouldEmit * @@ -133,38 +117,7 @@ static public function emitPatternMatches($name, $pattern) { * @return bool */ public function shouldEmit($fileName, $name, $lineNumber) { - if (isset($this->silenced[$name]) && $this->silenced[$name] > 0) { - return false; - } - foreach ($this->emitList as $entry) { - if ( - is_array($entry) - ) { - if (isset($entry['emit']) && !self::emitPatternMatches($name, $entry['emit'])) { - continue; - } - if (isset($entry['glob']) && !Glob::match( "/" . $fileName, "/" . $entry['glob'])) { - continue; - } - if (isset($entry['ignore']) && Glob::match("/" . $fileName, "/" . $entry['ignore'])) { - continue; - } - if ( - isset($entry['when']) && - $entry['when'] == 'new' && - ( - !$this->config->getFilter() || - !$this->config->getFilter()->shouldEmit($fileName, $name, $lineNumber) - ) - ) { - continue; - } - return true; - } else if (is_string($entry) && self::emitPatternMatches($name, $entry)) { - return true; - } - } - return false; + return EmitFilterApplier::shouldEmit($fileName, $name, $lineNumber, $this->emitList, $this->silenced, $this->config->getFilter()); } /** diff --git a/src/Phases/AnalyzingPhase.php b/src/Phases/AnalyzingPhase.php index 4b0e7e63..1bc9c174 100644 --- a/src/Phases/AnalyzingPhase.php +++ b/src/Phases/AnalyzingPhase.php @@ -214,7 +214,7 @@ function analyzeFile($file, Config $config) { */ public function phase2(Config $config, OutputInterface $output, $toProcess) { $processingCount = 0; - $metricOutput = new JsonMetricOutput($config->getMetricOutputFile()); + $metricOutput = new JsonMetricOutput($config); $pm = new ProcessManager(); diff --git a/src/bin/Build.sh b/src/bin/Build.sh index e264ca4b..2e36f63a 100755 --- a/src/bin/Build.sh +++ b/src/bin/Build.sh @@ -1,2 +1,3 @@ #!/bin/bash -php -d phar.readonly=false Build.php +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +php -d phar.readonly=false ${DIR}/Build.php diff --git a/src/bin/aggregate_metrics.php b/src/bin/aggregate_metrics.php new file mode 100644 index 00000000..498aeca8 --- /dev/null +++ b/src/bin/aggregate_metrics.php @@ -0,0 +1,61 @@ +type) { + case ErrorConstants::TYPE_DEPRECATED_USER: + aggregateDeprecatedCalls($metric, $deprecatedAggregatesByClass); + break; + } +} + +$namespaceCounts = []; +foreach ($deprecatedAggregatesByClass as $className => $classAggregate) { + foreach ($interestingNamespaces as $namespace) { + var_dump($namespace); + $found = preg_match($namespace, $className, $matches); + if ($found) { + $namespace = $matches[0]; + $namespaceCounts[$namespace] += $classAggregate['callsToDeprecatedMethods']; + } + } +} +ksort($namespaceCounts); +var_dump($namespaceCounts); + +function aggregateDeprecatedCalls($metric, &$deprecatedAggregatesByClass) { + if (!isset($deprecatedAggregatesByClass[$metric->data->class])) { + $deprecatedAggregatesByClass[$metric->data->class] = [ + 'class' => $metric->data->class, + 'callsToDeprecatedMethods' => 0 + ]; + } + if (!isset($deprecatedAggregatesByClass[$metric->data->class]['methods'][$metric->data->method])) { + $deprecatedAggregatesByClass[$metric->data->class]['methods'][$metric->data->method] = [ + 'method' => $metric->data->method, + 'calls' => 0 + ]; + } + $deprecatedAggregatesByClass[$metric->data->class]['callsToDeprecatedMethods']++; + $deprecatedAggregatesByClass[$metric->data->class]['methods'][$metric->data->method]['calls']++; +} diff --git a/tests/units/Checks/TestData/TestDefinedConstantCheck.2.inc b/tests/units/Checks/TestData/TestDefinedConstantCheck.2.inc index 628c6272..e96c3567 100644 --- a/tests/units/Checks/TestData/TestDefinedConstantCheck.2.inc +++ b/tests/units/Checks/TestData/TestDefinedConstantCheck.2.inc @@ -6,7 +6,7 @@ namespace { } namespace Space1 { - const MY_CONST_2; + const MY_CONST_2 = 2; echo MY_CONST; echo \Space1\MY_CONST_2; diff --git a/tests/units/EmitFilterApplierTest.php b/tests/units/EmitFilterApplierTest.php new file mode 100644 index 00000000..1e0fff52 --- /dev/null +++ b/tests/units/EmitFilterApplierTest.php @@ -0,0 +1,16 @@ +assertFalse(EmitFilterApplier::findEmitEntry([], 'Standard.Method.Call')); + $this->assertEquals('Standard.Method.Call', EmitFilterApplier::findEmitEntry([ + 'Standard.Method.Call' + ], 'Standard.Method.Call')); + $this->assertEquals(['emit' => 'Standard.Method.Call'], EmitFilterApplier::findEmitEntry([ + ['emit' => 'Standard.Method.Call'] + ], 'Standard.Method.Call')); + } +} \ No newline at end of file From 0cc308e2de68b5bcc9178aca7da043d88b2c82ea Mon Sep 17 00:00:00 2001 From: John LeSueur Date: Wed, 10 Feb 2021 10:01:14 -0700 Subject: [PATCH 5/5] WIP metric configuration. --- src/CommandLineRunner.php | 28 ++++++++------- src/Config.php | 17 ++++++++-- src/Filters/EmitFilterApplier.php | 8 +++-- src/Metrics/JsonMetricOutput.php | 16 ++++++--- tests/units/EmitFilterApplierTest.php | 49 ++++++++++++++++++++++++--- 5 files changed, 91 insertions(+), 27 deletions(-) diff --git a/src/CommandLineRunner.php b/src/CommandLineRunner.php index 7aca428a..67879b98 100644 --- a/src/CommandLineRunner.php +++ b/src/CommandLineRunner.php @@ -25,34 +25,36 @@ public function usage() { echo " Usage: php guardrail.phar [-a] [-i] [-n #] [--format xunit|text] [-o output_file_name] [-p #/#] [--timings] config_file -where: -p #/# = Define the number of partitions and the current partition. +where: -p #/# = Define the number of partitions and the current partition. Use for multiple hosts. Example: -p 1/4 - --format {format} = Use \"xunit\" format or a more console friendly \"text\" format + --format {format} = Use \"xunit\" format or a more console friendly \"text\" format - -n # = number of child process to run. + -n # = number of child process to run. Use for multiple processes on a single host. - -a = run the \"analyze\" operation + -a = run the \"analyze\" operation - -i = run the \"index\" operation. + -i = run the \"index\" operation. Defaults to yes if using in memory index. - -s = prefer sqlite index + -s = prefer sqlite index - -j = prefer json index (new) + -j = prefer json index (new) - -m = prefer in memory index (only available when -n=1 and -p=1/1) + -m = prefer in memory index (only available when -n=1 and -p=1/1) - -o output_file_name = Output results to the specified filename + -o output_file_name = Output results to the specified filename - -v = Increase verbosity level. Can be used once or twice. + -v = Increase verbosity level. Can be used once or twice. - -h or --help = Ignore all other options and show this page. + -h or --help = Ignore all other options and show this page. - -l or --list = Ignore all other options and list standard test names. + -l or --list = Ignore all other options and list standard test names. - --timings = Output a summary of how long each check ran for. + --timings = Output a summary of how long each check ran for. + + --metrics-file metrics.json = Output metrics to the specified filename "; } diff --git a/src/Config.php b/src/Config.php index be4b5590..1f33523c 100644 --- a/src/Config.php +++ b/src/Config.php @@ -85,6 +85,9 @@ class Config { /** @var string */ private $filterFileName = ""; + /** @var string */ + private $metricOutputFile; + /** @var bool */ static private $useDocBlockForProperties = false; @@ -153,7 +156,7 @@ public function __construct($argv) { } if (isset($this->config['emitMetrics']) && is_array($this->config['emitMetrics'])) { - $this->emitList = $this->config['emitMetrics']; + $this->emitMetricList = $this->config['emitMetrics']; } if ($this->processes > 1 && $this->preferredTable == self::MEMORY_SYMBOL_TABLE) { @@ -356,6 +359,13 @@ private function parseArgv(array $argv) { $filter->display(); $this->filter = $filter; break; + case '--metrics-file': + if ($argCount + 1 >= count($argv)) { + echo "not enough arguments"; + throw new InvalidConfigException(); + } + $this->metricOutputFile = $argv[++$argCount]; + break; case '-h': case '--help': throw new InvalidConfigException; @@ -517,6 +527,8 @@ public function getEmitList() { } public function getMetricEmitList() { + return $this->emitMetricList; + /* return [ [ 'emit' => 'Standard.Method.Call', @@ -527,6 +539,7 @@ public function getMetricEmitList() { ], 'Standard.*' ]; + */ } /** @@ -557,6 +570,6 @@ public function getOutputFile() { } public function getMetricOutputFile() { - return "metrics.json"; + return $this->metricOutputFile; } } \ No newline at end of file diff --git a/src/Filters/EmitFilterApplier.php b/src/Filters/EmitFilterApplier.php index 76377938..d45e6948 100644 --- a/src/Filters/EmitFilterApplier.php +++ b/src/Filters/EmitFilterApplier.php @@ -36,10 +36,14 @@ static public function shouldEmit($fileName, $name, $lineNumber, $emitList, $sil if (isset($silenced[$name]) && $silenced[$name] > 0) { return false; } - $entry = static::findEmitEntry($emitList, $name); + $entry = static::findEmitEntry($emitList, $fileName, $name); if ($entry === false) { return $entry; } + return static::applyEmitEntry($entry, $filter, $fileName, $name, $lineNumber); + } + + static public function applyEmitEntry($entry, ?FilterInterface $filter, $fileName, $name, $lineNumber) { if ( isset($entry['when']) && $entry['when'] == 'new' && @@ -53,7 +57,7 @@ static public function shouldEmit($fileName, $name, $lineNumber, $emitList, $sil return true; } - static public function findEmitEntry($emitList, $name) { + static public function findEmitEntry($emitList, $fileName, $name) { foreach ($emitList as $entry) { if ( is_array($entry) diff --git a/src/Metrics/JsonMetricOutput.php b/src/Metrics/JsonMetricOutput.php index 4d5a4595..aac57eef 100644 --- a/src/Metrics/JsonMetricOutput.php +++ b/src/Metrics/JsonMetricOutput.php @@ -4,15 +4,21 @@ use BambooHR\Guardrail\Exceptions\InvalidConfigException; class JsonMetricOutput implements MetricOutputInterface { + /** @var resource */ + private $fileHandle; public function __construct(Config $config) { - $this->fileHandle = fopen($config->getMetricOutputFile(), "w+"); - if (!$this->fileHandle) { - throw new InvalidConfigException("Cannot write to the metric file: {$config->getMetricOutputFile()}"); + if ($config->getMetricOutputFile() !== null) { + $this->fileHandle = fopen($config->getMetricOutputFile(), "w+"); + if (!$this->fileHandle) { + throw new InvalidConfigException("Cannot write to the metric file: {$config->getMetricOutputFile()}"); + } + $this->emitList = $config->getEmitList(); } - $this->emitList = $config->getEmitList(); } public function emitMetric(MetricInterface $metric) { - fwrite($this->fileHandle, json_encode($metric) . "\n"); + if ($this->fileHandle) { + fwrite($this->fileHandle, json_encode($metric) . "\n"); + } } } \ No newline at end of file diff --git a/tests/units/EmitFilterApplierTest.php b/tests/units/EmitFilterApplierTest.php index 1e0fff52..18ce0e21 100644 --- a/tests/units/EmitFilterApplierTest.php +++ b/tests/units/EmitFilterApplierTest.php @@ -5,12 +5,51 @@ class EmitFilterApplierTest extends TestCase { public function testFindEntry() { - $this->assertFalse(EmitFilterApplier::findEmitEntry([], 'Standard.Method.Call')); - $this->assertEquals('Standard.Method.Call', EmitFilterApplier::findEmitEntry([ + $emitList = []; + $fileName = 'asdf.php'; + $emittedMetric = 'Standard.Method.Call'; + $this->assertFalse(EmitFilterApplier::findEmitEntry($emitList, $fileName, $emittedMetric)); + $emitList = [ 'Standard.Method.Call' - ], 'Standard.Method.Call')); - $this->assertEquals(['emit' => 'Standard.Method.Call'], EmitFilterApplier::findEmitEntry([ + ]; + $this->assertEquals($emitList[0], EmitFilterApplier::findEmitEntry($emitList, $fileName, $emittedMetric)); + $emitList = [ ['emit' => 'Standard.Method.Call'] - ], 'Standard.Method.Call')); + ]; + $this->assertEquals($emitList[0], EmitFilterApplier::findEmitEntry($emitList, $fileName, $emittedMetric)); + $emitList = [ + ['emit' => 'Standard.*'] + ]; + $this->assertEquals($emitList[0], EmitFilterApplier::findEmitEntry($emitList, $fileName, $emittedMetric)); + $emitList = [ + [ + 'emit' => 'Standard.*', + 'ignore' => '*.php' + ] + ]; + $this->assertFalse(EmitFilterApplier::findEmitEntry($emitList, $fileName, $emittedMetric)); + $emitList = [ + [ + 'emit' => 'Standard.*', + 'ignore' => '1234.php' + ] + ]; + $this->assertEquals($emitList[0], EmitFilterApplier::findEmitEntry($emitList, $fileName, $emittedMetric)); + $emitList = [ + [ + 'emit' => 'Standard.*', + 'glob' => '1234.php' + ] + ]; + $this->assertFalse(EmitFilterApplier::findEmitEntry($emitList, $fileName, $emittedMetric)); + $emitList = [ + [ + 'emit' => 'Standard.*', + 'glob' => '*.php' + ] + ]; + $this->assertEquals($emitList[0], EmitFilterApplier::findEmitEntry($emitList, $fileName, $emittedMetric)); } + + //public function test } \ No newline at end of file