diff --git a/README.md b/README.md index dbaae4a..6f648fe 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Documentation of individual components: - [sf-stream](sf-stream) (Live streaming of detector data) - [sf-writer](sf-writer) (Read from buffer and write H5) - [sf-utils](sf-utils) (Small utilities for debugging and testing) +- [jf-live-writer](jf-live-writer) (Live writer to high performance detectors) ## Design goals diff --git a/docs/sf_daq_buffer-StoreStream.jpg b/docs/sf_daq_buffer-StoreStream.jpg new file mode 100644 index 0000000..ef7bff6 Binary files /dev/null and b/docs/sf_daq_buffer-StoreStream.jpg differ diff --git a/docs/sf_daq_buffer-overview-LiveWriter.jpg b/docs/sf_daq_buffer-overview-LiveWriter.jpg new file mode 100644 index 0000000..7a416c1 Binary files /dev/null and b/docs/sf_daq_buffer-overview-LiveWriter.jpg differ diff --git a/docs/sf_daq_buffer.drawio b/docs/sf_daq_buffer.drawio index 902d6e4..b7d9def 100644 --- a/docs/sf_daq_buffer.drawio +++ b/docs/sf_daq_buffer.drawio @@ -1 +1 @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/jf-live-writer/README.md b/jf-live-writer/README.md index 95e1139..21dd53e 100644 --- a/jf-live-writer/README.md +++ b/jf-live-writer/README.md @@ -1,11 +1,124 @@ # jf-live-writer -The jf-live-writer is packaged as a Docker container for development and -testing. +This component is a PHDF5 based MPI writer for high performance detectors +that need more than 2GB/s of write speed. It parallelizes the HDF5 writing to +multiple processes in order to overcome the cca. 3GB/s single stream +write limit on GPFS. -# Using the docker container +It expects an input ZMQ stream that contains metadata about what and where to +write and access to the RamBuffer of the images one wants to write. + +This writer is stateless but tied to a specific detector. There are no +configuration states, all the information for writing files is expected to +come from the input metadata stream. The images data is directly taken from the +existing buffer without additional memory copies. + +## Overview +![image_livewriter_overview](../docs/sf_daq_buffer-overview-LiveWriter.jpg) + +The ZMQ store stream is a PUB/SUB stream that gets distributed to all +ranks. This stream caries both ImageMetadata information and file writing +metadata and is generated and sent by the writer agent. + +For writing image data, each process decides based on its rank if the +particular store stream message is for him and writes the requested image. +For writing image metadata, the rank 0 is always responsible. The metadata is +always written only by the process with **RANK = 0**. + +When to start writing a new file or when to close an existing one is also +decided based on the metadata in the store stream. There is no state machine +in the writer, but which action to take is based solely on the received +metadata. This saves us the need to have inter-rank communication and makes +for a more simple writer. + +### ZMQ Store Stream format +This stream is composed by 2 parts. The first part is the already known +**ImageMetadata** the **jf-assembler** provides, and the second part is +provided by the **writer-agent**. + +Each message in the stream has this format: + +```c++ +#pragma pack(push) +#pragma pack(1) +struct StoreStream { + ImageMetadata image_metadata; + + int64_t run_id; + uint32_t i_image; + uint32_t n_images; + uint32_t image_y_size; + uint32_t image_x_size; + uint32_t op_code; + uint32_t bits_per_pixel; +}; +#pragma pack(pop) + +#pragma pack(push) +#pragma pack(1) +struct ImageMetadata { + uint64_t pulse_id; + uint64_t frame_index; + uint32_t daq_rec; + uint32_t is_good_image; +}; +#pragma pack(pop) +``` + +#### StoreStream + +| Name | Type | Comment | +| --- | --- | --- | +| run_id | int64 | Run id used to construct the output file name. | +| i_image | uint32_t | Current image index inside this run. | +| n_images | uint32_t | Total number of images in this run. | +| image_y_size | uint32_t | Y image size in pixels. | +| image_x_size | uint32_t | X image size in pixels. | +| op_code | uint32_t | State transition information for the writer. | +| bits_per_pixel | uint32_t | How many bits does 1 pixel have. 8, 16 or 32. | + +Some details regarding how this fields are used: + +- **run\_id**: Currently the output file name is simply **[run\_id].h5**. +- **i\_images**: Based on this each rank decides if the received message is for +itself and needs to write the corresponding image data to file. +- **op_code**: This is used to steer the file writing and to avoid the need +for a state machine: + - op_code = 0 (Continue - just write to the same file as you already are) + - op_code = 1 (Start - create a new file for this run_id) + - op_code = 2 (Stop - close the current file) + +Since the writer is relying on the correct sequence of messages in the input +stream instead of having an internal state machine, +the input stream must always follow a valid pattern of messages: + +![image_store_stream](../docs/sf_daq_buffer-StoreStream.jpg) + +The sequence must always follow: + +- op_code = 1 (also a new run_id if you do not want to overwrite the previous file) +- op_code = 0 (same run_id as the last message) +- op_code = 2 (same run_id as the last message) +- etc. + +In case the sequence is broken (wrong send order from the writer agent or lost +messages, etc.) the writer will ignore the received message. An operational +state can be restored by sending a **op\_code = 2** message. + +Images are written only when op_code = 0, meaning that the start and stop +message are not used for writing data. This allows to send the stop message +in case we need to reset the writer status at any point. + +#### ImageMetadata +This comes from jf_assembler without modifications for a particular +image. + +## Build + +### Build inside docker The easiest way to build and test the jf-live-writer is to use the -provided docker container. You need to start it from the project **root**: +provided docker container. You need to start building it +from the project **root**: ```bash docker build -f jf-live-writer/debug.Dockerfile -t jf-live-writer . @@ -13,9 +126,22 @@ docker build -f jf-live-writer/debug.Dockerfile -t jf-live-writer . (Running this command from the project root is mandatory as the entire project folder needs to be part of the build context.) -# Build on your local machine +This will copy your current working directory to the image and build the +jf-live-writer. Once you've dont this, you can start -## Building +### Build on your machine + +In addition to the libraries needed for sf_daq, you need **mpich** installed: + +```bash +yum install mpich-devel +ln -v -s /usr/include/mpich-x86_64/* /usr/include/ +``` + +Making the soft links for mpich headers to your /usr/include is +necessary due to HDF5. + +#### Building with cmake In order to build this executable you need to specify the cmake variable ``` cmake3 -DBUILD_JF_LIVE_WRITER=ON @@ -23,7 +149,7 @@ cmake3 -DBUILD_JF_LIVE_WRITER=ON The project will not build if you do not have installed the PHDF5 library. Please follow instructions below on how to do that manually. -## Install PHDF5 +#### Install PHDF5 ``` wget https://support.hdfgroup.org/ftp/HDF5/releases/hdf5-1.12/hdf5-1.12.0/src/hdf5-1.12.0.tar.gz tar -xzf hdf5-1.12.0.tar.gz diff --git a/jf-live-writer/include/JFH5Writer.hpp b/jf-live-writer/include/JFH5Writer.hpp index ee71200..f62306e 100644 --- a/jf-live-writer/include/JFH5Writer.hpp +++ b/jf-live-writer/include/JFH5Writer.hpp @@ -1,5 +1,5 @@ -#ifndef SFWRITER_HPP -#define SFWRITER_HPP +#ifndef JF_LIVE_WRITER_HPP +#define JF_LIVE_WRITER_HPP #include #include @@ -32,28 +32,28 @@ class JFH5Writer { hid_t daq_rec_dataset_id_ = -1; hid_t is_good_dataset_id_ = -1; - hid_t get_datatype(const int bits_per_pixel); - void open_file(const std::string& output_file, const uint32_t n_images); + static hid_t get_datatype(int bits_per_pixel); + void open_file(const std::string& output_file, uint32_t n_images); void close_file(); public: - JFH5Writer(const BufferUtils::DetectorConfig config); + explicit JFH5Writer(const BufferUtils::DetectorConfig& config); ~JFH5Writer(); - void open_run(const int64_t run_id, - const uint32_t n_images, - const uint32_t image_y_size, - const uint32_t image_x_size, - const uint32_t bits_per_pixel); + void open_run(int64_t run_id, + uint32_t n_images, + uint32_t image_y_size, + uint32_t image_x_size, + uint32_t bits_per_pixel); void close_run(); - void write_data(const int64_t run_id, - const uint32_t index, + void write_data(int64_t run_id, + uint32_t index, const char* data); - void write_meta(const int64_t run_id, - const uint32_t index, + void write_meta(int64_t run_id, + uint32_t index, const ImageMetadata& meta); }; -#endif //SFWRITER_HPP +#endif //JF_LIVE_WRITER_HPP diff --git a/jf-live-writer/include/WriterStats.hpp b/jf-live-writer/include/WriterStats.hpp index 4d70e25..ba48a18 100644 --- a/jf-live-writer/include/WriterStats.hpp +++ b/jf-live-writer/include/WriterStats.hpp @@ -9,25 +9,23 @@ class WriterStats { const std::string detector_name_; - const size_t stats_modulo_; - uint32_t image_n_bytes_; + uint32_t image_n_bytes_{}; - int image_counter_; - uint64_t total_bytes_; + int image_counter_{}; + uint64_t total_bytes_{}; - uint32_t total_buffer_write_us_; - uint32_t max_buffer_write_us_; + uint32_t total_buffer_write_us_{}; + uint32_t max_buffer_write_us_{}; std::chrono::time_point stats_interval_start_; void reset_counters(); void print_stats(); public: - WriterStats( - const std::string &detector_name, - const size_t stats_modulo); - void setup_run(const StoreStream& meta); + explicit WriterStats(std::string detector_name); + void start_run(const StoreStream& meta); + void end_run(); void start_image_write(); void end_image_write(); }; diff --git a/jf-live-writer/include/broker_format.hpp b/jf-live-writer/include/broker_format.hpp index ecdca44..dd45175 100644 --- a/jf-live-writer/include/broker_format.hpp +++ b/jf-live-writer/include/broker_format.hpp @@ -3,21 +3,18 @@ #include "formats.hpp" -const static uint8_t OP_START = 1; -const static uint8_t OP_END = 2; #pragma pack(push) #pragma pack(1) struct StoreStream { + ImageMetadata image_metadata; + int64_t run_id; uint32_t i_image; uint32_t n_images; uint32_t image_y_size; uint32_t image_x_size; - uint32_t op_code; uint32_t bits_per_pixel; - - ImageMetadata image_metadata; }; #pragma pack(pop) #endif //SF_DAQ_BUFFER_BROKER_FORMAT_HPP diff --git a/jf-live-writer/src/JFH5Writer.cpp b/jf-live-writer/src/JFH5Writer.cpp index 08ec2a9..7034bd7 100644 --- a/jf-live-writer/src/JFH5Writer.cpp +++ b/jf-live-writer/src/JFH5Writer.cpp @@ -1,11 +1,7 @@ -#include "JFH5Writer.hpp" - -#include -#include #include #include - +#include "JFH5Writer.hpp" #include "live_writer_config.hpp" #include "buffer_config.hpp" #include "formats.hpp" @@ -19,7 +15,7 @@ using namespace std; using namespace buffer_config; using namespace live_writer_config; -JFH5Writer::JFH5Writer(const BufferUtils::DetectorConfig config): +JFH5Writer::JFH5Writer(const BufferUtils::DetectorConfig& config): root_folder_(config.buffer_folder), detector_name_(config.detector_name) { @@ -140,14 +136,14 @@ void JFH5Writer::open_file(const string& output_file, const uint32_t n_images) } hsize_t image_dataset_dims[] = {n_images, image_y_size_, image_x_size_}; - auto image_space_id = H5Screate_simple(3, image_dataset_dims, NULL); + auto image_space_id = H5Screate_simple(3, image_dataset_dims, nullptr); if (image_space_id < 0) { throw runtime_error("Cannot create image dataset space."); } // TODO: Enable compression. // bshuf_register_h5filter(); -// uint filter_prop[] = {PIXEL_N_BYTES, BSHUF_H5_COMPRESS_LZ4}; +// uint filter_prop[] = {0, BSHUF_H5_COMPRESS_LZ4}; // if (H5Pset_filter(dcpl_id, BSHUF_H5FILTER, H5Z_FLAG_MANDATORY, // 2, filter_prop) < 0) { // throw runtime_error("Cannot set compression filter on dataset."); @@ -162,12 +158,12 @@ void JFH5Writer::open_file(const string& output_file, const uint32_t n_images) // Create metadata datasets. hsize_t meta_dataset_dims[] = {n_images}; - auto meta_space_id = H5Screate_simple(1, meta_dataset_dims, NULL); + auto meta_space_id = H5Screate_simple(1, meta_dataset_dims, nullptr); if (meta_space_id < 0) { throw runtime_error("Cannot create meta dataset space."); } - auto create_meta_dataset = [&](string name, hid_t data_type) { + auto create_meta_dataset = [&](const string& name, hid_t data_type) { auto dataset_id = H5Dcreate( data_group_id, name.c_str(), data_type, meta_space_id, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); diff --git a/jf-live-writer/src/WriterStats.cpp b/jf-live-writer/src/WriterStats.cpp index 9a9a22b..87f2800 100644 --- a/jf-live-writer/src/WriterStats.cpp +++ b/jf-live-writer/src/WriterStats.cpp @@ -1,14 +1,12 @@ #include +#include #include "WriterStats.hpp" using namespace std; using namespace chrono; -WriterStats::WriterStats( - const string& detector_name, - const size_t stats_modulo) : - detector_name_(detector_name), - stats_modulo_(stats_modulo) +WriterStats::WriterStats(string detector_name) : + detector_name_(std::move(detector_name)) { reset_counters(); } @@ -26,13 +24,6 @@ void WriterStats::start_image_write() stats_interval_start_ = steady_clock::now(); } -void WriterStats::setup_run(const StoreStream& meta) -{ - image_n_bytes_ = (meta.image_y_size * - meta.image_x_size * - meta.bits_per_pixel) / 8; -} - void WriterStats::end_image_write() { image_counter_++; @@ -43,11 +34,19 @@ void WriterStats::end_image_write() total_buffer_write_us_ += write_us_duration; max_buffer_write_us_ = max(max_buffer_write_us_, write_us_duration); +} - if (image_counter_ == stats_modulo_) { - print_stats(); - reset_counters(); - } +void WriterStats::start_run(const StoreStream& meta) +{ + image_n_bytes_ = (meta.image_y_size * + meta.image_x_size * + meta.bits_per_pixel) / 8; +} + +void WriterStats::end_run() +{ + print_stats(); + reset_counters(); } void WriterStats::print_stats() diff --git a/jf-live-writer/src/main.cpp b/jf-live-writer/src/main.cpp index 6a57521..179e3aa 100644 --- a/jf-live-writer/src/main.cpp +++ b/jf-live-writer/src/main.cpp @@ -1,14 +1,14 @@ #include #include #include -#include -#include +#include + +#include "RamBuffer.hpp" +#include "BufferUtils.hpp" #include "live_writer_config.hpp" #include "WriterStats.hpp" #include "broker_format.hpp" -#include -#include - +#include "JFH5Writer.hpp" using namespace std; using namespace buffer_config; @@ -27,7 +27,7 @@ int main (int argc, char *argv[]) auto const config = BufferUtils::read_json_config(string(argv[1])); - MPI_Init(NULL, NULL); + MPI_Init(nullptr, nullptr); int n_writers; MPI_Comm_size(MPI_COMM_WORLD, &n_writers); @@ -43,27 +43,21 @@ int main (int argc, char *argv[]) RamBuffer ram_buffer(config.detector_name, config.n_modules); JFH5Writer writer(config); - WriterStats stats(config.detector_name, STATS_MODULO); + WriterStats stats(config.detector_name); StoreStream meta = {}; while (true) { zmq_recv(receiver, &meta, sizeof(meta), 0); - if (meta.op_code == OP_START) { + // i_image == 0 -> we have a new run. + if (meta.i_image == 0) { writer.open_run(meta.run_id, meta.n_images, meta.image_y_size, meta.image_x_size, meta.bits_per_pixel); - stats.setup_run(meta); - - continue; - } - - if (meta.op_code == OP_END) { - writer.close_run(); - continue; + stats.start_run(meta); } // Fair distribution of images among writers. @@ -79,7 +73,12 @@ int main (int argc, char *argv[]) if (i_writer == 0) { writer.write_meta(meta.run_id, meta.i_image, meta.image_metadata); } - } - MPI_Finalize(); + // i_image + 1 == meta.n_images -> we received the last image. + if (meta.i_image+1 == meta.n_images) { + writer.close_run(); + + stats.end_run(); + } + } }