Skip to content

Commit 851be40

Browse files
committed
Merge gc_status() callbacks to prevent inconsistent metrics if gc triggered in between callbacks
1 parent cc565ab commit 851be40

2 files changed

Lines changed: 60 additions & 77 deletions

File tree

src/Metrics/Runtime/src/GarbageCollectionMetrics.php

Lines changed: 43 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use OpenTelemetry\API\Metrics\MeterInterface;
88
use OpenTelemetry\API\Metrics\ObserverInterface;
9+
use const PHP_VERSION_ID;
910

1011
/**
1112
* @internal
@@ -36,72 +37,66 @@ public static function register(MeterInterface $meter): void
3637
'{object}',
3738
'Current number of objects in the root buffer',
3839
);
40+
$collectorTime = $meter->createObservableCounter(
41+
'php.gc.collector_time',
42+
's',
43+
'Cumulative time spent in the garbage collector',
44+
);
45+
$destructorTime = $meter->createObservableCounter(
46+
'php.gc.destructor_time',
47+
's',
48+
'Cumulative time spent running destructors during GC',
49+
);
50+
$freeTime = $meter->createObservableCounter(
51+
'php.gc.free_time',
52+
's',
53+
'Cumulative time spent freeing memory during GC',
54+
);
55+
$processUptime = $meter->createObservableGauge(
56+
'process.uptime',
57+
's',
58+
'The time the process has been running',
59+
);
3960

4061
$meter->batchObserve(
4162
static function (
4263
ObserverInterface $runsObs,
4364
ObserverInterface $collectedObs,
4465
ObserverInterface $thresholdObs,
4566
ObserverInterface $rootsObs,
67+
ObserverInterface $collectorObs,
68+
ObserverInterface $destructorObs,
69+
ObserverInterface $freeObs,
70+
ObserverInterface $uptimeObs,
4671
): void {
4772
$status = gc_status();
4873
$runsObs->observe($status['runs']);
4974
$collectedObs->observe($status['collected']);
5075
$thresholdObs->observe($status['threshold']);
5176
$rootsObs->observe($status['roots']);
77+
78+
if (PHP_VERSION_ID < 80300) { // Timing metrics available since PHP 8.3
79+
return;
80+
}
81+
82+
/** @var array<string, int|float> $status */
83+
// @phan-suppress-next-line PhanTypeInvalidDimOffset, PhanTypeMismatchArgument -- fields added in PHP 8.3
84+
$collectorObs->observe($status['collector_time']);
85+
// @phan-suppress-next-line PhanTypeInvalidDimOffset, PhanTypeMismatchArgument -- fields added in PHP 8.3
86+
$destructorObs->observe($status['destructor_time']);
87+
// @phan-suppress-next-line PhanTypeInvalidDimOffset, PhanTypeMismatchArgument -- fields added in PHP 8.3
88+
$freeObs->observe($status['free_time']);
89+
// @phan-suppress-next-line PhanTypeInvalidDimOffset, PhanTypeMismatchArgument -- fields added in PHP 8.3
90+
$uptimeObs->observe($status['application_time']);
5291
},
5392
$runs,
5493
$collected,
5594
$threshold,
5695
$roots,
96+
$collectorTime,
97+
$destructorTime,
98+
$freeTime,
99+
$processUptime,
57100
);
58-
59-
// Timing metrics available since PHP 8.3
60-
if (PHP_VERSION_ID >= 80300) {
61-
$collectorTime = $meter->createObservableCounter(
62-
'php.gc.collector_time',
63-
's',
64-
'Cumulative time spent in the garbage collector',
65-
);
66-
$destructorTime = $meter->createObservableCounter(
67-
'php.gc.destructor_time',
68-
's',
69-
'Cumulative time spent running destructors during GC',
70-
);
71-
$freeTime = $meter->createObservableCounter(
72-
'php.gc.free_time',
73-
's',
74-
'Cumulative time spent freeing memory during GC',
75-
);
76-
$processUptime = $meter->createObservableGauge(
77-
'process.uptime',
78-
's',
79-
'The time the process has been running',
80-
);
81-
82-
$meter->batchObserve(
83-
static function (
84-
ObserverInterface $collectorObs,
85-
ObserverInterface $destructorObs,
86-
ObserverInterface $freeObs,
87-
ObserverInterface $uptimeObs,
88-
): void {
89-
/** @var array<string, int|float> $status */
90-
$status = gc_status();
91-
// @phan-suppress-next-line PhanTypeInvalidDimOffset, PhanTypeMismatchArgument -- fields added in PHP 8.3
92-
$collectorObs->observe($status['collector_time']);
93-
// @phan-suppress-next-line PhanTypeInvalidDimOffset, PhanTypeMismatchArgument -- fields added in PHP 8.3
94-
$destructorObs->observe($status['destructor_time']);
95-
// @phan-suppress-next-line PhanTypeInvalidDimOffset, PhanTypeMismatchArgument -- fields added in PHP 8.3
96-
$freeObs->observe($status['free_time']);
97-
// @phan-suppress-next-line PhanTypeInvalidDimOffset, PhanTypeMismatchArgument -- fields added in PHP 8.3
98-
$uptimeObs->observe($status['application_time']);
99-
},
100-
$collectorTime,
101-
$destructorTime,
102-
$freeTime,
103-
$processUptime,
104-
);
105-
}
106101
}
107102
}

