Skip to content
This repository was archived by the owner on Oct 3, 2023. It is now read-only.

Commit 30bfa18

Browse files
authored
Capture stacktrace (#21)
* Add failing test for returning a backtrace when capturing spans * Capture the backtrace at the moment we create a span and store it * Send the right number of arguments to zend_fetch_debug_backtrace * Add failing test for capturing the backtrace when a TraceSpan is created * Capture a filtered backtrace when a TraceSpan is created * Add failing test for parsing backtrace into the /stacktrace label for GoogleCloudReporter * Format and save the /stackframe label from the backtrace * None of the stackframe keys are guaranteed to be present * array_filter preserves indexes, we don't want to
1 parent b50c5bd commit 30bfa18

9 files changed

Lines changed: 206 additions & 3 deletions

ext/opencensus_trace.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,8 @@ static opencensus_trace_span_t *opencensus_trace_begin(zend_string *function_nam
244244
{
245245
opencensus_trace_span_t *span = opencensus_trace_span_alloc();
246246

247+
zend_fetch_debug_backtrace(&span->backtrace, 1, DEBUG_BACKTRACE_IGNORE_ARGS, 0);
248+
247249
span->start = opencensus_now();
248250
span->name = zend_string_copy(function_name);
249251
span->kind = OPENCENSUS_TRACE_SPAN_KIND_UNKNOWN;
@@ -610,6 +612,8 @@ PHP_FUNCTION(opencensus_trace_list)
610612
ZVAL_ARR(&label, trace_span->labels);
611613
zend_update_property(opencensus_trace_span_ce, &span, "labels", sizeof("labels") - 1, &label);
612614

615+
zend_update_property(opencensus_trace_span_ce, &span, "backtrace", sizeof("backtrace") - 1, &trace_span->backtrace);
616+
613617
add_next_index_zval(return_value, &span);
614618
} ZEND_HASH_FOREACH_END();
615619
}

ext/opencensus_trace_span.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,23 @@ static PHP_METHOD(OpenCensusTraceSpan, endTime) {
209209
RETURN_ZVAL(val, 1, 0);
210210
}
211211

212+
/**
213+
* Fetch the backtrace from the moment the span was started
214+
*
215+
* @return array
216+
*/
217+
static PHP_METHOD(OpenCensusTraceSpan, backtrace) {
218+
zval *val, rv;
219+
220+
if (zend_parse_parameters_none() == FAILURE) {
221+
return;
222+
}
223+
224+
val = zend_read_property(opencensus_trace_span_ce, getThis(), "backtrace", sizeof("backtrace") - 1, 1, &rv);
225+
226+
RETURN_ZVAL(val, 1, 0);
227+
}
228+
212229
/**
213230
* Fetch the span kind
214231
*
@@ -235,6 +252,7 @@ static zend_function_entry opencensus_trace_span_methods[] = {
235252
PHP_ME(OpenCensusTraceSpan, labels, NULL, ZEND_ACC_PUBLIC)
236253
PHP_ME(OpenCensusTraceSpan, startTime, NULL, ZEND_ACC_PUBLIC)
237254
PHP_ME(OpenCensusTraceSpan, endTime, NULL, ZEND_ACC_PUBLIC)
255+
PHP_ME(OpenCensusTraceSpan, backtrace, NULL, ZEND_ACC_PUBLIC)
238256
PHP_ME(OpenCensusTraceSpan, kind, NULL, ZEND_ACC_PUBLIC)
239257
PHP_FE_END
240258
};
@@ -257,6 +275,7 @@ int opencensus_trace_span_minit(INIT_FUNC_ARGS) {
257275
zend_declare_property_null(opencensus_trace_span_ce, "endTime", sizeof("endTime") - 1, ZEND_ACC_PROTECTED TSRMLS_CC);
258276
zend_declare_property_null(opencensus_trace_span_ce, "kind", sizeof("kind") - 1, ZEND_ACC_PROTECTED TSRMLS_CC);
259277
zend_declare_property_null(opencensus_trace_span_ce, "labels", sizeof("labels") - 1, ZEND_ACC_PROTECTED TSRMLS_CC);
278+
zend_declare_property_null(opencensus_trace_span_ce, "backtrace", sizeof("backtrace") - 1, ZEND_ACC_PROTECTED TSRMLS_CC);
260279

261280
REGISTER_TRACE_SPAN_CONSTANT(KIND_UNKNOWN);
262281
REGISTER_TRACE_SPAN_CONSTANT(KIND_CLIENT);

ext/opencensus_trace_span.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ typedef struct opencensus_trace_span_t {
3434
double start;
3535
double stop;
3636
struct opencensus_trace_span_t *parent;
37+
zval backtrace;
3738
zend_long kind;
3839

3940
// zend_string* => zval*

ext/tests/backtrace_test.phpt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
--TEST--
2+
OpenCensus Trace: Customize the trace span options for a function
3+
--FILE--
4+
<?php
5+
6+
function abcd()
7+
{
8+
return 3;
9+
}
10+
11+
function myFunction()
12+
{
13+
abcd();
14+
}
15+
16+
opencensus_trace_function('abcd');
17+
myFunction();
18+
19+
$traces = opencensus_trace_list();
20+
echo "Number of traces: " . count($traces) . "\n";
21+
22+
foreach ($traces as $span) {
23+
var_dump($span->backtrace());
24+
}
25+
?>
26+
--EXPECTF--
27+
Number of traces: 1
28+
array(2) {
29+
[0]=>
30+
array(3) {
31+
["file"]=>
32+
string(%d) "%s/backtrace_test.php"
33+
["line"]=>
34+
int(10)
35+
["function"]=>
36+
string(4) "abcd"
37+
}
38+
[1]=>
39+
array(3) {
40+
["file"]=>
41+
string(%d) "%s/backtrace_test.php"
42+
["line"]=>
43+
int(14)
44+
["function"]=>
45+
string(10) "myFunction"
46+
}
47+
}

ext/tests/manual_spans_default_options.phpt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ Array
3333
(
3434
)
3535

36+
[backtrace:protected] => Array
37+
(
38+
)
39+
3640
)
3741

3842
[1] => OpenCensus\Trace\Span Object
@@ -47,6 +51,10 @@ Array
4751
(
4852
)
4953

54+
[backtrace:protected] => Array
55+
(
56+
)
57+
5058
)
5159

5260
)

src/Trace/Reporter/GoogleCloudReporter.php

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,19 +211,47 @@ public function convertSpans(TracerInterface $tracer)
211211
$kind = array_key_exists($span->kind(), $spanKindMap)
212212
? $spanKindMap[$span->kind()]
213213
: TraceSpan::SPAN_KIND_UNSPECIFIED;
214+
$labels = $span->labels();
215+
$labels[self::STACKTRACE] = $this->formatBacktrace($span->backtrace());
214216
return new TraceSpan([
215217
'name' => $span->name(),
216218
'startTime' => $span->startTime(),
217219
'endTime' => $span->endTime(),
218220
'spanId' => $span->spanId(),
219221
'parentSpanId' => $span->parentSpanId(),
220-
'labels' => $span->labels(),
221-
'kind' => $kind
222+
'labels' => $labels,
223+
'kind' => $kind,
222224
]);
223225
$span->info();
224226
}, $tracer->spans());
225227
}
226228

229+
private function formatBacktrace($bt)
230+
{
231+
return json_encode([
232+
'stack_frame' => array_map([$this, 'mapStackframe'], $bt)
233+
]);
234+
}
235+
236+
private function mapStackframe($sf)
237+
{
238+
// file and line should always be set
239+
$data = [];
240+
if (isset($sf['line'])) {
241+
$data['line_number'] = $sf['line'];
242+
}
243+
if (isset($sf['file'])) {
244+
$data['file_name'] = $sf['file'];
245+
}
246+
if (isset($sf['function'])) {
247+
$data['method_name'] = $sf['function'];
248+
}
249+
if (isset($sf['class'])) {
250+
$data['class_name'] = $sf['class'];
251+
}
252+
return $data;
253+
}
254+
227255
/**
228256
* Returns an array representation of a callback which will be used to write
229257
* batch items.

src/Trace/TraceSpan.php

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,13 @@ public function __construct($options = [])
8282
$this->info['spanId'] = $this->generateSpanId();
8383
}
8484

85+
if (array_key_exists('backtrace', $options)) {
86+
$this->info['backtrace'] = $options['backtrace'];
87+
unset($options['backtrace']);
88+
} else {
89+
$this->info['backtrace'] = $this->generateBacktrace();
90+
}
91+
8592
if (array_key_exists('kind', $options)) {
8693
$this->info['kind'] = $options['kind'];
8794
unset($options['kind']);
@@ -101,6 +108,7 @@ public function __construct($options = [])
101108
unset($options['parentSpanId']);
102109
}
103110

111+
104112
$this->info['metadata'] = $options;
105113
}
106114

@@ -192,6 +200,16 @@ public function labels()
192200
: [];
193201
}
194202

203+
/**
204+
* Retrieve the backtrace at the moment this span was created
205+
*
206+
* @return array
207+
*/
208+
public function backtrace()
209+
{
210+
return $this->info['backtrace'];
211+
}
212+
195213
/**
196214
* Retrieve the kind of span
197215
*
@@ -275,6 +293,20 @@ private function generateSpanId()
275293
return mt_rand();
276294
}
277295

296+
/**
297+
* Return a filtered backtrace where we strip out all functions from the OpenCensus\Trace namespace
298+
*
299+
* @return array
300+
*/
301+
private function generateBacktrace()
302+
{
303+
return array_values(
304+
array_filter(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), function ($bt) {
305+
return !array_key_exists('class', $bt) || substr($bt['class'], 0, 16) != 'OpenCensus\Trace';
306+
})
307+
);
308+
}
309+
278310
/**
279311
* Generate a name for this span. Attempts to generate a name
280312
* based on the caller's code.
@@ -284,7 +316,7 @@ private function generateSpanId()
284316
private function generateSpanName()
285317
{
286318
// Try to find the first stacktrace class entry that doesn't start with OpenCensus\Trace
287-
foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $bt) {
319+
foreach ($this->backtrace() as $bt) {
288320
$bt += ['line' => null];
289321
if (!array_key_exists('class', $bt)) {
290322
return implode('/', array_filter(['app', basename($bt['file']), $bt['function'], $bt['line']]));

tests/unit/Trace/Reporter/GoogleCloudReporterTest.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,38 @@ public function labelHeaders()
113113
['HTTP_X_APPENGINE_COUNTRY', 'US', '/http/client_country', 'US']
114114
];
115115
}
116+
117+
public function testStacktraceLabel()
118+
{
119+
$backtrace = [
120+
[
121+
'file' => '/path/to/file.php',
122+
'class' => 'Foo',
123+
'line' => 1234,
124+
'function' => 'asdf',
125+
'type' => '::'
126+
]
127+
];
128+
$tracer = new ContextTracer(new TraceContext('testtraceid'));
129+
$tracer->inSpan(['backtrace' => $backtrace], function () {});
130+
131+
$reporter = new GoogleCloudReporter(['client' => $this->client->reveal()]);
132+
$spans = $reporter->convertSpans($tracer);
133+
134+
$labels = $spans[0]->info()['labels'];
135+
$this->assertArrayHasKey('/stacktrace', $labels);
136+
137+
$expected = [
138+
'stack_frame' => [
139+
[
140+
'file_name' => '/path/to/file.php',
141+
'line_number' => 1234,
142+
'method_name' => 'asdf',
143+
'class_name' => 'Foo'
144+
]
145+
]
146+
];
147+
$data = json_decode($labels['/stacktrace'], true);
148+
$this->assertEquals($expected, $data);
149+
}
116150
}

tests/unit/Trace/TraceSpanTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,36 @@ public function testIgnoresUnknownFields()
130130
$this->assertArrayNotHasKey('extravalue', $info);
131131
}
132132

133+
public function testGeneratesBacktrace()
134+
{
135+
$traceSpan = new TraceSpan();
136+
$this->assertInternalType('array', $traceSpan->backtrace());
137+
$this->assertTrue(count($traceSpan->backtrace()) > 0);
138+
$stackframe = $traceSpan->backtrace()[0];
139+
$this->assertEquals('testGeneratesBacktrace', $stackframe['function']);
140+
$this->assertEquals(self::class, $stackframe['class']);
141+
}
142+
143+
public function testOverrideBacktrace()
144+
{
145+
$backtrace = [
146+
[
147+
'class' => 'Foo',
148+
'line' => 1234,
149+
'function' => 'asdf',
150+
'type' => '::'
151+
]
152+
];
153+
$traceSpan = new TraceSpan([
154+
'backtrace' => $backtrace
155+
]);
156+
157+
$this->assertCount(1, $traceSpan->backtrace());
158+
$stackframe = $traceSpan->backtrace()[0];
159+
$this->assertEquals('asdf', $stackframe['function']);
160+
$this->assertEquals('Foo', $stackframe['class']);
161+
}
162+
133163
/**
134164
* @dataProvider timestampFields
135165
*/

0 commit comments

Comments
 (0)