Skip to content

Commit

Permalink
basic IO calls auto instrumentation (#110)
Browse files Browse the repository at this point in the history
* basic IO calls auto instrumentation

* php-http/discovery plugin disabling

* udpdate test matrix

* suppress warning

* fixing tests

* add unit dir

* running php-cs-fixer

* observer api for  internal functions were instroduced in 8.2

* update .gitsplit.yml
  • Loading branch information
pdelewski authored Feb 14, 2023
1 parent 3c52850 commit 2070a38
Show file tree
Hide file tree
Showing 11 changed files with 323 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ jobs:
'Instrumentation/Slim',
'Instrumentation/Psr15',
'Instrumentation/Psr18',
'Instrumentation/IO',
'Instrumentation/PDO',
'Symfony'
]
Expand All @@ -34,6 +35,12 @@ jobs:
php-version: 7.4
- project: 'Instrumentation/Psr18'
php-version: 7.4
- project: 'Instrumentation/IO'
php-version: 7.4
- project: 'Instrumentation/IO'
php-version: 8.0
- project: 'Instrumentation/IO'
php-version: 8.1
- project: 'Instrumentation/PDO'
php-version: 7.4
- project: 'Instrumentation/PDO'
Expand Down
2 changes: 2 additions & 0 deletions .gitsplit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ splits:
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-aws.git"
- prefix: "src/Symfony"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-sdk-bundle.git"
- prefix: "src/Instrumentation/IO"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-auto-io.git"
- prefix: "src/Instrumentation/PDO"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-auto-pdo.git"
- prefix: "src/Instrumentation/Psr15"
Expand Down
43 changes: 43 additions & 0 deletions src/Instrumentation/IO/.php-cs-fixer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php
$finder = PhpCsFixer\Finder::create()
->exclude('vendor')
->exclude('var/cache')
->in(__DIR__);

$config = new PhpCsFixer\Config();
return $config->setRules([
'concat_space' => ['spacing' => 'one'],
'declare_equal_normalize' => ['space' => 'none'],
'is_null' => true,
'modernize_types_casting' => true,
'ordered_imports' => true,
'php_unit_construct' => true,
'single_line_comment_style' => true,
'yoda_style' => false,
'@PSR2' => true,
'array_syntax' => ['syntax' => 'short'],
'blank_line_after_opening_tag' => true,
'blank_line_before_statement' => true,
'cast_spaces' => true,
'declare_strict_types' => true,
'function_typehint_space' => true,
'include' => true,
'lowercase_cast' => true,
'new_with_braces' => true,
'no_extra_blank_lines' => true,
'no_leading_import_slash' => true,
'echo_tag_syntax' => true,
'no_unused_imports' => true,
'no_useless_else' => true,
'no_useless_return' => true,
'phpdoc_order' => true,
'phpdoc_scalar' => true,
'phpdoc_types' => true,
'short_scalar_cast' => true,
'single_blank_line_before_namespace' => true,
'single_quote' => true,
'trailing_comma_in_multiline' => true,
])
->setRiskyAllowed(true)
->setFinder($finder);

7 changes: 7 additions & 0 deletions src/Instrumentation/IO/_register.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

declare(strict_types=1);

assert(extension_loaded('otel_instrumentation'));

\OpenTelemetry\Contrib\Instrumentation\IO\IOInstrumentation::register();
39 changes: 39 additions & 0 deletions src/Instrumentation/IO/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "open-telemetry/opentelemetry-auto-io",
"description": "OpenTelemetry auto-instrumentation for IO",
"keywords": ["opentelemetry", "otel", "open-telemetry", "tracing", "io", "instrumentation"],
"type": "library",
"homepage": "https://opentelemetry.io/docs/php",
"readme": "./README.md",
"license": "Apache-2.0",
"minimum-stability": "dev",
"require": {
"php": "^8.2",
"ext-otel_instrumentation": "*",
"open-telemetry/api": "^1"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3",
"phan/phan": "^5.0",
"php-http/mock-client": "*",
"phpstan/phpstan": "^1.1",
"phpstan/phpstan-phpunit": "^1.0",
"psalm/plugin-phpunit": "^0.16",
"open-telemetry/sdk": "^1.0",
"phpunit/phpunit": "^9.5",
"vimeo/psalm": "^4.0"
},
"autoload": {
"psr-4": {
"OpenTelemetry\\Contrib\\Instrumentation\\IO\\": "src/"
},
"files": [
"_register.php"
]
},
"autoload-dev": {
"psr-4": {
"OpenTelemetry\\Tests\\Instrumentation\\IO\\": "tests/"
}
}
}
9 changes: 9 additions & 0 deletions src/Instrumentation/IO/phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
includes:
- vendor/phpstan/phpstan-phpunit/extension.neon

parameters:
tmpDir: var/cache/phpstan
level: 5
paths:
- src
- tests
47 changes: 47 additions & 0 deletions src/Instrumentation/IO/phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>

<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
cacheResult="false"
colors="false"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
forceCoversAnnotation="false"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
stopOnIncomplete="false"
stopOnSkipped="false"
stopOnRisky="false"
timeoutForSmallTests="1"
timeoutForMediumTests="10"
timeoutForLargeTests="60"
verbose="true">

<coverage processUncoveredFiles="true" disableCodeCoverageIgnore="false">
<include>
<directory>src</directory>
</include>
</coverage>

