Skip to content

Commit 709832f

Browse files
authored
Merge branch 'main' into erikayasuda/test-op
2 parents 406000c + 6f2816b commit 709832f

22 files changed

Lines changed: 473 additions & 18 deletions

ddtrace/internal/datadog/profiling/stack/__init__.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def set_adaptive_sampling_baseline(baseline_core_pct: float) -> None: ...
2525
def set_p_stable_window_s(p_stable_window_s: int) -> None: ...
2626
def set_p_stable_percentile(p_stable_percentile: float) -> None: ...
2727
def set_max_threads(max_threads: int) -> None: ...
28+
def set_max_tasks(max_tasks: int) -> None: ...
2829
def set_uvloop_mode(thread_id: int, uvloop_mode: bool) -> None:
2930
"""Enable uvloop-specific stack unwinding in the native profiler for a specific thread.
3031

ddtrace/internal/datadog/profiling/stack/_stack.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def set_adaptive_sampling(do_adaptive_sampling: bool = False) -> None: ...
1717
def set_target_overhead(target_overhead: float) -> None: ...
1818
def set_max_sampling_period(max_interval_us: int) -> None: ...
1919
def set_max_threads(max_threads: int) -> None: ...
20+
def set_max_tasks(max_tasks: int) -> None: ...
2021
def set_uvloop_mode(thread_id: int, uvloop_mode: bool) -> None: ...
2122
def set_interval(new_interval: float) -> None: ...
2223

ddtrace/internal/datadog/profiling/stack/echion/echion/echion_sampler.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <cstdint>
44
#include <optional>
5+
#include <random>
56
#include <unordered_map>
67
#include <unordered_set>
78

@@ -10,6 +11,7 @@
1011
#include <echion/strings.h>
1112
#include <echion/threads.h>
1213

14+
#include "constants.hpp"
1315
#include "stack_renderer.hpp"
1416

1517
// Forward declaration
@@ -53,6 +55,13 @@ class EchionSampler
5355
// Only accessed from the sampling thread, so no lock/atomic is needed.
5456
size_t asyncio_task_count_ = 0;
5557

58+
// Maximum number of leaf tasks / greenlets to unwind and emit per cycle.
59+
// 0 means unlimited.
60+
unsigned int max_tasks_per_sample_ = g_default_max_tasks_per_sample;
61+
62+
// RNG used for task / greenlet reservoir sampling.
63+
std::minstd_rand rng_{ std::random_device{}() };
64+
5665
// Caches
5766
StringTable string_table_;
5867
LRUCache<uintptr_t, Frame> frame_cache_;
@@ -100,6 +109,11 @@ class EchionSampler
100109
void add_asyncio_task_count(size_t count) { asyncio_task_count_ += count; }
101110
size_t asyncio_task_count() const { return asyncio_task_count_; }
102111

112+
unsigned int max_tasks_per_sample() const { return max_tasks_per_sample_; }
113+
void set_max_tasks_per_sample(unsigned int value) { max_tasks_per_sample_ = value; }
114+
115+
std::minstd_rand& rng() { return rng_; }
116+
103117
// Accessor for StringTable operations
104118
StringTable& string_table() { return string_table_; }
105119
const StringTable& string_table() const { return string_table_; }
@@ -138,6 +152,7 @@ class EchionSampler
138152
asyncio_frame_cache_key_.reset();
139153
uvloop_frame_cache_key_.reset();
140154
asyncio_task_count_ = 0;
155+
rng_ = std::minstd_rand{ std::random_device{}() };
141156

142157
new (&seen_frames_scratch_) std::unordered_set<PyObject*>();
143158

ddtrace/internal/datadog/profiling/stack/echion/echion/stacks.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#define PY_SSIZE_T_CLEAN
88
#include <Python.h>
99

10+
#include <optional>
1011
#include <unordered_set>
1112
#include <utility>
1213
#include <vector>
@@ -70,6 +71,10 @@ class StackInfo
7071
bool on_cpu;
7172
FrameStack stack;
7273

74+
// Per-task override wall-time to use in reservoir sampling.
75+
// nullopt means "use the thread-level wall time"
76+
std::optional<int64_t> walltime_ns = std::nullopt;
77+
7378
StackInfo(TaskName task_name, bool on_cpu, uint64_t task_id)
7479
: task_name(std::move(task_name))
7580
, task_id(task_id)

ddtrace/internal/datadog/profiling/stack/echion/echion/threads.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class ThreadInfo
6161
[[nodiscard]] Result<void> update_cpu_time();
6262

6363
[[nodiscard]] Result<void> sample(EchionSampler&, PyThreadState*, microsecond_t);
64-
void unwind(EchionSampler&, PyThreadState*);
64+
void unwind(EchionSampler&, PyThreadState*, microsecond_t wall_time_us);
6565

6666
// ------------------------------------------------------------------------
6767
#if defined PL_LINUX
@@ -116,8 +116,8 @@ class ThreadInfo
116116
private:
117117
void reset_cycle_state() noexcept;
118118
void render_unwound_stacks(EchionSampler&);
119-
[[nodiscard]] Result<void> unwind_tasks(EchionSampler&, PyThreadState*);
120-
void unwind_greenlets(EchionSampler&, PyThreadState*, unsigned long);
119+
[[nodiscard]] Result<void> unwind_tasks(EchionSampler&, PyThreadState*, microsecond_t wall_time_us);
120+
void unwind_greenlets(EchionSampler&, PyThreadState*, unsigned long, microsecond_t wall_time_us);
121121
[[nodiscard]] Result<std::vector<TaskInfo::Ptr>> get_all_tasks(EchionSampler&, PyThreadState* tstate);
122122
#if PY_VERSION_HEX >= 0x030e0000
123123
[[nodiscard]] Result<void> get_tasks_from_thread_linked_list(EchionSampler& echion,

ddtrace/internal/datadog/profiling/stack/fuzz/fuzz_echion_thread_unwind.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
3737

3838
// Only flexes the "unwind thread stack" path, greenlets
3939
// and asyncio aren't used here
40-
thread.unwind(echion_sampler, &tstate);
40+
thread.unwind(echion_sampler, &tstate, 0);
4141

4242
g_data = nullptr;
4343
g_size = 0;

ddtrace/internal/datadog/profiling/stack/fuzz/fuzz_echion_thread_unwind_tasks.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
5151

5252
// Tries to unwind_python_stack (which does nothing because we
5353
// have bogus data), then unwind_tasks (the one we test here)
54-
thread.unwind(echion_sampler, &tstate);
54+
thread.unwind(echion_sampler, &tstate, 0);
5555

5656
g_data = nullptr;
5757
g_size = 0;

ddtrace/internal/datadog/profiling/stack/include/constants.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,9 @@ constexpr double g_target_overhead = 0.01; // 1% overhead
1515
// 0 means no limit (sample all threads).
1616
constexpr unsigned int g_default_max_threads_per_sample = 25;
1717

18+
// Maximum number of leaf tasks / greenlets to sample per cycle. When the number of
19+
// leaf tasks exceeds this, reservoir sampling is used. 0 means sample all tasks.
20+
constexpr unsigned int g_default_max_tasks_per_sample = 50;
21+
1822
// Echion maintains a cache of frames--the size of this cache is specified up-front.
1923
constexpr unsigned int g_default_echion_frame_cache_size = 1024;

ddtrace/internal/datadog/profiling/stack/include/sampler.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ class Sampler
156156
max_sampling_period_us = std::max(max_interval_us, static_cast<microsecond_t>(g_min_sampling_period_us));
157157
}
158158
void set_max_threads_per_sample(unsigned int value) { max_threads_per_sample = value; }
159+
void set_max_tasks_per_sample(unsigned int value);
159160

160161
// Set the absolute overhead floor as "core percent" units (1 = 0.01 core = 10 mcores).
161162
// Converted to us of CPU budget per adaptation window.

ddtrace/internal/datadog/profiling/stack/include/stack_renderer.hpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <cstdint>
44
#include <memory>
5+
#include <optional>
56
#include <string>
67
#include <string_view>
78

@@ -87,7 +88,10 @@ class StackRenderer
8788
microsecond_t wall_time_us,
8889
uintptr_t thread_id,
8990
unsigned long native_id);
90-
void render_task_begin(std::string_view task_name, bool on_cpu, uint64_t task_id);
91+
void render_task_begin(std::string_view task_name,
92+
bool on_cpu,
93+
uint64_t task_id,
94+
std::optional<int64_t> walltime_ns_override = std::nullopt);
9195
void render_frame(Frame& frame);
9296
void render_cpu_time(microsecond_t cpu_time_us);
9397
void render_native_frame(const std::string& name, const std::string& module);

0 commit comments

Comments
 (0)