Skip to content

Commit 8402cb5

Browse files
fix(profiling): ddup support arm, fix import on osx/windows [backport 1.18] (#6670)
Backport d995ad3 from #6529 to 1.18. The recent libdatadog merge introduced a few issues on various platforms * arm Linux * Windows * macos This patch * adds better guards for not-supported platforms * completes the support for arm-linux * fixes a bug in the memory profiler when ddup is enabled * rationalizes the return type of various profiling interfaces to using a common DDFrame namedtuple (legacy-exporter is not modified for purposes of stability) * eliminates the auto-backtrace capability of the ddup exporter; this was causing issues on some musl distributions and was also interfering with other tools for native backtraces ## Checklist - [X] Change(s) are motivated and described in the PR description. - [X] Testing strategy is described if automated tests are not included in the PR. - [X] Risk is outlined (performance impact, potential for breakage, maintainability, etc). - [X] Change is maintainable (easy to change, telemetry, documentation). - [X] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed. If no release note is required, add label `changelog/no-changelog`. - [X] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)). - [X] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Title is accurate. - [x] No unnecessary changes are introduced. - [x] Description motivates each change. - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes unless absolutely necessary. - [x] Testing strategy adequately addresses listed risk(s). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] Release note makes sense to a user of the library. - [x] Reviewer has explicitly acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment. - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: David Sanchez <[email protected]>
1 parent a176097 commit 8402cb5

File tree

16 files changed

+183
-51
lines changed

16 files changed

+183
-51
lines changed

ddtrace/internal/datadog/profiling/ddup.pyx renamed to ddtrace/internal/datadog/profiling/_ddup.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ from ddtrace.internal.constants import DEFAULT_SERVICE_NAME
99
from ddtrace.span import Span
1010

1111

12-
IF UNAME_SYSNAME == "Linux" and UNAME_MACHINE == "x86_64":
12+
IF UNAME_SYSNAME == "Linux":
1313
cdef extern from "exporter.hpp":
1414
ctypedef enum ProfileType "ProfileType":
1515
CPU "ProfileType::CPU"
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
try:
2+
from ._ddup import * # noqa: F403, F401
3+
except ImportError:
4+
from typing import Optional
5+
from typing import Dict
6+
from ddtrace.span import Span
7+
8+
# Decorator for not-implemented
9+
def not_implemented(func):
10+
def wrapper(*args, **kwargs):
11+
raise NotImplementedError("{} is not implemented on this platform".format(func.__name__))
12+
13+
@not_implemented
14+
def init(
15+
env, # type: Optional[str]
16+
service, # type: Optional[str]
17+
version, # type: Optional[str]
18+
tags, # type: Optional[Dict[str, str]]
19+
max_nframes, # type: Optional[int]
20+
url, # type: Optional[str]
21+
):
22+
pass
23+
24+
@not_implemented
25+
def start_sample(nframes): # type: (int) -> None
26+
pass
27+
28+
@not_implemented
29+
def push_cputime(value, count): # type: (int, int) -> None
30+
pass
31+
32+
@not_implemented
33+
def push_walltime(value, count): # type: (int, int) -> None
34+
pass
35+
36+
@not_implemented
37+
def push_acquire(value, count): # type: (int, int) -> None
38+
pass
39+
40+
@not_implemented
41+
def push_release(value, count): # type: (int, int) -> None
42+
pass
43+
44+
@not_implemented
45+
def push_alloc(value, count): # type: (int, int) -> None
46+
pass
47+
48+
@not_implemented
49+
def push_heap(value): # type: (int) -> None
50+
pass
51+
52+
@not_implemented
53+
def push_lock_name(lock_name): # type: (str) -> None
54+
pass
55+
56+
@not_implemented
57+
def push_frame(name, filename, address, line): # type: (str, str, int, int) -> None
58+
pass
59+
60+
@not_implemented
61+
def push_threadinfo(thread_id, thread_native_id, thread_name): # type: (int, int, Optional[str]) -> None
62+
pass
63+
64+
@not_implemented
65+
def push_taskinfo(task_id, task_name): # type: (int, str) -> None
66+
pass
67+
68+
@not_implemented
69+
def push_exceptioninfo(exc_type, count): # type: (type, int) -> None
70+
pass
71+
72+
@not_implemented
73+
def push_class_name(class_name): # type: (str) -> None
74+
pass
75+
76+
@not_implemented
77+
def push_span(span, endpoint_collection_enabled): # type: (Optional[Span], bool) -> None
78+
pass
79+
80+
@not_implemented
81+
def flush_sample(): # type: () -> None
82+
pass
83+
84+
@not_implemented
85+
def upload(): # type: () -> None
86+
pass

ddtrace/internal/datadog/profiling/src/exporter.cpp

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,6 @@
22
// under the Apache License Version 2.0. This product includes software
33
// developed at Datadog (https://www.datadoghq.com/). Copyright 2021-Present
44
// Datadog, Inc.
5-
6-
// High-level skip for invalid architectures
7-
#ifndef __linux__
8-
#elif __aarch64__
9-
#elif __i386__
10-
#else
11-
125
#include "exporter.hpp"
136
#include <iostream>
147

@@ -675,5 +668,3 @@ Profile::push_class_name(std::string_view class_name)
675668
}
676669
return true;
677670
}
678-
679-
#endif

