Skip to content
Merged
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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ set(DD_PROFILING_SOURCES
src/ringbuffer_utils.cc
src/signal_helper.cc
src/sys_utils.cc
src/tracepoint_config.cc
src/user_override.cc)

if(BUILD_UNIVERSAL_DDPROF)
Expand Down
37 changes: 37 additions & 0 deletions include/ddprof_perf_event.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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

#include <linux/perf_event.h>
#include <type_traits>

// Extend the perf event types
// There are <30 different perf events (starting at 1000 seems safe)
enum : uint32_t {
PERF_CUSTOM_EVENT_DEALLOCATION = 1000,
PERF_CUSTOM_EVENT_CLEAR_LIVE_ALLOCATION
};

static_assert(static_cast<uint32_t>(PERF_CUSTOM_EVENT_DEALLOCATION) >
PERF_RECORD_MAX,
"Error from PERF_CUSTOM_EVENT_DEALLOCATION definition");

namespace ddprof {

// Custom sample type
struct DeallocationEvent {
perf_event_header hdr;
struct sample_id sample_id;
uintptr_t ptr;
};

// Event to notify we have tracked too many allocations
struct ClearLiveAllocationEvent {
perf_event_header hdr;
struct sample_id sample_id;
};

} // namespace ddprof
2 changes: 2 additions & 0 deletions include/ddprof_worker_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#pragma once

#include "live_allocation.hpp"
#include "pevent.hpp"
#include "proc_status.hpp"

