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