Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 13
feat(nativemem): categorized native-memory accounting — first cut#669
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
61e1d7549c99dada03a78f51aa212b4b8116a9d6f07d93828cbffe7710f608a69a508744a57d312ba784ab07edcde2418521c6823d457b521194e303bc4b039ebd615File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -12,6 +12,7 @@ | ||
| #include "context.h" | ||
| #include "context_api.h" | ||
| #include "counters.h" | ||
| #include "nativeMem.h" | ||
| #include "dictionary.h" | ||
| #include "flightRecorder.inline.h" | ||
| #include "incbin.h" | ||
| @@ -73,6 +74,10 @@ SharedLineNumberTable::~SharedLineNumberTable() { | ||
| if (_ptr != nullptr) { | ||
| free(_ptr); | ||
| Counters::decrement(LINE_NUMBER_TABLES); | ||
| // _size is the JVMTI entry count passed at construction (see | ||
| // fillJavaMethodInfo), so the byte size matches the allocation. | ||
| NativeMem::record(NM_LINE_TABLES, -(long long)((size_t)_size * | ||
| sizeof(jvmtiLineNumberEntry))); | ||
| } | ||
| } | ||
| @@ -420,6 +425,8 @@ void Lookup::fillJavaMethodInfo(MethodInfo *mi, jmethodID method, | ||
| line_number_table_size, owned_table); | ||
| // Increment counter for tracking live line number tables | ||
| Counters::increment(LINE_NUMBER_TABLES); | ||
| NativeMem::record(NM_LINE_TABLES, (long long)((size_t)line_number_table_size * | ||
| sizeof(jvmtiLineNumberEntry))); | ||
| } | ||
| } | ||
| @@ -810,7 +817,9 @@ off_t Recording::finishChunk(bool end_recording, bool do_cleanup) { | ||
| // dictionary) will reflect the previous serialization. That is, some level of | ||
| // familiarity with the code base will be required to use this diagnostic | ||
| // information for now. | ||
| updateNativeMemStats(); | ||
| writeCounters(_buf); | ||
| writeNativeMem(_buf); | ||
| // Keep a simple stats for where we failed to unwind | ||
| // For the sakes of simplicity we are not keeping the count of failed unwinds which would also be | ||
| @@ -1776,6 +1785,69 @@ void Recording::writeLogLevels(Buffer *buf) { | ||
| } | ||
| } | ||
| void Recording::updateNativeMemStats() { | ||
| // Refresh the moving-window averages and the observed total peak. Per-category | ||
| // peaks are maintained precisely at allocation time, so they are not sampled | ||
| // here; the total peak is bracketed instead (see writeNativeMem). | ||
| NativeMem::sample(); | ||
| // Mirror the totals into the flat counter table so they flow out through the | ||
| // existing counter path (JFR T_DATADOG_COUNTER events and the JNI debug | ||
| // counters). NATIVE_MEM_MAX_BYTES carries the upper bound on the total peak | ||
| // (sum of precise per-category peaks); the observed sampled total and the | ||
| // per-category values are emitted by writeNativeMem(). | ||
| Counters::set(NATIVE_MEM_LIVE_BYTES, NativeMem::liveTotal()); | ||
| Counters::set(NATIVE_MEM_AVG_BYTES, NativeMem::avgTotal()); | ||
| Counters::set(NATIVE_MEM_MAX_BYTES, NativeMem::maxTotal()); | ||
| } | ||
rkennke marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| void Recording::writeNativeMem(Buffer *buf) { | ||
| // Emit native-memory stats as counter events, reusing the counter event format | ||
| // so they land alongside the totals without needing a dedicated event type or | ||
| // a slot in the counter table. | ||
| auto emit = [&](const char *label, long long value) { | ||
| // Clamp to 0 before encoding: the value is serialized as an unsigned varint | ||
| // (putVar64), so a negative live gauge would emit a huge value and corrupt | ||
| // the counter stream. avg/max are already non-negative; live is clamped | ||
| // here to match sample()/liveTotal(). | ||
| if (value < 0) { | ||
| value = 0; | ||
| } | ||
| int start = buf->skip(1); | ||
| buf->putVar64(T_DATADOG_COUNTER); | ||
| buf->putVar64(_start_ticks); | ||
| buf->putUtf8(label); | ||
| buf->putVar64(value); | ||
| writeEventSizePrefix(buf, start); | ||
| flushIfNeeded(buf); | ||
| }; | ||
rkennke marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| // Per-category live/avg/max, named "<metric>.<category>". The max here is the | ||
| // precise per-category peak tracked at allocation time. | ||
| for (int c = 0; c < NM_NUM_CATEGORIES; c++) { | ||
| NativeMemCategory cat = (NativeMemCategory)c; | ||
| const char *name = NativeMem::categoryName(cat); | ||
| const struct { | ||
| const char *prefix; | ||
| long long value; | ||
| } metrics[] = { | ||
| {"native_mem_live_bytes.", NativeMem::live(cat)}, | ||
| {"native_mem_avg_bytes.", NativeMem::avg(cat)}, | ||
| {"native_mem_max_bytes.", NativeMem::max(cat)}, | ||
| }; | ||
rkennke marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| for (const auto &m : metrics) { | ||
| char label[64]; | ||
| snprintf(label, sizeof(label), "%s%s", m.prefix, name); | ||
| emit(label, m.value); | ||
| } | ||
| } | ||
| // NATIVE_MEM_MAX_BYTES already carries the upper bound on the total peak (sum | ||
| // of precise per-category peaks); here we also emit the largest observed | ||
| // sampled total (a non-atomic per-category sum; approximate). | ||
| emit("native_mem_max_observed_total_bytes", NativeMem::maxTotalObserved()); | ||
| } | ||
| void Recording::writeCounters(Buffer *buf) { | ||
| long long *counters = Counters::getCounters(); | ||
| if (counters) { | ||
| @@ -2064,6 +2136,9 @@ Error FlightRecorder::newRecording(bool reset) { | ||
| } | ||
| _rec = new Recording(fd, _args); | ||
| // The Recording embeds the JFR RecordingBuffer array and the cpu-monitor | ||
| // buffer, so its allocation size is the profiler's JFR buffer footprint. | ||
| NativeMem::record(NM_JFR_BUFFERS, (long long)sizeof(Recording)); | ||
| return Error::OK; | ||
| } | ||
| @@ -2074,7 +2149,12 @@ void FlightRecorder::stop() { | ||
| if (rec != nullptr) { | ||
| // NULL first, deallocate later | ||
| _rec = nullptr; | ||
| // Decrement AFTER delete: ~Recording() runs finishChunk(), which emits the | ||
| // native-memory counters for the final chunk. The Recording buffers are | ||
| // still live during that serialization, so account the free only once it | ||
| // has actually happened. | ||
| delete rec; | ||
| NativeMem::record(NM_JFR_BUFFERS, -(long long)sizeof(Recording)); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| /* | ||
| * Copyright 2026, Datadog, Inc. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
Copilot marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| #include "nativeMem.h" | ||
| volatile long long NativeMem::_live[NM_NUM_CATEGORIES] = {}; | ||
| volatile long long NativeMem::_max[NM_NUM_CATEGORIES] = {}; | ||
| long long NativeMem::_window[NM_NUM_CATEGORIES][NativeMem::WINDOW] = {}; | ||
| long long NativeMem::_total_window[NativeMem::WINDOW] = {}; | ||
| int NativeMem::_window_pos = 0; | ||
| int NativeMem::_window_count = 0; | ||
| long long NativeMem::_avg[NM_NUM_CATEGORIES] = {}; | ||
| long long NativeMem::_total_avg = 0; | ||
| long long NativeMem::_total_max_observed = 0; | ||
| long long NativeMem::liveTotal() { | ||
| long long total = 0; | ||
| for (int c = 0; c < NM_NUM_CATEGORIES; c++) { | ||
| // Clamp per-category negatives to 0 (see sample()): the total is exported | ||
| // as an unsigned varint, so a stray negative would otherwise serialize as a | ||
| // huge value and corrupt the counter stream. | ||
| long long v = load(_live[c]); | ||
| if (v > 0) { | ||
| total += v; | ||
| } | ||
| } | ||
| return total; | ||
| } | ||
| long long NativeMem::maxTotal() { | ||
| long long total = 0; | ||
| for (int c = 0; c < NM_NUM_CATEGORIES; c++) { | ||
| total += load(_max[c]); | ||
| } | ||
| return total; | ||
| } | ||
| void NativeMem::sample() { | ||
| long long total = 0; | ||
| for (int c = 0; c < NM_NUM_CATEGORIES; c++) { | ||
| long long v = load(_live[c]); | ||
| // A category's live bytes are never negative under correct pairing (asserted | ||
| // in record()). This clamp is a release-mode safety net: should an accounting | ||
| // bug slip past the assert under NDEBUG, it keeps a negative from skewing the | ||
| // window average and total rather than propagating garbage. | ||
| if (v < 0) { | ||
| v = 0; | ||
| } | ||
| _window[c][_window_pos] = v; | ||
| total += v; | ||
rkennke marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| // The per-category peaks are maintained precisely at allocation time by | ||
| // record(); here we only track the largest observed total. Note `total` is a | ||
| // non-atomic sum of the per-category gauges read moments apart, so it is an | ||
| // approximate sampled figure, not a strict instantaneous total. | ||
| _total_window[_window_pos] = total; | ||
| if (total > _total_max_observed) { | ||
| _total_max_observed = total; | ||
| } | ||
| _window_pos = (_window_pos + 1) % WINDOW; | ||
| if (_window_count < WINDOW) { | ||
| _window_count++; | ||
| } | ||
| for (int c = 0; c < NM_NUM_CATEGORIES; c++) { | ||
| long long sum = 0; | ||
| for (int i = 0; i < _window_count; i++) { | ||
| sum += _window[c][i]; | ||
| } | ||
| _avg[c] = sum / _window_count; | ||
| } | ||
| long long total_sum = 0; | ||
| for (int i = 0; i < _window_count; i++) { | ||
| total_sum += _total_window[i]; | ||
| } | ||
| _total_avg = total_sum / _window_count; | ||
| } | ||
| void NativeMem::reset() { | ||
| for (int c = 0; c < NM_NUM_CATEGORIES; c++) { | ||
| store(_live[c], (long long)0); | ||
| store(_max[c], (long long)0); | ||
| _avg[c] = 0; | ||
| for (int i = 0; i < WINDOW; i++) { | ||
| _window[c][i] = 0; | ||
| } | ||
| } | ||
| for (int i = 0; i < WINDOW; i++) { | ||
| _total_window[i] = 0; | ||
| } | ||
| _window_pos = 0; | ||
| _window_count = 0; | ||
| _total_avg = 0; | ||
| _total_max_observed = 0; | ||
| } | ||
| const char *NativeMem::categoryName(NativeMemCategory category) { | ||
| #define X_NM_NAME(a, b) b, | ||
| static const char *const names[] = {DD_NATIVE_MEM_CATEGORY_TABLE(X_NM_NAME)}; | ||
| #undef X_NM_NAME | ||
| if (category < 0 || category >= NM_NUM_CATEGORIES) { | ||
| return "unknown"; | ||
| } | ||
| return names[category]; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.