Architecture

The SMRB component provides asynchronous, inter-process communication mechanisms for meta data and data. This C++ implementation provides an object oriented interace to the PSRDADA C library and the interfaces required for monitoring and control. The library provides efficient, atomic access to the Shared Memory system of the Linux kernel. The primary application (ska-pst-smrb-core) provides the monitoring & control via a gRPC interace and initialisation and destruction of the required resources. The library’s API provides interfaces for the RECV, STAT and DSP components to interact with SRMB as writers, viewers or readers. The A TANGO software device will provide an interface to SMRB for control and monitoring.

Ring Buffers

A shared memory ring buffer is a data structure that uses non-contiguous allocations of shared memory segments issued by the Linux kernel that are created and persist until they are explicitly de-allocated. Logically, the segments in shared memory form a circular ring buffer, as depicted in the figure below. The SMRB allows a writing process to fill blocks in the ring and a reading process to read and then clear those blocks. Once the reader has cleared a block, the writer is then free to fill it with data once more. The SMRB utilises both inter-process communication semaphores and multi-threaded mutual exclusion (mutex) concurrency controls to facilitate correct access to the memory structures, prevent race conditions, and ensure that the reader never overtakes the writer (and vice versa). Additional state information for the SMRB is stored within shared memory structures.

../_images/ring_buffer.png

Data is transferred through the SMRB via the concept of a data stream that has a start and end with no constraints on the length of data. Each data stream corresponds directly to a single scan/observation and consists of a short, static header that contains meta-data that fully describes the variable length data stream.

Decomposition

SMRB consists of a monitoring and control module (SMRB.MGMT) which interacts with the MGMT component and controls the other sub-components of the SMRB. SMRB.MGMT controls instances of DataBlockManager, one for each sub-band. Each DataBlockManager, contains DataBlock instances for the Data and Weights streams. The DataBlock contains ring buffers for the Header (meta-data) and Data (time-series). Each of these ring buffers have a configurable number of elements and element size. The structure and relationship of of these classes are shown in the figure below.

../_images/DDD.SMRB.Composition.png

Data and Control Flow

The data and control connections between these sub-components and modules are shown in the Connector and Component diagram below. The SMRB component is largely a passive component as the RECV component writes to memory addresses managed by the SMRB and the STAT and DSP components read from the same memory addresses.

../_images/DDD.SMRB.DataControlFlow.png

DataBlockManager

Each instance of a DataBlockManager handles the data stream for one of the 4 sub-bands for a beam, receiving the meta-data and data from the corresponding RECV.CORE instance and providing them to the corresponding DSP and STAT instances. Each DataBlockManager contains a Data and Weights Ring buffer and each of those consists of a header ring and data ring. Each scan (a continuous stream of data) will use

  • a single buffer from the data and weights header blocks, filled with the meta-data,

  • 1 or more buffers from the data block, filled with the tied-array voltage data stream time samples, and

  • 1 or more buffers from the weights block, filled with the relative weights of blocks of time samples

Each header buffer element will be a fixed size (e.g. 16 KB) and contain an ASCII string with keyword/value pairs for the meta-data. Each pair will be newline delimited, with the keyword/value separated by whitespace. The metadata in the header will contain sufficient information to fully describe the data within the data block, but not any information about the processing that the PST will be performing upon that data. The components that read data from the SMRB (STAT and DSP) will not require any further information about the data other than what is contained within the header.

The tied-array voltage data for each scan will be written to the buffers of the data block in a circular fashion. Shared control structures within the SMRB will indicate in which buffer a data stream starts and in which buffer a data stream will end. The writing process (RECV) will set these flags and the reading processes (STAT and DSP) will acknowledge them. Similarly, the relative weights data for each scan will be written to, and read from, the buffers of the weights block.

Classes

The SmrbSegmentProducer represents a time series of data, weights, and scale factors from ring buffers in shared memory, composed of a series of segments.

The SmrbBlockProducer encapsulates access to the Blocks of either the data ring buffer or the weights and scale factors ring buffer.

For a complete description of the relationships between Segments, Heaps, Packets, and Blocks, please see the ska-pst-common architecture.

The following diagram shows the classes involved in the definition and implementation of a time series.

@startuml PST Data Segment class diagram

package ska::pst::common
{
  class SegmentProducer
  class BlockProducer
  class BlockSegmentProducer
}

interface SegmentProducer 
{
    + num_heaps_per_segment: int
    + next_segment() : Segment
}

interface BlockProducer 
{
    + next_block(): Block
}

' A source of heaps that have been divided into blocks
class BlockSegmentProducer 
{
    - data: BlockProducer
    - weights: BlockProducer
    + next_segment(): Segment
}

SegmentProducer <|-- BlockSegmentProducer
BlockSegmentProducer *-- BlockProducer : uses 2 >

class SmrbBlockProducer 
{
    - accessor : DataBlockView
    + get_header(): AsciiHeader
    + next_block(): Block
}
note left of SmrbBlockProducer::accessor
  Could be generalized to
  a new parent class of 
  DataBlockView and 
  DataBlockRead
end note

BlockProducer <|- SmrbBlockProducer

class SmrbSegmentProducer 
{
    - data: SmrbBlockProducer
    - weights: SmrbBlockProducer
    + next_segment() : Segment
}

BlockSegmentProducer <|- SmrbSegmentProducer
SmrbSegmentProducer *-- SmrbBlockProducer : uses 2 >

class DataBlockAccessor
{
    + connect(timeout:int)
    + disconnet()
    + lock()
    + unlock()
    + open()
    + close()
    + get_header() : char*
    + open_block() : char*
    + close_block(bytes:uint64_t) : ssize_t
}

SmrbBlockProducer *-- DataBlockAccessor

@enduml

Class diagram showing main classes involved in the definition of the SmrbSegmentProducer