Skip to content

Commit 9fc994d

Browse files
jbachorikclaude
andcommitted
Add Poisson sampling, PID rate-limiting, and weight to native malloc
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 057714a commit 9fc994d

5 files changed

Lines changed: 87 additions & 8 deletions

File tree

ddprof-lib/src/main/cpp/event.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ class MallocEvent : public Event {
9494
u64 _start_time;
9595
uintptr_t _address;
9696
u64 _size; // 0 for free events
97+
float _weight;
98+
99+
MallocEvent() : Event(), _start_time(0), _address(0), _size(0), _weight(1.0f) {}
97100
};
98101

99102
class WallClockEpochEvent {

ddprof-lib/src/main/cpp/flightRecorder.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,6 +1585,7 @@ void Recording::recordMallocSample(Buffer *buf, int tid, u64 call_trace_id,
15851585
buf->putVar64(event->_address);
15861586
if (event->_size != 0) {
15871587
buf->putVar64(event->_size);
1588+
buf->putFloat(event->_weight);
15881589
}
15891590
writeEventSizePrefix(buf, start);
15901591
flushIfNeeded(buf);

ddprof-lib/src/main/cpp/jfrMetadata.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,8 @@ void JfrMetadata::initialize(
293293
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
294294
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
295295
<< field("address", T_LONG, "Address", F_ADDRESS)
296-
<< field("size", T_LONG, "Size", F_BYTES))
296+
<< field("size", T_LONG, "Size", F_BYTES)
297+
<< field("weight", T_FLOAT, "Sample weight"))
297298

298299
<< (type("profiler.Free", T_FREE, "free")
299300
<< category("Java Virtual Machine", "Native Memory")

ddprof-lib/src/main/cpp/mallocTracer.cpp

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7+
#include <cmath>
78
#include <dlfcn.h>
89
#include <pthread.h>
910
#include <stdlib.h>
@@ -12,6 +13,7 @@
1213
#include "libraries.h"
1314
#include "mallocTracer.h"
1415
#include "os.h"
16+
#include "pidController.h"
1517
#include "profiler.h"
1618
#include "symbols.h"
1719
#include "tsc.h"
@@ -102,9 +104,12 @@ extern "C" void* aligned_alloc_hook(size_t alignment, size_t size) {
102104
return ret;
103105
}
104106

105-
u64 MallocTracer::_interval;
107+
volatile u64 MallocTracer::_interval;
106108
bool MallocTracer::_nofree;
107-
volatile u64 MallocTracer::_allocated_bytes;
109+
volatile u64 MallocTracer::_bytes_until_sample;
110+
u64 MallocTracer::_configured_interval;
111+
volatile u64 MallocTracer::_sample_count;
112+
u64 MallocTracer::_last_config_update_ts;
108113

109114
Mutex MallocTracer::_patch_lock;
110115
int MallocTracer::_patched_libs = 0;
@@ -225,14 +230,71 @@ void MallocTracer::patchLibraries() {
225230
}
226231
}
227232

233+
u64 MallocTracer::nextPoissonInterval() {
234+
u64 s = TSC::ticks();
235+
s ^= s >> 12;
236+
s ^= s << 25;
237+
s ^= s >> 27;
238+
double u = (double)(s * 0x2545F4914F6CDD1DULL >> 11) / (double)(1ULL << 53);
239+
if (u < 1e-18) u = 1e-18;
240+
return (u64)((double)_interval * -log(u));
241+
}
242+
243+
bool MallocTracer::shouldSample(size_t size) {
244+
if (_interval <= 1) return true;
245+
while (true) {
246+
u64 prev = _bytes_until_sample;
247+
if (size < prev) {
248+
if (__sync_bool_compare_and_swap(&_bytes_until_sample, prev, prev - size))
249+
return false;
250+
} else {
251+
u64 next = nextPoissonInterval();
252+
if (__sync_bool_compare_and_swap(&_bytes_until_sample, prev, next))
253+
return true;
254+
}
255+
}
256+
}
257+
258+
void MallocTracer::updateConfiguration(u64 events, double time_coefficient) {
259+
static PidController pid(TARGET_SAMPLES_PER_WINDOW,
260+
31, 511, 3, CONFIG_UPDATE_CHECK_PERIOD_SECS, 15);
261+
double signal = pid.compute(events, time_coefficient);
262+
int64_t new_interval = (int64_t)_interval - (int64_t)signal;
263+
if (new_interval < (int64_t)_configured_interval)
264+
new_interval = (int64_t)_configured_interval;
265+
if (new_interval > (int64_t)(1ULL << 40))
266+
new_interval = (int64_t)(1ULL << 40);
267+
_interval = (u64)new_interval;
268+
}
269+
228270
void MallocTracer::recordMalloc(void* address, size_t size) {
229-
if (updateCounter(_allocated_bytes, size, _interval)) {
271+
if (shouldSample(size)) {
272+
u64 current_interval = _interval;
230273
MallocEvent event;
231274
event._start_time = TSC::ticks();
232275
event._address = (uintptr_t)address;
233276
event._size = size;
277+
event._weight = (float)(size == 0 || current_interval <= 1
278+
? 1.0
279+
: 1.0 / (1.0 - exp(-(double)size / (double)current_interval)));
234280

235281
Profiler::instance()->recordSample(NULL, size, OS::threadId(), BCI_NATIVE_MALLOC, 0, &event);
282+
283+
u64 current_samples = __sync_add_and_fetch(&_sample_count, 1);
284+
if ((current_samples % TARGET_SAMPLES_PER_WINDOW) == 0) {
285+
u64 now = OS::nanotime();
286+
u64 prev_ts = __atomic_load_n(&_last_config_update_ts, __ATOMIC_RELAXED);
287+
u64 time_diff = now - prev_ts;
288+
u64 check_period_ns = (u64)CONFIG_UPDATE_CHECK_PERIOD_SECS * 1000000000ULL;
289+
if (time_diff > check_period_ns) {
290+
if (__atomic_compare_exchange(&_last_config_update_ts, &prev_ts, &now,
291+
false, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED)) {
292+
__sync_fetch_and_add(&_sample_count, -current_samples);
293+
updateConfiguration(current_samples,
294+
(double)check_period_ns / time_diff);
295+
}
296+
}
297+
}
236298
}
237299
}
238300

@@ -246,9 +308,12 @@ void MallocTracer::recordFree(void* address) {
246308
}
247309

248310
Error MallocTracer::start(Arguments& args) {
249-
_interval = args._nativemem > 0 ? args._nativemem : 0;
311+
_configured_interval = args._nativemem > 0 ? args._nativemem : 0;
312+
_interval = _configured_interval;
250313
_nofree = args._nofree;
251-
_allocated_bytes = 0;
314+
_bytes_until_sample = _configured_interval > 1 ? nextPoissonInterval() : 0;
315+
_sample_count = 0;
316+
_last_config_update_ts = OS::nanotime();
252317

253318
if (!_initialized) {
254319
initialize();

ddprof-lib/src/main/cpp/mallocTracer.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,15 @@
1414

1515
class MallocTracer : public Engine {
1616
private:
17-
static u64 _interval;
17+
static volatile u64 _interval;
1818
static bool _nofree;
19-
static volatile u64 _allocated_bytes;
19+
static volatile u64 _bytes_until_sample;
20+
21+
static u64 _configured_interval;
22+
static volatile u64 _sample_count;
23+
static u64 _last_config_update_ts;
24+
static const int CONFIG_UPDATE_CHECK_PERIOD_SECS = 1;
25+
static const int TARGET_SAMPLES_PER_WINDOW = 100;
2026

2127
static Mutex _patch_lock;
2228
static int _patched_libs;
@@ -25,6 +31,9 @@ class MallocTracer : public Engine {
2531

2632
static void initialize();
2733
static void patchLibraries();
34+
static u64 nextPoissonInterval();
35+
static bool shouldSample(size_t size);
36+
static void updateConfiguration(u64 events, double time_coefficient);
2837

2938
public:
3039
const char* name() {

0 commit comments

Comments
 (0)