Expand Down Expand Up @@ -36,4 +37,5 @@ struct DDProfWorkerContext {
int64_t send_nanos; // Last time an export was sent
uint32_t count_worker; // exports since last cache clear
std::array<uint64_t, MAX_TYPE_WATCHER> lost_events_per_watcher;
ddprof::LiveAllocation live_allocation;
};
1 change: 1 addition & 0 deletions include/ddres_list.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
X(DSO, "") \
X(JIT, "Error parsing JIT files") \
X(NO_JIT_FILE, "File not readable for JIT") \
X(UNHANDLED_CONFIG, "unhandled configuration") \
X(UNHANDLED_DSO, "ignore dso type") \
X(WORKERLOOP_INIT, "error initializing the worker loop") \
X(SYS_CONFIG, "error checking system configuration") \
Expand Down
3 changes: 2 additions & 1 deletion include/dwfl_hdr.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ struct DwflWrapper {
class DwflHdr {
public:
DwflWrapper &get_or_insert(pid_t pid);
void clear_unvisited();
std::vector<pid_t> get_unvisited() const;
void reset_unvisited();
void clear_pid(pid_t pid);

// get number of accessed modules
Expand Down
54 changes: 30 additions & 24 deletions include/event_config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,45 @@

#pragma once

#include <string>

#include <stdint.h>
#include <string>

// Defines how a sample is aggregated when it is received
enum class EventConfMode {
enum class EventConfMode : uint32_t {
kDisabled = 0,
kCallgraph = 1 << 0,
kMetric = 1 << 1,
kAll = kCallgraph | kMetric,
kCallgraph = 1 << 0, // flamegraph of resource usage
kMetric = 1 << 1, // gauge of resource usage
kLiveCallgraph = 1 << 2, // report callgraph of resources still in use
kAll = kCallgraph | kMetric | kLiveCallgraph,
};

// EventConfMode &operator|=(EventConfMode &A, const EventConfMode &B);
// EventConfMode operator&(const EventConfMode &A, const EventConfMode &B);
// bool operator<=(const EventConfMode A, const EventConfMode B); // inclusion
//
constexpr EventConfMode &operator|=(EventConfMode &A, const EventConfMode &B) {
A = static_cast<EventConfMode>(static_cast<unsigned>(A) |
static_cast<unsigned>(B));
return A;
bool operator<=(EventConfMode A, EventConfMode B) = delete;
bool operator<(EventConfMode A, EventConfMode B) = delete;
bool operator>(EventConfMode A, EventConfMode B) = delete;
bool operator>=(EventConfMode A, EventConfMode B) = delete;

constexpr EventConfMode operator|(EventConfMode A, const EventConfMode B) {
return static_cast<EventConfMode>(static_cast<uint32_t>(A) |
static_cast<uint32_t>(B));
}

constexpr EventConfMode operator|=(EventConfMode &A, const EventConfMode B) {
return A = A | B;
}

constexpr EventConfMode operator&(const EventConfMode A,
const EventConfMode B) {
return static_cast<EventConfMode>(static_cast<uint32_t>(A) &
static_cast<uint32_t>(B));
}

constexpr EventConfMode operator&(const EventConfMode &A,
const EventConfMode &B) {
// & on bitmask enums is valid only in the space spanned by the values
return static_cast<EventConfMode>(static_cast<uint64_t>(A) &
static_cast<uint64_t>(B) &
static_cast<uint64_t>(EventConfMode::kAll));
constexpr bool Any(EventConfMode arg) {
return arg != EventConfMode::kDisabled;
}

// Bitmask inclusion
constexpr bool operator<=(const EventConfMode A, const EventConfMode B) {
return EventConfMode::kDisabled != ((EventConfMode::kAll & A) & B);
constexpr bool AnyCallgraph(EventConfMode arg) {
return Any((arg & EventConfMode::kLiveCallgraph) |
(arg & EventConfMode::kCallgraph));
}

// Defines how samples are weighted
Expand Down Expand Up @@ -139,7 +145,7 @@ enum class EventConfField {
struct EventConf {
EventConfMode mode;

uint64_t id;
int64_t id;

std::string eventname;
std::string groupname;
Expand Down
2 changes: 2 additions & 0 deletions include/ipc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ struct RingBufferInfo {
};

struct ReplyMessage {
enum { kLiveCallgraph = 0 };
// reply with the request flags from the request
uint32_t request = 0;
// profiler pid
Expand All @@ -102,6 +103,7 @@ struct ReplyMessage {
// RingBufferInfo is returned if request & kRingBuffer
// cppcheck-suppress unusedStructMember
RingBufferInfo ring_buffer;
int32_t allocation_flags = 0;
};

class Client {
Expand Down
42 changes: 38 additions & 4 deletions include/lib/allocation_tracker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <cstddef>
#include <mutex>
#include <random>
#include <unordered_set>

namespace ddprof {

Expand All @@ -38,6 +39,8 @@ class AllocationTracker {
AllocationTracker(const AllocationTracker &) = delete;
AllocationTracker &operator=(const AllocationTracker &) = delete;

~AllocationTracker() { free(); }

enum AllocationTrackingFlags {
kTrackDeallocations = 0x1,
kDeterministicSampling = 0x2
Expand All @@ -60,13 +63,22 @@ class AllocationTracker {
static inline bool is_active();

private:
using AdressSet = std::unordered_set<uintptr_t>;

struct TrackerState {
void init(bool track_alloc, bool track_dealloc) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not very idiomatic... Should I rely on constructor ? Can we afford to re-init the mutex.

track_allocations = track_alloc;
track_deallocations = track_dealloc;
lost_count = 0;
failure_count = 0;
pid = 0;
}
std::mutex mutex;
std::atomic<bool> track_allocations = false;
std::atomic<bool> track_deallocations = false;
std::atomic<uint64_t> lost_count; // count number of lost events
std::atomic<uint32_t> failure_count;
std::atomic<pid_t> pid; // cache of pid
std::atomic<pid_t> pid; // lazy cache of pid (0 is un-init value)
};

AllocationTracker();
Expand All @@ -82,16 +94,24 @@ class AllocationTracker {
TrackerThreadLocalState &tl_state);
void track_deallocation(uintptr_t addr, TrackerThreadLocalState &tl_state);

DDRes push_sample(uint64_t allocated_size, TrackerThreadLocalState &tl_state);
DDRes push_alloc_sample(uintptr_t addr, uint64_t allocated_size,
TrackerThreadLocalState &tl_state);

// Return true if consumer should be notified
// If notify_needed is true, consumer should be notified
DDRes push_lost_sample(MPSCRingBufferWriter &writer, bool &notify_needed);

DDRes push_dealloc_sample(uintptr_t addr, TrackerThreadLocalState &tl_state);

DDRes push_clear_live_allocation(TrackerThreadLocalState &tl_state);

void free_on_consecutive_failures(bool success);

TrackerState _state;
uint64_t _sampling_interval;
std::mt19937 _gen;
PEvent _pevent;
bool _deterministic_sampling;
AdressSet _address_set;

static thread_local TrackerThreadLocalState _tl_state;
static AllocationTracker *_instance;
Expand Down Expand Up @@ -128,7 +148,21 @@ void AllocationTracker::track_allocation(uintptr_t addr, size_t size) {
}
}

void AllocationTracker::track_deallocation(uintptr_t) {}
void AllocationTracker::track_deallocation(uintptr_t addr) {
// same pattern as track_allocation
AllocationTracker *instance = _instance;

if (!instance) {
return;
}
TrackerThreadLocalState &tl_state = _tl_state;

if (instance->_state.track_deallocations.load(std::memory_order_relaxed)) {
// not cool as we are always calling this (high overhead). Can we do better
// ?
instance->track_deallocation(addr, tl_state);
}
}

bool AllocationTracker::is_active() {
auto instance = _instance;
Expand Down
17 changes: 17 additions & 0 deletions include/live_allocation-c.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// 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

namespace ddprof {
namespace liveallocation {
#ifdef KMAX_TRACKED_ALLOCATIONS
// build time override to reduce execution time of test
static constexpr auto kMaxTracked = KMAX_TRACKED_ALLOCATIONS;
#else
static constexpr auto kMaxTracked = 500000;
#endif
} // namespace liveallocation
} // namespace ddprof
92 changes: 92 additions & 0 deletions include/live_allocation.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// 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

#include "ddprof_defs.hpp"
#include "unlikely.hpp"
#include "unwind_output_hash.hpp"

#include <cstddef>
#include <sys/types.h>
#include <unordered_map>

namespace ddprof {

template <typename T>
T &access_resize(std::vector<T> &v, size_t index,
const T &default_value = T()) {
if (unlikely(index >= v.size())) {
v.resize(index + 1, default_value);
}
return v[index];
}

class LiveAllocation {
public:
// For allocations Value is the size
// This is the cumulative value and count for a given stack
struct ValueAndCount {
int64_t _value = 0;
int64_t _count = 0;
};

using PprofStacks =
std::unordered_map<UnwindOutput, ValueAndCount, UnwindOutputHash>;

struct ValuePerAddress {
int64_t _value = 0;
PprofStacks::value_type *_unique_stack = nullptr;
};

using AddressMap = std::unordered_map<uintptr_t, ValuePerAddress>;
struct PidStacks {
AddressMap _address_map;
PprofStacks _unique_stacks;
};

using PidMap = std::unordered_map<pid_t, PidStacks>;
using WatcherVector = std::vector<PidMap>;
WatcherVector _watcher_vector;

// Allocation should be aggregated per stack trace
// instead of a stack, we would have a total size for this unique stack trace
// and a count.
void register_allocation(const UnwindOutput &uo, uintptr_t addr, size_t size,
int watcher_pos, pid_t pid) {
PidMap &pid_map = access_resize(_watcher_vector, watcher_pos);
PidStacks &pid_stacks = pid_map[pid];
register_allocation(uo, addr, size, pid_stacks._unique_stacks,
pid_stacks._address_map);
}

void register_deallocation(uintptr_t addr, int watcher_pos, pid_t pid) {
PidMap &pid_map = access_resize(_watcher_vector, watcher_pos);
PidStacks &pid_stacks = pid_map[pid];
register_deallocation(addr, pid_stacks._unique_stacks,
pid_stacks._address_map);
}

void clear_pid_for_watcher(int watcher_pos, pid_t pid) {
PidMap &pid_map = access_resize(_watcher_vector, watcher_pos);
pid_map.erase(pid);
}

void clear_pid(pid_t pid) {
for (auto &pid_map : _watcher_vector) {
pid_map.erase(pid);
}
}

private:
static void register_deallocation(uintptr_t address, PprofStacks &stacks,
AddressMap &address_map);

static void register_allocation(const UnwindOutput &uo, uintptr_t address,
int64_t value, PprofStacks &stacks,
AddressMap &address_map);
};

} // namespace ddprof
3 changes: 3 additions & 0 deletions include/perf.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,6 @@ all_perf_configs_from_watcher(const PerfWatcher *watcher, bool extras);
uint64_t perf_value_from_sample(const PerfWatcher *watcher,
const perf_event_sample *sample);
} // namespace ddprof

perf_event_attr perf_config_from_watcher(const PerfWatcher *watcher,
bool extras);
Loading