<php>
<ini name="date.timezone" value="UTC" />
<ini name="display_errors" value="On" />
<ini name="display_startup_errors" value="On" />
<ini name="error_reporting" value="E_ALL" />
</php>

<testsuites>
<testsuite name="unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="integration">
<directory>tests/Integration</directory>
</testsuite>
</testsuites>

</phpunit>
15 changes: 15 additions & 0 deletions src/Instrumentation/IO/psalm.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<psalm
errorLevel="3"
cacheDirectory="var/cache/psalm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd">
<projectFiles>
<directory name="src"/>
<directory name="tests"/>
</projectFiles>
<plugins>
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
</plugins>
</psalm>
81 changes: 81 additions & 0 deletions src/Instrumentation/IO/src/IOInstrumentation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Contrib\Instrumentation\IO;

use OpenTelemetry\API\Common\Instrumentation\CachedInstrumentation;
use OpenTelemetry\API\Trace\Span;
use OpenTelemetry\API\Trace\SpanBuilderInterface;
use OpenTelemetry\API\Trace\StatusCode;
use OpenTelemetry\Context\Context;
use function OpenTelemetry\Instrumentation\hook;
use OpenTelemetry\SemConv\TraceAttributes;
use Throwable;

class IOInstrumentation
{
public static function register(): void
{
$instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.io');

self::_hook($instrumentation, null, 'fopen', 'fopen');
self::_hook($instrumentation, null, 'fwrite', 'fwrite');
self::_hook($instrumentation, null, 'fread', 'fread');
self::_hook($instrumentation, null, 'file_get_contents', 'file_get_contents');
self::_hook($instrumentation, null, 'file_put_contents', 'file_put_contents');

self::_hook($instrumentation, null, 'curl_exec', 'curl_exec');
}

/**
* Simple generic hook function which starts and ends a minimal span
*/
private static function _hook(CachedInstrumentation $instrumentation, ?string $class, string $function, string $name): void
{
hook(
class: $class,
function: $function,
pre: static function ($object, ?array $params, ?string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation, $name) {
/** @psalm-suppress ArgumentTypeCoercion */
$builder = self::makeBuilder($instrumentation, $name, $function, $filename, $lineno);
$parent = Context::getCurrent();
$span = $builder->startSpan();
Context::storage()->attach($span->storeInContext($parent));
},
post: static function ($object, ?array $params, mixed $return, ?Throwable $exception) {
self::end($exception);
}
);
}

private static function makeBuilder(
CachedInstrumentation $instrumentation,
string $name,
string $function,
?string $filename,
?int $lineno
): SpanBuilderInterface {
/** @psalm-suppress ArgumentTypeCoercion */
return $instrumentation->tracer()
->spanBuilder($name)
->setAttribute('code.function', $function)
->setAttribute('code.filepath', $filename)
->setAttribute('code.lineno', $lineno);
}
private static function end(?Throwable $exception): void
{
$scope = Context::storage()->scope();
if (!$scope) {
return;
}
$scope->detach();
$span = Span::fromContext($scope->context());
if ($exception) {
$span->recordException($exception, [TraceAttributes::EXCEPTION_ESCAPED => true]);
$span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage());
}

$span->end();
}
}
73 changes: 73 additions & 0 deletions src/Instrumentation/IO/tests/Integration/IOInstrumentationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Tests\Instrumentation\IO\tests\Integration;

use ArrayObject;
use OpenTelemetry\API\Common\Instrumentation\Configurator;
use OpenTelemetry\Context\ScopeInterface;
use OpenTelemetry\SDK\Trace\SpanExporter\InMemoryExporter;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
use PHPUnit\Framework\TestCase;

class IOInstrumentationTest extends TestCase
{
private ScopeInterface $scope;
private ArrayObject $storage;
private TracerProvider $tracerProvider;

public function setUp(): void
{
$this->storage = new ArrayObject();
$this->tracerProvider = new TracerProvider(
new SimpleSpanProcessor(
new InMemoryExporter($this->storage)
)
);

$this->scope = Configurator::create()
->withTracerProvider($this->tracerProvider)
->activate();
}

public function tearDown(): void
{
$this->scope->detach();
}

public function test_io_calls(): void
{
$resource = fopen('php://memory', 'r');
$this->assertCount(1, $this->storage);
$span = $this->storage->offsetGet(0);
$this->assertSame('fopen', $span->getName());

file_put_contents('php://memory', 'data');
$this->assertCount(2, $this->storage);
$span = $this->storage->offsetGet(1);
$this->assertSame('file_put_contents', $span->getName());

$str = file_get_contents('php://memory');
$this->assertCount(3, $this->storage);
$span = $this->storage->offsetGet(2);
$this->assertSame('file_get_contents', $span->getName());

fwrite($resource, 'data');
$this->assertCount(4, $this->storage);
$span = $this->storage->offsetGet(3);
$this->assertSame('fwrite', $span->getName());

fread($resource, 1);
$this->assertCount(5, $this->storage);
$span = $this->storage->offsetGet(4);
$this->assertSame('fread', $span->getName());

$ch = curl_init();
curl_exec($ch);
$this->assertCount(6, $this->storage);
$span = $this->storage->offsetGet(5);
$this->assertSame('curl_exec', $span->getName());
}
}
Empty file.

0 comments on commit 2070a38

Please sign in to comment.