Skip to content
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

new(userspace): plugin api to dump async events #2152

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
16 changes: 16 additions & 0 deletions userspace/libsinsp/dumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
#include <libsinsp/sinsp_int.h>
#include <libscap/scap.h>
#include <libsinsp/dumper.h>
#include <libsinsp/plugin.h>
#include <libsinsp/plugin_manager.h>

sinsp_dumper::sinsp_dumper() {
m_dumper = NULL;
Expand Down Expand Up @@ -67,6 +69,13 @@
inspector->m_container_manager.dump_containers(*this);
inspector->m_usergroup_manager.dump_users_groups(*this);

// ask registered ASYNC plugins for a dump of their state
for(auto& p : inspector->m_plugin_manager->plugins()) {
if(p->caps() & CAP_ASYNC) {
p->dump_state(*this);
}
}

m_nevts = 0;
}

Expand All @@ -87,6 +96,13 @@
inspector->m_container_manager.dump_containers(*this);
inspector->m_usergroup_manager.dump_users_groups(*this);

// ask registered ASYNC plugins for a dump of their state
for(auto& p : inspector->m_plugin_manager->plugins()) {
if(p->caps() & CAP_ASYNC) {
p->dump_state(*this);

Check warning on line 102 in userspace/libsinsp/dumper.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/dumper.cpp#L102

Added line #L102 was not covered by tests
}
}

m_nevts = 0;
}

Expand Down
69 changes: 69 additions & 0 deletions userspace/libsinsp/plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1175,3 +1175,72 @@

return rc == SS_PLUGIN_SUCCESS;
}

