diff --git a/userspace/libsinsp/parsers.cpp b/userspace/libsinsp/parsers.cpp index 21584d0f26..dd7ec9c393 100644 --- a/userspace/libsinsp/parsers.cpp +++ b/userspace/libsinsp/parsers.cpp @@ -1276,9 +1276,14 @@ void sinsp_parser::parse_clone_exit_caller(sinsp_evt *evt, int64_t child_tid) { return; } - /* If there's a listener, invoke it */ + // + // If there's a listener, add a callback to later invoke it. + // if(m_inspector->get_observer()) { - m_inspector->get_observer()->on_clone(evt, new_child.get(), tid_collision); + m_inspector->m_post_process_cbs.emplace( + [new_child, tid_collision](sinsp_observer *observer, sinsp_evt *evt) { + observer->on_clone(evt, new_child.get(), tid_collision); + }); } /* If we had to erase a previous entry for this tid and rebalance the table, @@ -1292,8 +1297,6 @@ void sinsp_parser::parse_clone_exit_caller(sinsp_evt *evt, int64_t child_tid) { new_child->m_comm.c_str()); } /*=============================== ADD THREAD TO THE TABLE ===========================*/ - - return; } void sinsp_parser::parse_clone_exit_child(sinsp_evt *evt) { @@ -1761,17 +1764,19 @@ void sinsp_parser::parse_clone_exit_child(sinsp_evt *evt) { evt->set_tinfo(new_child.get()); // - // If there's a listener, invoke it + // If there's a listener, add a callback to later invoke it. // if(m_inspector->get_observer()) { - m_inspector->get_observer()->on_clone(evt, new_child.get(), tid_collision); + m_inspector->m_post_process_cbs.emplace( + [new_child, tid_collision](sinsp_observer *observer, sinsp_evt *evt) { + observer->on_clone(evt, new_child.get(), tid_collision); + }); } /* If we had to erase a previous entry for this tid and rebalance the table, * make sure we reinitialize the child_tinfo pointer for this event, as the thread * generating it might have gone away. */ - if(tid_collision != -1) { reset(evt); /* Right now we have collisions only on the clone() caller */ @@ -1781,7 +1786,6 @@ void sinsp_parser::parse_clone_exit_child(sinsp_evt *evt) { } /*=============================== CREATE NEW THREAD-INFO ===========================*/ - return; } void sinsp_parser::parse_clone_exit(sinsp_evt *evt) { @@ -2230,15 +2234,11 @@ void sinsp_parser::parse_execve_exit(sinsp_evt *evt) { evt->get_tinfo()->m_flags |= PPM_CL_NAME_CHANGED; // - // Recompute the program hash - // - evt->get_tinfo()->compute_program_hash(); - - // - // If there's a listener, invoke it + // If there's a listener, add a callback to later invoke it. // if(m_inspector->get_observer()) { - m_inspector->get_observer()->on_execve(evt); + m_inspector->m_post_process_cbs.emplace( + [](sinsp_observer *observer, sinsp_evt *evt) { observer->on_execve(evt); }); } /* If any of the threads in a thread group performs an @@ -2787,10 +2787,11 @@ void sinsp_parser::parse_bind_exit(sinsp_evt *evt) { evt->get_fd_info()->m_name = evt->get_param_as_str(1, &parstr, sinsp_evt::PF_SIMPLE); // - // If there's a listener callback, invoke it + // If there's a listener, add a callback to later invoke it. // if(m_inspector->get_observer()) { - m_inspector->get_observer()->on_bind(evt); + m_inspector->m_post_process_cbs.emplace( + [](sinsp_observer *observer, sinsp_evt *evt) { observer->on_bind(evt); }); } } @@ -2982,7 +2983,6 @@ inline void sinsp_parser::fill_client_socket_info(sinsp_evt *evt, void sinsp_parser::parse_connect_exit(sinsp_evt *evt) { const sinsp_evt_param *parinfo; - uint8_t *packed_data; int64_t retval; int64_t fd; bool force_overwrite_stale_data = false; @@ -3048,15 +3048,18 @@ void sinsp_parser::parse_connect_exit(sinsp_evt *evt) { return; } - packed_data = (uint8_t *)parinfo->m_val; + uint8_t *packed_data = (uint8_t *)parinfo->m_val; fill_client_socket_info(evt, packed_data, force_overwrite_stale_data); // - // If there's a listener callback, invoke it + // If there's a listener, add a callback to later invoke it. // if(m_inspector->get_observer()) { - m_inspector->get_observer()->on_connect(evt, packed_data); + m_inspector->m_post_process_cbs.emplace( + [packed_data](sinsp_observer *observer, sinsp_evt *evt) { + observer->on_connect(evt, packed_data); + }); } } @@ -3144,8 +3147,14 @@ void sinsp_parser::parse_accept_exit(sinsp_evt *evt) { fdi->m_name = evt->get_param_as_str(1, &parstr, sinsp_evt::PF_SIMPLE); fdi->m_flags = 0; + // + // If there's a listener, add a callback to later invoke it. + // if(m_inspector->get_observer()) { - m_inspector->get_observer()->on_accept(evt, fd, packed_data, fdi.get()); + m_inspector->m_post_process_cbs.emplace( + [fd, packed_data](sinsp_observer *observer, sinsp_evt *evt) { + observer->on_accept(evt, fd, packed_data, evt->get_fd_info()); + }); } // @@ -3205,8 +3214,21 @@ void sinsp_parser::erase_fd(erase_fd_params *params) { m_inspector->get_fds_to_remove().push_back(params->m_fd); } + // + // If there's a listener, add a callback to later invoke it. + // if(m_inspector->get_observer()) { - m_inspector->get_observer()->on_erase_fd(params); + auto ts = params->m_ts; + auto remove_from_table = params->m_remove_from_table; + auto fd = params->m_fd; + auto tinfo = params->m_tinfo; + auto fdinfo = params->m_fdinfo; + m_inspector->m_post_process_cbs.emplace( + [ts, remove_from_table, fd, tinfo, fdinfo](sinsp_observer *observer, + sinsp_evt *evt) { + erase_fd_params p = {remove_from_table, fd, tinfo, fdinfo, ts}; + observer->on_erase_fd(&p); + }); } } @@ -3669,8 +3691,6 @@ void sinsp_parser::parse_rw_exit(sinsp_evt *evt) { } if(eflags & EF_READS_FROM_FD) { - const char *data; - uint32_t datalen; int32_t tupleparam = -1; if(etype == PPME_SOCKET_RECVFROM_X) { @@ -3729,20 +3749,23 @@ void sinsp_parser::parse_rw_exit(sinsp_evt *evt) { parinfo = evt->get_param(1); } - datalen = parinfo->m_len; - data = parinfo->m_val; + uint32_t datalen = parinfo->m_len; + const char *data = parinfo->m_val; // - // If there's an fd listener, call it now + // If there's a listener, add a callback to later invoke it. // if(m_inspector->get_observer()) { - m_inspector->get_observer()->on_read(evt, - tid, - evt->get_tinfo()->m_lastevent_fd, - evt->get_fd_info(), - data, - (uint32_t)retval, - datalen); + m_inspector->m_post_process_cbs.emplace( + [tid, data, retval, datalen](sinsp_observer *observer, sinsp_evt *evt) { + observer->on_read(evt, + tid, + evt->get_tinfo()->m_lastevent_fd, + evt->get_fd_info(), + data, + (uint32_t)retval, + datalen); + }); } // @@ -3791,7 +3814,6 @@ void sinsp_parser::parse_rw_exit(sinsp_evt *evt) { #endif } else { - const char *data; uint32_t datalen; int32_t tupleparam = -1; @@ -3849,19 +3871,22 @@ void sinsp_parser::parse_rw_exit(sinsp_evt *evt) { // parinfo = evt->get_param(1); datalen = parinfo->m_len; - data = parinfo->m_val; + const char *data = parinfo->m_val; // - // If there's an fd listener, call it now + // If there's a listener, add a callback to later invoke it. // if(m_inspector->get_observer()) { - m_inspector->get_observer()->on_write(evt, - tid, - evt->get_tinfo()->m_lastevent_fd, - evt->get_fd_info(), - data, - (uint32_t)retval, - datalen); + m_inspector->m_post_process_cbs.emplace( + [tid, data, retval, datalen](sinsp_observer *observer, sinsp_evt *evt) { + observer->on_write(evt, + tid, + evt->get_tinfo()->m_lastevent_fd, + evt->get_fd_info(), + data, + (uint32_t)retval, + datalen); + }); } // perform syslog decoding if applicable @@ -3873,8 +3898,14 @@ void sinsp_parser::parse_rw_exit(sinsp_evt *evt) { if(evt->get_fd_info()->m_type == SCAP_FD_IPV4_SOCK || evt->get_fd_info()->m_type == SCAP_FD_IPV6_SOCK) { evt->get_fd_info()->set_socket_failed(); + // + // If there's a listener, add a callback to later invoke it. + // if(m_inspector->get_observer()) { - m_inspector->get_observer()->on_socket_status_changed(evt); + m_inspector->m_post_process_cbs.emplace( + [](sinsp_observer *observer, sinsp_evt *evt) { + observer->on_socket_status_changed(evt); + }); } } } @@ -3897,7 +3928,6 @@ void sinsp_parser::parse_sendfile_exit(sinsp_evt *evt) { // if(retval >= 0) { sinsp_evt *enter_evt = &m_tmp_evt; - int64_t fdin; if(!retrieve_enter_event(enter_evt, evt)) { return; @@ -3906,13 +3936,16 @@ void sinsp_parser::parse_sendfile_exit(sinsp_evt *evt) { // // Extract the in FD // - fdin = enter_evt->get_param(1)->as(); + int64_t fdin = enter_evt->get_param(1)->as(); // - // If there's an fd listener, call it now + // If there's a listener, add a callback to later invoke it. // if(m_inspector->get_observer()) { - m_inspector->get_observer()->on_sendfile(evt, fdin, (uint32_t)retval); + m_inspector->m_post_process_cbs.emplace( + [fdin, retval](sinsp_observer *observer, sinsp_evt *evt) { + observer->on_sendfile(evt, fdin, (uint32_t)retval); + }); } } } @@ -4068,8 +4101,13 @@ void sinsp_parser::parse_shutdown_exit(sinsp_evt *evt) { return; } + // + // If there's a listener, add a callback to later invoke it. + // if(m_inspector->get_observer()) { - m_inspector->get_observer()->on_socket_shutdown(evt); + m_inspector->m_post_process_cbs.emplace([](sinsp_observer *observer, sinsp_evt *evt) { + observer->on_socket_shutdown(evt); + }); } } } @@ -5048,8 +5086,13 @@ void sinsp_parser::parse_getsockopt_exit(sinsp_evt *evt) { } else { evt->get_fd_info()->set_socket_connected(); } + // + // If there's a listener, add a callback to later invoke it. + // if(m_inspector->get_observer()) { - m_inspector->get_observer()->on_socket_status_changed(evt); + m_inspector->m_post_process_cbs.emplace([](sinsp_observer *observer, sinsp_evt *evt) { + observer->on_socket_status_changed(evt); + }); } } } diff --git a/userspace/libsinsp/sinsp.cpp b/userspace/libsinsp/sinsp.cpp index 5442b5e7a0..a7f512276d 100644 --- a/userspace/libsinsp/sinsp.cpp +++ b/userspace/libsinsp/sinsp.cpp @@ -1316,6 +1316,20 @@ int32_t sinsp::next(sinsp_evt** puevt) { // todo(jason): should we log parsing errors here? pp.process_event(evt, m_event_sources); } + + // Once we processed all events, make sure to call all + // requested post-process callbacks. + // At this point, any plugin could have modified state tables, + // thus we can guarantee that any post-process callback + // will see the full post-event-processed state. + // NOTE: we don't use a RAII object because + // we cannot guarantee that no exception will be thrown by the callbacks. + if(m_observer != nullptr) { + for(; !m_post_process_cbs.empty(); m_post_process_cbs.pop()) { + auto cb = m_post_process_cbs.front(); + cb(m_observer, evt); + } + } } // Finally set output evt; diff --git a/userspace/libsinsp/sinsp.h b/userspace/libsinsp/sinsp.h index 967aae49ac..0dfa6af1a4 100644 --- a/userspace/libsinsp/sinsp.h +++ b/userspace/libsinsp/sinsp.h @@ -82,6 +82,7 @@ limitations under the License. #include #include #include +#include #define ONE_SECOND_IN_NS 1000000000LL @@ -1242,6 +1243,8 @@ class SINSP_PUBLIC sinsp : public capture_stats_source { sinsp_observer* m_observer{nullptr}; + std::queue> m_post_process_cbs{}; + bool m_inited; static std::atomic instance_count; }; diff --git a/userspace/libsinsp/test/classes/sinsp_observer.cpp b/userspace/libsinsp/test/classes/sinsp_observer.cpp new file mode 100644 index 0000000000..ec298c7e3d --- /dev/null +++ b/userspace/libsinsp/test/classes/sinsp_observer.cpp @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2025 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + +#include +#include "sinsp_observer.h" + +class test_observer : public sinsp_observer { +public: + test_observer(): + m_clone_ctr(0), + m_execve_ctr(0), + m_open_ctr(0), + m_read_ctr(0), + m_close_ctr(0) {} + + void on_read(sinsp_evt* evt, + int64_t tid, + int64_t fd, + sinsp_fdinfo* fdinfo, + const char* data, + uint32_t original_len, + uint32_t len) override { + ASSERT_EQ(evt->get_num(), 5); + ASSERT_EQ(fdinfo->m_fd, 4); + ASSERT_STREQ(data, "hello"); + m_read_ctr++; + } + + void on_write(sinsp_evt* evt, + int64_t tid, + int64_t fd, + sinsp_fdinfo* fdinfo, + const char* data, + uint32_t original_len, + uint32_t len) override {} + + void on_sendfile(sinsp_evt* evt, int64_t fdin, uint32_t len) override {} + void on_connect(sinsp_evt* evt, uint8_t* packed_data) override {} + + void on_accept(sinsp_evt* evt, + int64_t newfd, + uint8_t* packed_data, + sinsp_fdinfo* new_fdinfo) override {} + + void on_file_open(sinsp_evt* evt, const std::string& fullpath, uint32_t flags) override { + ASSERT_EQ(evt->get_num(), 4); + ASSERT_EQ(fullpath, "/home/file.txt"); + m_open_ctr++; + } + void on_error(sinsp_evt* evt) override {} + void on_erase_fd(erase_fd_params* params) override { + ASSERT_EQ(params->m_fd, 4); + ASSERT_EQ(params->m_tinfo->m_tid, 1); + m_close_ctr++; + } + void on_socket_shutdown(sinsp_evt* evt) override {} + void on_execve(sinsp_evt* evt) override { + ASSERT_EQ(evt->get_num(), + 3); // (we create both execve enter and exit; the callback is called on the exit) + m_execve_ctr++; + } + + void on_clone(sinsp_evt* evt, sinsp_threadinfo* newtinfo, int64_t tid_collision) override { + ASSERT_EQ(evt->get_num(), 1); // first event created + ASSERT_EQ(newtinfo->m_tid, 22); + m_clone_ctr++; + } + + void on_bind(sinsp_evt* evt) override {} + + bool on_resolve_container(sinsp_container_manager* manager, + sinsp_threadinfo* tinfo, + bool query_os_for_missing_info) override { + return true; + } + + void on_socket_status_changed(sinsp_evt* evt) override {} + + int get_clone_counter() const { return m_clone_ctr; } + int get_execve_counter() const { return m_execve_ctr; }; + int get_open_counter() const { return m_open_ctr; } + int get_read_counter() const { return m_read_ctr; } + int get_close_ctr() const { return m_close_ctr; } + +private: + int m_clone_ctr; + int m_execve_ctr; + int m_open_ctr; + int m_read_ctr; + int m_close_ctr; +}; + +TEST_F(sinsp_with_test_input, sinsp_observer) { + add_default_init_thread(); + open_inspector(); + + test_observer observer; + + m_inspector.set_observer(&observer); + + /* clone exit event */ + generate_clone_x_event(22, INIT_TID, INIT_PID, INIT_PTID); + ASSERT_EQ(observer.get_clone_counter(), 1); + ASSERT_EQ(observer.get_execve_counter(), 0); + ASSERT_EQ(observer.get_open_counter(), 0); + ASSERT_EQ(observer.get_read_counter(), 0); + ASSERT_EQ(observer.get_close_ctr(), 0); + + /* execve exit event */ + generate_execve_enter_and_exit_event(0, 11, 11, 11, 11); + ASSERT_EQ(observer.get_clone_counter(), 1); + ASSERT_EQ(observer.get_execve_counter(), 1); + ASSERT_EQ(observer.get_open_counter(), 0); + ASSERT_EQ(observer.get_read_counter(), 0); + ASSERT_EQ(observer.get_close_ctr(), 0); + + generate_open_x_event(); + ASSERT_EQ(observer.get_clone_counter(), 1); + ASSERT_EQ(observer.get_execve_counter(), 1); + ASSERT_EQ(observer.get_open_counter(), 1); + ASSERT_EQ(observer.get_read_counter(), 0); + ASSERT_EQ(observer.get_close_ctr(), 0); + + std::string data = "hello"; + uint32_t size = data.size() + 1; // null terminator + add_event_advance_ts(increasing_ts(), + INIT_TID, + PPME_SYSCALL_READ_X, + 4, + (int64_t)size, + scap_const_sized_buffer{data.c_str(), size}, + sinsp_test_input::open_params::default_fd, + size); + ASSERT_EQ(observer.get_clone_counter(), 1); + ASSERT_EQ(observer.get_execve_counter(), 1); + ASSERT_EQ(observer.get_open_counter(), 1); + ASSERT_EQ(observer.get_read_counter(), 1); + ASSERT_EQ(observer.get_close_ctr(), 0); + + // Close the opened FD + add_event_advance_ts(increasing_ts(), + INIT_TID, + PPME_SYSCALL_CLOSE_E, + 1, + (int64_t)4); // default sinsp_test_input::open_params fd) + add_event_advance_ts(increasing_ts(), INIT_TID, PPME_SYSCALL_CLOSE_X, 1, (int64_t)0); + ASSERT_EQ(observer.get_clone_counter(), 1); + ASSERT_EQ(observer.get_execve_counter(), 1); + ASSERT_EQ(observer.get_open_counter(), 1); + ASSERT_EQ(observer.get_read_counter(), 1); + ASSERT_EQ(observer.get_close_ctr(), 1); +} diff --git a/userspace/libsinsp/threadinfo.cpp b/userspace/libsinsp/threadinfo.cpp index 4011baf1be..3733f34a28 100644 --- a/userspace/libsinsp/threadinfo.cpp +++ b/userspace/libsinsp/threadinfo.cpp @@ -100,8 +100,6 @@ libsinsp::state::static_struct::field_infos sinsp_threadinfo::static_fields() co define_static_field(ret, this, m_pgid, "pgid"); define_static_field(ret, this, m_pidns_init_start_ts, "pidns_init_start_ts"); define_static_field(ret, this, m_root, "root"); - // m_program_hash - // m_program_hash_scripts define_static_field(ret, this, m_tty, "tty"); // m_category // m_clone_ts @@ -145,8 +143,6 @@ void sinsp_threadinfo::init() { m_lastevent_fd = 0; m_last_latency_entertime = 0; m_latency = 0; - m_program_hash = 0; - m_program_hash_scripts = 0; m_lastevent_data = NULL; m_parent_loop_detected = false; m_tty = 0; @@ -203,57 +199,6 @@ void sinsp_threadinfo::fix_sockets_coming_from_proc() { }); } -#define STR_AS_NUM_JAVA 0x6176616a -#define STR_AS_NUM_RUBY 0x79627572 -#define STR_AS_NUM_PERL 0x6c726570 -#define STR_AS_NUM_NODE 0x65646f6e - -#define MAX_PROG_HASH_LEN 1024 - -void sinsp_threadinfo::compute_program_hash() { - auto curr_hash = std::hash()(m_exe); - hash_combine(curr_hash, m_container_id); - auto rem_len = MAX_PROG_HASH_LEN - (m_exe.size() + m_container_id.size()); - - // - // By default, the scripts hash is just exe+container - // - m_program_hash_scripts = curr_hash; - - // - // The program hash includes the arguments as well - // - for(auto arg = m_args.begin(); arg != m_args.end() && rem_len > 0; ++arg) { - if(arg->size() >= rem_len) { - auto partial_str = arg->substr(0, rem_len); - hash_combine(curr_hash, partial_str); - break; - } - - hash_combine(curr_hash, *arg); - rem_len -= arg->size(); - } - m_program_hash = curr_hash; - - // - // For some specific processes (essentially the scripting languages) - // we include the arguments in the scripts hash as well - // - if(m_comm.size() == 4) { - uint32_t ncomm; - memcpy(&ncomm, m_comm.c_str(), 4); - - if(ncomm == STR_AS_NUM_JAVA || ncomm == STR_AS_NUM_RUBY || ncomm == STR_AS_NUM_PERL || - ncomm == STR_AS_NUM_NODE) { - m_program_hash_scripts = m_program_hash; - } - } else if(m_comm.size() >= 6) { - if(m_comm.substr(0, 6) == "python") { - m_program_hash_scripts = m_program_hash; - } - } -} - void sinsp_threadinfo::add_fd_from_scap(scap_fdinfo* fdi) { auto newfdi = m_inspector->build_fdinfo(); bool do_add = true; @@ -1477,7 +1422,6 @@ const std::shared_ptr& sinsp_thread_manager::add_thread( "adding entry with incompatible dynamic defs to of file descriptor sub-table"); } - tinfo_shared_ptr->compute_program_hash(); if(m_sinsp_stats_v2 != nullptr) { m_sinsp_stats_v2->m_n_added_threads++; } diff --git a/userspace/libsinsp/threadinfo.h b/userspace/libsinsp/threadinfo.h index aed22fff92..7e14bde4fb 100644 --- a/userspace/libsinsp/threadinfo.h +++ b/userspace/libsinsp/threadinfo.h @@ -434,10 +434,8 @@ class SINSP_PUBLIC sinsp_threadinfo : public libsinsp::state::table_entry { int64_t m_pgid; // Process group id, as seen from the host pid namespace uint64_t m_pidns_init_start_ts; ///< The pid_namespace init task (child_reaper) start_time ts. std::string m_root; - size_t m_program_hash; ///< Unique hash of the current program - size_t m_program_hash_scripts; ///< Unique hash of the current program, including arguments for - ///< scripting programs (like python or ruby) - uint32_t m_tty; ///< Number of controlling terminal + + uint32_t m_tty; ///< Number of controlling terminal std::shared_ptr m_tginfo; std::list> m_children; uint64_t m_not_expired_children; @@ -497,27 +495,6 @@ class SINSP_PUBLIC sinsp_threadinfo : public libsinsp::state::table_entry { // sinsp* m_inspector; - struct hasher { - size_t operator()(sinsp_threadinfo* tinfo) const { - auto main_thread = tinfo->get_main_thread(); - if(main_thread == nullptr) { - return 0; - } - return main_thread->m_program_hash; - } - }; - - struct comparer { - size_t operator()(sinsp_threadinfo* lhs, sinsp_threadinfo* rhs) const { - auto lhs_main_thread = lhs->get_main_thread(); - auto rhs_main_thread = rhs->get_main_thread(); - if(lhs_main_thread == nullptr || rhs_main_thread == nullptr) { - return 0; - } - return lhs_main_thread->m_program_hash == rhs_main_thread->m_program_hash; - } - }; - /* Note that `fd_table` should be shared with the main thread only if `PPM_CL_CLONE_FILES` * is specified. Today we always specify `PPM_CL_CLONE_FILES` for all threads. */ @@ -556,7 +533,6 @@ class SINSP_PUBLIC sinsp_threadinfo : public libsinsp::state::table_entry { m_lastevent_cpuid = (uint16_t)-1; } } - void compute_program_hash(); inline const uint8_t* get_last_event_data() const { return m_lastevent_data; } diff --git a/userspace/libsinsp/utils.h b/userspace/libsinsp/utils.h index 0eb6dd7e0d..3c06041be3 100644 --- a/userspace/libsinsp/utils.h +++ b/userspace/libsinsp/utils.h @@ -325,16 +325,6 @@ bool set_socket_blocking(int sock, bool block); unsigned int read_num_possible_cpus(void); -/////////////////////////////////////////////////////////////////////////////// -// hashing helpers -/////////////////////////////////////////////////////////////////////////////// - -// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3876.pdf -template -inline void hash_combine(std::size_t& seed, const T& val) { - seed ^= std::hash()(val) + 0x9e3779b9 + (seed << 6) + (seed >> 2); -} - /////////////////////////////////////////////////////////////////////////////// // Log helpers ///////////////////////////////////////////////////////////////////////////////