Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions observability-pocs/poc_0_secret_manager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php

require __DIR__ . '/../vendor/autoload.php';

use OpenTelemetry\API\Trace\TracerProviderInterface;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\API\Trace\StatusCode;
use OpenTelemetry\SDK\Trace\TracerProviderFactory;
use OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporter;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;

class TelemetryConfiguration {
public static function isTracingEnabled(?TracerProviderInterface $constructorProvider = null): bool {
$env = getenv('GOOGLE_CLOUD_TELEMETRY_ENABLED');

// 1. Global Override
if ($env === 'false') {
return false;
}

// 2. Constructor-Based Enablement
if ($constructorProvider !== null) {
return true;
}

// 3. Environment-Based Enablement
if ($env === 'true') {
return true;
}

// 4. Default
return false;
}
}

class SecretManagerClientMock {
private ?TracerProviderInterface $tracerProvider;
private $tracer;

public function __construct(?TracerProviderInterface $tracerProvider = null) {
$this->tracerProvider = $tracerProvider;

if (TelemetryConfiguration::isTracingEnabled($tracerProvider)) {
$provider = $this->tracerProvider ?? \OpenTelemetry\API\Globals::tracerProvider();
$this->tracer = $provider->getTracer('google-cloud-secretmanager', '1.0.0');
} else {
$this->tracer = null;
}
}

public function accessSecretVersion(string $name) {
$span = null;
if ($this->tracer) {
$span = $this->tracer->spanBuilder('SecretManagerServiceClient.access_secret_version')
->setSpanKind(SpanKind::KIND_INTERNAL)
->setAttribute('gcp.client.service', 'secretmanager')
->startSpan();
$scope = $span->activate();
}

try {
if ($span) {
$t4 = $this->tracer->spanBuilder('GET /v1/' . $name . ':access')
->setSpanKind(SpanKind::KIND_CLIENT)
->setAttribute('rpc.system.name', 'http')
->setAttribute('http.request.method', 'GET')
->setAttribute('gcp.resource.destination.id', '//secretmanager.googleapis.com/' . $name)
->startSpan();

$t4->setAttribute('http.response.status_code', 200);
$t4->end();
}

return "super_secret_value";
} finally {
if ($span) {
$span->end();
$scope->detach();
}
}
}
}

use OpenTelemetry\SDK\Trace\SpanExporter\InMemoryExporter;
use OpenTelemetry\SDK\Trace\TracerProvider;

// Setup OTel SDK for testing
$exporter = new InMemoryExporter();
$tracerProvider = new TracerProvider(new SimpleSpanProcessor($exporter));

echo "--- Test 1: Disabled by default ---\n";
putenv('GOOGLE_CLOUD_TELEMETRY_ENABLED'); // Unset
$client1 = new SecretManagerClientMock();
$client1->accessSecretVersion('projects/my-project/secrets/my-secret');
echo "Spans generated: " . count($exporter->getSpans()) . "\n\n";

echo "--- Test 2: Enabled via Constructor ---\n";
$exporter2 = new InMemoryExporter();
$tracerProvider2 = new TracerProvider(new SimpleSpanProcessor($exporter2));
$client2 = new SecretManagerClientMock($tracerProvider2);
$client2->accessSecretVersion('projects/my-project/secrets/my-secret');
echo "Spans generated: " . count($exporter2->getSpans()) . "\n\n";

echo "--- Test 3: Global Override (Disabled) ---\n";
putenv('GOOGLE_CLOUD_TELEMETRY_ENABLED=false');
$exporter3 = new InMemoryExporter();
$tracerProvider3 = new TracerProvider(new SimpleSpanProcessor($exporter3));
$client3 = new SecretManagerClientMock($tracerProvider3);
$client3->accessSecretVersion('projects/my-project/secrets/my-secret');
echo "Spans generated: " . count($exporter3->getSpans()) . "\n\n";

