Skip to content
Open
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 examples/repl/commands.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ CommandResult HandleStartResource(State& state, const CommandInput& args);
CommandResult HandleStopResource(State& state, const CommandInput& args);
CommandResult HandleStopResourceWithError(State& state, const CommandInput& args);
CommandResult HandleAddError(State& state, const CommandInput& args);
CommandResult HandleAddLongTask(State& state, const CommandInput& args);
CommandResult HandleStartOperation(State& state, const CommandInput& args);
CommandResult HandleSucceedOperation(State& state, const CommandInput& args);
CommandResult HandleFailOperation(State& state, const CommandInput& args);
Expand Down
28 changes: 28 additions & 0 deletions examples/repl/commands_rum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2025-Present Datadog, Inc.

#include <charconv>
#include <optional>

#include "datadog.hpp"
Expand Down Expand Up @@ -467,6 +468,33 @@ CommandResult HandleAddError(State& state, const CommandInput& args) {
return CommandResult::OK("Rum::AddError()");
}

CommandResult HandleAddLongTask(State& state, const CommandInput& args) {
// RUM must be registered and SDK must be running
if (!state.rum) {
return CommandResult::Error("RUM is not registered!");
}
if (!state.started) {
return CommandResult::Error("SDK is not running!");
}

// Positional args
auto pos = args.Positional();
auto duration_str = pos[0];
if (duration_str.empty()) {
return CommandResult::Error("No duration given!");
}
uint64_t duration_ns{0};
auto res = std::from_chars(
duration_str.data(), duration_str.data() + duration_str.size(), duration_ns
);
if (res.ec != std::errc{}) {
return CommandResult::Error("Duration must be a non-negative integer!");
}

state.rum->AddLongTask(duration_ns);
return CommandResult::OK("Rum::AddLongTask()");
}