ddtrace/internal/datadog/profiling/src/interface.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ ddup_config_max_nframes(int max_nframes)
8787
profile_builder.set_max_nframes(max_nframes);
8888
}
8989

90+
#if DDUP_BACKTRACE_ENABLE
9091
inline static void
9192
print_backtrace()
9293
{
@@ -116,22 +117,27 @@ print_backtrace()
116117

117118
free(symbols);
118119
}
120+
119121
static void
120122
sigsegv_handler(int sig, siginfo_t* si, void* uc)
121123
{
122124
(void)uc;
123125
print_backtrace();
124126
exit(-1);
125127
}
128+
129+
#endif
126130
void
127131
ddup_init()
128132
{
129133
if (!is_initialized) {
134+
#if DDUP_BACKTRACE_ENABLE
130135
// Install segfault handler
131136
struct sigaction sigaction_handlers = {};
132137
sigaction_handlers.sa_sigaction = sigsegv_handler;
133138
sigaction_handlers.sa_flags = SA_SIGINFO;
134139
sigaction(SIGSEGV, &(sigaction_handlers), NULL);
140+
#endif
135141

136142
g_profile_real[0] = profile_builder.build_ptr();
137143
g_profile_real[1] = profile_builder.build_ptr();

ddtrace/profiling/collector/_memalloc.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,12 @@ PyInit__memalloc(void)
436436
if (m == NULL)
437437
return NULL;
438438

439+
// Initialize the DDFrame namedtuple class
440+
// Do this early so we don't have complicated cleanup
441+
if (!memalloc_ddframe_class_init()) {
442+
return NULL;
443+
}
444+
439445
#ifdef _PY37_AND_LATER
440446
if (PyThread_tss_create(&memalloc_reentrant_key) != 0) {
441447
#else

ddtrace/profiling/collector/_memalloc.pyi

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import typing
22

3-
# (filename, line number, function name)
4-
FrameType = typing.Tuple[str, int, str]
3+
from .. import event
54

6-
StackType = typing.Tuple[FrameType, ...]
5+
# (filename, line number, function name)
6+
FrameType = event.DDFrame
7+
StackType = event.StackTraceType
78

89
# (stack, nframe, thread_id)
910
TracebackType = typing.Tuple[StackType, int, int]

ddtrace/profiling/collector/_memalloc_tb.c

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,39 @@ static PyObject* empty_string = NULL;
1818

1919
#define TRACEBACK_SIZE(NFRAME) (sizeof(traceback_t) + sizeof(frame_t) * (NFRAME - 1))
2020

21+
static PyObject* ddframe_class = NULL;
22+
23+
bool
24+
memalloc_ddframe_class_init()
25+
{
26+
// If this is double-initialized for some reason, then clean up what we had
27+
if (ddframe_class) {
28+
Py_DECREF(ddframe_class);
29+
ddframe_class = NULL;
30+
}
31+
32+
// Import the module that contains the DDFrame class
33+
PyObject* mod_path = PyUnicode_DecodeFSDefault("ddtrace.profiling.event");
34+
PyObject* mod = PyImport_Import(mod_path);
35+
Py_XDECREF(mod_path);
36+
if (mod == NULL) {
37+
PyErr_Print();
38+
return false;
39+
}
40+
41+
// Get the DDFrame class object
42+
ddframe_class = PyObject_GetAttrString(mod, "DDFrame");
43+
Py_XDECREF(mod);
44+
45+
// Basic sanity check that the object is the type of object we actually want
46+
if (ddframe_class == NULL || !PyCallable_Check(ddframe_class)) {
47+
PyErr_Print();
48+
return false;
49+
}
50+
51+
return true;
52+
}
53+
2154
int
2255
memalloc_tb_init(uint16_t max_nframe)
2356
{
@@ -195,14 +228,24 @@ traceback_to_tuple(traceback_t* tb)
195228
PyTuple_SET_ITEM(frame_tuple, 3, empty_string);
196229
Py_INCREF(empty_string);
197230

231+
// Try to set the class. If we cannot (e.g., if the sofile is reloaded
232+
// without module initialization), then this will result in an error if
233+
// the underlying object is used via attributes
234+
if (ddframe_class) {
235+
#if PY_VERSION_HEX >= 0x03090000
236+
Py_SET_TYPE(frame_tuple, (PyTypeObject*)ddframe_class);
237+
#else
238+
Py_TYPE(frame_tuple) = (PyTypeObject*)ddframe_class;
239+
#endif
240+
Py_INCREF(ddframe_class);
241+
}
242+
198243
PyTuple_SET_ITEM(stack, nframe, frame_tuple);
199244
}
200245

201246
PyObject* tuple = PyTuple_New(3);
202-
203247
PyTuple_SET_ITEM(tuple, 0, stack);
204248
PyTuple_SET_ITEM(tuple, 1, PyLong_FromUnsignedLong(tb->total_nframe));
205249
PyTuple_SET_ITEM(tuple, 2, PyLong_FromUnsignedLong(tb->thread_id));
206-
207250
return tuple;
208251
}

ddtrace/profiling/collector/_memalloc_tb.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#ifndef _DDTRACE_MEMALLOC_TB_H
22
#define _DDTRACE_MEMALLOC_TB_H
33

4+
#include <stdbool.h>
45
#include <stdint.h>
56

67
#include <Python.h>
@@ -43,6 +44,9 @@ typedef struct
4344
/* The maximum number of frames we can store in `traceback_t.nframe` */
4445
#define TRACEBACK_MAX_NFRAME UINT16_MAX
4546

47+
bool
48+
memalloc_ddframe_class_init();
49+
4650
int
4751
memalloc_tb_init(uint16_t max_nframe);
4852
void

ddtrace/profiling/collector/_traceback.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ from .. import event
55

66
def traceback_to_frames(
77
traceback: types.TracebackType, max_nframes: int
8-
) -> typing.Tuple[typing.List[event.FrameType], int]: ...
9-
def pyframe_to_frames(frame: types.FrameType, max_nframes: int) -> typing.Tuple[typing.List[event.FrameType], int]: ...
8+
) -> typing.Tuple[typing.List[event.DDFrame], int]: ...
9+
def pyframe_to_frames(frame: types.FrameType, max_nframes: int) -> typing.Tuple[typing.List[event.DDFrame], int]: ...

0 commit comments

Comments
 (0)