echo "--- Test 4: Environment Enablement (Global Provider) ---\n";
putenv('GOOGLE_CLOUD_TELEMETRY_ENABLED=true');
$exporter4 = new InMemoryExporter();
$tracerProvider4 = new TracerProvider(new SimpleSpanProcessor($exporter4));
\OpenTelemetry\API\Globals::registerInitializer(function(\OpenTelemetry\API\Instrumentation\Configurator $configurator) use ($tracerProvider4) {
return $configurator->withTracerProvider($tracerProvider4);
});
$client4 = new SecretManagerClientMock();
$client4->accessSecretVersion('projects/my-project/secrets/my-secret');
echo "Spans generated: " . count($exporter4->getSpans()) . "\n";


59 changes: 59 additions & 0 deletions observability-pocs/poc_1_pubsub_streaming.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php
// Mocking the Pub/Sub Streaming Pull loop to verify Span Context does not leak.
require __DIR__ . '/../vendor/autoload.php';

use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\SDK\Trace\TracerProvider;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\SpanExporter\InMemoryExporter;

$exporter = new InMemoryExporter();
$tracerProvider = new TracerProvider(new SimpleSpanProcessor($exporter));
$tracer = $tracerProvider->getTracer('google-cloud-pubsub-mock', '1.0.0');

// Mock Streaming Pull Loop
echo "Starting Pub/Sub Streaming Pull PoC...\n";

// The overall subscriber span (T2)
$subscriberSpan = $tracer->spanBuilder('Subscriber.StreamingPull')
->setSpanKind(SpanKind::KIND_CLIENT)
->startSpan();
$subscriberScope = $subscriberSpan->activate();

try {
for ($i = 1; $i <= 3; $i++) {
// T3: Process individual message
$messageSpan = $tracer->spanBuilder("ProcessMessage $i")
->setSpanKind(SpanKind::KIND_INTERNAL)
->startSpan();

$messageScope = $messageSpan->activate();
try {
// Simulate user callback executing
echo "Processing message $i in trace " . $messageSpan->getContext()->getTraceId() . "\n";

// T4: Simulated network call inside the user callback (e.g. acking the message)
$ackSpan = $tracer->spanBuilder("POST /v1/projects/p/subscriptions/s:acknowledge")
->setSpanKind(SpanKind::KIND_CLIENT)
->startSpan();
$ackScope = $ackSpan->activate();
try {
// Network delay
} finally {
$ackSpan->end();
$ackScope->detach();
}
} finally {
$messageSpan->end();
$messageScope->detach(); // CRITICAL: Detaching prevents context leak to next message loop
}
}
} finally {
$subscriberSpan->end();
$subscriberScope->detach();
}

echo "\nTotal Spans Exported: " . count($exporter->getSpans()) . "\n";
foreach ($exporter->getSpans() as $span) {
echo "- Span: " . $span->getName() . " | Parent ID: " . $span->getParentContext()->getSpanId() . "\n";
}
66 changes: 66 additions & 0 deletions observability-pocs/poc_2_gapic_resource_name.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php
// Mocking the GAPIC generator dynamic path parsing heuristic

$tests = [
[
'api_domain' => 'pubsub.googleapis.com',
'http_annotation' => 'v1/{name=projects/*/topics/*}',
'runtime_path' => 'v1/projects/my-project/topics/my-topic'
],
[
'api_domain' => 'spanner.googleapis.com',
'http_annotation' => 'v1/{session=projects/*/instances/*/databases/*/sessions/*}:commit',
'runtime_path' => 'v1/projects/p/instances/i/databases/d/sessions/s:commit'
],
[
'api_domain' => 'storage.googleapis.com',
'http_annotation' => 'b/{bucket}/o/{object}',
'runtime_path' => 'b/my-bucket/o/my-file.txt'
]
];

echo "Starting GAPIC Resource Name Parser PoC...\n\n";

