Skip to content

Commit 4124e5f

Browse files
authored
Merge branch 'main' into zgu/fuzz_corpus_dir
2 parents 53f4cc2 + 4e0e178 commit 4124e5f

12 files changed

Lines changed: 924 additions & 67 deletions

File tree

.gitlab/reliability/chaos_check.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ case $ALLOCATOR in
123123
tcmalloc)
124124
echo "Running with tcmalloc"
125125
export LD_PRELOAD=$(find /usr/lib/ -name 'libtcmalloc_minimal.so.4')
126+
# thread-churn/dump-storm antagonists cycle many short-lived threads;
127+
# tcmalloc's defaults are slow to return their per-thread caches to the
128+
# OS, which was inflating container RSS past the OOM limit on aarch64.
129+
export TCMALLOC_RELEASE_RATE=10
130+
export TCMALLOC_AGGRESSIVE_DECOMMIT=1
126131
;;
127132
jemalloc)
128133
echo "Running with jemalloc"

README.md

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -169,23 +169,40 @@ By default, the script clones the repository at the current commit for clean bui
169169
# Force rebuild of base image only (useful after Alpine/Ubuntu updates)
170170
./utils/run-containers-tests.sh --libc=musl --rebuild-base
171171

172+
# Preview all supported musl cells (sanitizer configs are skipped, matching CI policy)
173+
./utils/run-containers-tests.sh --matrix --libc=musl
174+
175+
# Run all supported musl cells without an interactive prompt
176+
./utils/run-containers-tests.sh --matrix --libc=musl --run
177+
178+
# Preview selected JDKs across every supported libc/architecture pair
179+
./utils/run-containers-tests.sh --libc=all --jdk=8,17,21 --arch=all
180+
181+
# Run all OpenJ9 cells
182+
./utils/run-containers-tests.sh --matrix --jdk=j9 --run
183+
172184
# Show options
173185
./utils/run-containers-tests.sh --help
174186
```
175187

176188
Supported options:
177-
- `--libc=glibc|musl` (default: glibc)
178-
- `--jdk=8|11|17|21|25|8-j9|11-j9|17-j9|21-j9|17-graal|21-graal|25-graal` (default: 21)
179-
- `--arch=x64|aarch64` (default: auto-detect)
180-
- `--config=debug|release|asan|tsan` (default: debug)
189+
- `--libc=glibc|musl|all[,..]` (default: glibc)
190+
- `--jdk=8|11|17|21|25|8-j9|11-j9|17-j9|21-j9|17-graal|21-graal|25-graal|regular|j9|graal|all[,..]` (default: 21)
191+
- `--arch=x64|aarch64|all[,..]` (default: auto-detect)
192+
- `--config=debug|release|asan|tsan|all[,..]` (default: debug)
181193
- `--container=podman|docker` (default: podman)
182194
- `--tests="TestPattern"`
183195
- `--gtest` (enable C++ gtests, disabled by default for faster runs)
184-
- `--gtest-task=Task` (run one C++ gtest task; accepts `elfparser_ut` or a full task path like `:ddprof-lib:gtestAsan_elfparser_ut`)
196+
- `--gtest-task=Task` (run one C++ gtest task; matrix runs require a short name like `elfparser_ut`, while single-cell runs also accept a full task path like `:ddprof-lib:gtestAsan_elfparser_ut`)
185197
- `--shell` (interactive shell instead of running tests)
186198
- `--mount` (mount local repo instead of cloning - faster but may have stale artifacts)
187199
- `--rebuild` (force rebuild of all container images)
188200
- `--rebuild-base` (force rebuild of base image only)
201+
- `--matrix` (preview a full test matrix; unset dimensions expand to `all`)
202+
- `--run` (execute an inferred matrix without prompting)
203+
- `--fail-fast` (stop matrix execution on first failure)
204+
205+
Single-value commands run one configuration immediately. When any dimension expands to multiple cells, the script prints a compact status table first; interactive terminals ask for confirmation, while non-interactive runs require `--run` to execute. Matrix execution prints the status table again after all cells finish and writes summaries to `build/reports/container-matrix/summary.md` and `build/reports/container-matrix/summary.json`. Cells not run because of `--fail-fast` are reported as cancelled separately from unsupported cells that are skipped.
189206

190207
## Unwinding Validation Tool
191208

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
X(DICTIONARY_ENDPOINTS_ARENA_WASTE_BYTES, "dictionary_endpoints_arena_waste_bytes") \
4343
X(DICTIONARY_CONTEXT_ARENA_WASTE_BYTES, "dictionary_context_arena_waste_bytes") \
4444
X(DICTIONARY_DRAIN_TIMEOUTS, "dictionary_drain_timeouts") \
45+
X(CLASS_MAP_AT_CAPACITY, "class_map_at_capacity_drops") \
4546
X(CONTEXT_STORAGE_BYTES, "context_storage_bytes") \
4647
X(CONTEXT_STORAGE_PAGES, "context_storage_pages") \
4748
X(CONTEXT_BOUNDS_MISS_INITS, "context_bounds_miss_inits") \

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

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ SharedLineNumberTable::~SharedLineNumberTable() {
6868

6969
void Lookup::fillNativeMethodInfo(MethodInfo *mi, const char *name,
7070
const char *lib_name) {
71-
mi->_class = _classes->lookupDuringDump("", 0);
71+
mi->_class = _classes->lookupDuringDump("", 0, Profiler::maxClassMapSize());
7272
// TODO return the library name once we figured out how to cooperate with the
7373
// backend
7474
// if (lib_name == NULL) {
@@ -117,7 +117,8 @@ void Lookup::fillNativeMethodInfo(MethodInfo *mi, const char *name,
117117

118118
void Lookup::fillRemoteFrameInfo(MethodInfo *mi, const RemoteFrameInfo *rfi) {
119119
// Store build-id in the class name field
120-
mi->_class = _classes->lookupDuringDump(rfi->build_id, strlen(rfi->build_id));
120+
mi->_class = _classes->lookupDuringDump(rfi->build_id, strlen(rfi->build_id),
121+
Profiler::maxClassMapSize());
121122

122123
// Store PC offset in hex format in the signature field
123124
char offset_hex[32];
@@ -269,35 +270,40 @@ void Lookup::fillJavaMethodInfo(MethodInfo *mi, jmethodID method,
269270
"Ljdk/internal/reflect/GeneratedConstructorAccessor")) {
270271
class_name_id = _classes->lookupDuringDump(
271272
"jdk/internal/reflect/GeneratedConstructorAccessor",
272-
strlen("jdk/internal/reflect/GeneratedConstructorAccessor"));
273+
strlen("jdk/internal/reflect/GeneratedConstructorAccessor"),
274+
Profiler::maxClassMapSize());
273275
method_name_id =
274276
_symbols.lookup("Object "
275277
"jdk.internal.reflect.GeneratedConstructorAccessor."
276278
"newInstance(Object[])");
277279
method_sig_id = _symbols.lookup(method_sig);
278280
} else if (has_prefix(class_name,
279281
"Lsun/reflect/GeneratedConstructorAccessor")) {
280-
class_name_id =
281-
_classes->lookupDuringDump("sun/reflect/GeneratedConstructorAccessor",
282-
strlen("sun/reflect/GeneratedConstructorAccessor"));
282+
class_name_id = _classes->lookupDuringDump(
283+
"sun/reflect/GeneratedConstructorAccessor",
284+
strlen("sun/reflect/GeneratedConstructorAccessor"),
285+
Profiler::maxClassMapSize());
283286
method_name_id = _symbols.lookup(
284287
"Object "
285288
"sun.reflect.GeneratedConstructorAccessor.newInstance(Object[])");
286289
method_sig_id = _symbols.lookup(method_sig);
287290
} else if (has_prefix(class_name,
288291
"Ljdk/internal/reflect/GeneratedMethodAccessor")) {
289-
class_name_id =
290-
_classes->lookupDuringDump("jdk/internal/reflect/GeneratedMethodAccessor",
291-
strlen("jdk/internal/reflect/GeneratedMethodAccessor"));
292+
class_name_id = _classes->lookupDuringDump(
293+
"jdk/internal/reflect/GeneratedMethodAccessor",
294+
strlen("jdk/internal/reflect/GeneratedMethodAccessor"),
295+
Profiler::maxClassMapSize());
292296
method_name_id =
293297
_symbols.lookup("Object "
294298
"jdk.internal.reflect.GeneratedMethodAccessor."
295299
"invoke(Object, Object[])");
296300
method_sig_id = _symbols.lookup(method_sig);
297301
} else if (has_prefix(class_name,
298302
"Lsun/reflect/GeneratedMethodAccessor")) {
299-
class_name_id = _classes->lookupDuringDump("sun/reflect/GeneratedMethodAccessor",
300-
strlen("sun/reflect/GeneratedMethodAccessor"));
303+
class_name_id = _classes->lookupDuringDump(
304+
"sun/reflect/GeneratedMethodAccessor",
305+
strlen("sun/reflect/GeneratedMethodAccessor"),
306+
Profiler::maxClassMapSize());
301307
method_name_id = _symbols.lookup(
302308
"Object sun.reflect.GeneratedMethodAccessor.invoke(Object, "
303309
"Object[])");
@@ -308,30 +314,38 @@ void Lookup::fillJavaMethodInfo(MethodInfo *mi, jmethodID method,
308314
// we want to normalise to java/lang/invoke/LambdaForm$MH,
309315
// java/lang/invoke/LambdaForm$DMH, java/lang/invoke/LambdaForm$BMH,
310316
if (has_prefix(class_name + lambdaFormPrefixLength, "MH")) {
311-
class_name_id = _classes->lookupDuringDump("java/lang/invoke/LambdaForm$MH",
312-
strlen("java/lang/invoke/LambdaForm$MH"));
317+
class_name_id = _classes->lookupDuringDump(
318+
"java/lang/invoke/LambdaForm$MH",
319+
strlen("java/lang/invoke/LambdaForm$MH"),
320+
Profiler::maxClassMapSize());
313321
} else if (has_prefix(class_name + lambdaFormPrefixLength, "BMH")) {
314-
class_name_id = _classes->lookupDuringDump("java/lang/invoke/LambdaForm$BMH",
315-
strlen("java/lang/invoke/LambdaForm$BMH"));
322+
class_name_id = _classes->lookupDuringDump(
323+
"java/lang/invoke/LambdaForm$BMH",
324+
strlen("java/lang/invoke/LambdaForm$BMH"),
325+
Profiler::maxClassMapSize());
316326
} else if (has_prefix(class_name + lambdaFormPrefixLength, "DMH")) {
317-
class_name_id = _classes->lookupDuringDump("java/lang/invoke/LambdaForm$DMH",
318-
strlen("java/lang/invoke/LambdaForm$DMH"));
327+
class_name_id = _classes->lookupDuringDump(
328+
"java/lang/invoke/LambdaForm$DMH",
329+
strlen("java/lang/invoke/LambdaForm$DMH"),
330+
Profiler::maxClassMapSize());
319331
} else {
320332
// don't recognise the suffix, so don't normalise
321333
class_name_id = _classes->lookupDuringDump(
322-
normalized_class_name, normalized_class_name_len);
334+
normalized_class_name, normalized_class_name_len,
335+
Profiler::maxClassMapSize());
323336
}
324337
method_name_id = _symbols.lookup(method_name);
325338
method_sig_id = _symbols.lookup(method_sig);
326339
} else {
327-
class_name_id = _classes->lookupDuringDump(normalized_class_name,
328-
normalized_class_name_len);
340+
class_name_id = _classes->lookupDuringDump(
341+
normalized_class_name, normalized_class_name_len,
342+
Profiler::maxClassMapSize());
329343
method_name_id = _symbols.lookup(method_name);
330344
method_sig_id = _symbols.lookup(method_sig);
331345
}
332346
} else {
333347
Counters::increment(JMETHODID_SKIPPED);
334-
class_name_id = _classes->lookupDuringDump("", 0);
348+
class_name_id = _classes->lookupDuringDump("", 0, Profiler::maxClassMapSize());
335349
method_name_id = _symbols.lookup("jvmtiError");
336350
method_sig_id = _symbols.lookup("()L;");
337351
}
@@ -426,7 +440,7 @@ bool Lookup::resolveVTableReceiver(VMSymbol *sym, char *buf, size_t bufsize,
426440
// the stack frame's class_id absent from this chunk's class pool.
427441
// (Plain lookup() remains correct for non-dump callers — e.g. Profiler::
428442
// lookupClass on JVM threads — where the next rotate() will propagate.)
429-
u32 class_id = _classes->lookupDuringDump(buf, len);
443+
u32 class_id = _classes->lookupDuringDump(buf, len, Profiler::maxClassMapSize());
430444
// Apply synthetic-accessor/LambdaForm normalisation so that the many
431445
// distinct names HotSpot generates for these families (..Accessor1234,
432446
// LambdaForm$MH/0x...) collapse to one bucket each in the JFR class pool.
@@ -436,32 +450,32 @@ bool Lookup::resolveVTableReceiver(VMSymbol *sym, char *buf, size_t bufsize,
436450
if (has_prefix_n(buf, len,
437451
"jdk/internal/reflect/GeneratedConstructorAccessor")) {
438452
static const char kName[] = "jdk/internal/reflect/GeneratedConstructorAccessor";
439-
class_id = _classes->lookupDuringDump(kName, sizeof(kName) - 1);
453+
class_id = _classes->lookupDuringDump(kName, sizeof(kName) - 1, Profiler::maxClassMapSize());
440454
} else if (has_prefix_n(buf, len, "sun/reflect/GeneratedConstructorAccessor")) {
441455
static const char kName[] = "sun/reflect/GeneratedConstructorAccessor";
442-
class_id = _classes->lookupDuringDump(kName, sizeof(kName) - 1);
456+
class_id = _classes->lookupDuringDump(kName, sizeof(kName) - 1, Profiler::maxClassMapSize());
443457
} else if (has_prefix_n(buf, len,
444458
"jdk/internal/reflect/GeneratedMethodAccessor")) {
445459
static const char kName[] = "jdk/internal/reflect/GeneratedMethodAccessor";
446-
class_id = _classes->lookupDuringDump(kName, sizeof(kName) - 1);
460+
class_id = _classes->lookupDuringDump(kName, sizeof(kName) - 1, Profiler::maxClassMapSize());
447461
} else if (has_prefix_n(buf, len, "sun/reflect/GeneratedMethodAccessor")) {
448462
static const char kName[] = "sun/reflect/GeneratedMethodAccessor";
449-
class_id = _classes->lookupDuringDump(kName, sizeof(kName) - 1);
463+
class_id = _classes->lookupDuringDump(kName, sizeof(kName) - 1, Profiler::maxClassMapSize());
450464
} else if (has_prefix_n(buf, len, "java/lang/invoke/LambdaForm$")) {
451465
size_t prefix_len = strlen("java/lang/invoke/LambdaForm$");
452466
const char *suffix = buf + prefix_len;
453467
size_t suffix_len = len - prefix_len;
454468
if (suffix_len >= 2 && suffix[0] == 'M' && suffix[1] == 'H') {
455469
static const char kName[] = "java/lang/invoke/LambdaForm$MH";
456-
class_id = _classes->lookupDuringDump(kName, sizeof(kName) - 1);
470+
class_id = _classes->lookupDuringDump(kName, sizeof(kName) - 1, Profiler::maxClassMapSize());
457471
} else if (suffix_len >= 3 && suffix[0] == 'B' && suffix[1] == 'M' &&
458472
suffix[2] == 'H') {
459473
static const char kName[] = "java/lang/invoke/LambdaForm$BMH";
460-
class_id = _classes->lookupDuringDump(kName, sizeof(kName) - 1);
474+
class_id = _classes->lookupDuringDump(kName, sizeof(kName) - 1, Profiler::maxClassMapSize());
461475
} else if (suffix_len >= 3 && suffix[0] == 'D' && suffix[1] == 'M' &&
462476
suffix[2] == 'H') {
463477
static const char kName[] = "java/lang/invoke/LambdaForm$DMH";
464-
class_id = _classes->lookupDuringDump(kName, sizeof(kName) - 1);
478+
class_id = _classes->lookupDuringDump(kName, sizeof(kName) - 1, Profiler::maxClassMapSize());
465479
}
466480
}
467481
*out_class_id = class_id;
@@ -485,7 +499,7 @@ u32 Lookup::resolveVTableReceiverCached(void *sym) {
485499
// marker instead of an empty class name (which is indistinguishable
486500
// from a parser/encoder error downstream).
487501
static const char kName[] = "<unresolved_vtable_receiver>";
488-
class_id = _classes->lookupDuringDump(kName, sizeof(kName) - 1);
502+
class_id = _classes->lookupDuringDump(kName, sizeof(kName) - 1, Profiler::maxClassMapSize());
489503
}
490504
_vtable_receiver_cache[sym] = class_id;
491505
return class_id;

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,19 @@ void LivenessTracker::flush_table(std::set<int> *tracked_thread_ids) {
117117
env->DeleteLocalRef(clz);
118118
jniExceptionCheck(env);
119119
const char *name = env->GetStringUTFChars(name_str, nullptr);
120-
event._id = name != nullptr
121-
? Profiler::instance()->lookupClass(name, strlen(name))
122-
: 0;
120+
int class_id = name != nullptr
121+
? Profiler::instance()->lookupClass(name, strlen(name))
122+
: 0;
123123
env->ReleaseStringUTFChars(name_str, name);
124124

125-
Profiler::instance()->recordDeferredSample(_table[i].tid, _table[i].call_trace_id, BCI_LIVENESS, &event);
125+
// lookupClass() returns -1 when the class map is at capacity; do not
126+
// assign it to the u32 event id (it would wrap to 0xFFFFFFFF and
127+
// corrupt liveness attribution in the JFR output) — drop the sample
128+
// instead, matching ObjectSampler's convention for the same failure.
129+
if (class_id >= 0) {
130+
event._id = class_id;
131+
Profiler::instance()->recordDeferredSample(_table[i].tid, _table[i].call_trace_id, BCI_LIVENESS, &event);
132+
}
126133
}
127134

128135
env->DeleteLocalRef(ref);

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1832,10 +1832,16 @@ void Profiler::shutdown(Arguments &args) {
18321832
}
18331833

18341834
int Profiler::lookupClass(const char *key, size_t length) {
1835-
// StringDictionary::lookup() is internally thread-safe via _accepting +
1836-
// RefCountGuard; no external lock required (unlike the old Dictionary).
1837-
u32 id = _class_map.lookup(key, length);
1838-
return id != 0 ? static_cast<int>(id) : -1;
1835+
// StringDictionary::bounded_lookup() is internally thread-safe via
1836+
// _accepting + RefCountGuard; no external lock required. Bounded to
1837+
// MAX_CLASS_MAP_SIZE so unbounded distinct-class churn cannot grow this
1838+
// dictionary for the lifetime of the process.
1839+
u32 id = _class_map.bounded_lookup(key, length, MAX_CLASS_MAP_SIZE);
1840+
if (id == 0) {
1841+
Counters::increment(CLASS_MAP_AT_CAPACITY);
1842+
return -1;
1843+
}
1844+
return static_cast<int>(id);
18391845
}
18401846

18411847
int Profiler::status(char* status, int max_len) {

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ class alignas(alignof(SpinLock)) Profiler {
8181
NotifyClassUnloadedFunc _notify_class_unloaded_func;
8282
// --
8383

84+
// Caps the number of distinct class names _class_map will ever hold.
85+
// rotate()/clearStandby() carry the full accumulated set forward on every
86+
// JFR chunk (only clearAll() on profiler restart truly resets it), so
87+
// without a cap a workload with unbounded distinct-class churn (e.g. heavy
88+
// dynamic class generation) grows this dictionary for the lifetime of the
89+
// process. 256K distinct classes is far beyond any real application's
90+
// class count.
91+
static const int MAX_CLASS_MAP_SIZE = 1 << 18;
92+
8493
ThreadInfo _thread_info;
8594
StringDictionary _class_map{1};
8695
StringDictionary _string_label_map{2};
@@ -262,6 +271,10 @@ class alignas(alignof(SpinLock)) Profiler {
262271
Engine *wallEngine() { return _wall_engine; }
263272

264273
StringDictionary *classMap() { return &_class_map; }
274+
// Same cap lookupClass() enforces for sample-time inserts; dump-time
275+
// insertions (Lookup::_classes->lookupDuringDump in flightRecorder.cpp)
276+
// must use this too so _class_map cannot grow past it via that path.
277+
static int maxClassMapSize() { return MAX_CLASS_MAP_SIZE; }
265278
SharedLockGuard classMapSharedGuard() { return SharedLockGuard(&_class_map_lock); }
266279
StringDictionary *stringLabelMap() { return &_string_label_map; }
267280
StringDictionary *contextValueMap() { return &_context_value_map; }

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "tripleBuffer.h"
1313
#include "arch.h"
1414
#include <atomic>
15+
#include <climits>
1516
#include <map>
1617
#include <stddef.h>
1718
#include <stdlib.h>
@@ -550,7 +551,11 @@ class StringDictionary {
550551
// Resolve a key during the dump phase. Safe to call from the dump thread
551552
// after rotate(); must NOT be called from signal handlers or concurrently
552553
// with another lookupDuringDump call.
553-
u32 lookupDuringDump(const char* key, size_t len) {
554+
// size_limit bounds new insertions the same way bounded_lookup() does:
555+
// once active->size() reaches size_limit, previously-known keys still
556+
// resolve (dump/active hits above), but a genuinely new key returns 0
557+
// instead of growing the dictionary further.
558+
u32 lookupDuringDump(const char* key, size_t len, int size_limit = INT_MAX) {
554559
StringDictionaryBuffer* dump = _rot.dumpBuffer();
555560

556561
u32 id = dump->lookup(key, len);
@@ -571,6 +576,7 @@ class StringDictionary {
571576
StringDictionaryBuffer* active = _rot.active();
572577
RefCountGuard guard(active);
573578
if (!guard.isActive()) return 0;
579+
if (active->size() >= size_limit) return 0;
574580
u32 new_id = nextId();
575581
new_id = active->insert_with_id(key, len, new_id);
576582
if (new_id != 0) dump->insert_with_id(key, len, new_id);

0 commit comments

Comments
 (0)