Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ddprof-lib/src/main/cpp/arguments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,13 @@ Error Arguments::parse(const char *args) {
if (value != nullptr) {
if (strcmp(value, "false") == 0) {
_force_jmethodID = false;
_lazy_jmethodID = false;
} else if (strcmp(value, "true") == 0) {
_force_jmethodID = true;
_lazy_jmethodID = false;
} else if (strcmp(value, "lazy") == 0) {
_force_jmethodID = false;
_lazy_jmethodID = true;
} else {
msg = "Invalid jmethodID creation value";
}
Expand Down
4 changes: 3 additions & 1 deletion ddprof-lib/src/main/cpp/arguments.h
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ class Arguments {
bool _nativesocket;
long _nativesocket_interval; // initial sampling period in nanoseconds; 0 = engine default
bool _force_jmethodID; // Load all jmethodIDs, true by default
bool _lazy_jmethodID; // fjmethodid=lazy: allocate jmethodIDs only for sampled classes, at dump time

Arguments(bool persistent = false)
: _buf(NULL),
Expand Down Expand Up @@ -324,7 +325,8 @@ class Arguments {
_jvmtistacks(false),
_nativesocket(false),
_nativesocket_interval(0),
_force_jmethodID(true) {}
_force_jmethodID(true),
_lazy_jmethodID(false) {}

~Arguments();

Expand Down
34 changes: 34 additions & 0 deletions ddprof-lib/src/main/cpp/counters.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,40 @@
* resolves via its malloc fallback and does not land here. The frame \
* serializes as "unknown". */ \
X(METHOD_RESOLVE_SYMBOL_UNREADABLE, "method_resolve_symbol_unreadable") \
/* fjmethodid=lazy resolution outcomes. UNLOADED: the frame's class is no \
* longer registered or reachable. NOT_CURRENT: the class is alive but the \
* Method* is not among its current methods (freed or redefined). Both \
* serialize as "unknown". CLASS_LOADED: classes whose jmethodIDs were \
* allocated at dump time because one of their methods was sampled. */ \
X(METHOD_RESOLVE_LAZY_UNLOADED, "method_resolve_lazy_unloaded") \
X(METHOD_RESOLVE_LAZY_NOT_CURRENT, "method_resolve_lazy_not_current") \
X(METHOD_RESOLVE_LAZY_CLASS_LOADED, "method_resolve_lazy_class_loaded") \
/* STALE: the class now at the frame's holder address is a different load \
* (its trace ID differs); serializes as "unknown". \
* ADD_FAILED: registration failed, so the class was preloaded instead. \
* UNTAGGABLE: a Method* using address bits the raw-frame tag needs, or \
* an unreadable holder trace ID; the frame is stored as not walkable. */ \
X(METHOD_RESOLVE_LAZY_STALE, "method_resolve_lazy_stale") \
/* REDEFINED: raw frame of a class that has been redefined (its Method*s \
* may have been freed and reused); serializes as "unknown". */ \
X(METHOD_RESOLVE_LAZY_REDEFINED, "method_resolve_lazy_redefined") \
/* Registered classes marked by the redefinition hooks (once per class \
* per redefine/retransform call). */ \
X(KLASS_REGISTRY_REDEFINITIONS_MARKED, "klass_registry_redefinitions_marked") \
/* Registrations whose class trace ID no longer fits in a raw frame's \
* 25-bit tag. Nonzero means trace-ID tags of two classes can now collide: \
* a frame sampled from an unloaded class could then, if a newer class \
* also took over its Method* memory, resolve to that class's method. */ \
X(KLASS_TRACE_ID_BEYOND_TAG_RANGE, "klass_trace_id_beyond_tag_range") \
/* Raw Method* frame occurrences answered from Lookup's per-dump cache \
* instead of resolving them again. */ \
X(RAW_FRAME_RESOLVE_CACHE_HITS, "raw_frame_resolve_cache_hits") \
X(KLASS_REGISTRY_ADD_FAILED, "klass_registry_add_failed") \
X(WALKVM_RAW_FRAME_UNTAGGABLE, "walkvm_raw_frame_untaggable") \
/* Gauge, set at each fjmethodid=lazy start: the Klass::_trace_id offset \
* raw frames are tagged with, or -1 if it was not found (lazy mode then \
* falls back to fjmethodid=false). 0 if lazy mode was never requested. */ \
X(KLASS_TRACE_ID_OFFSET, "klass_trace_id_offset") \
/* Strict subset of SAMPLES_DROPPED_THREAD_LOCAL, not an independent count: \
* ThreadLocalDataPool::claim() increments this on capacity exhaustion, and \
* every acquireCurrent() caller that gets nullptr back -- for this or any \
Expand Down
14 changes: 13 additions & 1 deletion ddprof-lib/src/main/cpp/flightRecorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -702,13 +702,25 @@ MethodInfo *Lookup::resolveMethod(ASGCT_CallFrame &frame) {
return fillMethod(frame, method_id, bci, prof_thread);
}

jmethodID Lookup::resolveRawMethod(const ASGCT_CallFrame &frame) {
RawMethodKey key{FrameType::untagPointer(frame.method), FrameType::rawTag(frame.bci, frame.method)};
auto cached = _raw_method_cache.find(key);
if (cached != _raw_method_cache.end()) {
Counters::increment(RAW_FRAME_RESOLVE_CACHE_HITS);
return cached->second;
}
jmethodID method_id = JVMSupport::resolve(frame.method, frame.bci);
_raw_method_cache.emplace(key, method_id);
return method_id;
}

MethodInfo *Lookup::fillMethod(ASGCT_CallFrame &frame, jmethodID method_id,
jint bci, ProfiledThread* const prof_thread) {

assert(prof_thread != nullptr);
// Resolve native method
if (FrameType::isRawPointer(bci)) {
method_id = JVMSupport::resolve(frame.method);
method_id = resolveRawMethod(frame);
if (method_id == nullptr) {
return unknownMethod();
}
Expand Down
26 changes: 26 additions & 0 deletions ddprof-lib/src/main/cpp/flightRecorder.h
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,29 @@ class Lookup {
std::unordered_map<void*, u32, std::hash<void*>, std::equal_to<void*>,
CountingAllocator<std::pair<void* const, u32>, NM_JFR_BUFFERS>>
_vtable_receiver_cache;

// Per-dump cache of raw Method* frame resolutions (HotSpot cstack=vm frames
// whose class had no jmethodID when sampled). The same raw method recurs in
// many traces, and each resolution sets up crash protection, takes the
// KlassRegistry mutex and pins the class through JNI. Keyed by the untagged
// Method* and the frame's tag, so occurrences that differ only in bci or
// frame type share one entry. A cached result stays right for the whole
// dump: an accepted frame was sampled from that method, and a refused one
// stays refused.
struct RawMethodKey {
const void* method;
uint32_t tag;
bool operator==(const RawMethodKey& o) const { return method == o.method && tag == o.tag; }
};
struct RawMethodKeyHash {
size_t operator()(const RawMethodKey& k) const {
return std::hash<const void*>()(k.method) ^ ((size_t)k.tag * KNUTH_MULTIPLICATIVE_CONSTANT);
}
};
std::unordered_map<RawMethodKey, jmethodID, RawMethodKeyHash, std::equal_to<RawMethodKey>,
CountingAllocator<std::pair<const RawMethodKey, jmethodID>, NM_JFR_BUFFERS>>
_raw_method_cache;

Dictionary _packages;
Dictionary _symbols;

Expand Down Expand Up @@ -547,6 +570,9 @@ class Lookup {
_symbols() {}

MethodInfo *resolveMethod(ASGCT_CallFrame &frame);
// Resolves a raw Method* frame to a jmethodID (nullptr if it must be
// treated as unknown), at most once per distinct method and tag per dump.
jmethodID resolveRawMethod(const ASGCT_CallFrame &frame);
u32 getPackage(const char *class_name);
u32 getSymbol(const char *name);
};
Expand Down
52 changes: 51 additions & 1 deletion ddprof-lib/src/main/cpp/frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#define _FRAME_H

#include <cassert>
#include <cstdint>
#include "vmEntry.h"

enum FrameTypeId {
Expand All @@ -35,11 +36,22 @@ enum FrameTypeId {
// Packs frame type and BCI into a single int field stored in CallTrace frames.
//
// Bit layout of an encoded value (ENCODED_MASK set):
// bit 30 RAW_POINTER_MASK — value is a raw native PC (HotSpot only)
// bit 30 RAW_POINTER_MASK — method field is a raw Method* (HotSpot only)
// bits 29–24 raw tag, bits 9–4 — only with RAW_POINTER_MASK, see below
// bits 23–21 frame type — FrameTypeId (0–7)
// bit 20 ENCODED_MASK — set to distinguish encoded values from raw ASGCT BCIs
// bits 19–16 raw tag, bits 3–0 — only with RAW_POINTER_MASK, see below
// bits 15–0 BCI — bytecode index (0–65534; never 65535, see BCI_MASK)
//
// A raw Method* frame sampled in fjmethodid=lazy mode also carries a
// RAW_TAG_BITS-wide tag identifying the load of the class it was sampled from
// (see HotspotSupport::TRACE_ID_TAG). Its low 10 bits sit in the otherwise
// unused BCI bits above; its high 16 bits sit in the top 16 bits of the
// Method* pointer, which user-space addresses below 2^48 leave clear. The tag
// makes frames sampled from different loads of a class at the same
// Klass*/Method* addresses differ, and lets resolution reject frames whose
// class's memory has since been taken over by another.
//
// When ENCODED_MASK is not set the field is a raw, unencoded BCI from ASGCT (non-VM stack
// walking modes). Raw values may be negative: HotSpot uses -1 as a sentinel for method-entry
// and synchronization-entry samples. Both encode() and bci() clamp negative values to 0 so
Expand All @@ -53,7 +65,15 @@ class FrameType {
static constexpr int TYPE_SHIFT = 21;
static constexpr int ENCODED_MASK = 1 << 20; // distinguishes encoded values from raw ASGCT BCIs
static constexpr int RAW_POINTER_MASK = 1 << 30;
static constexpr int RAW_TAG_LOW_SHIFT = 16; // tag bits 3–0 -> bits 19–16
static constexpr int RAW_TAG_MID_SHIFT = 24; // tag bits 9–4 -> bits 29–24
static constexpr int RAW_TAG_BCI_BITS = 10;
static constexpr int POINTER_TAG_SHIFT = 48; // tag bits 25–10 -> pointer bits 63–48
static constexpr uintptr_t POINTER_ADDRESS_MASK = ((uintptr_t)1 << POINTER_TAG_SHIFT) - 1;
public:
static constexpr int RAW_TAG_BITS = 26;
static constexpr uint32_t RAW_TAG_MASK = (1u << RAW_TAG_BITS) - 1;

// Produces an encoded int from a frame type and BCI. Negative BCIs (HotSpot -1 sentinels
// for method-entry/sync-entry) are mapped to 0 rather than wrapping to 0xffff.
static inline int encode(int type, int bci, bool rawPointer = false) {
Expand All @@ -63,6 +83,36 @@ class FrameType {
return ENCODED_MASK | (type << TYPE_SHIFT) | bci_bits | (rawPointer ? RAW_POINTER_MASK : 0);
}

// Encodes a raw Method* frame carrying `tag` (see the layout above). The
// matching method field is tagPointer(method, tag).
static inline int encodeRaw(int type, int bci, uint32_t tag) {
return encode(type, bci, true) | (int)((tag & 0xf) << RAW_TAG_LOW_SHIFT) |
(int)(((tag >> 4) & 0x3f) << RAW_TAG_MID_SHIFT);
}

// True if `ptr` leaves the pointer tag bits clear, i.e. tagPointer() is reversible.
static inline bool canTagPointer(const void* ptr) {
return ((uintptr_t)ptr & ~POINTER_ADDRESS_MASK) == 0;
}

static inline const void* tagPointer(const void* ptr, uint32_t tag) {
assert(canTagPointer(ptr));
uintptr_t high = (uintptr_t)((tag & RAW_TAG_MASK) >> RAW_TAG_BCI_BITS);
return (const void*)((uintptr_t)ptr | (high << POINTER_TAG_SHIFT));
}

static inline const void* untagPointer(const void* ptr) {
return (const void*)((uintptr_t)ptr & POINTER_ADDRESS_MASK);
}

// Reassembles the tag of a raw Method* frame from its encoded BCI and method field.
static inline uint32_t rawTag(int bci, const void* ptr) {
uint32_t low = ((uint32_t)bci >> RAW_TAG_LOW_SHIFT) & 0xf;
uint32_t mid = ((uint32_t)bci >> RAW_TAG_MID_SHIFT) & 0x3f;
uint32_t high = (uint32_t)((uintptr_t)ptr >> POINTER_TAG_SHIFT);
return (low | (mid << 4) | (high << RAW_TAG_BCI_BITS)) & RAW_TAG_MASK;
}

// Extracts the BCI from either an encoded value or a raw ASGCT BCI.
// Negative values (HotSpot -1 sentinels) are clamped to 0, matching encode().
static inline int bci(int bci) {
Expand Down
Loading
Loading