foreach ($tests as $test) {
echo "Testing annotation: " . $test['http_annotation'] . "\n";

// In gapic-generator-php, this regex generation would happen at compile-time.
// We convert the annotation to a regex pattern capturing the dynamic parts.
// Simplified regex generation for PoC:
$pattern = preg_replace('/\{([a-zA-Z0-9_]+)=([^\}]+)\}/', '(?P<$1>$2)', $test['http_annotation']);
// Handle simple {bucket} cases without explicit wildcards
$pattern = preg_replace('/\{([a-zA-Z0-9_]+)\}/', '(?P<$1>[^/:]+)', $pattern);

// Replace * with regex wildcard for the generated pattern
$pattern = str_replace('*', '[^/:]+', $pattern);
$pattern = '#^' . $pattern . '$#';

echo " Generated Regex: $pattern\n";

// At runtime in the client library, we apply the regex to the actual path.
if (preg_match($pattern, $test['runtime_path'], $matches)) {
// Find the named capturing group that matches a valid GCP resource structure
// (For simplicity in PoC, we just take the first named match that contains a slash)
$extractedResource = null;
foreach ($matches as $key => $value) {
if (is_string($key) && strpos($value, '/') !== false) {
$extractedResource = $value;
break;
}
}

if (!$extractedResource && isset($matches[1])) {
// Fallback for simple non-slashed variables (like bucket name)
$extractedResource = $matches['bucket'] ?? null;
}

if ($extractedResource) {
$formattedId = '//' . $test['api_domain'] . '/' . $extractedResource;
echo " Extracted Resource ID: " . $formattedId . "\n\n";
} else {
echo " FAILED to extract resource.\n\n";
}
} else {
echo " FAILED to match runtime path.\n\n";
}
}
61 changes: 61 additions & 0 deletions observability-pocs/poc_3_metric_exemplars.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php
// Mocking Metric Exemplars with OpenTelemetry PHP SDK
require __DIR__ . '/../vendor/autoload.php';

use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\SDK\Trace\TracerProvider;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\SpanExporter\InMemoryExporter as InMemoryTraceExporter;
use OpenTelemetry\SDK\Metrics\MeterProvider;
use OpenTelemetry\SDK\Metrics\MetricExporter\InMemoryExporter as InMemoryMetricExporter;
use OpenTelemetry\SDK\Metrics\MetricReader\ExportingReader;

$traceExporter = new InMemoryTraceExporter();
$tracerProvider = new TracerProvider(new SimpleSpanProcessor($traceExporter));
$tracer = $tracerProvider->getTracer('poc-tracer');

$metricExporter = new InMemoryMetricExporter();
$meterProvider = OpenTelemetry\SDK\Metrics\MeterProvider::builder()
->addReader(new ExportingReader($metricExporter))
->build();
$meter = $meterProvider->getMeter('poc-meter');

$histogram = $meter->createHistogram(
'gcp.client.request.duration',
'ms',
'Duration of client request'
);

echo "Starting Metric Exemplars PoC...\n";

// Start a trace
$span = $tracer->spanBuilder('Storage.Upload')->setSpanKind(SpanKind::KIND_CLIENT)->startSpan();
$scope = $span->activate();

try {
echo "Active Trace ID: " . $span->getContext()->getTraceId() . "\n";
echo "Active Span ID: " . $span->getContext()->getSpanId() . "\n";

// Simulate workload
usleep(150000); // 150ms

// Record the metric WHILE the trace context is active.
// The OTel SDK should automatically attach the active trace as an exemplar.
$histogram->record(150.5, ['gcp.client.service' => 'storage']);
echo "Recorded metric: 150.5 ms\n";
} finally {
$span->end();
$scope->detach();
}

// Force flush the metrics reader to the memory exporter
$meterProvider->shutdown();

echo "\nVerifying Metric Export for Exemplars:\n";
$metrics = $metricExporter->collect();

foreach ($metrics as $metric) {
if ($metric->name === 'gcp.client.request.duration') {
echo print_r($metric->data, true);
}
}
Loading