CommandResult HandleStartOperation(State& state, const CommandInput& args) {
// RUM must be registered and SDK must be running
if (!state.rum) {
Expand Down
3 changes: 3 additions & 0 deletions examples/repl/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ CommandResult Handle(State& state, const CommandInput& input) {
if (input.Peek() == "add-error") {
return HandleAddError(state, input.Shift());
}
if (input.Peek() == "add-long-task") {
return HandleAddLongTask(state, input.Shift());
}
if (input.Peek() == "start-operation") {
return HandleStartOperation(state, input.Shift());
}
Expand Down
14 changes: 14 additions & 0 deletions include-c/datadog/rum.h
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,20 @@ DATADOG_API void dd_rum_add_error(
dd_attribute_t* attributes
);

// === RUM long tasks ===

/**
* Records that the application encountered a long task (a period during which the main
* thread was blocked for an extended duration) in the context of the current view.
*
* @param duration_ns - The duration of the long task, in nanoseconds. Must be positive.
* @param attributes - An optional set of custom attributes describing the long task,
* provided as a dd_attribute_t value with DD_VALUE_TYPE_OBJECT.
*/
DATADOG_API void dd_rum_add_long_task(
dd_rum_t* rum, uint64_t duration_ns, dd_attribute_t* attributes
);

#ifdef __cplusplus
}
#endif
Expand Down
14 changes: 14 additions & 0 deletions include-cpp/datadog/rum.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,20 @@ class Rum {
const Attribute& attributes = Attribute()
);

/**
* Records that the application encountered a long task (a period during which the
* main thread was blocked for an extended duration) in the context of the current
* view.
*
* @param duration_ns - The duration of the long task, in nanoseconds. Must be
* positive.
* @param attributes - An optional set of custom attributes describing the long task,
* provided as an Attribute with ValueType::Object.
*/
DATADOG_API void AddLongTask(
uint64_t duration_ns, const Attribute& attributes = Attribute()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

FWIW we expose a datadog::Duration (which is just an alias for std::chrono::nanoseconds) in the C++ API, which would give us stricter type safety and convenient conversion from other std::chrono formats.

That said, I'm totally fine with just using uint64_t here for the sake of simplicity.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Completely your call here. I feel like uint64_t might be more convenient for clients, but datadog::Duration is definitely the safer option.

Just say the word and I'l change it over.

);

/**
* Records the start of a operation (e.g. login, checkout, upload).
*
Expand Down
26 changes: 26 additions & 0 deletions src/datadog/c/rum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,32 @@ void dd_rum_add_error(
};
rum->impl->AddError(datadog::RumErrorSource_FromC(source), error, cpp_attributes);
}

void dd_rum_add_long_task(
dd_rum_t* rum, uint64_t duration_ns, dd_attribute_t* attributes
) {
// If the underlying feature is NULL, this call is a no-op
if (!rum || !rum->impl) {
return;
}

// The schema for RUM long_task events requires a positive duration; reject it,
// logging a warning
if (duration_ns == 0) {
rum->diagnostic_logger.Warning(
"dd_rum_add_long_task call ignored: application must supply a positive duration"
);
return;
}

// If we've been given a valid object attribute, convert it to the equivalent C++ type
datadog::Attribute cpp_attributes; // Default-initialized to Attribute::Null()
if (attributes && attributes->type == DD_VALUE_TYPE_OBJECT) {
cpp_attributes = datadog::impl::AttributeConversion::CopyFromC(*attributes);
}

rum->impl->AddLongTask(datadog::Duration(duration_ns), cpp_attributes);
}
}

// NOLINTEND(cppcoreguidelines-owning-memory)
16 changes: 16 additions & 0 deletions src/datadog/cpp/rum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "datadog/rum.hpp"

#include <cctype>
#include <chrono>

#include "datadog/core.hpp"

Expand Down Expand Up @@ -324,6 +325,21 @@ void Rum::AddError(
}
}

void Rum::AddLongTask(uint64_t duration_ns, const Attribute& attributes) {
if (_impl) {
// The schema for RUM long_task events requires a positive duration; reject it,
// logging a warning
if (duration_ns == 0) {
impl::DiagnosticLogger{_diagnostic_handler, _diagnostic_threshold}.Warning(
"Rum::AddLongTask call ignored: application must supply a positive duration"
);
return;
}

_impl->AddLongTask(Duration(duration_ns), attributes);
}
}

void Rum::StartOperation(
std::string_view name, std::string_view operation_key, const Attribute& attributes
) {
Expand Down
217 changes: 217 additions & 0 deletions src/datadog/impl/core/feature_types/rum.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2047,6 +2047,223 @@ DATADOG_JSON_STRUCT(
// NYI: feature_flags
)

// === Long Task Event ===

struct RumLongTaskEvent {
struct Application {
// From _common-schema.json
UUID id;
OmitIfEmpty<std::string> current_locale;

explicit Application(const UUID& in_id) : id(in_id) {}
};
struct Session {
// From _common-schema.json
UUID id;
StringRumSessionType type;
OmitIfFalse<bool> has_replay{false};

explicit Session(const UUID& in_id, RumSessionType in_type)
: id(in_id), type(in_type) {}
};
struct View {
// From _common-schema.json
UUID id;
OmitIfEmpty<std::string> referrer;
std::string url;
OmitIfEmpty<std::string> name;

explicit View(UUID in_id, std::string_view in_url) : id(in_id), url(in_url) {}
};
struct Display {
// From _common-schema.json
OmitIfNoValue<RumViewportProperties> viewport;

Display() {};
};
struct Action {
// From _action-child-schema.json
UUID id; // Schema permits array of UUIDs; we only support a single value

explicit Action(UUID in_id) : id(in_id) {}
};
struct LongTask {
// From long_task-schema.json
UUID id;
int64_t duration;
OmitIfNoValue<bool> is_frozen_frame;
// NYI: entry_type, render_start, style_and_layout_start, first_ui_event_timestamp,
// blocking_duration, scripts (all long-animation-frame/browser-only properties)

explicit LongTask(const UUID& in_id, int64_t in_duration)
: id(in_id), duration(in_duration) {}
};
struct Internal {
struct Session {
// From _common-schema.json
OmitIfZero<uint8_t> plan{};
OmitIfNoValue<StringRumSessionPrecondition> session_precondition;

Session() {};
};
struct Configuration {
// From _common-schema.json
float session_sample_rate;
OmitIfNoValue<float> session_replay_sample_rate;
OmitIfNoValue<float> profiling_sample_rate;

explicit Configuration(float in_session_sample_rate)
: session_sample_rate(in_session_sample_rate) {}
};
// From _common-schema.json
uint8_t format_version{2};
OmitIfNoValue<Session> session;
OmitIfNoValue<Configuration> configuration;
OmitIfEmpty<std::string> browser_sdk_version;
// NYI: discarded (long_task-schema.json; backend-computed)

Internal() {}
};
// From _common-schema.json
MilliTimestamp date;
Application application;
OmitIfEmpty<std::string> service;
OmitIfEmpty<std::string> version;
OmitIfEmpty<std::string> build_version;
OmitIfEmpty<std::string> build_id;
OmitIfEmpty<std::string> ddtags;
Session session;
OmitIfNoValue<StringRumSource> source;
View view;
OmitIfNoValue<RumUserProperties> usr;
OmitIfNoValue<RumAccountProperties> account;
OmitIfNoValue<RumConnectivityProperties> connectivity;
OmitIfNoValue<Display> display;
OmitIfNoValue<RumSyntheticsProperties> synthetics;
OmitIfNoValue<RumCITestProperties> ci_test;
OmitIfNoValue<RumOSProperties> os;
OmitIfNoValue<RumDeviceProperties> device;
Internal _dd;
OmitIfZero<Attribute> context;
// NYI: stream

// From _action-child-schema.json
OmitIfNoValue<Action> action;

// From long_task-schema.json
std::string_view type{"long_task"};
LongTask long_task;

explicit RumLongTaskEvent(
Timestamp in_date,
const UUID& in_application_id,
const UUID& in_session_id,
RumSessionType in_session_type,
const UUID& in_view_id,
std::string_view in_view_url,
const UUID& in_long_task_id,
int64_t in_long_task_duration
)
: date(in_date),
application(Application{in_application_id}),
session(Session{in_session_id, in_session_type}),
view(View{in_view_id, in_view_url}),
_dd(Internal{}),
long_task(in_long_task_id, in_long_task_duration) {}
};
DATADOG_JSON_STRUCT(
RumLongTaskEvent::Application,
// From _common-schema.json
DATADOG_JSON_FIELD(id),
DATADOG_JSON_FIELD(current_locale)
)
DATADOG_JSON_STRUCT(
RumLongTaskEvent::Session,
// From _common-schema.json
DATADOG_JSON_FIELD(id),
DATADOG_JSON_FIELD(type),
DATADOG_JSON_FIELD(has_replay)
)
DATADOG_JSON_STRUCT(
RumLongTaskEvent::View,
// From _common-schema.json
DATADOG_JSON_FIELD(id),
DATADOG_JSON_FIELD(referrer),
DATADOG_JSON_FIELD(url),
DATADOG_JSON_FIELD(name)
)
DATADOG_JSON_STRUCT(
RumLongTaskEvent::Display,
// From _common-schema.json
DATADOG_JSON_FIELD(viewport)
)
DATADOG_JSON_STRUCT(
RumLongTaskEvent::Action,
// From _action-child-schema.json
DATADOG_JSON_FIELD(id)
)
DATADOG_JSON_STRUCT(
RumLongTaskEvent::LongTask,
// From long_task-schema.json
DATADOG_JSON_FIELD(id),
DATADOG_JSON_FIELD(duration),
DATADOG_JSON_FIELD(is_frozen_frame)
// NYI: entry_type, render_start, style_and_layout_start, first_ui_event_timestamp,
// blocking_duration, scripts
)
DATADOG_JSON_STRUCT(
RumLongTaskEvent::Internal::Session,
// From _common-schema.json
DATADOG_JSON_FIELD(plan),
DATADOG_JSON_FIELD(session_precondition)
)
DATADOG_JSON_STRUCT(
RumLongTaskEvent::Internal::Configuration,
// From _common-schema.json
DATADOG_JSON_FIELD(session_sample_rate),
DATADOG_JSON_FIELD(session_replay_sample_rate),
DATADOG_JSON_FIELD(profiling_sample_rate)
)
DATADOG_JSON_STRUCT(
RumLongTaskEvent::Internal,
// From _common-schema.json
DATADOG_JSON_FIELD(format_version),
DATADOG_JSON_FIELD(session),
DATADOG_JSON_FIELD(configuration),
DATADOG_JSON_FIELD(browser_sdk_version)
// NYI: discarded
)
DATADOG_JSON_STRUCT(
RumLongTaskEvent,
// From _common-schema.json
DATADOG_JSON_FIELD(date),
DATADOG_JSON_FIELD(application),
DATADOG_JSON_FIELD(service),
DATADOG_JSON_FIELD(version),
DATADOG_JSON_FIELD(build_version),
DATADOG_JSON_FIELD(build_id),
DATADOG_JSON_FIELD(ddtags),
DATADOG_JSON_FIELD(session),
DATADOG_JSON_FIELD(source),
DATADOG_JSON_FIELD(view),
DATADOG_JSON_FIELD(usr),
DATADOG_JSON_FIELD(account),
DATADOG_JSON_FIELD(connectivity),
DATADOG_JSON_FIELD(display),
DATADOG_JSON_FIELD(synthetics),
DATADOG_JSON_FIELD(ci_test),
DATADOG_JSON_FIELD(os),
DATADOG_JSON_FIELD(device),
DATADOG_JSON_FIELD(_dd),
DATADOG_JSON_FIELD(context),
// NYI: stream
// From _action-child-schema.json
DATADOG_JSON_FIELD(action),
// From long_task-schema.json
DATADOG_JSON_FIELD(type),
DATADOG_JSON_FIELD(long_task)
)

// === Vital Operation Step Event ===

enum class RumVitalType : uint8_t { OperationStep };
Expand Down
Loading