Merge branch 'developer' into dev/fix_tests
@@ -67,6 +67,7 @@ set(SPHINX_SOURCE_FILES
|
||||
src/binaryfileformat.rst
|
||||
src/hdf5fileformat.rst
|
||||
src/zmqjsonheaderformat.rst
|
||||
src/dataformat.rst
|
||||
)
|
||||
|
||||
foreach(filename ${SPHINX_SOURCE_FILES})
|
||||
@@ -84,11 +85,16 @@ configure_file(
|
||||
"${SPHINX_BUILD}/gen_server_doc.py"
|
||||
@ONLY)
|
||||
|
||||
configure_file(
|
||||
configure_file(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/static/extra.css"
|
||||
"${SPHINX_BUILD}/static/css/extra.css"
|
||||
@ONLY)
|
||||
|
||||
|
||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/src/images
|
||||
DESTINATION ${SPHINX_BUILD}/src)
|
||||
|
||||
|
||||
add_custom_target(server_rst python gen_server_doc.py)
|
||||
|
||||
add_custom_target(docs
|
||||
|
||||
328
docs/src/dataformat.rst
Normal file
@@ -0,0 +1,328 @@
|
||||
Data Format
|
||||
================================
|
||||
|
||||
Each UDP port creates its own output file, which contains the data of the image transmitted over that port. More on number of files and naming for each file in the `File format <fileformat.html>`_ section.
|
||||
|
||||
Jungfrau
|
||||
-------------
|
||||
|
||||
Single Port Configuration
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. image:: images/Jungfrau_module.png
|
||||
:target: _images/Jungfrau_module.png
|
||||
:width: 650px
|
||||
:align: center
|
||||
:alt: Jungfrau Module Single Port Configuration
|
||||
|
||||
By default, only the outer 10GbE interface is enabled, transmitting the full image over a single UDP port. This results in one file per module containing the complete image.
|
||||
|
||||
Total image size = 524,288 bytes
|
||||
- 8 chips (2 x 4 grid)
|
||||
- 256 x 256 pixels (chip size)
|
||||
- 2 bytes (pixel width)
|
||||
|
||||
|
||||
|
||||
Double Port Configuration
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. image:: images/Jungfrau_two_port.png
|
||||
:target: _images/Jungfrau_two_port.png
|
||||
:width: 500px
|
||||
:align: center
|
||||
:alt: Jungfrau Module Two Port Configuration
|
||||
|
||||
If both interfaces are enabled using the `numinterfaces <commandline.html#term-numinterfaces-1-2>`_ command on compatible hardware and firmware, the image splits into top and bottom halves sent over two UDP ports:
|
||||
|
||||
- The top half transmits via the inner interface (`udp_dstport2 <commandline.html#term-udp_dstport2-n>`_ and `udp_dstip2 <commandline.html#term-udp_dstip2-x.x.x.x-or-auto>`_).
|
||||
|
||||
- The bottom half uses the outer interface(`udp_dstport <commandline.html#term-udp_dstport-n>`_ and `udp_dstip <commandline.html#term-udp_dstip-x.x.x.x-or-auto>`_).
|
||||
|
||||
The number of files per module equals the active UDP ports—two files per module when both interfaces are used.
|
||||
|
||||
Image size per UDP port or File = 262,144 bytes
|
||||
- **Complete Image size / 2**
|
||||
|
||||
|
||||
|
||||
Read Partial Rows
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. image:: images/Jungfrau_read_rows.png
|
||||
:target: _images/Jungfrau_read_rows.png
|
||||
:width: 550px
|
||||
:align: center
|
||||
:alt: Jungfrau Module Read Partial Rows Configuration
|
||||
|
||||
|
||||
The number of image rows per port can be adjusted using the `readnrows <commandline.html#term-readnrows>`_ command. By default, 512 rows are read, but a smaller value centers the readout vertically (e.g., 8 rows reads 4 above and 4 below the center). Increasing the value symmetrically expands the region toward the top and bottom. Permissible values are multiples of 8.
|
||||
|
||||
|
||||
Total image size = 32,768 bytes
|
||||
- 8 chips (2 x 4 grid)
|
||||
- **8** x 256 pixels (chip size: **8 rows**)
|
||||
- 2 bytes (pixel width)
|
||||
|
||||
Note: Still in prototype stage, writes complete image (padded or not depending on `rx_padding <commandline.html#term-rx_padding-0-1>`_ parameter) to file. Only the summary written to console in the receiver handles the `readnrows <commandline.html#term-readnrows>`_ to calculate to calculate complete images received. Only reduces network load, not file size. Use `rx_roi <commandline.html#term-rx_roi-xmin-xmax-ymin-ymax>`_ for file size.
|
||||
|
||||
Moench
|
||||
-------------
|
||||
|
||||
Single Port Configuration
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. image:: images/Moench_module.png
|
||||
:target: _images/Moench_module.png
|
||||
:width: 550px
|
||||
:align: center
|
||||
:alt: Moench Module Single Port Configuration
|
||||
|
||||
By default, only the outer 10GbE interface is enabled, transmitting the full image over a single UDP port. This results in one file per module containing the complete image.
|
||||
|
||||
Total image size = 320,000 bytes
|
||||
- 400 x 400 pixels (chip size)
|
||||
- 2 bytes (pixel width)
|
||||
|
||||
|
||||
|
||||
Double Port Configuration
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. image:: images/Moench_two_port.png
|
||||
:target: _images/Moench_two_port.png
|
||||
:width: 400px
|
||||
:align: center
|
||||
:alt: Moench Module Two Port Configuration
|
||||
|
||||
If both interfaces are enabled using the `numinterfaces <commandline.html#term-numinterfaces-1-2>`_ command on compatible hardware and firmware, the image splits into top and bottom halves sent over two UDP ports:
|
||||
|
||||
- The top half transmits via the inner interface (`udp_dstport2 <commandline.html#term-udp_dstport2-n>`_ and `udp_dstip2 <commandline.html#term-udp_dstip2-x.x.x.x-or-auto>`_).
|
||||
|
||||
- The bottom half uses the outer interface(`udp_dstport <commandline.html#term-udp_dstport-n>`_ and `udp_dstip <commandline.html#term-udp_dstip-x.x.x.x-or-auto>`_).
|
||||
|
||||
The number of files per module equals the active UDP ports—two files per module when both interfaces are used.
|
||||
|
||||
Image size per UDP port or File = 160,000 bytes
|
||||
- **Complete Image size / 2**
|
||||
|
||||
|
||||
|
||||
Read Partial Rows
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. image:: images/Moench_read_rows.png
|
||||
:target: _images/Moench_read_rows.png
|
||||
:width: 400px
|
||||
:align: center
|
||||
:alt: Moench Module Read Partial Rows Configuration
|
||||
|
||||
|
||||
The number of image rows per port can be adjusted using the `readnrows <commandline.html#term-readnrows>`_ command. By default, 400 rows are read, but a smaller value centers the readout vertically (e.g., 16 rows reads 8 above and 8 below the center). Increasing the value symmetrically expands the region toward the top and bottom. Permissible values are multiples of 16.
|
||||
|
||||
|
||||
Total image size = 12,800 bytes
|
||||
- **16** x 400 pixels (chip size: **16 rows**)
|
||||
- 2 bytes (pixel width)
|
||||
|
||||
Note: Still in prototype stage, writes complete image (padded or not depending on `rx_padding <commandline.html#term-rx_padding-0-1>`_ parameter) to file. Only the summary written to console in the receiver handles the read n rows to calculate complete images received. Only reduces network load, not file size. Use `rx_roi <commandline.html#term-rx_roi-xmin-xmax-ymin-ymax>`_ for file size.
|
||||
|
||||
Eiger
|
||||
-------------
|
||||
|
||||
Default Configuration
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. image:: images/Eiger_module.png
|
||||
:target: _images/Eiger_module.png
|
||||
:width: 350px
|
||||
:align: center
|
||||
:alt: Eiger Module Default Configuration
|
||||
|
||||
|
||||
Each Eiger module has two independent identical readout systems (other than firmware), each with its own control port and `hostname <commandline.html#term-hostname>`_ to be configured with. They are referred to as the 'top' and 'bottom' half modules. The bottom half module is flipped vertically.
|
||||
|
||||
Each half module has 2 parallel UDP ports for 2 chips each. The left UDP port is configured with `udp_dstport <commandline.html#term-udp_dstport-n>`_, while the right UDP port is configured with `udp_dstport2 <commandline.html#term-udp_dstport2-n>`_. This is vice versa for the bottom half module.
|
||||
|
||||
|
||||
Image size per UDP port or File = 262,144 bytes
|
||||
- 2 chips (1 x 2 grid)
|
||||
- 256 x 256 pixels (chip size)
|
||||
- 2 bytes (default pixel width)
|
||||
|
||||
|
||||
The myth, the legend, the bottom ports: Demystifying them
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
.. figure:: images/Eiger_bottom_1.png
|
||||
:target: _images/Eiger_bottom_1.png
|
||||
:width: 350px
|
||||
:align: center
|
||||
:alt: Eiger Bottom as Firmware gets it
|
||||
|
||||
How the firmware gets the images
|
||||
|
||||
|
||||
.. figure:: images/Eiger_bottom_2.png
|
||||
:target: _images/Eiger_bottom_2.png
|
||||
:width: 350px
|
||||
:align: center
|
||||
:alt: Eiger Bottom after the firmware flips it horizontally
|
||||
|
||||
After the firmware flips it horizontally
|
||||
|
||||
|
||||
.. figure:: images/Eiger_bottom_3.png
|
||||
:target: _images/Eiger_bottom_3.png
|
||||
:width: 350px
|
||||
:align: center
|
||||
:alt: After the software swaps the udp ports
|
||||
|
||||
After the software swaps the udp ports
|
||||
|
||||
|
||||
.. figure:: images/Eiger_bottom_4.png
|
||||
:target: _images/Eiger_bottom_4.png
|
||||
:width: 400px
|
||||
:align: center
|
||||
:alt: After the gui has flipped the bottom vertically
|
||||
|
||||
After the gui has flipped the bottom vertically
|
||||
|
||||
|
||||
Note: The same process happens for the bottom 2 udp ports of the quad.
|
||||
|
||||
|
||||
Pixel width
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
The pixel width can be configured to 4, 8, 16 (default) or 32 bits using the command `dr <commandline.html#term-dr-value>`_. This affects image size per UDP port or file.
|
||||
|
||||
Flip rows
|
||||
^^^^^^^^^^
|
||||
|
||||
One can use the command `fliprows <commandline.html#term-fliprows-0-1>`_ to flip the rows vertically for the bottom or top half module. It is sent out to the reciever, but does not flip rows in the output file itself, but rather streams out this info via the json header and thus instructs the GUI to display them correctly.
|
||||
|
||||
1GbE/ 10GbE Interfaces
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Both UDP ports `udp_dstport <commandline.html#term-udp_dstport-n>`_ and `udp_dstport2 <commandline.html#term-udp_dstport2-n>`_ are used in Eiger as shows in the figure. Both of them can be set to use either the 1GbE or the 10GbE interface for data. The 1GbE interface is used also for control and configuration. For data, the 1GbE interface is enabled by default. It can be disabled by enabling the `tengiga <commandline.html#term-tengiga-0-1>`_ command and updating both the `udp_dstport <commandline.html#term-udp_dstport-n>`_ , `udp_dstport2 <commandline.html#term-udp_dstport2-n>`_ , `udp_dstip <commandline.html#term-udp_dstip-x.x.x.x-or-auto>`_ commands to match the 1GbE or 10GbE interface. This setting only affects packetsize and number of packets, but does not affect the total image size.
|
||||
|
||||
Reducing network load
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
**Activate**: By default, the `hostname <commandline.html#term-hostname>`_ command activates the respective half module it connects to, enabling all UDP ports. To deactivate an entire half module (i.e., both UDP ports), use the `activate <commandline.html#term-activate-0-1>`_ command. This disables both UDP ports for that half module, so no data is transmitted from it — only half of the module is read out. To specify which half module to activate or deactivate, use the module index (for Eiger, each half module has its own module index).
|
||||
|
||||
|
||||
**Datastream**: The `datastream <commandline.html#term-datastream-left-right-0-1>`_ command with arguments to specify which port can be used to disable the data streaming from one or both the two UDP ports in a half module. This allows for more flexible configurations, such as reading only two chips or one UDP port of a half module. To specify which half module to activate or deactivate, use the module index (for Eiger, each half module has its own module index).
|
||||
|
||||
Note: Only the activated ports will write data as it does not make sense to write an empty file.
|
||||
|
||||
**Read Partial Rows**: The number of image rows per port can be adjusted using the `readnrows <commandline.html#term-readnrows>`_ command. By default, 256 rows are read, but a smaller value centers the readout vertically (e.g., 8 rows reads 4 above and 4 below the center). Increasing the value symmetrically expands the region toward the top and bottom. Permissible values depend on dynamic range and 10GbE enable.
|
||||
|
||||
.. image:: images/Eiger_read_rows.png
|
||||
:target: _images/Eiger_read_rows.png
|
||||
:width: 400px
|
||||
:align: center
|
||||
:alt: Eiger Module Read Partial Rows Configuration
|
||||
|
||||
|
||||
|
||||
Total image size per UDP Port = 8,192 bytes
|
||||
- 2 chips (1 x 2 grid)
|
||||
- **8** x 256 pixels (chip size: **8 rows**)
|
||||
- 2 bytes (default pixel width)
|
||||
|
||||
Note: Still in prototype stage, writes complete image (padded or not depending on `rx_padding <commandline.html#term-rx_padding-0-1>`_ parameter) to file. Only the summary written to console in the receiver handles the `readnrows <commandline.html#term-readnrows>`_ to calculate complete images received. Only reduces network load, not file size. Use `rx_roi <commandline.html#term-rx_roi-xmin-xmax-ymin-ymax>`_ for file size.
|
||||
|
||||
Quad
|
||||
^^^^^^
|
||||
|
||||
The Eiger quad is a special hardware configuration that uses only the top half-module to create a quad layout. In this setup, the second half of the top module—normally associated with the right-side UDP port—is used to represent the inverted bottom half of the quad.
|
||||
|
||||
As with any standard half-module, it includes one control TCP port (with a hostname) and two UDP data ports (top and bottom). When the quad option is enabled, the firmware automatically flips the second UDP port vertically.
|
||||
|
||||
In this configuration, the fliprows command cannot be used to flip the entire half-module. Instead, the receiver automatically includes row-flipping information only for the second UDP port in the JSON header, so the GUI can apply the correct orientation during display
|
||||
|
||||
.. image:: images/Eiger_quad.png
|
||||
:target: _images/Eiger_quad.png
|
||||
:width: 300px
|
||||
:align: center
|
||||
:alt: Eiger Quad Configuration
|
||||
|
||||
Image size per UDP port = same as a normal Eiger UDP port
|
||||
|
||||
Mythen3
|
||||
-------------
|
||||
|
||||
|
||||
|
||||
Default Configuration
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. image:: images/Mythen3_module.png
|
||||
:target: _images/Mythen3_module.png
|
||||
:align: center
|
||||
:alt: Mythen3 Module Default Configuration
|
||||
|
||||
Each Mythen3 module is a 1D detector with 10 chips, each with 128 channels. Each channel has 3 counters that are enabled by default.
|
||||
|
||||
Image size = 15,360 bytes
|
||||
- 10 chips (1 x 10 grid)
|
||||
- 128 channels
|
||||
- 3 counters
|
||||
- 4 bytes (default pixel width)
|
||||
|
||||
Counters
|
||||
^^^^^^^^^^^
|
||||
|
||||
If all 3 counters are enabeld, the frame size for each channel is multiplied by 3. The counters are stored consecutively per channel. One can disable one or more of the counters using the `counters <commandline.html#term-counters-i0-i1-i2-...>`_ command. The frame size will then be reduced accordingly.
|
||||
|
||||
Image size = 10,240 bytes
|
||||
- 10 chips (1 x 10 grid)
|
||||
- 128 channels
|
||||
- 2 counters (0, 1 enabled)
|
||||
- 4 bytes (default pixel width)
|
||||
|
||||
Pixel width
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
The pixel width can be configured to 8, 16 or 32 (default) bits using the command `dr <commandline.html#term-dr-value>`_. 32 bits is actually 24 bits in the chip. This setting does affect image size.
|
||||
|
||||
1GbE/ 10GbE Interfaces
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
The UDP port can be set to use either the 1GbE or the 10GbE interface for data. The 1GbE interface is used also for control and configuration. For data, the 10GbE interface is enabled by default. It can be disabled by using the `tengiga <commandline.html#term-tengiga-0-1>`_ command and updating the `udp_dstport <commandline.html#term-udp_dstport-n>`_ and `udp_dstip <commandline.html#term-udp_dstip-x.x.x.x-or-auto>`_ commands to match the 1GbE or 10GbE interface. This setting only affects packetsize and number of packets, but does not affect the total image size.
|
||||
|
||||
|
||||
Gotthard2
|
||||
-------------
|
||||
|
||||
|
||||
Default Configuration
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. image:: images/Gotthard2_module.png
|
||||
:target: _images/Gotthard2_module.png
|
||||
:align: center
|
||||
:alt: Gotthard2 Module Default Configuration
|
||||
|
||||
Each Gotthard2 module is a 1D detector with 10 chips, each with 128 channels.
|
||||
|
||||
Image size = 2,560 bytes
|
||||
- 10 chips (1 x 10 grid)
|
||||
- 128 channels
|
||||
- 2 bytes (pixel width)
|
||||
|
||||
Veto Info
|
||||
^^^^^^^^^^^
|
||||
|
||||
One can enable veto data in the chip of the Gotthard2 module using the `veto <commandline.html#term-veto-0-1>`_ command. By default, this is disabled. This does not affect the image size as veto information is not sent out through the same 10GbE interface.
|
||||
|
||||
One can either stream out the veto info through the low latency link (2.5 gbps) or for debugging purposes through another 10GbE interface.
|
||||
|
||||
For debugging purposes, the veto info can be enabled using the `numinterfaces <commandline.html#term-numinterfaces-1-2>`_ command and the following parameters are updated: `udp_dstport2 <commandline.html#term-udp_dstport2-n>`_ and `udp_dstip2 <commandline.html#term-udp_dstip2-x.x.x.x-or-auto>`_. The veto data from this port is of course written to a separate file and is not combined in the virtual HDF5 file mapping (complete image mapped).
|
||||
|
||||
|
||||
BIN
docs/src/images/Eiger_bottom_1.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
docs/src/images/Eiger_bottom_2.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
docs/src/images/Eiger_bottom_3.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
docs/src/images/Eiger_bottom_4.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
docs/src/images/Eiger_module.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
docs/src/images/Eiger_quad.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
docs/src/images/Eiger_read_rows.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
docs/src/images/Gotthard2_module.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
docs/src/images/Jungfrau_module.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
docs/src/images/Jungfrau_read_rows.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
docs/src/images/Jungfrau_two_port.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
docs/src/images/Moench_module.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
docs/src/images/Moench_read_rows.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
docs/src/images/Moench_two_port.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
docs/src/images/Mythen3_module.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
@@ -95,6 +95,7 @@ Welcome to slsDetectorPackage's documentation!
|
||||
:caption: Receiver Files
|
||||
:maxdepth: 3
|
||||
|
||||
dataformat
|
||||
fileformat
|
||||
slsreceiverheaderformat
|
||||
masterfileattributes
|
||||
|
||||
@@ -56,6 +56,7 @@ class CtbConfig {
|
||||
std::string getSlowADCName(size_t index) const;
|
||||
std::vector<std::string> getSlowADCNames() const;
|
||||
static const char *shm_tag();
|
||||
bool isValid{true}; // false if freed to block access from python or c++ api
|
||||
};
|
||||
|
||||
} // namespace sls
|
||||
|
||||
@@ -29,6 +29,8 @@ void freeSharedMemory(const int detectorIndex, const int moduleIndex) {
|
||||
if (moduleIndex >= 0) {
|
||||
SharedMemory<sharedModule> moduleShm(detectorIndex, moduleIndex);
|
||||
if (moduleShm.exists()) {
|
||||
moduleShm.openSharedMemory(false);
|
||||
moduleShm()->isValid = false;
|
||||
moduleShm.removeSharedMemory();
|
||||
}
|
||||
return;
|
||||
@@ -41,18 +43,26 @@ void freeSharedMemory(const int detectorIndex, const int moduleIndex) {
|
||||
if (detectorShm.exists()) {
|
||||
detectorShm.openSharedMemory(false);
|
||||
numDetectors = detectorShm()->totalNumberOfModules;
|
||||
detectorShm()->isValid = false;
|
||||
detectorShm.removeSharedMemory();
|
||||
}
|
||||
|
||||
for (int i = 0; i < numDetectors; ++i) {
|
||||
SharedMemory<sharedModule> moduleShm(detectorIndex, i);
|
||||
if (moduleShm.exists()) {
|
||||
moduleShm.openSharedMemory(false);
|
||||
moduleShm()->isValid = false;
|
||||
}
|
||||
moduleShm.removeSharedMemory();
|
||||
}
|
||||
|
||||
// Ctb configuration
|
||||
SharedMemory<CtbConfig> ctbShm(detectorIndex, -1, CtbConfig::shm_tag());
|
||||
if (ctbShm.exists())
|
||||
if (ctbShm.exists()) {
|
||||
ctbShm.openSharedMemory(false);
|
||||
ctbShm()->isValid = false;
|
||||
ctbShm.removeSharedMemory();
|
||||
}
|
||||
}
|
||||
|
||||
using defs = slsDetectorDefs;
|
||||
|
||||
@@ -24,7 +24,7 @@ class detectorData;
|
||||
class Module;
|
||||
|
||||
#define DETECTOR_SHMAPIVERSION 0x190809
|
||||
#define DETECTOR_SHMVERSION 0x250616
|
||||
#define DETECTOR_SHMVERSION 0x250729
|
||||
#define SHORT_STRING_LENGTH 50
|
||||
|
||||
/**
|
||||
@@ -51,6 +51,8 @@ struct sharedDetector {
|
||||
int totalNumberOfModules;
|
||||
slsDetectorDefs::detectorType detType;
|
||||
|
||||
bool isValid{true}; // false if freed to block access from python or c++ api
|
||||
|
||||
/** END OF FIXED PATTERN
|
||||
* -----------------------------------------------*/
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace sls {
|
||||
class ServerInterface;
|
||||
|
||||
#define MODULE_SHMAPIVERSION 0x190726
|
||||
#define MODULE_SHMVERSION 0x230913
|
||||
#define MODULE_SHMVERSION 0x250729
|
||||
|
||||
/**
|
||||
* @short structure allocated in shared memory to store Module settings for
|
||||
@@ -32,6 +32,7 @@ struct sharedModule {
|
||||
int shmversion;
|
||||
char hostname[MAX_STR_LENGTH];
|
||||
slsDetectorDefs::detectorType detType;
|
||||
bool isValid{true}; // false if freed to block access from python or c++ api
|
||||
|
||||
/** END OF FIXED PATTERN -----------------------------------------------*/
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*@short functions basic implemenation of shared memory
|
||||
*/
|
||||
|
||||
#include "sls/TypeTraits.h"
|
||||
#include "sls/logger.h"
|
||||
#include "sls/sls_detector_exceptions.h"
|
||||
|
||||
@@ -26,11 +27,18 @@
|
||||
|
||||
namespace sls {
|
||||
|
||||
struct sharedDetector;
|
||||
|
||||
#define SHM_DETECTOR_PREFIX "/slsDetectorPackage_detector_"
|
||||
#define SHM_MODULE_PREFIX "_module_"
|
||||
#define SHM_ENV_NAME "SLSDETNAME"
|
||||
|
||||
template <typename T> class SharedMemory {
|
||||
|
||||
static_assert(has_bool_isValid<T>::value,
|
||||
"SharedMemory requires the struct to have a bool member "
|
||||
"named 'isValid'");
|
||||
|
||||
static constexpr int NAME_MAX_LENGTH = 255;
|
||||
std::string name;
|
||||
T *shared_struct{nullptr};
|
||||
@@ -65,15 +73,21 @@ template <typename T> class SharedMemory {
|
||||
}
|
||||
|
||||
T *operator()() {
|
||||
if (shared_struct)
|
||||
return shared_struct;
|
||||
throw SharedMemoryError(getNoShmAccessMessage());
|
||||
if (!shared_struct)
|
||||
throw SharedMemoryError(getNoShmAccessMessage());
|
||||
if (!shared_struct->isValid) {
|
||||
throw SharedMemoryError(getInvalidShmMessage());
|
||||
}
|
||||
return shared_struct;
|
||||
}
|
||||
|
||||
const T *operator()() const {
|
||||
if (shared_struct)
|
||||
return shared_struct;
|
||||
throw SharedMemoryError(getNoShmAccessMessage());
|
||||
if (!shared_struct)
|
||||
throw SharedMemoryError(getNoShmAccessMessage());
|
||||
if (!shared_struct->isValid) {
|
||||
throw SharedMemoryError(getInvalidShmMessage());
|
||||
}
|
||||
return shared_struct;
|
||||
}
|
||||
|
||||
std::string getName() const { return name; }
|
||||
@@ -215,10 +229,15 @@ template <typename T> class SharedMemory {
|
||||
}
|
||||
}
|
||||
|
||||
const char *getNoShmAccessMessage() const {
|
||||
inline const char *getNoShmAccessMessage() const {
|
||||
return ("No shared memory to access. Create it first with "
|
||||
"hostname or config command.");
|
||||
};
|
||||
|
||||
inline const char *getInvalidShmMessage() const {
|
||||
return ("Shared memory is invalid or freed. Close resources before "
|
||||
"access.");
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace sls
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
namespace sls {
|
||||
|
||||
TEST_CASE("Default construction") {
|
||||
static_assert(sizeof(CtbConfig) == ((18 + 32 + 64 + 5 + 8) * 20),
|
||||
static_assert(sizeof(CtbConfig) == ((18 + 32 + 64 + 5 + 8) * 20 + 1),
|
||||
"Size of CtbConfig does not match");
|
||||
|
||||
CtbConfig c;
|
||||
|
||||
@@ -5,27 +5,35 @@
|
||||
#include "catch.hpp"
|
||||
#include "sls/string_utils.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace sls {
|
||||
|
||||
struct Data {
|
||||
int x;
|
||||
double y;
|
||||
char mess[50];
|
||||
bool isValid{true};
|
||||
};
|
||||
|
||||
void freeShm(const int dindex, const int mIndex) {
|
||||
SharedMemory<Data> shm(dindex, mIndex);
|
||||
if (shm.exists()) {
|
||||
shm.openSharedMemory(false);
|
||||
shm()->isValid = false;
|
||||
shm.removeSharedMemory();
|
||||
}
|
||||
}
|
||||
|
||||
constexpr int shm_id = 10;
|
||||
|
||||
TEST_CASE("Create SharedMemory read and write", "[detector]") {
|
||||
const char *env_p = std::getenv("SLSDETNAME");
|
||||
std::string env_name = env_p ? ("_" + std::string(env_p)) : "";
|
||||
|
||||
SharedMemory<Data> shm(shm_id, -1);
|
||||
if (shm.exists()) {
|
||||
shm.removeSharedMemory();
|
||||
}
|
||||
shm.createSharedMemory();
|
||||
|
||||
const char *env_p = std::getenv("SLSDETNAME");
|
||||
std::string env_name = env_p ? ("_" + std::string(env_p)) : "";
|
||||
CHECK(shm.getName() == std::string("/slsDetectorPackage_detector_") +
|
||||
std::to_string(shm_id) + env_name);
|
||||
|
||||
@@ -44,16 +52,19 @@ TEST_CASE("Create SharedMemory read and write", "[detector]") {
|
||||
}
|
||||
|
||||
TEST_CASE("Open existing SharedMemory and read", "[detector]") {
|
||||
|
||||
{
|
||||
SharedMemory<double> shm(shm_id, -1);
|
||||
SharedMemory<Data> shm(shm_id, -1);
|
||||
if (shm.exists()) {
|
||||
shm.removeSharedMemory();
|
||||
}
|
||||
shm.createSharedMemory();
|
||||
*shm() = 5.3;
|
||||
shm()->x = 3;
|
||||
shm()->y = 5.9;
|
||||
}
|
||||
|
||||
SharedMemory<double> shm2(shm_id, -1);
|
||||
SharedMemory<Data> shm2(shm_id, -1);
|
||||
shm2.openSharedMemory(true);
|
||||
CHECK(*shm2() == 5.3);
|
||||
CHECK(shm2()->y == 5.9);
|
||||
|
||||
shm2.removeSharedMemory();
|
||||
}
|
||||
@@ -61,8 +72,8 @@ TEST_CASE("Open existing SharedMemory and read", "[detector]") {
|
||||
TEST_CASE("Creating a second shared memory with the same name throws",
|
||||
"[detector]") {
|
||||
|
||||
SharedMemory<double> shm0(shm_id, -1);
|
||||
SharedMemory<double> shm1(shm_id, -1);
|
||||
SharedMemory<Data> shm0(shm_id, -1);
|
||||
SharedMemory<Data> shm1(shm_id, -1);
|
||||
|
||||
shm0.createSharedMemory();
|
||||
CHECK_THROWS(shm1.createSharedMemory());
|
||||
@@ -120,19 +131,18 @@ TEST_CASE("Create several shared memories", "[detector]") {
|
||||
std::string env_name = env_p ? ("_" + std::string(env_p)) : "";
|
||||
|
||||
constexpr int N = 5;
|
||||
std::vector<SharedMemory<int>> v;
|
||||
std::vector<SharedMemory<Data>> v;
|
||||
v.reserve(N);
|
||||
for (int i = 0; i != N; ++i) {
|
||||
std::cout << "i:" << i << std::endl;
|
||||
v.emplace_back(shm_id + i, -1);
|
||||
CHECK(v[i].exists() == false);
|
||||
v[i].createSharedMemory();
|
||||
*v[i]() = i;
|
||||
CHECK(*v[i]() == i);
|
||||
v[i]()->x = i;
|
||||
CHECK(v[i]()->x == i);
|
||||
}
|
||||
|
||||
for (int i = 0; i != N; ++i) {
|
||||
CHECK(*v[i]() == i);
|
||||
CHECK(v[i]()->x == i);
|
||||
CHECK(v[i].getName() == std::string("/slsDetectorPackage_detector_") +
|
||||
std::to_string(i + shm_id) + env_name);
|
||||
}
|
||||
@@ -147,7 +157,7 @@ TEST_CASE("Create create a shared memory with a tag") {
|
||||
const char *env_p = std::getenv("SLSDETNAME");
|
||||
std::string env_name = env_p ? ("_" + std::string(env_p)) : "";
|
||||
|
||||
SharedMemory<int> shm(0, -1, "ctbdacs");
|
||||
SharedMemory<Data> shm(0, -1, "ctbdacs");
|
||||
REQUIRE(shm.getName() ==
|
||||
"/slsDetectorPackage_detector_0" + env_name + "_ctbdacs");
|
||||
}
|
||||
@@ -162,7 +172,7 @@ TEST_CASE("Create create a shared memory with a tag when SLSDETNAME is set") {
|
||||
unsetenv(SHM_ENV_NAME);
|
||||
setenv(SHM_ENV_NAME, "myprefix", 1);
|
||||
|
||||
SharedMemory<int> shm(0, -1, "ctbdacs");
|
||||
SharedMemory<Data> shm(0, -1, "ctbdacs");
|
||||
REQUIRE(shm.getName() == "/slsDetectorPackage_detector_0_myprefix_ctbdacs");
|
||||
|
||||
// Clean up after us
|
||||
@@ -172,15 +182,14 @@ TEST_CASE("Create create a shared memory with a tag when SLSDETNAME is set") {
|
||||
setenv(SHM_ENV_NAME, old_slsdetname.c_str(), 1);
|
||||
}
|
||||
|
||||
TEST_CASE("map int64 to int32 throws") {
|
||||
SharedMemory<int32_t> shm(shm_id, -1);
|
||||
TEST_CASE("Access to already freed shm object", "[detector]") {
|
||||
SharedMemory<Data> shm(shm_id, -1);
|
||||
shm.createSharedMemory();
|
||||
*shm() = 7;
|
||||
shm()->x = 10;
|
||||
|
||||
SharedMemory<int64_t> shm2(shm_id, -1);
|
||||
REQUIRE_THROWS(shm2.openSharedMemory(true));
|
||||
|
||||
shm.removeSharedMemory();
|
||||
freeShm(shm_id, -1);
|
||||
CHECK(shm.exists() == false);
|
||||
REQUIRE_THROWS(shm()); // trying to access should throw
|
||||
}
|
||||
|
||||
} // namespace sls
|
||||
|
||||
@@ -112,4 +112,12 @@ struct Conjunction<B1, Bn...>
|
||||
template <typename T, typename... Ts>
|
||||
using AllSame =
|
||||
typename std::enable_if<Conjunction<std::is_same<T, Ts>...>::value>::type;
|
||||
|
||||
// Trait to detect if T has a bool member named 'isValid'
|
||||
template <typename, typename = std::void_t<>>
|
||||
struct has_bool_isValid : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_bool_isValid<T, std::void_t<decltype(std::declval<T>().isValid)>>
|
||||
: std::is_same<decltype(std::declval<T>().isValid), bool> {};
|
||||
} // namespace sls
|
||||
@@ -63,3 +63,4 @@ configure_file(scripts/test_simulators.py ${CMAKE_BINARY_DIR}/bin/test_simulator
|
||||
configure_file(scripts/test_frame_synchronizer.py ${CMAKE_BINARY_DIR}/bin/test_frame_synchronizer.py COPYONLY)
|
||||
configure_file(scripts/utils_for_test.py ${CMAKE_BINARY_DIR}/bin/utils_for_test.py COPYONLY)
|
||||
configure_file(scripts/test_roi.py ${CMAKE_BINARY_DIR}/bin/test_roi.py COPYONLY)
|
||||
configure_file(scripts/test_free.py ${CMAKE_BINARY_DIR}/bin/test_free.py COPYONLY)
|
||||
|
||||
164
tests/scripts/test_free.py
Normal file
@@ -0,0 +1,164 @@
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-other
|
||||
# Copyright (C) 2021 Contributors to the SLS Detector Package
|
||||
'''
|
||||
This file is used to start up simulators and test for freeing shm and accessing it from python.
|
||||
Run this using: pytest -s test_free.py
|
||||
'''
|
||||
|
||||
import pytest, sys
|
||||
|
||||
from slsdet import Detector, Ctb, freeSharedMemory
|
||||
from utils_for_test import (
|
||||
Log,
|
||||
LogLevel,
|
||||
cleanup,
|
||||
startDetectorVirtualServer,
|
||||
connectToVirtualServers,
|
||||
SERVER_START_PORTNO
|
||||
)
|
||||
|
||||
'''
|
||||
scope = module =>Once per test file/module
|
||||
to share expensive setup like startDetectorVirtualServer
|
||||
'''
|
||||
@pytest.fixture(scope="module")
|
||||
def det_config():
|
||||
return {
|
||||
"name": "ctb",
|
||||
"num_mods": 1
|
||||
}
|
||||
|
||||
@pytest.fixture(scope="module", autouse=True)
|
||||
def setup_simulator(det_config):
|
||||
"""Fixture to start the detector server once and clean up at the end."""
|
||||
fp = sys.stdout
|
||||
|
||||
cleanup(fp)
|
||||
startDetectorVirtualServer(det_config["name"], det_config["num_mods"], fp)
|
||||
|
||||
Log(LogLevel.INFOBLUE, f'Waiting for server to start up and connect')
|
||||
connectToVirtualServers(det_config["name"], det_config["num_mods"])
|
||||
Log(LogLevel.INFOBLUE, f'Freeing shm before tests')
|
||||
freeSharedMemory()
|
||||
|
||||
yield # tests run here
|
||||
|
||||
cleanup(fp)
|
||||
|
||||
|
||||
|
||||
def test_exptime_after_free_should_raise(setup_simulator):
|
||||
Log(LogLevel.INFOBLUE, f'\nRunning test_exptime_after_free_should_raise')
|
||||
|
||||
|
||||
d = Ctb() # creates multi shm (assuming no shm exists)
|
||||
d.hostname = f"localhost:{SERVER_START_PORTNO}" # hostname command creates mod shm, d maps to it
|
||||
|
||||
d.free() # frees the shm, d should not map to it anymore
|
||||
|
||||
# accessing invalid shm should throw
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
_ = d.exptime
|
||||
|
||||
Log(LogLevel.INFOGREEN, f"✅ Test passed, exception was: {exc_info.value}")
|
||||
assert str(exc_info.value) == "Shared memory is invalid or freed. Close resources before access."
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def free_and_create_shm():
|
||||
k = Ctb() # opens existing shm if it exists
|
||||
k.hostname = f"localhost:{SERVER_START_PORTNO}" # free and recreate shm, maps to local shm struct
|
||||
|
||||
|
||||
def test_exptime_after_not_passing_var_should_raise(setup_simulator):
|
||||
Log(LogLevel.INFOBLUE, f'\nRunning test_exptime_after_not_passing_var_should_raise')
|
||||
|
||||
|
||||
d = Ctb() # creates multi shm (assuming no shm exists)
|
||||
d.hostname = f"localhost:{SERVER_START_PORTNO}" # hostname command creates mod shm, d maps to it
|
||||
|
||||
free_and_create_shm() # ctb() opens multi shm, hostname command frees and recreates mod shm but shm struct is local. d still maps to old shm struct
|
||||
|
||||
# accessing invalid shm should throw
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
_ = d.exptime
|
||||
|
||||
Log(LogLevel.INFOGREEN, f"✅ Test passed, exception was: {exc_info.value}")
|
||||
assert str(exc_info.value) == "Shared memory is invalid or freed. Close resources before access."
|
||||
|
||||
|
||||
|
||||
|
||||
def free_and_create_shm_passing_ctb_var(k):
|
||||
k = Ctb() # opens existing shm if it exists (disregards k as its new Ctb only local to this function)
|
||||
k.hostname = f"localhost:{SERVER_START_PORTNO}" # free and recreate shm, maps to local shm struct
|
||||
|
||||
|
||||
def test_exptime_after_passing_ctb_var_should_raise(setup_simulator):
|
||||
Log(LogLevel.INFOBLUE, f'\nRunning test_exptime_after_passing_ctb_var_should_raise')
|
||||
|
||||
d = Ctb() # creates multi shm (assuming no shm exists)
|
||||
d.hostname = f"localhost:{SERVER_START_PORTNO}" # hostname command creates mod shm, d maps to it
|
||||
|
||||
free_and_create_shm_passing_ctb_var(d) # ctb() opens multi shm, hostname command frees and recreates mod shm but shm struct is local. d still maps to old shm struct
|
||||
|
||||
# accessing invalid shm should throw
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
_ = d.exptime
|
||||
|
||||
Log(LogLevel.INFOGREEN, f"✅ Test passed, exception was: {exc_info.value}")
|
||||
assert str(exc_info.value) == "Shared memory is invalid or freed. Close resources before access."
|
||||
|
||||
|
||||
|
||||
def free_and_create_shm_returning_ctb():
|
||||
k = Ctb() # opens existing shm if it exists (disregards k as its new Ctb only local to this function)
|
||||
k.hostname = f"localhost:{SERVER_START_PORTNO}" # free and recreate shm, maps to local shm struct
|
||||
return k
|
||||
|
||||
|
||||
def test_exptime_after_returning_ctb_should_raise(setup_simulator):
|
||||
Log(LogLevel.INFOBLUE, f'\nRunning test_exptime_after_returning_ctb_should_raise')
|
||||
|
||||
d = Ctb() # creates multi shm (assuming no shm exists)
|
||||
|
||||
d = free_and_create_shm_returning_ctb() # ctb() opens multi shm, hostname command frees and recreates mod shm but shm struct is local but returned. d now maps to the new sturct
|
||||
|
||||
# this should not throw
|
||||
exptime_val = d.exptime
|
||||
|
||||
Log(LogLevel.INFOGREEN, f"✅ Test passed, exptime was: {exptime_val}")
|
||||
assert isinstance(exptime_val, float)
|
||||
|
||||
free_and_create_shm_returning_ctb() # this time d is not updated, it maps to the old shm struct
|
||||
|
||||
# accessing invalid shm should throw
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
_ = d.exptime
|
||||
|
||||
Log(LogLevel.INFOGREEN, f"✅ Test passed, exception was: {exc_info.value}")
|
||||
assert str(exc_info.value) == "Shared memory is invalid or freed. Close resources before access."
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def test_hostname_twice_acess_old_should_raise(setup_simulator):
|
||||
Log(LogLevel.INFOBLUE, f'\nRunning test_hostname_twice_acess_old_should_raise')
|
||||
|
||||
d = Ctb() # creates multi shm (assuming no shm exists)
|
||||
d.hostname = f"localhost:{SERVER_START_PORTNO}" # hostname command creates mod shm, d maps to it
|
||||
d.hostname = f"localhost:{SERVER_START_PORTNO}" # Freeing and recreating shm while mapping d to it (old shm is out of scope)
|
||||
|
||||
# this should not throw
|
||||
exptime_val = d.exptime
|
||||
|
||||
Log(LogLevel.INFOGREEN, f"✅ Test passed, exptime was: {exptime_val}")
|
||||
assert isinstance(exptime_val, float)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from enum import Enum
|
||||
from colorama import Fore, Style, init
|
||||
from datetime import timedelta
|
||||
|
||||
from slsdet import Detector, detectorSettings, burstMode
|
||||
from slsdet import Detector, Ctb, detectorSettings, burstMode
|
||||
from slsdet.defines import DEFAULT_TCP_RX_PORTNO, DEFAULT_UDP_DST_PORTNO
|
||||
SERVER_START_PORTNO=1900
|
||||
|
||||
@@ -167,9 +167,12 @@ def startDetectorVirtualServer(name :str, num_mods, fp):
|
||||
time.sleep(3)
|
||||
|
||||
|
||||
def connectToVirtualServers(name, num_mods):
|
||||
def connectToVirtualServers(name, num_mods, ctb_object=False):
|
||||
try:
|
||||
d = Detector()
|
||||
if ctb_object:
|
||||
d = Ctb()
|
||||
else:
|
||||
d = Detector()
|
||||
except Exception as e:
|
||||
raise RuntimeException(f'Could not create Detector object for {name}. Error: {str(e)}') from e
|
||||
|
||||
|
||||