diff --git a/cmake/Findlibdatadog.cmake b/cmake/Findlibdatadog.cmake index 69920f3f..bd93c7e4 100644 --- a/cmake/Findlibdatadog.cmake +++ b/cmake/Findlibdatadog.cmake @@ -4,15 +4,24 @@ # libdatadog : common profiler imported libraries https://github.com/DataDog/libdatadog/releases set(TAG_LIBDATADOG - "v26.0.0" + "v28.0.2" CACHE STRING "libdatadog github tag") -set(Datadog_ROOT ${VENDOR_PATH}/libdatadog-${TAG_LIBDATADOG}) +# Override with a local build by passing -DDatadog_LOCAL_ROOT=/path/to/libdatadog +set(Datadog_LOCAL_ROOT + "" + CACHE PATH "Path to a local libdatadog build (skips GitHub download)") -message(STATUS "${CMAKE_SOURCE_DIR}/tools/fetch_libddprof.sh ${TAG_LIBDATADOG} ${LIBDATADOG_ROOT}") -execute_process( - COMMAND "${CMAKE_SOURCE_DIR}/tools/fetch_libdatadog.sh" ${TAG_LIBDATADOG} ${Datadog_ROOT} - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND_ERROR_IS_FATAL ANY) +if(Datadog_LOCAL_ROOT) + message(STATUS "Using local libdatadog override: ${Datadog_LOCAL_ROOT}") + set(Datadog_ROOT ${Datadog_LOCAL_ROOT}) +else() + set(Datadog_ROOT ${VENDOR_PATH}/libdatadog-${TAG_LIBDATADOG}) + message(STATUS "${CMAKE_SOURCE_DIR}/tools/fetch_libdatadog.sh ${TAG_LIBDATADOG} ${Datadog_ROOT}") + execute_process( + COMMAND "${CMAKE_SOURCE_DIR}/tools/fetch_libdatadog.sh" ${TAG_LIBDATADOG} ${Datadog_ROOT} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND_ERROR_IS_FATAL ANY) +endif() set(DataDog_DIR "${Datadog_ROOT}/cmake") diff --git a/docs/profile_sample_type_mapping.md b/docs/profile_sample_type_mapping.md new file mode 100644 index 00000000..bb4ad5e6 --- /dev/null +++ b/docs/profile_sample_type_mapping.md @@ -0,0 +1,114 @@ +# Profile Sample Type Mapping + +## Context + +ddprof instruments the kernel via perf events and reports profiling data as +pprof profiles. Each pprof profile has a fixed set of **value columns** +(sample types), each identified by a `(type, unit)` string pair that the +Datadog backend uses to interpret the data (e.g. `cpu-time/nanoseconds`, +`alloc-space/bytes`). + +libdatadog v28+ represents these as a `ddog_prof_SampleType` enum rather than +free-form strings. ddprof maps each watcher to the appropriate enum values via +the `WatcherSampleTypes` struct defined in `include/watcher_sample_types.hpp`. + +--- + +## Two Dimensions + +### Primary vs. Count companion + +Most profiling events are reported as a pair of columns: + +| Role | Meaning | Example | +|---|---|---| +| **Primary** | The measured quantity (bytes, nanoseconds, event count) | `alloc-space/bytes` | +| **Count companion** | Number of samples that contributed to the primary value | `alloc-samples/count` | + +When no count companion applies (tracepoints, plain sample counts), the count +slot is left empty. In `WatcherSampleTypes`, the sentinel value +`k_stype_val_sample` in `count_types[]` signals "no count companion". + +### Aggregation mode + +Each watcher can operate in one or both of two modes, controlled by the `mode=` +event configuration key: + +| Mode | Constant | `mode=` flag | Meaning | +|---|---|---|---| +| Sum | `kSumPos` (0) | `s` (default) | Cumulative totals over the collection interval | +| Live | `kLiveSumPos` (1) | `l` | Snapshot of currently live/in-use resources | + +The two modes produce **different pprof columns**. For example, allocation +profiling in sum mode reports how much was allocated; in live mode it reports +what is still alive (useful for leak detection). + +--- + +## Watcher → Column Mapping + +### `k_stype_cpu` — CPU profiling (`sCPU`) + +| Mode | Primary column | Count companion | +|---|---|---| +| Sum (`kSumPos`) | `cpu-time / nanoseconds` | `cpu-samples / count` | +| Live (`kLiveSumPos`) | `cpu-samples / count` | *(none)* | + +CPU profiling is almost always used in sum mode. Live mode is not a meaningful +concept for CPU time; the live slot exists for completeness but is unused in +practice. + +### `k_stype_alloc` — Allocation profiling (`sALLOC`) + +| Mode | Primary column | Count companion | +|---|---|---| +| Sum (`kSumPos`) | `alloc-space / bytes` | `alloc-samples / count` | +| Live (`kLiveSumPos`) | `inuse-space / bytes` | `inuse-objects / count` | + +The same `sALLOC` watcher produces different columns depending on the mode: +- `mode=s` (default): total bytes and sample count allocated over the interval. +- `mode=l`: bytes and object count still alive (not yet freed) — the basis for + heap leak profiles. +- `mode=sl`: all four columns are emitted simultaneously. + +### `k_stype_tracepoint` — Hardware counters and perf tracepoints + +| Mode | Primary column | Count companion | +|---|---|---| +| Sum (`kSumPos`) | `tracepoint / events` | *(none)* | +| Live (`kLiveSumPos`) | `tracepoint / events` | *(none)* | + +Used by hardware performance counters (`hCPU`, `hREF`, `hINST`, …), software +events (`sPF`, `sCS`, …), and named kernel tracepoints (e.g. +`event=tlb:tlb_flush`). Each sample represents one event occurrence; there is +no meaningful count companion. + +> **Backend note**: prior to libdatadog v28, the `(tracepoint, events)` strings +> were passed as free-form values. In v28+, `DDOG_PROF_SAMPLE_TYPE_TRACEPOINT` +> is the canonical enum value. The integer value of this enum is verified at +> compile time in `ddprof_pprof.cc` via `static_assert`. + +--- + +## Sentinel and Safety + +`k_stype_val_sample` (the enum integer for `DDOG_PROF_SAMPLE_TYPE_SAMPLE`) is +used as a sentinel in `count_types[]` to mean "no count companion". The profile +creation code in `pprof_create_profile()` skips registering a count column when +it sees this value: + +```cpp +if (count_t != static_cast(DDOG_PROF_SAMPLE_TYPE_SAMPLE)) { + w.pprof_indices[m].pprof_count_index = slots.ensure(count_t); +} +``` + +The integer values of all `k_stype_val_*` constants are verified against the +libdatadog enum at compile time via `static_assert` in `ddprof_pprof.cc`. If +libdatadog reorders or adds enum variants in a future release, these asserts +will fail and force a deliberate update of the constants and the string mappings +in `sample_type_name()`. + +The `sample_type_name()` function in `ddprof_pprof.cc` (and the corresponding +`static_assert`s) cross-checks that the integer-to-string mapping matches the +strings the backend expects in the pprof wire format. diff --git a/include/perf_watcher.hpp b/include/perf_watcher.hpp index 71c11f55..9284e13c 100644 --- a/include/perf_watcher.hpp +++ b/include/perf_watcher.hpp @@ -7,10 +7,11 @@ #include "ddprof_defs.hpp" #include "event_config.hpp" -#include +#include "watcher_sample_types.hpp" #include #include +#include namespace ddprof { @@ -55,7 +56,9 @@ struct PerfWatcher { int64_t sample_period; uint64_t sample_frequency; }; - int sample_type_id; // index into the sample types defined in this header + WatcherSampleTypes sample_type_info; // pprof types for each aggregation mode + bool + pprof_active; // false = watcher does not contribute to pprof (e.g., sDUM) EventConfValueSource value_source; // how to normalize the sample value EventAggregationMode aggregation_mode; @@ -76,27 +79,6 @@ struct PerfWatcher { bool instrument_self; // do my own perf_event_open, etc }; -// The Datadog backend only understands pre-configured event types. Those -// types are defined here, and then referenced in the watcher -// The last column is a dependent type which is always aggregated as a count -// whenever the main type is aggregated. -// type, pprof, unit, live-pprof, sample_type, -// a, b, c, d, e, -#define PROFILE_TYPE_TABLE(X) \ - X(NOCOUNT, "nocount", nocount, "undef", NOCOUNT) \ - X(TRACEPOINT, "tracepoint", events, "undef", NOCOUNT) \ - X(CPU_NANOS, "cpu-time", nanoseconds, "undef", CPU_SAMPLE) \ - X(CPU_SAMPLE, "cpu-samples", count, "undef", NOCOUNT) \ - X(ALLOC_SAMPLE, "alloc-samples", count, "inuse-objects", NOCOUNT) \ - X(ALLOC_SPACE, "alloc-space", bytes, "inuse-space", ALLOC_SAMPLE) - -// defines enum of profile types -#define X_ENUM(a, b, c, d, e) DDPROF_PWT_##a, -enum DDPROF_SAMPLE_TYPES : uint8_t { - PROFILE_TYPE_TABLE(X_ENUM) DDPROF_PWT_LENGTH, -}; -#undef X_ENUM - // Define our own event type on top of perf event types enum DDProfTypeId : uint8_t { kDDPROF_TYPE_CUSTOM = PERF_TYPE_MAX + 100 }; @@ -128,33 +110,33 @@ enum DDProfCustomCountId : uint8_t { // events are marked as tracepoint unless they represent a well-known profiling // type! // clang-format off -// short desc perf event type perf event count type period/freq profile sample type addtl. configs +// short desc perf event type perf event count type period/freq sample types pprof_active addtl. configs // cppcheck-suppress preprocessorErrorDirective #define EVENT_CONFIG_TABLE(X) \ - X(hCPU, "CPU Cycles", PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES, 99, DDPROF_PWT_TRACEPOINT, IS_FREQ) \ - X(hREF, "Ref. CPU Cycles", PERF_TYPE_HARDWARE, PERF_COUNT_HW_REF_CPU_CYCLES, 1000, DDPROF_PWT_TRACEPOINT, IS_FREQ) \ - X(hINST, "Instr. Count", PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS, 1000, DDPROF_PWT_TRACEPOINT, IS_FREQ) \ - X(hCREF, "Cache Ref.", PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_REFERENCES, 999, DDPROF_PWT_TRACEPOINT, {}) \ - X(hCMISS, "Cache Miss", PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_MISSES, 999, DDPROF_PWT_TRACEPOINT, {}) \ - X(hBRANCH, "Branche Instr.", PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_INSTRUCTIONS, 999, DDPROF_PWT_TRACEPOINT, {}) \ - X(hBMISS, "Branch Miss", PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_MISSES, 999, DDPROF_PWT_TRACEPOINT, {}) \ - X(hBUS, "Bus Cycles", PERF_TYPE_HARDWARE, PERF_COUNT_HW_BUS_CYCLES, 1000, DDPROF_PWT_TRACEPOINT, IS_FREQ) \ - X(hBSTF, "Bus Stalls(F)", PERF_TYPE_HARDWARE, PERF_COUNT_HW_STALLED_CYCLES_FRONTEND, 1000, DDPROF_PWT_TRACEPOINT, IS_FREQ) \ - X(hBSTB, "Bus Stalls(B)", PERF_TYPE_HARDWARE, PERF_COUNT_HW_STALLED_CYCLES_BACKEND, 1000, DDPROF_PWT_TRACEPOINT, IS_FREQ) \ - X(sCPU, "CPU Time", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_TASK_CLOCK, 99, DDPROF_PWT_CPU_NANOS, IS_FREQ_TRY_KERNEL) \ - X(sPF, "Page Faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS, 1, DDPROF_PWT_TRACEPOINT, USE_KERNEL) \ - X(sCS, "Con. Switch", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CONTEXT_SWITCHES, 1, DDPROF_PWT_TRACEPOINT, USE_KERNEL) \ - X(sMig, "CPU Migrations", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_MIGRATIONS, 99, DDPROF_PWT_TRACEPOINT, IS_FREQ) \ - X(sPFMAJ, "Major Faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS_MAJ, 99, DDPROF_PWT_TRACEPOINT, USE_KERNEL) \ - X(sPFMIN, "Minor Faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS_MIN, 99, DDPROF_PWT_TRACEPOINT, USE_KERNEL) \ - X(sALGN, "Align. Faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_ALIGNMENT_FAULTS, 99, DDPROF_PWT_TRACEPOINT, IS_FREQ) \ - X(sEMU, "Emu. Faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_EMULATION_FAULTS, 99, DDPROF_PWT_TRACEPOINT, IS_FREQ) \ - X(sDUM, "Dummy", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_DUMMY, 1, DDPROF_PWT_NOCOUNT, {}) \ - X(sALLOC, "Allocations", kDDPROF_TYPE_CUSTOM, kDDPROF_COUNT_ALLOCATIONS, 524288, DDPROF_PWT_ALLOC_SPACE, SKIP_FRAMES) + X(hCPU, "CPU Cycles", PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES, 99, k_stype_tracepoint, true, IS_FREQ) \ + X(hREF, "Ref. CPU Cycles", PERF_TYPE_HARDWARE, PERF_COUNT_HW_REF_CPU_CYCLES, 1000, k_stype_tracepoint, true, IS_FREQ) \ + X(hINST, "Instr. Count", PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS, 1000, k_stype_tracepoint, true, IS_FREQ) \ + X(hCREF, "Cache Ref.", PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_REFERENCES, 999, k_stype_tracepoint, true, {}) \ + X(hCMISS, "Cache Miss", PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_MISSES, 999, k_stype_tracepoint, true, {}) \ + X(hBRANCH, "Branche Instr.", PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_INSTRUCTIONS, 999, k_stype_tracepoint, true, {}) \ + X(hBMISS, "Branch Miss", PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_MISSES, 999, k_stype_tracepoint, true, {}) \ + X(hBUS, "Bus Cycles", PERF_TYPE_HARDWARE, PERF_COUNT_HW_BUS_CYCLES, 1000, k_stype_tracepoint, true, IS_FREQ) \ + X(hBSTF, "Bus Stalls(F)", PERF_TYPE_HARDWARE, PERF_COUNT_HW_STALLED_CYCLES_FRONTEND, 1000, k_stype_tracepoint, true, IS_FREQ) \ + X(hBSTB, "Bus Stalls(B)", PERF_TYPE_HARDWARE, PERF_COUNT_HW_STALLED_CYCLES_BACKEND, 1000, k_stype_tracepoint, true, IS_FREQ) \ + X(sCPU, "CPU Time", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_TASK_CLOCK, 99, k_stype_cpu, true, IS_FREQ_TRY_KERNEL) \ + X(sPF, "Page Faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS, 1, k_stype_tracepoint, true, USE_KERNEL) \ + X(sCS, "Con. Switch", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CONTEXT_SWITCHES, 1, k_stype_tracepoint, true, USE_KERNEL) \ + X(sMig, "CPU Migrations", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_MIGRATIONS, 99, k_stype_tracepoint, true, IS_FREQ) \ + X(sPFMAJ, "Major Faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS_MAJ, 99, k_stype_tracepoint, true, USE_KERNEL) \ + X(sPFMIN, "Minor Faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS_MIN, 99, k_stype_tracepoint, true, USE_KERNEL) \ + X(sALGN, "Align. Faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_ALIGNMENT_FAULTS, 99, k_stype_tracepoint, true, IS_FREQ) \ + X(sEMU, "Emu. Faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_EMULATION_FAULTS, 99, k_stype_tracepoint, true, IS_FREQ) \ + X(sDUM, "Dummy", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_DUMMY, 1, k_stype_tracepoint, false, {}) \ + X(sALLOC, "Allocations", kDDPROF_TYPE_CUSTOM, kDDPROF_COUNT_ALLOCATIONS, 524288, k_stype_alloc, true, SKIP_FRAMES) // clang-format on -#define X_ENUM(a, b, c, d, e, f, g) DDPROF_PWE_##a, +#define X_ENUM(a, b, c, d, e, f, g, h) DDPROF_PWE_##a, enum DDPROF_EVENT_NAMES : int8_t { DDPROF_PWE_TRACEPOINT = -1, EVENT_CONFIG_TABLE(X_ENUM) DDPROF_PWE_LENGTH, @@ -165,16 +147,9 @@ enum DDPROF_EVENT_NAMES : int8_t { const PerfWatcher *ewatcher_from_idx(int idx); const PerfWatcher *ewatcher_from_str(const char *str); const PerfWatcher *tracepoint_default_watcher(); -bool watcher_has_countable_sample_type(const PerfWatcher *watcher); bool watcher_has_tracepoint(const PerfWatcher *watcher); -int watcher_to_count_sample_type_id(const PerfWatcher *watcher); const char *event_type_name_from_idx(int idx); -// Helper functions for sample types -const char *sample_type_name_from_idx(int idx, EventAggregationModePos pos); -const char *sample_type_unit_from_idx(int idx); -int sample_type_id_to_count_sample_type_id(int idx); - // Helper functions, mostly for tests uint64_t perf_event_default_sample_type(); void log_watcher(const PerfWatcher *w, int idx); diff --git a/include/watcher_sample_types.hpp b/include/watcher_sample_types.hpp new file mode 100644 index 00000000..36cad664 --- /dev/null +++ b/include/watcher_sample_types.hpp @@ -0,0 +1,56 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. This product includes software +// developed at Datadog (https://www.datadoghq.com/). Copyright 2021-Present +// Datadog, Inc. + +#pragma once + +// Maps a watcher's event to pprof sample types for each aggregation mode. +// Values are ddog_prof_SampleType stored as uint32_t to avoid including +// in this header. Static asserts in ddprof_pprof.cc +// verify the values match the libdatadog enum. +// count_types[pos] == k_stype_val_sample signals "no count companion". + +#include "event_config.hpp" + +#include + +namespace ddprof { + +struct WatcherSampleTypes { + uint32_t sample_types[kNbEventAggregationModes]; // [kSumPos, kLiveSumPos] + uint32_t count_types[kNbEventAggregationModes]; // companion counts +}; + +// ddog_prof_SampleType integer values for libdatadog v28. +// Stored as uint32_t to avoid including here. +// Static asserts in ddprof_pprof.cc verify these against the actual enum. +inline constexpr uint32_t k_stype_val_sample = 37; // SAMPLE +inline constexpr uint32_t k_stype_val_tracepoint = 38; // TRACEPOINT +inline constexpr uint32_t k_stype_val_cpu_time = 4; // CPU_TIME +inline constexpr uint32_t k_stype_val_cpu_samples = 5; // CPU_SAMPLES +inline constexpr uint32_t k_stype_val_alloc_space = 3; // ALLOC_SPACE +inline constexpr uint32_t k_stype_val_alloc_samples = 0; // ALLOC_SAMPLES +inline constexpr uint32_t k_stype_val_inuse_space = 28; // INUSE_SPACE +inline constexpr uint32_t k_stype_val_inuse_objects = 27; // INUSE_OBJECTS + +// Generic sample counting (hardware events, misc software events). +// count_types use the sentinel (k_stype_val_sample) because tracepoints +// have no count companion — each sample represents one event occurrence. +// clang-format off +inline constexpr WatcherSampleTypes k_stype_tracepoint = { + {k_stype_val_tracepoint, k_stype_val_tracepoint}, + {k_stype_val_sample, k_stype_val_sample}}; + +// CPU: wall/cpu nanoseconds in sum mode, sample count in live mode. +inline constexpr WatcherSampleTypes k_stype_cpu = { + {k_stype_val_cpu_time, k_stype_val_cpu_samples}, + {k_stype_val_cpu_samples, k_stype_val_sample}}; + +// Allocation: bytes allocated / live bytes, with object-count companions. +inline constexpr WatcherSampleTypes k_stype_alloc = { + {k_stype_val_alloc_space, k_stype_val_inuse_space}, + {k_stype_val_alloc_samples, k_stype_val_inuse_objects}}; +// clang-format on + +} // namespace ddprof diff --git a/src/exporter/ddprof_exporter.cc b/src/exporter/ddprof_exporter.cc index 7e60296d..19c688b8 100644 --- a/src/exporter/ddprof_exporter.cc +++ b/src/exporter/ddprof_exporter.cc @@ -49,7 +49,7 @@ DDRes create_pprof_file(ddog_Timespec start, const char *dbg_pprof_prefix, strftime(time_start, std::size(time_start), "%Y%m%dT%H%M%SZ", tm_start); char filename[PATH_MAX]; - snprintf(filename, std::size(filename), "%s%s.pprof.lz4", dbg_pprof_prefix, + snprintf(filename, std::size(filename), "%s%s.pprof.zst", dbg_pprof_prefix, time_start); LG_NTC("[EXPORTER] Writing pprof to file %s", filename); constexpr int read_write_user_only = 0600; @@ -261,12 +261,17 @@ DDRes ddprof_exporter_new(const UserTags *user_tags, DDProfExporter *exporter) { fill_stable_tags(user_tags, exporter, tags_exporter); ddog_CharSlice const base_url = to_CharSlice(exporter->_url); + // ddprof is an out-of-process profiler and does not fork during export, + // so the system DNS resolver (/etc/resolv.conf) is safe and preferred. + constexpr bool k_use_system_resolver = true; ddog_prof_Endpoint endpoint; if (exporter->_agent) { - endpoint = ddog_prof_Endpoint_agent(base_url, k_timeout_ms); + endpoint = + ddog_prof_Endpoint_agent(base_url, k_timeout_ms, k_use_system_resolver); } else { ddog_CharSlice const api_key = to_CharSlice(exporter->_input.api_key); - endpoint = ddog_prof_Endpoint_agentless(base_url, api_key, k_timeout_ms); + endpoint = ddog_prof_Endpoint_agentless(base_url, api_key, k_timeout_ms, + k_use_system_resolver); } ddog_prof_ProfileExporter_Result res_exporter = ddog_prof_Exporter_new( diff --git a/src/perf_watcher.cc b/src/perf_watcher.cc index afd5f03f..13c41b80 100644 --- a/src/perf_watcher.cc +++ b/src/perf_watcher.cc @@ -19,48 +19,9 @@ namespace ddprof { uint64_t perf_event_default_sample_type() { return BASE_STYPES; } -#define X_STR(a, b, c, d, e) std::array{b, d}, -const char *sample_type_name_from_idx(int idx, EventAggregationModePos pos) { - static constexpr std::array< - std::array, DDPROF_PWT_LENGTH + 1> - sample_names = {PROFILE_TYPE_TABLE(X_STR){nullptr, nullptr}}; - if (idx < 0 || idx >= DDPROF_PWT_LENGTH) { - return nullptr; - } - return sample_names[idx][pos]; -} -#undef X_STR -#define X_STR(a, b, c, d, e) #c, -const char *sample_type_unit_from_idx(int idx) { - static const char *sample_units[] = {PROFILE_TYPE_TABLE(X_STR)}; - if (idx < 0 || idx >= DDPROF_PWT_LENGTH) { - return nullptr; - } - return sample_units[idx]; -} -#undef X_STR -#define X_DEP(a, b, c, d, e) DDPROF_PWT_##e, -int sample_type_id_to_count_sample_type_id(int idx) { - static const int count_ids[] = {PROFILE_TYPE_TABLE(X_DEP)}; - if (idx < 0 || idx >= DDPROF_PWT_LENGTH) { - return DDPROF_PWT_NOCOUNT; - } - return count_ids[idx]; -} -#undef X_DEP - -int watcher_to_count_sample_type_id(const PerfWatcher *watcher) { - int const idx = watcher->sample_type_id; - return sample_type_id_to_count_sample_type_id(idx); -} - -bool watcher_has_countable_sample_type(const PerfWatcher *watcher) { - return DDPROF_PWT_NOCOUNT != watcher_to_count_sample_type_id(watcher); -} - -// putting parentheses around "g" param breaks compilation +// putting parentheses around "h" param breaks compilation // NOLINTBEGIN(bugprone-macro-parentheses) -#define X_EVENTS(a, b, c, d, e, f, g) \ +#define X_EVENTS(a, b, c, d, e, f, g, h) \ { \ .sample_type = BASE_STYPES, \ .config = (d), \ @@ -72,10 +33,11 @@ bool watcher_has_countable_sample_type(const PerfWatcher *watcher) { .ddprof_event_type = DDPROF_PWE_##a, \ .type = (c), \ .sample_frequency = (e), \ - .sample_type_id = (f), \ + .sample_type_info = (f), \ + .pprof_active = (g), \ .value_source = EventConfValueSource::kSample, \ .aggregation_mode = EventAggregationMode::kSum, \ - .options = g, \ + .options = h, \ .pprof_indices = {}, \ .regno = 0, \ .raw_off = 0, \ @@ -86,7 +48,7 @@ bool watcher_has_countable_sample_type(const PerfWatcher *watcher) { }, // NOLINTEND(bugprone-macro-parentheses) -#define X_STR(a, b, c, d, e, f, g) #a, +#define X_STR(a, b, c, d, e, f, g, h) #a, const char *event_type_name_from_idx(int idx) { static const char *event_names[] = {EVENT_CONFIG_TABLE(X_STR)}; // NOLINT if (idx < 0 || idx >= DDPROF_PWE_LENGTH) { @@ -137,7 +99,8 @@ const PerfWatcher *tracepoint_default_watcher() { .ddprof_event_type = DDPROF_PWE_TRACEPOINT, .type = PERF_TYPE_TRACEPOINT, .sample_period = 1, - .sample_type_id = DDPROF_PWT_TRACEPOINT, + .sample_type_info = k_stype_tracepoint, + .pprof_active = true, .value_source = EventConfValueSource::kSample, .aggregation_mode = EventAggregationMode::kSum, .options = {.use_kernel = PerfWatcherUseKernel::kRequired}, @@ -153,7 +116,8 @@ const PerfWatcher *tracepoint_default_watcher() { } bool watcher_has_tracepoint(const PerfWatcher *watcher) { - return DDPROF_PWT_TRACEPOINT == watcher->sample_type_id; + return watcher->pprof_active && + watcher->sample_type_info.sample_types[kSumPos] == k_stype_val_tracepoint; } void log_watcher(const PerfWatcher *w, int idx) { @@ -175,17 +139,19 @@ void log_watcher(const PerfWatcher *w, int idx) { } // check all associated reported values - std::string sample_types; - for (int i = 0; i < kNbEventAggregationModes; ++i) { - if (Any(static_cast(1 << i) & w->aggregation_mode)) { - if (!sample_types.empty()) { - sample_types += ","; + if (!w->pprof_active) { + PRINT_NFO(" SampleTypes: (none — excluded from pprof)"); + } else { + static constexpr const char *k_mode_names[] = {"sum", "live"}; + for (int i = 0; i < kNbEventAggregationModes; ++i) { + if (Any(static_cast(1 << i) & + w->aggregation_mode)) { + PRINT_NFO(" SampleType[%s]: primary=%d count=%d", k_mode_names[i], + static_cast(w->sample_type_info.sample_types[i]), + static_cast(w->sample_type_info.count_types[i])); } - sample_types += std::string(sample_type_name_from_idx( - w->sample_type_id, static_cast(i))); } } - PRINT_NFO(" SampleTypes: %s", sample_types.c_str()); PRINT_NFO(" EventName: %s, GroupName: %s, Label: %s", w->tracepoint_event.c_str(), w->tracepoint_group.c_str(), w->tracepoint_label.c_str()); diff --git a/src/pprof/ddprof_pprof.cc b/src/pprof/ddprof_pprof.cc index 66f7c573..5fe57268 100644 --- a/src/pprof/ddprof_pprof.cc +++ b/src/pprof/ddprof_pprof.cc @@ -30,16 +30,69 @@ using namespace std::string_view_literals; namespace ddprof { +// Verify that the uint32_t constants in watcher_sample_types.hpp match the +// libdatadog enum values. If any of these fail, update the k_stype_val_* +// constants in watcher_sample_types.hpp to match the new libdatadog version. +static_assert(static_cast(DDOG_PROF_SAMPLE_TYPE_SAMPLE) == 37); +static_assert(static_cast(DDOG_PROF_SAMPLE_TYPE_TRACEPOINT) == 38); +static_assert(static_cast(DDOG_PROF_SAMPLE_TYPE_CPU_TIME) == 4); +static_assert(static_cast(DDOG_PROF_SAMPLE_TYPE_CPU_SAMPLES) == 5); +static_assert(static_cast(DDOG_PROF_SAMPLE_TYPE_ALLOC_SPACE) == 3); +static_assert(static_cast(DDOG_PROF_SAMPLE_TYPE_ALLOC_SAMPLES) == 0); +static_assert(static_cast(DDOG_PROF_SAMPLE_TYPE_INUSE_SPACE) == 28); +static_assert(static_cast(DDOG_PROF_SAMPLE_TYPE_INUSE_OBJECTS) == 27); + namespace { constexpr size_t k_max_pprof_labels{8}; -constexpr int k_max_value_types = - DDPROF_PWT_LENGTH * static_cast(kNbEventAggregationModes); +// Maps a ddog_prof_SampleType integer value to the kebab-case name used in +// debug log output (must match what simple_malloc-ut.sh greps for). +constexpr const char *sample_type_name(uint32_t raw_type) { + switch (raw_type) { + case k_stype_val_sample: + return "sample"; + case k_stype_val_tracepoint: + return "tracepoint"; + case k_stype_val_cpu_time: + return "cpu-time"; + case k_stype_val_cpu_samples: + return "cpu-samples"; + case k_stype_val_alloc_space: + return "alloc-space"; + case k_stype_val_alloc_samples: + return "alloc-samples"; + case k_stype_val_inuse_space: + return "inuse-space"; + case k_stype_val_inuse_objects: + return "inuse-objects"; + default: + return "unknown"; + } +} -struct ActiveIdsResult { - EventAggregationMode output_mode[DDPROF_PWT_LENGTH] = {}; - PerfWatcher *default_watcher = nullptr; -}; +// Verify that sample_type_name() returns the strings the backend expects. +// These must match what libdatadog writes into the pprof string table for each +// ddog_prof_SampleType enum value. +static_assert(std::string_view(sample_type_name(k_stype_val_cpu_time)) == + "cpu-time"); +static_assert(std::string_view(sample_type_name(k_stype_val_cpu_samples)) == + "cpu-samples"); +static_assert(std::string_view(sample_type_name(k_stype_val_alloc_space)) == + "alloc-space"); +static_assert(std::string_view(sample_type_name(k_stype_val_alloc_samples)) == + "alloc-samples"); +static_assert(std::string_view(sample_type_name(k_stype_val_inuse_space)) == + "inuse-space"); +static_assert(std::string_view(sample_type_name(k_stype_val_inuse_objects)) == + "inuse-objects"); +static_assert(std::string_view(sample_type_name(k_stype_val_tracepoint)) == + "tracepoint"); +static_assert(std::string_view(sample_type_name(k_stype_val_sample)) == + "sample"); + +// Upper bound on distinct ddog_prof_SampleType slots (sum + live types + +// their count companions across all watcher kinds). +constexpr int k_max_value_types = 16; std::string_view pid_str(pid_t pid, std::unordered_map &pid_strs) { @@ -99,10 +152,10 @@ void ddprof_print_sample(std::span locations, EventAggregationModePos value_mode_pos, const PerfWatcher &watcher) { - const char *sample_name = - sample_type_name_from_idx(watcher.sample_type_id, value_mode_pos); + const char *const type_name = + sample_type_name(watcher.sample_type_info.sample_types[value_mode_pos]); std::string buf = - absl::Substitute("sample[type=$0;pid=$1;tid=$2] ", sample_name, pid, tid); + absl::Substitute("sample[type=$0;pid=$1;tid=$2] ", type_name, pid, tid); for (auto loc_it = locations.rbegin(); loc_it != locations.rend(); ++loc_it) { if (loc_it != locations.rbegin()) { @@ -136,105 +189,29 @@ void ddprof_print_sample(std::span locations, PRINT_NFO("%s %ld", buf.c_str(), value); } -// Figure out which sample_type_ids are used by active watchers -// We also record the watcher with the lowest valid sample_type id, since that -// will serve as the default for the pprof -DDRes get_active_ids(std::span watchers, ActiveIdsResult &result) { - - for (unsigned i = 0; i < watchers.size(); ++i) { - const int sample_type_id = watchers[i].sample_type_id; - const int count_id = sample_type_id_to_count_sample_type_id(sample_type_id); - if (sample_type_id < 0 || sample_type_id == DDPROF_PWT_NOCOUNT || - sample_type_id >= DDPROF_PWT_LENGTH) { - if (sample_type_id != DDPROF_PWT_NOCOUNT) { - DDRES_RETURN_ERROR_LOG(DD_WHAT_PPROF, - "Watcher %s (%d) has invalid sample_type_id %d", - watchers[i].desc.c_str(), i, sample_type_id); - } - continue; - } - - if (!result.default_watcher || - sample_type_id <= result.default_watcher->sample_type_id) { - result.default_watcher = &watchers[i]; // update default - } - result.output_mode[sample_type_id] |= watchers[i].aggregation_mode; - if (count_id != DDPROF_PWT_NOCOUNT) { - // if the count is valid, update mask for it - result.output_mode[count_id] |= watchers[i].aggregation_mode; - } - } - return {}; -} - -class ProfValueTypes { -private: - int watcher_type_to_pprof_indices[DDPROF_PWT_LENGTH] - [kNbEventAggregationModes]; - ddog_prof_ValueType perf_value_type[k_max_value_types] = {}; - int num_sample_type_ids = 0; - -public: - ProfValueTypes() { - for (auto &indices : watcher_type_to_pprof_indices) { - for (auto &index : indices) { - index = -1; +// Slot registry: maps ddog_prof_SampleType values to pprof value-array indices. +// Uses linear search — at most k_max_value_types unique types in practice. +struct SlotRegistry { + ddog_prof_SampleType types[k_max_value_types] = {}; + int count = 0; + + int ensure(uint32_t raw_type) { + const auto t = static_cast(raw_type); + for (int i = 0; i < count; ++i) { + if (types[i] == t) { + return i; } } + assert(count < k_max_value_types); + types[count] = t; + return count++; } - void set_index(int watcher_type, EventAggregationModePos pos, int value) { - watcher_type_to_pprof_indices[watcher_type][pos] = value; - } - - void add_value_type(const char *name, const char *unit, int watcher_type, - EventAggregationModePos pos) { - perf_value_type[num_sample_type_ids].type_ = to_CharSlice(name); - perf_value_type[num_sample_type_ids].unit = to_CharSlice(unit); - set_index(watcher_type, pos, num_sample_type_ids); - ++num_sample_type_ids; - } - - [[nodiscard]] int get_index(int watcher_type, - EventAggregationModePos pos) const { - return watcher_type_to_pprof_indices[watcher_type][pos]; - } - - [[nodiscard]] int get_num_sample_type_ids() const { - return num_sample_type_ids; - } - - [[nodiscard]] ddog_prof_Slice_ValueType get_sample_types_slice() const { - return {.ptr = perf_value_type, - .len = static_cast(num_sample_type_ids)}; + [[nodiscard]] ddog_prof_Slice_SampleType slice() const { + return {.ptr = types, .len = static_cast(count)}; } }; -ProfValueTypes compute_pprof_values(const ActiveIdsResult &active_ids) { - ProfValueTypes result{}; - for (int i = 0; i < DDPROF_PWT_LENGTH; ++i) { - if (active_ids.output_mode[i] == EventAggregationMode::kDisabled) { - continue; - } - assert(i != DDPROF_PWT_NOCOUNT); - for (int value_pos = 0; value_pos < kNbEventAggregationModes; ++value_pos) { - if (Any(active_ids.output_mode[i] & - static_cast(1 << value_pos))) { - const char *value_name = sample_type_name_from_idx( - i, static_cast(value_pos)); - const char *value_unit = sample_type_unit_from_idx(i); - if (!value_name || !value_unit) { - LG_WRN("Malformed sample type (%d), ignoring", i); - continue; - } - result.add_value_type(value_name, value_unit, i, - static_cast(value_pos)); - } - } - } - return result; -} - size_t prepare_labels(const UnwindOutput &uw_output, const PerfWatcher &watcher, std::unordered_map &pid_strs, std::span labels) { @@ -389,83 +366,69 @@ DDRes process_symbolization( } // namespace DDRes pprof_create_profile(DDProfPProf *pprof, DDProfContext &ctx) { - size_t const num_watchers = ctx.watchers.size(); - - ActiveIdsResult active_ids = {}; - DDRES_CHECK_FWD(get_active_ids(std::span(ctx.watchers), active_ids)); -#ifdef DEBUG - LG_DBG("Active IDs :"); - for (int i = 0; i < DDPROF_PWT_LENGTH; ++i) { - LG_DBG("%s --> %d ", sample_type_name_from_idx(i, kOccurrencePos), - static_cast(active_ids.output_mode[i])); - } -#endif - // Based on active IDs, prepare the list pf pprof values - // pprof_values should stay alive while we create the pprof - const ProfValueTypes pprof_values = compute_pprof_values(active_ids); - - // Update each watcher with matching types - for (unsigned i = 0; i < num_watchers; ++i) { - int const this_id = ctx.watchers[i].sample_type_id; - if (this_id < 0 || this_id == DDPROF_PWT_NOCOUNT || - this_id >= DDPROF_PWT_LENGTH) { + SlotRegistry slots; + PerfWatcher *default_watcher = nullptr; + + for (auto &w : ctx.watchers) { + // Reset indices from any previous profile creation + for (auto &pi : w.pprof_indices) { + pi = {}; + } + + if (!w.pprof_active) { continue; } - for (int value_pos = 0; value_pos < kNbEventAggregationModes; ++value_pos) { - ctx.watchers[i].pprof_indices[value_pos].pprof_index = - pprof_values.get_index( - ctx.watchers[i].sample_type_id, - static_cast(value_pos)); - const int count_id = sample_type_id_to_count_sample_type_id( - ctx.watchers[i].sample_type_id); - if (count_id != DDPROF_PWT_NOCOUNT) { - ctx.watchers[i].pprof_indices[value_pos].pprof_count_index = - pprof_values.get_index( - count_id, static_cast(value_pos)); + + if (!default_watcher) { + default_watcher = &w; + } + + for (int m = 0; m < kNbEventAggregationModes; ++m) { + if (!Any(w.aggregation_mode & + static_cast(1 << m))) { + continue; + } + w.pprof_indices[m].pprof_index = + slots.ensure(w.sample_type_info.sample_types[m]); + const uint32_t count_t = w.sample_type_info.count_types[m]; + if (count_t != static_cast(DDOG_PROF_SAMPLE_TYPE_SAMPLE)) { + w.pprof_indices[m].pprof_count_index = slots.ensure(count_t); } } } - pprof->_nb_values = pprof_values.get_num_sample_type_ids(); - const ddog_prof_Slice_ValueType sample_types = - pprof_values.get_sample_types_slice(); - ddog_prof_Period period; + pprof->_nb_values = slots.count; + const ddog_prof_Slice_SampleType sample_types = slots.slice(); + ddog_prof_Period period{}; if (pprof->_nb_values > 0) { - if (!active_ids.default_watcher) { + if (!default_watcher) { DDRES_RETURN_ERROR_LOG(DD_WHAT_PPROF, "Unable to find default watcher"); } // Populate the default. If we have a frequency, assume it is given in // hertz and convert to a period in nanoseconds. This is broken for many // event-based types (but providing frequency would also be broken in those // cases) - int64_t default_period = active_ids.default_watcher->sample_period; - if (active_ids.default_watcher->options.is_freq) { - // convert to period (nano seconds) + int64_t default_period = default_watcher->sample_period; + if (default_watcher->options.is_freq) { default_period = std::chrono::nanoseconds(std::chrono::seconds{1}).count() / default_period; } int default_index = -1; - int value_pos = 0; - while (default_index == -1 && - value_pos < EventAggregationModePos::kNbEventAggregationModes) { - default_index = - active_ids.default_watcher->pprof_indices[value_pos].pprof_index; - ++value_pos; + for (int m = 0; m < kNbEventAggregationModes && default_index == -1; ++m) { + default_index = default_watcher->pprof_indices[m].pprof_index; } if (default_index == -1) { DDRES_RETURN_ERROR_LOG(DD_WHAT_PPROF, "Unable to find default watcher's value"); } - // period is the default watcher's type. period = { - .type_ = sample_types.ptr[default_index], + .sample_type = sample_types.ptr[default_index], .value = default_period, }; } - auto prof_res = ddog_prof_Profile_new( - sample_types, - pprof_values.get_num_sample_type_ids() > 0 ? &period : nullptr); + auto prof_res = + ddog_prof_Profile_new(sample_types, slots.count > 0 ? &period : nullptr); if (prof_res.tag != DDOG_PROF_PROFILE_NEW_RESULT_OK) { ddog_Error_drop(&prof_res.err); @@ -522,8 +485,7 @@ DDRes pprof_aggregate(const UnwindOutput *uw_output, int64_t values[k_max_value_types] = {}; assert(pprof_indices.pprof_index != -1); values[pprof_indices.pprof_index] = pack.value; - if (watcher_has_countable_sample_type(watcher)) { - assert(pprof_indices.pprof_count_index != -1); + if (pprof_indices.pprof_count_index != -1) { values[pprof_indices.pprof_count_index] = pack.count; } diff --git a/tools/libdatadog_checksums.txt b/tools/libdatadog_checksums.txt index a8b41386..b54bb15d 100644 --- a/tools/libdatadog_checksums.txt +++ b/tools/libdatadog_checksums.txt @@ -1,6 +1,6 @@ -38b83da2781f20f004d278c077b071441f40671de2e0adf72f7e14e37b10db15 libdatadog-x86_64-apple-darwin.tar.gz -1420ba4970ff9158aec4bd8a80d139abe8c19cfd71ae31c6c518f8a2ad1416b8 libdatadog-aarch64-apple-darwin.tar.gz -1778bed8bb4ec5a63af792ed6d7b0acd2564e5c7633d9b65d7c715e7f8635743 libdatadog-x86_64-unknown-linux-gnu.tar.gz -c90bd4959026f7fddb9012036fdc5b1e49bdf57d716cb429cdde291af6108740 libdatadog-aarch64-alpine-linux-musl.tar.gz -c67ada4359cd6a806adafcb44043bc8fb0dffd463e3aa328856496e2883142ac libdatadog-aarch64-unknown-linux-gnu.tar.gz -394b13591400b36d90755bc9851be047e6d31813347ed9d0e2638355cc9617d4 libdatadog-x86_64-alpine-linux-musl.tar.gz +0b557141a3accc301d5ffa73d4fe1a90c84b7ceaad45b96c4b573f69170520a7 libdatadog-aarch64-alpine-linux-musl.tar.gz +5cbde7937d1661cc0483f7cea0c6ec3d0c7bd1540fc4bbe57f9a0a35296c579d libdatadog-x86_64-alpine-linux-musl.tar.gz +298346e13092c057bb2515da5b45e56a34d7a0357589f91e4bb65a375aa23656 libdatadog-aarch64-apple-darwin.tar.gz +2017e6a0070414875677cd7ad5f07b15451bae30f8078a6769e870311503be7d libdatadog-x86_64-apple-darwin.tar.gz +617831c7fb9d0d9e01aa1bc232f89c6a21482d9cfefba4dcc527e8561128eafb libdatadog-x86_64-unknown-linux-gnu.tar.gz +a61b00551d8e2e2fbc590be08a620c2d6d610c360d94ce8bd4a0283c9ceb3db3 libdatadog-aarch64-unknown-linux-gnu.tar.gz