diff --git a/CMakeLists.txt b/CMakeLists.txt index 86473c50..4f3b8c6f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,6 +115,7 @@ target_include_directories(${CLP_FFI_JS_BIN_NAME} PRIVATE src/) set(CLP_FFI_JS_SRC_MAIN src/clp_ffi_js/ir/decoding_methods.cpp src/clp_ffi_js/ir/IRStreamReader.cpp + src/clp_ffi_js/ir/StreamReader.cpp ) set(CLP_FFI_JS_SRC_CLP_CORE diff --git a/src/clp_ffi_js/ir/IRStreamReader.cpp b/src/clp_ffi_js/ir/IRStreamReader.cpp index e48b7310..ecab8a79 100644 --- a/src/clp_ffi_js/ir/IRStreamReader.cpp +++ b/src/clp_ffi_js/ir/IRStreamReader.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include using namespace std::literals::string_literals; @@ -241,13 +242,9 @@ auto IRStreamReader::create_deserializer_and_data_context( namespace { EMSCRIPTEN_BINDINGS(ClpIRStreamReader) { - emscripten::register_type("Uint8Array"); - emscripten::register_type( - "Array<[string, number, number, number]>" - ); - emscripten::register_type("number[] | null"); - - emscripten::class_("ClpIRStreamReader") + emscripten::class_< + clp_ffi_js::ir::IRStreamReader, + emscripten::base>("ClpIRStreamReader") .constructor( &clp_ffi_js::ir::IRStreamReader::create, emscripten::return_value_policy::take_ownership() diff --git a/src/clp_ffi_js/ir/IRStreamReader.hpp b/src/clp_ffi_js/ir/IRStreamReader.hpp index db267afa..137a2996 100644 --- a/src/clp_ffi_js/ir/IRStreamReader.hpp +++ b/src/clp_ffi_js/ir/IRStreamReader.hpp @@ -13,20 +13,19 @@ #include #include +#include #include using clp::ir::four_byte_encoded_variable_t; namespace clp_ffi_js::ir { -EMSCRIPTEN_DECLARE_VAL_TYPE(DataArrayTsType); -EMSCRIPTEN_DECLARE_VAL_TYPE(DecodedResultsTsType); -EMSCRIPTEN_DECLARE_VAL_TYPE(FilteredLogEventMapTsType); - /** * Class to deserialize and decode Zstandard-compressed CLP IR streams as well as format decoded * log events. */ -class IRStreamReader { +class IRStreamReader : public StreamReader { + friend StreamReader; + public: /** * Mapping between an index in the filtered log events collection to an index in the unfiltered @@ -44,7 +43,7 @@ class IRStreamReader { [[nodiscard]] static auto create(DataArrayTsType const& data_array) -> IRStreamReader; // Destructor - ~IRStreamReader() = default; + ~IRStreamReader() override = default; // Disable copy constructor and assignment operator IRStreamReader(IRStreamReader const&) = delete; @@ -59,19 +58,19 @@ class IRStreamReader { /** * @return The number of events buffered. */ - [[nodiscard]] auto get_num_events_buffered() const -> size_t; + [[nodiscard]] auto get_num_events_buffered() const -> size_t override; /** * @return The filtered log events map. */ - [[nodiscard]] auto get_filtered_log_event_map() const -> FilteredLogEventMapTsType; + [[nodiscard]] auto get_filtered_log_event_map() const -> FilteredLogEventMapTsType override; /** * Generates a filtered collection from all log events. * * @param log_level_filter Array of selected log levels */ - void filter_log_events(emscripten::val const& log_level_filter); + void filter_log_events(emscripten::val const& log_level_filter) override; /** * Deserializes all log events in the stream. After the stream has been exhausted, it will be @@ -79,7 +78,7 @@ class IRStreamReader { * * @return The number of successfully deserialized ("valid") log events. */ - [[nodiscard]] auto deserialize_stream() -> size_t; + [[nodiscard]] auto deserialize_stream() -> size_t override; /** * Decodes log events in the range `[beginIdx, endIdx)` of the filtered or unfiltered @@ -96,8 +95,8 @@ class IRStreamReader { * @return null if any log event in the range doesn't exist (e.g. the range exceeds the number * of log events in the collection). */ - [[nodiscard]] auto - decode_range(size_t begin_idx, size_t end_idx, bool use_filter) const -> DecodedResultsTsType; + [[nodiscard]] auto decode_range(size_t begin_idx, size_t end_idx, bool use_filter) const + -> DecodedResultsTsType override; private: // Constructor diff --git a/src/clp_ffi_js/ir/StreamReader.cpp b/src/clp_ffi_js/ir/StreamReader.cpp new file mode 100644 index 00000000..6906d7e0 --- /dev/null +++ b/src/clp_ffi_js/ir/StreamReader.cpp @@ -0,0 +1,70 @@ +#include "StreamReader.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace clp_ffi_js::ir { +auto StreamReader::create(DataArrayTsType const& data_array) -> std::unique_ptr { + auto const length{data_array["length"].as()}; + SPDLOG_INFO("KVPairIRStreamReader::create: got buffer of length={}", length); + + // Copy array from JavaScript to C++ + clp::Array data_buffer{length}; + // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) + emscripten::val::module_property("HEAPU8") + .call("set", data_array, reinterpret_cast(data_buffer.data())); + // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) + + auto zstd_decompressor{std::make_unique()}; + zstd_decompressor->open(data_buffer.data(), length); + + auto const version{get_version(*zstd_decompressor)}; + if (version == "v0.0.0") { + auto stream_reader_data_context{IRStreamReader::create_deserializer_and_data_context( + std::move(zstd_decompressor), + std::move(data_buffer) + )}; + + return std::unique_ptr( + new IRStreamReader(std::move(stream_reader_data_context)) + ); + } + + throw ClpFfiJsException{ + clp::ErrorCode::ErrorCode_Unsupported, + __FILENAME__, + __LINE__, + std::format("Unable to create stream reader for IR data with version {}.", version) + }; +} +} // namespace clp_ffi_js::ir + +namespace { +EMSCRIPTEN_BINDINGS(ClpStreamReader) { + emscripten::register_type("Uint8Array"); + emscripten::register_type( + "Array<[string, number, number, number]>" + ); + emscripten::register_type("number[] | null"); + + emscripten::class_("ClpStreamReader") + .constructor( + &clp_ffi_js::ir::StreamReader::create, + emscripten::return_value_policy::take_ownership() + ); +} +} // namespace diff --git a/src/clp_ffi_js/ir/StreamReader.hpp b/src/clp_ffi_js/ir/StreamReader.hpp new file mode 100644 index 00000000..ad9cbaa0 --- /dev/null +++ b/src/clp_ffi_js/ir/StreamReader.hpp @@ -0,0 +1,89 @@ +#ifndef CLP_FFI_JS_IR_STREAM_READER_HPP +#define CLP_FFI_JS_IR_STREAM_READER_HPP + +#include +#include + +#include + +namespace clp_ffi_js::ir { +EMSCRIPTEN_DECLARE_VAL_TYPE(DataArrayTsType); +EMSCRIPTEN_DECLARE_VAL_TYPE(DecodedResultsTsType); +EMSCRIPTEN_DECLARE_VAL_TYPE(FilteredLogEventMapTsType); + +/** + * Class to deserialize and decode Zstandard-compressed CLP IR streams as well as format decoded + * log events. + */ +class StreamReader { +public: + /** + * Creates a StreamReader to read from the given array. + * + * @param data_array An array containing a Zstandard-compressed IR stream. + * @return The created instance. + * @throw ClpFfiJsException if any error occurs. + */ + [[nodiscard]] static auto create(DataArrayTsType const& data_array + ) -> std::unique_ptr; + + // Destructor + virtual ~StreamReader() = default; + + // Disable copy constructor and assignment operator + StreamReader(StreamReader const&) = delete; + auto operator=(StreamReader const&) -> StreamReader& = delete; + + // Define default move constructor + StreamReader(StreamReader&&) = default; + // Delete move assignment operator since it's also disabled in `clp::ir::LogEventDeserializer`. + auto operator=(StreamReader&&) -> StreamReader& = delete; + + /** + * @return The number of events buffered. + */ + [[nodiscard]] virtual auto get_num_events_buffered() const -> size_t = 0; + + /** + * @return The filtered log events map. + */ + [[nodiscard]] virtual auto get_filtered_log_event_map() const -> FilteredLogEventMapTsType = 0; + + /** + * Generates a filtered collection from all log events. + * + * @param log_level_filter Array of selected log levels + */ + virtual void filter_log_events(emscripten::val const& log_level_filter) = 0; + + /** + * Deserializes all log events in the stream. After the stream has been exhausted, it will be + * deallocated. + * + * @return The number of successfully deserialized ("valid") log events. + */ + [[nodiscard]] virtual auto deserialize_stream() -> size_t = 0; + + /** + * Decodes log events in the range `[beginIdx, endIdx)` of the filtered or unfiltered + * (depending on the value of `useFilter`) log events collection. + * + * @param begin_idx + * @param end_idx + * @param use_filter Whether to decode from the filtered or unfiltered log events collection. + * @return An array where each element is a decoded log event represented by an array of: + * - The log event's message + * - The log event's timestamp as milliseconds since the Unix epoch + * - The log event's log level as an integer that indexes into `cLogLevelNames` + * - The log event's number (1-indexed) in the stream + * @return null if any log event in the range doesn't exist (e.g. the range exceeds the number + * of log events in the collection). + */ + [[nodiscard]] virtual auto decode_range(size_t begin_idx, size_t end_idx, bool use_filter) const + -> DecodedResultsTsType = 0; + +protected: + explicit StreamReader() = default; +}; +} // namespace clp_ffi_js::ir +#endif // CLP_FFI_JS_IR_STREAM_READER_HPP