diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fad89c5..d62ea44 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,47 @@ permissions: contents: read jobs: + lint: + runs-on: ubuntu-latest + name: "Lint | PHP ${{ matrix.php-version }}" + strategy: + matrix: + php-version: + - "8.1" + - "8.2" + - "8.3" + steps: + - uses: actions/checkout@v4 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + coverage: "none" + php-version: "${{ matrix.php-version }}" + tools: composer:v2 + extensions: json, opcache + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v4 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + run: composer install --prefer-dist --no-progress --ignore-platform-req=php+ --ignore-platform-req=ext-perfidious + + - name: phpcs + run: php vendor/bin/phpcs + + - name: phpstan + run: php vendor/bin/phpstan analyze + test: runs-on: ubuntu-latest name: "Test | PHP ${{ matrix.php-version }}" diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist new file mode 100644 index 0000000..cb90a3f --- /dev/null +++ b/.phpcs.xml.dist @@ -0,0 +1,34 @@ + + + perfidious.stub.php + examples + + */.direnv/* + */vendor/* + + + + + + + + + + + + + + + + + + + + + */examples/* + + + + *.stub.php + + diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b80aa98 --- /dev/null +++ b/composer.json @@ -0,0 +1,19 @@ +{ + "name": "jbboehr/perfidious", + "description": "", + "type": "library", + "license": "AGPL-3.0-or-later", + "authors": [ + { + "name": "John Boehr", + "email": "jbboehr@gmail.com" + } + ], + "require-dev": { + "phpstan/phpstan": "^1.10", + "squizlabs/php_codesniffer": "^3.9" + }, + "suggest": { + "ext-perfidious": "*" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..7f65c2d --- /dev/null +++ b/composer.lock @@ -0,0 +1,161 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "fdab2d62bd06268ee579f527fa27272d", + "packages": [], + "packages-dev": [ + { + "name": "phpstan/phpstan", + "version": "1.10.66", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "94779c987e4ebd620025d9e5fdd23323903950bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/94779c987e4ebd620025d9e5fdd23323903950bd", + "reference": "94779c987e4ebd620025d9e5fdd23323903950bd", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2024-03-28T16:17:31+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.9.1", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "267a4405fff1d9c847134db3a3c92f1ab7f77909" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/267a4405fff1d9c847134db3a3c92f1ab7f77909", + "reference": "267a4405fff1d9c847134db3a3c92f1ab7f77909", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-03-31T21:03:09+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/examples/all-events.php b/examples/all-events.php index d96a93f..051031b 100755 --- a/examples/all-events.php +++ b/examples/all-events.php @@ -13,6 +13,7 @@ ], $rest_index); $show = $opts['show'] ?? 'present'; +$show = is_string($show) ? $show : 'present'; if (array_key_exists('help', $opts)) { fprintf(STDERR, "Usage: " . $argv[0] . PHP_EOL); diff --git a/examples/estimate-overhead.php b/examples/estimate-overhead.php index 1dc4c21..a84102f 100755 --- a/examples/estimate-overhead.php +++ b/examples/estimate-overhead.php @@ -51,7 +51,16 @@ $stddev = stddev($data); $min = min($data); $max = max($data); - printf("%s\n samples=%d\n mean=%g\n variance=%g\n stddev=%g\n min=%g\n max=%g\n", $k, count($data), $mean, $variance, $stddev, $min, $max); + printf( + "%s\n samples=%d\n mean=%g\n variance=%g\n stddev=%g\n min=%g\n max=%g\n", + $k, + count($data), + $mean, + $variance, + $stddev, + $min, + $max + ); } /** @@ -59,7 +68,10 @@ * @license https://github.com/phpbench/phpbench/blob/master/LICENSE */ -function variance(array $values, bool $sample = false) +/** + * @param list $values + */ +function variance(array $values, bool $sample = false): int|float { $average = mean($values); $sum = 0; @@ -78,6 +90,9 @@ function variance(array $values, bool $sample = false) return $variance; } +/** + * @param list $values + */ function stddev(array $values, bool $sample = false): float { $variance = variance($values, $sample); @@ -85,7 +100,10 @@ function stddev(array $values, bool $sample = false): float return \sqrt($variance); } -function mean(array $values) +/** + * @param list $values + */ +function mean(array $values): int|float { if (empty($values)) { return 0; diff --git a/examples/sieve.php b/examples/sieve.php index fe4ffe2..f5b06ac 100644 --- a/examples/sieve.php +++ b/examples/sieve.php @@ -21,6 +21,7 @@ } $count = $opts['count'] ?? 2000000; +$count = is_numeric($opts['count']) ? (int) $opts['count'] : 2000000; $handle = open($pos_args); $handle->enable(); @@ -33,7 +34,10 @@ var_dump($primes); var_dump($stats); -function sieve(int $n) +/** + * @return list + */ +function sieve(int $n): array { $lut = array_fill(0, $n, null); for ($i = 2; $i < $n; $i++) { diff --git a/examples/sieve2.php b/examples/sieve2.php index 691792a..442305b 100644 --- a/examples/sieve2.php +++ b/examples/sieve2.php @@ -21,6 +21,7 @@ } $count = $opts['count'] ?? 2000000; +$count = is_numeric($opts['count']) ? (int) $opts['count'] : 2000000; $handle = open($pos_args); $handle->enable(); @@ -33,9 +34,12 @@ var_dump($primes); var_dump($stats); +/** + * @return list + */ function sieve(int $n): array { - $n2 = ceil($n / 8); + $n2 = (int) ceil($n / 8); $lut = str_repeat("\0", $n2); for ($i = 2; $i < $n; $i++) { for ($j = $i + $i; $j < $n; $j += $i) { diff --git a/examples/three-sw-clock.php b/examples/three-sw-clock.php index c7056fc..8307928 100755 --- a/examples/three-sw-clock.php +++ b/examples/three-sw-clock.php @@ -9,4 +9,3 @@ var_dump($handle->readArray()); sleep(1); } - diff --git a/examples/watch.php b/examples/watch.php index d02a4d7..d36e2b3 100755 --- a/examples/watch.php +++ b/examples/watch.php @@ -4,7 +4,8 @@ use function Perfidious\open; // If specifying pid, you may need to grant cap_perfmon -// sudo capsh --caps="cap_perfmon,cap_setgid,cap_setuid,cap_setpcap+eip" --user=`whoami` --addamb='cap_perfmon' -- -c 'php -dextension=modules/perfidious.so examples/watch.php --interval 2 --pid 3319' +// sudo capsh --caps="cap_perfmon,cap_setgid,cap_setuid,cap_setpcap+eip" --user=`whoami` --addamb='cap_perfmon' -- \ +// -c 'php -dextension=modules/perfidious.so examples/watch.php --interval 2 --pid 3319' $rest_index = null; $opts = getopt('', [ @@ -22,17 +23,23 @@ } $pid = $opts['pid'] ?? 0; +$pid = is_numeric($pid) ? (int) $opts['pid'] : 0; $cpu = $opts['cpu'] ?? -1; -$interval = (float) ($opts['interval'] ?? 0) ?: 2; +$cpu = is_numeric($cpu) ? (int) $cpu : -1; +$interval = $opts['interval'] ?? 2; +$interval = is_numeric($interval) ? (int) $interval : 2; $interval *= 1000000; $handle = open($pos_args, $pid, $cpu); $handle->enable(); -while (true) { + +while (true) { // @phpstan-ignore-line $stats = $handle->read(); $percent_running = $stats->timeEnabled > 0 ? 100 * $stats->timeRunning / $stats->timeEnabled : 0; + //\PHPStan\dumpType($stats); + printf("cpu=%d pid=%d\n", $cpu, $pid); printf("time_enabled=%d time_running=%d percent_running=%d%%\n", $stats->timeEnabled, $stats->timeRunning, $percent_running); foreach ($stats->values as $k => $v) { @@ -41,7 +48,3 @@ usleep($interval); } - -foreach ($tmp as $k => $v) { - echo $k; -} diff --git a/flake.nix b/flake.nix index 34e00de..80f266c 100644 --- a/flake.nix +++ b/flake.nix @@ -145,6 +145,8 @@ # opcache isn't getting loaded for tests because tests are run with '-n' and nixos doesn't compile # in opcache and relies on mkWrapper to load extensions export TEST_PHP_ARGS='-c ${package.php.phpIni}' + # php.unwrapped from the buildDeps is overwriting php + export PATH="${package.php}/bin:$PATH" ''; }; diff --git a/perfidious.stub.php b/perfidious.stub.php index 2828b5f..b247b78 100644 --- a/perfidious.stub.php +++ b/perfidious.stub.php @@ -13,6 +13,9 @@ function get_pmu_info(int $pmu): PmuInfo { } +/** + * @phpstan-return ?Handle> + */ function global_handle(): ?Handle { } @@ -37,11 +40,18 @@ function list_pmu_events(int $pmu): array /** * @param list $event_names a list of libpfm event names, see list_pmu_events * @throws PmuEventNotFoundException|IOException|OverflowException + * + * @phpstan-template T of string + * @phpstan-param list $event_names + * @phpstan-return Handle> */ function open(array $event_names, int $pid = 0, int $cpu = -1): Handle { } +/** + * @phpstan-return ?Handle> + */ function request_handle(): ?Handle { } @@ -66,48 +76,75 @@ final class PmuEventNotFoundException extends \InvalidArgumentException implemen { } +/** + * @phpstan-template T of list + */ final class Handle { /** + * @return $this * @throws IOException */ - final public function enable(): self {} + final public function enable(): self + { + } /** + * @return $this * @throws IOException */ - final public function disable(): self {} + final public function disable(): self + { + } /** * Get a raw byte stream from the handle's file descriptor * @note closing this resource will cause subsequent calls to read to fail * @return resource */ - final public function rawStream() {} + final public function rawStream() + { + } /** - * @return array * @throws OverflowException|IOException + * + * @phpstan-return ReadResult */ - final public function read(): ReadResult {} + final public function read(): ReadResult + { + } /** * @return array * @throws OverflowException|IOException + * + * @phpstan-return array, int> */ - final public function readArray(): array {} + final public function readArray(): array + { + } /** + * @return $this * @throws IOException */ - final public function reset(): self {} + final public function reset(): self + { + } } +/** + * @phpstan-template T of list + */ final class ReadResult { public readonly int $timeEnabled; public readonly int $timeRunning; - /** @var array **/ + /** + * @var array + * @phpstan-var array, int> + */ public readonly array $values; } diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..5912fd7 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,9 @@ +includes: + - vendor/phpstan/phpstan/conf/bleedingEdge.neon +parameters: + level: max + paths: + - perfidious.stub.php + - examples + stubFiles: + - perfidious.stub.php