src/Metrics/Runtime/tests/Unit/GarbageCollectionMetricsTest.php

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,15 @@ public function test_register_creates_all_instruments(): void
1919
{
2020
$meter = $this->createMock(MeterInterface::class);
2121

22-
$expectedCounters = PHP_VERSION_ID >= 80300 ? 5 : 2;
23-
$meter->expects($this->exactly($expectedCounters))
22+
$meter->expects($this->exactly(5))
2423
->method('createObservableCounter')
2524
->willReturn($this->createMock(ObservableCounterInterface::class));
2625

27-
$meter->expects($this->exactly(PHP_VERSION_ID >= 80300 ? 3 : 2))
26+
$meter->expects($this->exactly(3))
2827
->method('createObservableGauge')
2928
->willReturn($this->createMock(ObservableGaugeInterface::class));
3029

31-
$expectedBatchObserve = PHP_VERSION_ID >= 80300 ? 2 : 1;
32-
$meter->expects($this->exactly($expectedBatchObserve))
30+
$meter->expects($this->exactly(1))
3331
->method('batchObserve')
3432
->willReturn($this->createMock(ObservableCallbackInterface::class));
3533

@@ -64,52 +62,42 @@ public function test_gc_runs_callback_observes_integer(): void
6462
$this->createMock(ObserverInterface::class),
6563
$this->createMock(ObserverInterface::class),
6664
$this->createMock(ObserverInterface::class),
65+
$this->createMock(ObserverInterface::class),
66+
$this->createMock(ObserverInterface::class),
67+
$this->createMock(ObserverInterface::class),
68+
$this->createMock(ObserverInterface::class),
6769
);
6870
}
6971

70-
public function test_gc_timing_metrics_registered_on_php83(): void
71-
{
72-
if (PHP_VERSION_ID < 80300) {
73-
$this->markTestSkipped('GC timing metrics require PHP 8.3+');
74-
}
75-
76-
$meter = $this->createMock(MeterInterface::class);
77-
$meter->expects($this->exactly(5))
78-
->method('createObservableCounter')
79-
->willReturn($this->createMock(ObservableCounterInterface::class));
80-
$meter->method('createObservableGauge')
81-
->willReturn($this->createMock(ObservableGaugeInterface::class));
82-
$meter->expects($this->exactly(2))
83-
->method('batchObserve')
84-
->willReturn($this->createMock(ObservableCallbackInterface::class));
85-
86-
GarbageCollectionMetrics::register($meter);
87-
}
88-
8972
public function test_gc_timing_callback_observes_float(): void
9073
{
9174
if (PHP_VERSION_ID < 80300) {
9275
$this->markTestSkipped('GC timing metrics require PHP 8.3+');
9376
}
9477

95-
$callbacks = [];
78+
$callback = null;
9679
$meter = $this->createMock(MeterInterface::class);
9780
$meter->method('createObservableCounter')->willReturn($this->createMock(ObservableCounterInterface::class));
9881
$meter->method('createObservableGauge')->willReturn($this->createMock(ObservableGaugeInterface::class));
9982
$meter->method('batchObserve')
100-
->willReturnCallback(function (callable $cb) use (&$callbacks): ObservableCallbackInterface {
101-
$callbacks[] = $cb;
83+
->willReturnCallback(function (callable $cb) use (&$callback): ObservableCallbackInterface {
84+
$callback = $cb;
10285

10386
return $this->createMock(ObservableCallbackInterface::class);
10487
});
10588

10689
GarbageCollectionMetrics::register($meter);
10790

108-
// $callbacks[1] is the PHP 8.3 timing batchObserve (collector_time, destructor_time, free_time)
10991
$collectorObs = $this->createMock(ObserverInterface::class);
11092
$collectorObs->expects($this->once())->method('observe')->with($this->isType('float'));
11193

112-
$callbacks[1](
94+
$this->assertNotNull($callback);
95+
96+
$callback(
97+
$this->createMock(ObserverInterface::class),
98+
$this->createMock(ObserverInterface::class),
99+
$this->createMock(ObserverInterface::class),
100+
$this->createMock(ObserverInterface::class),
113101
$collectorObs,
114102
$this->createMock(ObserverInterface::class),
115103
$this->createMock(ObserverInterface::class),

0 commit comments

Comments
 (0)