bool sinsp_plugin::dump_state(sinsp_dumper& dumper) {
if(!m_inited) {
throw sinsp_exception(std::string(s_not_init_err) + ": " + m_name);
}

if(!(m_caps & CAP_ASYNC)) {
throw sinsp_exception("plugin " + m_name + "without async events cap used as dumper");
}

if(!m_handle->api.dump_state) {
return false;
}

m_async_dump_handler = [&dumper](auto e) { dumper.dump(e.get()); };
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume we don't have thread safety issue here compared to the async event handler, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Exactly!


const auto callback =
[](ss_plugin_owner_t* o, const ss_plugin_event* e, char* err) -> ss_plugin_rc {
auto p = static_cast<sinsp_plugin*>(o);
// We only support dumping of PPME_ASYNCEVENT_E events
if(e->type != PPME_ASYNCEVENT_E || e->nparams != 3) {
if(err) {
auto e = "malformed async event produced by plugin: " + p->name();

Check warning on line 1200 in userspace/libsinsp/plugin.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/plugin.cpp#L1200

Added line #L1200 was not covered by tests
strlcpy(err, e.c_str(), PLUGIN_MAX_ERRLEN);
}
return SS_PLUGIN_FAILURE;

Check warning on line 1203 in userspace/libsinsp/plugin.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/plugin.cpp#L1203

Added line #L1203 was not covered by tests
}

// Event name must be one of the async event names
auto name = (const char*)((uint8_t*)e + sizeof(ss_plugin_event) + 4 + 4 + 4 + 4);
if(p->async_event_names().find(name) == p->async_event_names().end()) {
if(err) {
auto e = "incompatible async event '" + std::string(name) +
"' produced by plugin: " + p->name();
strlcpy(err, e.c_str(), PLUGIN_MAX_ERRLEN);
}
return SS_PLUGIN_FAILURE;

Check warning on line 1214 in userspace/libsinsp/plugin.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/plugin.cpp#L1214

Added line #L1214 was not covered by tests
}

try {
auto evt = std::make_unique<sinsp_evt>();
ASSERT(evt->get_scap_evt_storage() == nullptr);
evt->set_scap_evt_storage(new char[e->len]);
memcpy(evt->get_scap_evt_storage(), e, e->len);
evt->set_cpuid(0);
evt->set_num(0);
evt->set_scap_evt((scap_evt*)evt->get_scap_evt_storage());
evt->init();
// note: plugin ID and timestamp will be set by the inspector
p->m_async_dump_handler(std::move(evt));
} catch(const std::exception& _e) {
if(err) {
strlcpy(err, _e.what(), PLUGIN_MAX_ERRLEN);

Check warning on line 1230 in userspace/libsinsp/plugin.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/plugin.cpp#L1230

Added line #L1230 was not covered by tests
}
return SS_PLUGIN_FAILURE;
} catch(...) {

Check warning on line 1233 in userspace/libsinsp/plugin.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/plugin.cpp#L1232-L1233

Added lines #L1232 - L1233 were not covered by tests
if(err) {
strlcpy(err, "unknwon error in dumping async event", PLUGIN_MAX_ERRLEN);

Check warning on line 1235 in userspace/libsinsp/plugin.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/plugin.cpp#L1235

Added line #L1235 was not covered by tests
}
return SS_PLUGIN_FAILURE;

Check warning on line 1237 in userspace/libsinsp/plugin.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/plugin.cpp#L1237

Added line #L1237 was not covered by tests
}
return SS_PLUGIN_SUCCESS;
};

if(m_handle->api.dump_state(m_state, this, callback) != SS_PLUGIN_SUCCESS) {
throw sinsp_exception("dump error for plugin '" + m_name + "' : " + m_last_owner_err);
}
return true;
}
9 changes: 9 additions & 0 deletions userspace/libsinsp/plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ limitations under the License.
#include <atomic>
#include <libscap/engine/source_plugin/source_plugin_public.h>
#include <libsinsp/event.h>
#include <libsinsp/dumper.h>
#include <libsinsp/sinsp_filtercheck.h>
#include <libsinsp/version.h>
#include <libsinsp/events/sinsp_events.h>
Expand Down Expand Up @@ -161,6 +162,7 @@ class sinsp_plugin {
sinsp_thread_pool::routine_id_t subscribe_routine(ss_plugin_routine_fn_t routine_fn,
ss_plugin_routine_state_t* routine_state);
bool unsubscribe_routine(sinsp_thread_pool::routine_id_t routine_id);
bool dump_state(sinsp_dumper& dumper);

/** Event Sourcing **/
inline uint32_t id() const { return m_id; }
Expand Down Expand Up @@ -206,6 +208,8 @@ class sinsp_plugin {
using async_event_handler_t =
std::function<void(const sinsp_plugin&, std::unique_ptr<sinsp_evt>)>;

using async_dump_handler_t = std::function<void(std::unique_ptr<sinsp_evt>)>;

bool set_async_event_handler(async_event_handler_t handler);

// note(jasondellaluce): we set these as protected in order to allow unit
Expand Down Expand Up @@ -248,9 +252,14 @@ class sinsp_plugin {
std::unordered_set<std::string> m_async_event_names;
std::atomic<async_event_handler_t*>
m_async_evt_handler; // note: we don't have thread-safe smart pointers
async_dump_handler_t m_async_dump_handler;

static ss_plugin_rc handle_plugin_async_event(ss_plugin_owner_t* o,
const ss_plugin_event* evt,
char* err);
static ss_plugin_rc handle_plugin_async_dump(ss_plugin_owner_t* o,
const ss_plugin_event* evt,
char* err);

/** Generic helpers **/
void validate_config(std::string& config);
Expand Down
8 changes: 4 additions & 4 deletions userspace/libsinsp/sinsp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ bool sinsp::is_initialstate_event(scap_evt* pevent) const {
return pevent->type == PPME_CONTAINER_E || pevent->type == PPME_CONTAINER_JSON_E ||
pevent->type == PPME_CONTAINER_JSON_2_E || pevent->type == PPME_USER_ADDED_E ||
pevent->type == PPME_USER_DELETED_E || pevent->type == PPME_GROUP_ADDED_E ||
pevent->type == PPME_GROUP_DELETED_E;
pevent->type == PPME_GROUP_DELETED_E || pevent->type == PPME_ASYNCEVENT_E;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Initial state events parsing enablement for PPME_ASYNCEVENT_E.

}

void sinsp::consume_initialstate_events() {
Expand All @@ -181,7 +181,7 @@ void sinsp::consume_initialstate_events() {
if(res == SCAP_SUCCESS) {
// Setting these to non-null will make sinsp::next use them as a scap event
// to avoid a call to scap_next. In this way, we can avoid the state parsing phase
// once we reach a container-unrelated event.
// once we reach a non-initialstate event.
m_replay_scap_evt = pevent;
m_replay_scap_cpuid = pcpuid;
m_replay_scap_flags = flags;
Expand Down Expand Up @@ -228,9 +228,9 @@ void sinsp::init() {
m_fds_to_remove.clear();

//
// If we're reading from file, we try to pre-parse the container events before
// If we're reading from file, we try to pre-parse all initial state-building events before
// importing the thread table, so that thread table filtering will work with
// container filters
// full information.
//
if(is_capture()) {
consume_initialstate_events();
Expand Down
2 changes: 1 addition & 1 deletion userspace/libsinsp/sinsp_cycledumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ void sinsp_cycledumper::autodump_start(const std::string& dump_filename) {
std::for_each(m_open_file_callbacks.begin(), m_open_file_callbacks.end(), std::ref(*this));

m_dumper->open(m_inspector,
dump_filename.c_str(),
dump_filename,
m_compress ? SCAP_COMPRESSION_GZIP : SCAP_COMPRESSION_NONE);

m_inspector->set_dumping(true);
Expand Down
70 changes: 70 additions & 0 deletions userspace/libsinsp/test/plugins.ut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,76 @@ TEST_F(sinsp_with_test_input, plugin_custom_source) {
ASSERT_EQ(next_event(), nullptr); // EOF is expected
}

class plugin_test_event_processor : public libsinsp::event_processor {
public:
explicit plugin_test_event_processor(const char* ev_name) {
num_async_evts = 0;
event_name = ev_name;
}

void on_capture_start() override {}

void process_event(sinsp_evt* evt, libsinsp::event_return rc) override {
if(evt->get_type() == PPME_ASYNCEVENT_E) {
// Retrieve internal event name
auto ev_name = evt->get_param(1)->as<std::string>();
if(ev_name == event_name) {
num_async_evts++;
}
}
}

int num_async_evts;

private:
std::string event_name;
};

// scenario: a plugin with dump capability is requested a dump and then the capture file is read.
// * register a plugin with async event capability
// * open inspector in no driver mode
// * request a scap file dump to a temporary text file
// * at this stage, the plugin will be requested to dump its state; in our test case, the plugin
// will just dump 10 fake events
// * open a replay inspector to read the generated scap file
// * register our event processor that just counts number of PPME_ASYNC_EVENT_E
// * remove the test scap file
// note: emscripten has trouble with the nodriver engine and async events
#if !defined(__EMSCRIPTEN__)
TEST_F(sinsp_with_test_input, plugin_dump) {
FedeDP marked this conversation as resolved.
Show resolved Hide resolved
uint64_t max_count = 1;
uint64_t period_ns = 1000000; // 1ms
std::string async_pl_cfg = std::to_string(max_count) + ":" + std::to_string(period_ns);
register_plugin(&m_inspector, get_plugin_api_sample_syscall_async);

// we will not use the test scap engine here, but open the src plugin instead
// note: we configure the plugin to just emit 1 event through its open params
m_inspector.open_nodriver();

auto evt = next_event();
ASSERT_NE(evt, nullptr);
ASSERT_EQ(evt->get_type(), PPME_ASYNCEVENT_E);

auto sinspdumper = sinsp_dumper();
sinspdumper.open(&m_inspector, "test.scap", false);
sinspdumper.close();

m_inspector.close();

// Here we open a replay inspector just to trigger the initstate events parsing
auto replay_inspector = sinsp();
//
auto processor = plugin_test_event_processor("sampleticker");
replay_inspector.register_external_event_processor(processor);
ASSERT_NO_THROW(replay_inspector.open_savefile("test.scap"));

ASSERT_EQ(processor.num_async_evts, 10);

replay_inspector.close();
remove("test.scap");
}
#endif

TEST(sinsp_plugin, plugin_extract_compatibility) {
std::string tmp;
sinsp i;
Expand Down
42 changes: 40 additions & 2 deletions userspace/libsinsp/test/plugins/syscall_async.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@
err,
PPME_ASYNCEVENT_E,
3,
0,
(uint32_t)0,
"unsupportedname",
scap_const_sized_buffer{data, strlen(data) + 1});
ps->async_evt->tid = 1;
Expand All @@ -161,7 +161,7 @@
err,
PPME_ASYNCEVENT_E,
3,
0,
(uint32_t)0,
name,
scap_const_sized_buffer{data, strlen(data) + 1});
ps->async_evt->tid = 1;
Expand All @@ -188,6 +188,43 @@
return SS_PLUGIN_SUCCESS;
}

ss_plugin_rc plugin_dump_state(ss_plugin_t* s,
ss_plugin_owner_t* owner,
const ss_plugin_async_event_handler_t handler) {
static uint8_t evt_buf[256];
static ss_plugin_event* evt;
char err[PLUGIN_MAX_ERRLEN];
const char* name = "sampleticker";

for(int i = 0; i < 10; i++) {
evt = (ss_plugin_event*)evt_buf;

std::string data = "hello world #" + std::to_string(i);

char error[SCAP_LASTERR_SIZE];
int32_t encode_res = scap_event_encode_params(
scap_sized_buffer{evt, sizeof(evt_buf)},
nullptr,
error,
PPME_ASYNCEVENT_E,
3,
(uint32_t)0,
name,
scap_const_sized_buffer{(void*)data.c_str(), data.length() + 1});

if(encode_res == SCAP_FAILURE) {
return SS_PLUGIN_FAILURE;
}
if(SS_PLUGIN_SUCCESS != handler(owner, evt, err)) {
printf("sample_syscall_async: unexpected failure in sending asynchronous event "
"from plugin: %s\n",
err);
exit(1);

Check warning on line 222 in userspace/libsinsp/test/plugins/syscall_async.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/test/plugins/syscall_async.cpp#L222

Added line #L222 was not covered by tests
}
}
return SS_PLUGIN_SUCCESS;
}

} // anonymous namespace

void get_plugin_api_sample_syscall_async(plugin_api& out) {
Expand All @@ -203,4 +240,5 @@
out.get_async_event_sources = plugin_get_async_event_sources;
out.get_async_events = plugin_get_async_events;
out.set_async_event_handler = plugin_set_async_event_handler;
out.dump_state = plugin_dump_state;
}
21 changes: 20 additions & 1 deletion userspace/plugin/plugin_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ extern "C" {
//
// todo(jasondellaluce): when/if major changes to v4, check and solve all todos
#define PLUGIN_API_VERSION_MAJOR 3
#define PLUGIN_API_VERSION_MINOR 9
#define PLUGIN_API_VERSION_MINOR 10
#define PLUGIN_API_VERSION_PATCH 0

//
Expand Down Expand Up @@ -1053,6 +1053,25 @@ typedef struct {
ss_plugin_rc (*set_async_event_handler)(ss_plugin_t* s,
ss_plugin_owner_t* owner,
const ss_plugin_async_event_handler_t handler);

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't fully enjoy the fact that this is tied to the async events capability. For instances, certain plugins may collect some state through parse_event and make it available through the table API too, but maybe will never emit async events.

However, I see the point. It also makes sense to "recycle" get_async_event_sources and get_async_events, since they are indeed useful for this use case. Given that this symbol is optional, my interpretation is that defining set_async_event_handler will be a requirement even if not used. If so, we'll have the opportunity later on to change our minds and loosen the requirements without incurring in a breaking change. So I think we can sleep peacefully with this for now 👍

//
// Called by the framework when a capture file dump is requested.
//
// Required: no
// Arguments:
// - s: the plugin state, returned by init(). Can be NULL.
// - owner: Opaque pointer to the plugin's owner. Must be passed
// as an argument to the async event function handler.
// - handler: Function handler to be used for sending events to be dumped
// to the plugin's owner. The handler must be invoked with
// the same owner opaque pointer passed to this function, and with
// an event pointer owned and controlled by the plugin. The event
// pointer is not retained by the handler after it returns.
//
// Return value: A ss_plugin_rc with values SS_PLUGIN_SUCCESS or SS_PLUGIN_FAILURE.
ss_plugin_rc (*dump_state)(ss_plugin_t* s,
ss_plugin_owner_t* owner,
const ss_plugin_async_event_handler_t handler);
};

// Sets a new plugin configuration when provided by the framework.
Expand Down
1 change: 1 addition & 0 deletions userspace/plugin/plugin_loader.c
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ plugin_handle_t* plugin_load(const char* path, char* err) {
SYM_RESOLVE(ret, get_async_event_sources);
SYM_RESOLVE(ret, get_async_events);
SYM_RESOLVE(ret, set_async_event_handler);
SYM_RESOLVE(ret, dump_state);
SYM_RESOLVE(ret, set_config);
SYM_RESOLVE(ret, get_metrics);
SYM_RESOLVE(ret, capture_open);
Expand Down
Loading