From a3c8c5edf19fae7d2cb4db1d01a207e011affeea Mon Sep 17 00:00:00 2001
From: Jonathan Hanks <jonathan.hanks@ligo.org>
Date: Fri, 11 Oct 2019 16:48:20 -0700
Subject: [PATCH] Tracking down an issue with 16bit code.  Extending mbuf probe
 and tests.

Added an 'analyze' mode to mbuf_probe to allow it to dump data in real
time with some basic data extracted.

Extended tests with new synthentic generators to help highlight issues.

It appears that there is a byte swap issue with 16bit ints.  Committing
code before looking into the daqd code.
---
 src/drv/mbuf/mbuf_probe/CMakeLists.txt        |  11 +-
 .../mbuf/mbuf_probe/analyze_daq_multi_dc.cc   | 178 +++++++++++++
 .../mbuf/mbuf_probe/analyze_daq_multi_dc.hh   |  19 ++
 src/drv/mbuf/mbuf_probe/analyze_rmipc.cc      | 124 +++++++++
 src/drv/mbuf/mbuf_probe/analyze_rmipc.hh      |  19 ++
 src/drv/mbuf/mbuf_probe/mbuf_decoders.cc      | 247 ++++++++++++++++++
 src/drv/mbuf/mbuf_probe/mbuf_decoders.hh      |  54 ++++
 src/drv/mbuf/mbuf_probe/mbuf_probe.cc         | 196 +++++++-------
 src/drv/mbuf/mbuf_probe/mbuf_probe.hh         | 141 ++++++++++
 src/fe_stream_test/fe_multi_stream_test.cc    |  12 +-
 src/fe_stream_test/fe_stream_generator.cc     | 151 ++++++-----
 src/fe_stream_test/fe_stream_generator.hh     |  85 ++++++
 src/fe_stream_test/lookup_chan_from_ini.py    | 113 ++++++++
 13 files changed, 1187 insertions(+), 163 deletions(-)
 create mode 100644 src/drv/mbuf/mbuf_probe/analyze_daq_multi_dc.cc
 create mode 100644 src/drv/mbuf/mbuf_probe/analyze_daq_multi_dc.hh
 create mode 100644 src/drv/mbuf/mbuf_probe/analyze_rmipc.cc
 create mode 100644 src/drv/mbuf/mbuf_probe/analyze_rmipc.hh
 create mode 100644 src/drv/mbuf/mbuf_probe/mbuf_decoders.cc
 create mode 100644 src/drv/mbuf/mbuf_probe/mbuf_decoders.hh
 create mode 100644 src/drv/mbuf/mbuf_probe/mbuf_probe.hh
 create mode 100644 src/fe_stream_test/lookup_chan_from_ini.py

diff --git a/src/drv/mbuf/mbuf_probe/CMakeLists.txt b/src/drv/mbuf/mbuf_probe/CMakeLists.txt
index 660e95c85..41da74017 100644
--- a/src/drv/mbuf/mbuf_probe/CMakeLists.txt
+++ b/src/drv/mbuf/mbuf_probe/CMakeLists.txt
@@ -1,4 +1,11 @@
 
-add_executable(mbuf_probe mbuf_probe.cc)
-target_include_directories(mbuf_probe PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/..)
+add_executable(mbuf_probe
+        mbuf_probe.cc
+        mbuf_decoders.cc
+        analyze_daq_multi_dc.cc
+        analyze_rmipc.cc)
+target_include_directories(mbuf_probe PRIVATE
+        ${CMAKE_CURRENT_SOURCE_DIR}/..
+        ${CMAKE_CURRENT_SOURCE_DIR}/../../include
+        )
 target_link_libraries(mbuf_probe PUBLIC driver::shmem)
\ No newline at end of file
diff --git a/src/drv/mbuf/mbuf_probe/analyze_daq_multi_dc.cc b/src/drv/mbuf/mbuf_probe/analyze_daq_multi_dc.cc
new file mode 100644
index 000000000..52fb85f21
--- /dev/null
+++ b/src/drv/mbuf/mbuf_probe/analyze_daq_multi_dc.cc
@@ -0,0 +1,178 @@
+//
+// Created by jonathan.hanks on 10/11/19.
+//
+
+#include "analyze_daq_multi_dc.hh"
+
+#include "mbuf.h"
+#include "daq_core.h"
+
+#include <algorithm>
+#include <iterator>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+
+namespace analyze
+{
+    namespace multi_dc
+    {
+        void
+        simple_running_dump_single_dcu(
+            volatile daq_multi_cycle_data_t* multi_data,
+            int                              dcu_id,
+            const DataDecoder&               decoder )
+        {
+
+            unsigned int last_cycle = multi_data->header.curCycle;
+
+            bool use_decoder = ( decoder.required_size( ) > 0 );
+
+            while ( true )
+            {
+                unsigned int cur_cycle = wait_until_changed(
+                    &( multi_data->header.curCycle ), last_cycle );
+                if ( cur_cycle > 1024 )
+                {
+                    break;
+                }
+
+                last_cycle = cur_cycle;
+
+                unsigned int data_size = multi_data->header.cycleDataSize;
+                std::size_t  cycle_offset = data_size * cur_cycle;
+
+                volatile daq_dc_data_t* dc_data =
+                    reinterpret_cast< volatile daq_dc_data_t* >(
+                        &( multi_data->dataBlock[ 0 ] ) + cycle_offset );
+                unsigned int dcu_count = dc_data->header.dcuTotalModels;
+                volatile daq_msg_header_t* header =
+                    (volatile daq_msg_header_t*)0;
+                volatile char* data = &( dc_data->dataBlock[ 0 ] );
+                for ( int i = 0; i < dcu_count; ++i )
+                {
+                    if ( dc_data->header.dcuheader[ i ].dcuId == dcu_id )
+                    {
+                        header = &( dc_data->header.dcuheader[ i ] );
+                        break;
+                    }
+                    data += dc_data->header.dcuheader[ i ].dataBlockSize +
+                        dc_data->header.dcuheader[ i ].tpBlockSize;
+                }
+
+                std::ostringstream os;
+                os << "Cycle: " << std::setw( 2 ) << cur_cycle;
+                if ( header )
+                {
+                    os << " dcu: " << std::setw( 3 ) << header->dcuId;
+                    os << " gps: " << std::setw( 10 ) << header->timeSec << ":"
+                       << std::setw( 2 ) << header->timeNSec;
+                    os << " data: " << header->dataBlockSize;
+                    os << " tp: " << header->tpBlockSize;
+                    if ( header->tpCount > 0 )
+                    {
+                        os << "(";
+                        std::copy(
+                            const_cast< unsigned int* >(
+                                &( header->tpNum[ 0 ] ) ),
+                            const_cast< unsigned int* >(
+                                &( header->tpNum[ header->tpCount ] ) ),
+                            std::ostream_iterator< unsigned int >( os, "," ) );
+                        os << ") ";
+                    }
+                    if ( use_decoder )
+                    {
+                        os << "  -  ";
+                        decoder.decode(
+                            reinterpret_cast< volatile void* >( data ), os );
+                    }
+                }
+                else
+                {
+                    os << " not found in this cycle\n";
+                }
+
+                std::cout << os.str( ) << std::endl;
+            }
+        }
+
+        void
+        simple_running_dump( volatile daq_multi_cycle_data_t* multi_data )
+        {
+
+            unsigned int last_cycle = multi_data->header.curCycle;
+
+            while ( true )
+            {
+                unsigned int cur_cycle = wait_until_changed(
+                    &( multi_data->header.curCycle ), last_cycle );
+                if ( cur_cycle > 1024 )
+                {
+                    break;
+                }
+
+                unsigned int data_size = multi_data->header.cycleDataSize;
+                unsigned int max_cycle = multi_data->header.maxCycle;
+
+                std::size_t cycle_offset = data_size * cur_cycle;
+
+                volatile daq_dc_data_t* dc_data =
+                    reinterpret_cast< volatile daq_dc_data_t* >(
+                        &( multi_data->dataBlock[ 0 ] ) + cycle_offset );
+                unsigned int dcu_count = dc_data->header.dcuTotalModels;
+
+                last_cycle = cur_cycle;
+
+                std::ostringstream os;
+
+                os << "Cycle: " << std::setw( 2 ) << cur_cycle << "/"
+                   << std::setw( 2 ) << max_cycle;
+                os << " DataSize: " << data_size;
+                os << " DcuCount: " << dcu_count;
+
+                for ( unsigned int i = 0; i < dcu_count; ++i )
+                {
+                    volatile daq_msg_header_t& header =
+                        dc_data->header.dcuheader[ i ];
+                    os << "\n\tdcu: " << std::setw( 3 ) << header.dcuId;
+                    os << " gps: " << std::setw( 10 ) << header.timeSec << ":"
+                       << std::setw( 2 ) << header.timeNSec;
+                    os << " data: " << header.dataBlockSize;
+                    os << " tp: " << header.tpBlockSize;
+                    if ( header.tpCount > 0 )
+                    {
+                        os << "(";
+                        std::copy(
+                            const_cast< unsigned int* >(
+                                &( header.tpNum[ 0 ] ) ),
+                            const_cast< unsigned int* >(
+                                &( header.tpNum[ header.tpCount ] ) ),
+                            std::ostream_iterator< unsigned int >( os, "," ) );
+                        os << ")";
+                    }
+                }
+
+                std::cout << os.str( ) << std::endl;
+            }
+        }
+    } // namespace multi_dc
+
+    void
+    analyze_multi_dc( volatile void*    buffer,
+                      std::size_t       size,
+                      const ConfigOpts& options )
+    {
+        volatile daq_multi_cycle_data_t* multi_data =
+            reinterpret_cast< volatile daq_multi_cycle_data_t* >( buffer );
+
+        if ( options.dcu_id > 0 )
+        {
+            multi_dc::simple_running_dump_single_dcu(
+                multi_data, options.dcu_id, options.decoder );
+        }
+        else
+        {
+            multi_dc::simple_running_dump( multi_data );
+        }
+    }
+} // namespace analyze
diff --git a/src/drv/mbuf/mbuf_probe/analyze_daq_multi_dc.hh b/src/drv/mbuf/mbuf_probe/analyze_daq_multi_dc.hh
new file mode 100644
index 000000000..b5f8799ce
--- /dev/null
+++ b/src/drv/mbuf/mbuf_probe/analyze_daq_multi_dc.hh
@@ -0,0 +1,19 @@
+//
+// Created by jonathan.hanks on 10/11/19.
+//
+
+#ifndef DAQD_MBUF_ANALYZE_DAQ_MULTI_DC_HH
+#define DAQD_MBUF_ANALYZE_DAQ_MULTI_DC_HH
+
+#include <cstddef>
+
+#include "mbuf_probe.hh"
+
+namespace analyze
+{
+    void analyze_multi_dc( volatile void*    buffer,
+                           std::size_t       size,
+                           const ConfigOpts& options );
+}
+
+#endif // DAQD_MBUF_ANALYZE_DAQ_MULTI_DC_HH
diff --git a/src/drv/mbuf/mbuf_probe/analyze_rmipc.cc b/src/drv/mbuf/mbuf_probe/analyze_rmipc.cc
new file mode 100644
index 000000000..393ac58dc
--- /dev/null
+++ b/src/drv/mbuf/mbuf_probe/analyze_rmipc.cc
@@ -0,0 +1,124 @@
+//
+// Created by jonathan.hanks on 10/10/19.
+//
+
+#include "analyze_rmipc.hh"
+
+#include <time.h>
+#include <unistd.h>
+#include <daqmap.h>
+#include <daq_core_defs.h>
+#include <drv/fb.h>
+
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+
+namespace analyze
+{
+
+    namespace rmipc
+    {
+        /*!
+         * @brief a grouping of commonly needed offsets and structures in a
+         * rmIpcStr style buffer
+         */
+        struct memory_layout
+        {
+            explicit memory_layout( volatile char* buf )
+                : ipc( reinterpret_cast< volatile rmIpcStr* >(
+                      buf + CDS_DAQ_NET_IPC_OFFSET ) ),
+                  data( buf + CDS_DAQ_NET_DATA_OFFSET ),
+                  tp_num( reinterpret_cast< volatile cdsDaqNetGdsTpNum* >(
+                      buf + CDS_DAQ_NET_GDS_TP_TABLE_OFFSET ) )
+            {
+            }
+
+            volatile rmIpcStr*          ipc;
+            volatile char*              data;
+            volatile cdsDaqNetGdsTpNum* tp_num;
+        };
+
+        static const std::size_t buf_size =
+            DAQ_DCU_BLOCK_SIZE * DAQ_NUM_SWING_BUFFERS;
+
+        double
+        time_diff( const struct timespec& t0, const struct timespec& t1 )
+        {
+            double t0d = static_cast< double >( t0.tv_sec ) +
+                static_cast< double >( t0.tv_nsec ) / 1000000000.0;
+            double t1d = static_cast< double >( t1.tv_sec ) +
+                static_cast< double >( t1.tv_nsec ) / 1000000000.0;
+            return t1d - t0d;
+        }
+
+        void
+        simple_running_dump( memory_layout layout, const DataDecoder& decoder )
+        {
+            unsigned int last_cycle = layout.ipc->cycle;
+
+            bool use_decoder = decoder.required_size( ) > 0;
+
+            struct timespec last_time;
+            clock_gettime( CLOCK_MONOTONIC, &last_time );
+            while ( true )
+            {
+                unsigned int cur_cycle =
+                    wait_until_changed( &( layout.ipc->cycle ), last_cycle );
+                struct timespec cur_time;
+                clock_gettime( CLOCK_MONOTONIC, &cur_time );
+
+                if ( cur_cycle > 1024 )
+                {
+                    break;
+                }
+
+                unsigned int dcu_id = layout.ipc->dcuId;
+                unsigned int status = layout.ipc->status;
+                unsigned int channel_count = layout.ipc->channelCount;
+                unsigned int data_size = layout.ipc->dataBlockSize;
+                unsigned int blk_sec = layout.ipc->bp[ cur_cycle ].timeSec;
+                unsigned int blk_nsec = layout.ipc->bp[ cur_cycle ].timeNSec;
+                unsigned int blk_crc = layout.ipc->bp[ cur_cycle ].crc;
+
+                last_cycle = cur_cycle;
+                std::ostringstream os;
+                os << "Cycle: " << std::setw( 2 ) << cur_cycle
+                   << std::setw( 0 );
+                os << " DCU: " << dcu_id;
+                os << " Chan#: " << channel_count;
+                os << " DataSize: " << data_size;
+                os << " Status: " << status;
+                os << " BlkCrc: " << blk_crc;
+                os << " BlkSec: " << std::setw( 10 ) << blk_sec;
+                os << ":" << std::setw( 2 ) << blk_nsec;
+
+                if ( use_decoder )
+                {
+                    os << "  -  ";
+                    decoder.decode( reinterpret_cast< volatile void* >(
+                                        layout.data + cur_cycle * buf_size ),
+                                    os );
+                }
+
+                double time_delta = time_diff( last_time, cur_time );
+                last_time = cur_time;
+
+                os << " delta(ms): " << time_delta * 1000;
+
+                std::cout << os.str( ) << std::endl;
+            }
+        }
+    } // namespace rmipc
+
+    void
+    analyze_rmipc( volatile void*    buffer,
+                   std::size_t       size,
+                   const ConfigOpts& options )
+    {
+        rmipc::memory_layout layout(
+            reinterpret_cast< volatile char* >( buffer ) );
+
+        rmipc::simple_running_dump( layout, options.decoder );
+    }
+} // namespace analyze
diff --git a/src/drv/mbuf/mbuf_probe/analyze_rmipc.hh b/src/drv/mbuf/mbuf_probe/analyze_rmipc.hh
new file mode 100644
index 000000000..306e62684
--- /dev/null
+++ b/src/drv/mbuf/mbuf_probe/analyze_rmipc.hh
@@ -0,0 +1,19 @@
+//
+// Created by jonathan.hanks on 10/10/19.
+//
+
+#ifndef DAQD_MBUF_ANALYSE_RMIPC_HH
+#define DAQD_MBUF_ANALYSE_RMIPC_HH
+
+#include <cstddef>
+
+#include "mbuf_probe.hh"
+
+namespace analyze
+{
+    void analyze_rmipc( volatile void*    buffer,
+                        std::size_t       size,
+                        const ConfigOpts& options );
+}
+
+#endif // DAQD_MBUF_ANALYSE_RMIPC_HH
diff --git a/src/drv/mbuf/mbuf_probe/mbuf_decoders.cc b/src/drv/mbuf/mbuf_probe/mbuf_decoders.cc
new file mode 100644
index 000000000..4bc982fc3
--- /dev/null
+++ b/src/drv/mbuf/mbuf_probe/mbuf_decoders.cc
@@ -0,0 +1,247 @@
+//
+// Created by jonathan.hanks on 10/10/19.
+//
+
+#include "mbuf_decoders.hh"
+
+#include <algorithm>
+#include <numeric>
+#include <iostream>
+#include <sstream>
+
+namespace mbuf_decoders
+{
+    template < typename T >
+    class BasicDecoder : public Decoder
+    {
+    public:
+        explicit BasicDecoder( std::size_t count ) : count_( count )
+        {
+        }
+
+        virtual ~BasicDecoder( )
+        {
+        }
+
+        virtual volatile void*
+        decode( volatile void* data, std::ostream& os ) const
+        {
+            volatile T* cur = reinterpret_cast< volatile T* >( data );
+            for ( std::size_t i = 0; i < count_; ++i )
+            {
+                os << " " << *cur;
+                ++cur;
+            }
+            return reinterpret_cast< volatile void* >( cur );
+        }
+
+        virtual std::size_t
+        required_size( ) const
+        {
+            return sizeof( T ) * count_;
+        }
+
+        virtual std::string
+        describe( ) const
+        {
+            std::ostringstream os;
+            os << "BasicDecoder data_size = " << sizeof( T )
+               << " count = " << count_ << " req_size = " << required_size( );
+            return os.str( );
+        }
+
+    private:
+        std::size_t count_;
+    };
+
+    class PadDecoder : public Decoder
+    {
+    public:
+        explicit PadDecoder( std::size_t count ) : count_( count )
+        {
+        }
+
+        virtual ~PadDecoder( )
+        {
+        }
+
+        virtual volatile void*
+        decode( volatile void* data, std::ostream& os ) const
+        {
+            volatile char* cur = reinterpret_cast< volatile char* >( data );
+            cur += count_;
+            return reinterpret_cast< volatile void* >( cur );
+        }
+
+        virtual std::size_t
+        required_size( ) const
+        {
+            return count_;
+        }
+
+        virtual std::string
+        describe( ) const
+        {
+            return "pad";
+        }
+
+    private:
+        std::size_t count_;
+    };
+
+    std::tr1::shared_ptr< Decoder >
+    create_decoder( char format, std::size_t count = 1 )
+    {
+        std::tr1::shared_ptr< Decoder > ptr;
+
+        switch ( format )
+        {
+        case 'x':
+            ptr = std::tr1::shared_ptr< Decoder >( new PadDecoder( count ) );
+            break;
+        case 'c':
+            ptr = std::tr1::shared_ptr< Decoder >(
+                new BasicDecoder< char >( count ) );
+            break;
+        case 'b':
+            ptr = std::tr1::shared_ptr< Decoder >(
+                new BasicDecoder< signed char >( count ) );
+            break;
+        case 'B':
+            ptr = std::tr1::shared_ptr< Decoder >(
+                new BasicDecoder< unsigned char >( count ) );
+            break;
+        case 'h':
+            ptr = std::tr1::shared_ptr< Decoder >(
+                new BasicDecoder< short >( count ) );
+            break;
+        case 'H':
+            ptr = std::tr1::shared_ptr< Decoder >(
+                new BasicDecoder< unsigned short >( count ) );
+            break;
+        case 'i':
+            ptr = std::tr1::shared_ptr< Decoder >(
+                new BasicDecoder< int >( count ) );
+            break;
+        case 'I':
+            ptr = std::tr1::shared_ptr< Decoder >(
+                new BasicDecoder< unsigned int >( count ) );
+            break;
+        case 'f':
+            ptr = std::tr1::shared_ptr< Decoder >(
+                new BasicDecoder< float >( count ) );
+            break;
+        case 'd':
+            ptr = std::tr1::shared_ptr< Decoder >(
+                new BasicDecoder< double >( count ) );
+            break;
+        default:
+            throw std::runtime_error(
+                "Unknown format specifier for a decoder" );
+        }
+        return ptr;
+    }
+} // namespace mbuf_decoders
+
+class not_equal
+{
+public:
+    not_equal( char c ) : val( c )
+    {
+    }
+    not_equal( const not_equal& other ) : val( other.val )
+    {
+    }
+    not_equal&
+    operator=( const not_equal& other )
+    {
+        val = other.val;
+        return *this;
+    }
+
+    bool
+    operator( )( char cur ) const
+    {
+        return cur != val;
+    }
+
+private:
+    char val;
+};
+
+DataDecoder::DataDecoder( std::size_t offset, const std::string& format )
+    : offset_( offset ), decoders_( )
+{
+    decoders_.reserve( format.size( ) );
+
+    std::string::const_iterator cur = format.begin( );
+    std::string::const_iterator end = format.end( );
+
+    while ( cur != end )
+    {
+        std::string::const_iterator next =
+            find_if( cur, end, not_equal( *cur ) );
+        decoders_.push_back(
+            mbuf_decoders::create_decoder( *cur, next - cur ) );
+        cur = next;
+    }
+
+    std::cout << "Decoder:\n";
+    for ( int i = 0; i < decoders_.size( ); ++i )
+    {
+        std::cout << "\t" << decoders_[ i ]->describe( ) << "\n";
+    }
+}
+
+DataDecoder::DataDecoder( const DataDecoder& other )
+    : offset_( other.offset_ ), decoders_( other.decoders_ )
+{
+}
+
+DataDecoder&
+DataDecoder::operator=( const DataDecoder& other )
+{
+    if ( &other != this )
+    {
+        decoders_ = other.decoders_;
+        offset_ = other.offset_;
+    }
+    return *this;
+}
+
+static std::size_t
+required_size_accumulator( std::size_t                     cur,
+                           std::tr1::shared_ptr< Decoder > decoder )
+{
+    return cur + decoder->required_size( );
+}
+
+std::size_t
+DataDecoder::required_size( ) const
+{
+    return std::accumulate( decoders_.begin( ),
+                            decoders_.end( ),
+                            std::size_t( 0 ),
+                            required_size_accumulator );
+}
+
+void
+DataDecoder::decode( volatile void* data, std::ostream& os ) const
+{
+    std::vector< std::size_t > steps( decoders_.size( ), 0 );
+    os << "offset: " << offset_;
+    volatile void* cur = reinterpret_cast< volatile void* >(
+        reinterpret_cast< volatile char* >( data ) + offset_ );
+    for ( std::size_t i = 0; i < decoders_.size( ); ++i )
+    {
+        volatile void* tmp = decoders_[ i ]->decode( cur, os );
+        steps[ i ] = ( (char*)tmp ) - ( (char*)cur );
+        cur = tmp;
+    }
+
+    os << " steps -";
+    for ( std::size_t i = 0; i < steps.size( ); ++i )
+    {
+        os << " " << steps[ i ];
+    }
+}
\ No newline at end of file
diff --git a/src/drv/mbuf/mbuf_probe/mbuf_decoders.hh b/src/drv/mbuf/mbuf_probe/mbuf_decoders.hh
new file mode 100644
index 000000000..ddbffdfd4
--- /dev/null
+++ b/src/drv/mbuf/mbuf_probe/mbuf_decoders.hh
@@ -0,0 +1,54 @@
+//
+// Created by jonathan.hanks on 10/10/19.
+//
+
+#ifndef DAQD_MBUF_DECODERS_HH
+#define DAQD_MBUF_DECODERS_HH
+
+#include <cstddef>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include <tr1/memory>
+
+class Decoder
+{
+public:
+    Decoder( )
+    {
+    }
+    virtual ~Decoder( ){};
+
+    virtual volatile void* decode( volatile void* data,
+                                   std::ostream&  os ) const = 0;
+    virtual std::size_t    required_size( ) const = 0;
+
+    virtual std::string describe( ) const = 0;
+};
+
+class DataDecoder
+{
+public:
+    DataDecoder( ) : offset_( 0 ), decoders_( )
+    {
+    }
+    DataDecoder( std::size_t offset, const std::string& format );
+    DataDecoder( const DataDecoder& other );
+    DataDecoder& operator=( const DataDecoder& other );
+
+    std::size_t
+    offset( ) const
+    {
+        return offset_;
+    }
+    std::size_t required_size( ) const;
+
+    void decode( volatile void* data, std::ostream& os ) const;
+
+private:
+    std::size_t                                    offset_;
+    std::vector< std::tr1::shared_ptr< Decoder > > decoders_;
+};
+
+#endif // DAQD_TRUNK_MBUF_DECODERS_HH
diff --git a/src/drv/mbuf/mbuf_probe/mbuf_probe.cc b/src/drv/mbuf/mbuf_probe/mbuf_probe.cc
index d3d5080e8..5fb26e3a7 100644
--- a/src/drv/mbuf/mbuf_probe/mbuf_probe.cc
+++ b/src/drv/mbuf/mbuf_probe/mbuf_probe.cc
@@ -24,97 +24,9 @@
 #include "drv/shmem.h"
 #include "mbuf.h"
 
-enum MBufCommands
-{
-    INVALID,
-    CREATE,
-    LIST,
-    COPY,
-    DELETE,
-};
-
-struct ConfigOpts
-{
-    ConfigOpts( )
-        : action( INVALID ), buffer_size( 0 ), buffer_name( "" ),
-          output_fname( "probe_out.bin" ), error_msg( "" )
-    {
-    }
-    MBufCommands action;
-    std::size_t  buffer_size;
-    std::string  buffer_name;
-    std::string  output_fname;
-    std::string  error_msg;
-
-    bool
-    select_action( MBufCommands selected_action )
-    {
-        if ( action != INVALID )
-        {
-            set_error( "Please only select one action" );
-            return false;
-        }
-        action = selected_action;
-        return true;
-    }
-
-    void
-    set_error( const std::string& msg )
-    {
-        action = INVALID;
-        error_msg = msg;
-    }
-
-    void
-    validate_options( )
-    {
-        if ( !error_msg.empty( ) )
-        {
-            return;
-        }
-        switch ( action )
-        {
-        case CREATE:
-            if ( buffer_name.empty( ) || buffer_size == 0 )
-            {
-                set_error( "Both a buffer name and buffer size are required to "
-                           "create a buffer" );
-            }
-            break;
-        case LIST:
-            break;
-        case COPY:
-            if ( buffer_name.empty( ) || buffer_size == 0 ||
-                 output_fname.empty( ) )
-            {
-                set_error( "To copy a buffer a buffer name, size, and output "
-                           "filename must be provided" );
-            }
-            break;
-        case DELETE:
-            if ( buffer_name.empty( ) )
-            {
-                set_error( "To delete a buffer you must specify its name" );
-            }
-            break;
-        case INVALID:
-        default:
-            set_error( "Please select a valid action" );
-        }
-    }
-
-    bool
-    should_show_help( )
-    {
-        return action == INVALID;
-    }
-
-    bool
-    is_in_error( )
-    {
-        return ( should_show_help( ) ? !error_msg.empty( ) : false );
-    }
-};
+#include "mbuf_probe.hh"
+#include "analyze_daq_multi_dc.hh"
+#include "analyze_rmipc.hh"
 
 void
 usage( const char* progname )
@@ -126,6 +38,7 @@ usage( const char* progname )
     std::cout << "\tcreate - create a mbuf\n";
     std::cout << "\tcopy - copy a mbuf to a file\n";
     std::cout << "\tdelete - decrement the usage count of an mbuf\n";
+    std::cout << "\tanalyze - continually read the mbuf and do some analysis\n";
     std::cout << "\t-b <buffer name> - The name of the buffer to act on\n";
     std::cout
         << "\t-m <buffer size in MB> - The size of the buffer in megabytes\n";
@@ -133,8 +46,22 @@ usage( const char* progname )
                  "multiple of 4k)\n";
     std::cout << "\t-o <filename> - Output file for the copy operation "
                  "(defaults to probe_out.bin)\n";
+    std::cout
+        << "\t--struct <type> - Type of structure to analyze [rmIpcStr]\n";
+    std::cout << "\t--dcu <dcuid> - Optional DCU id used to select a dcu for "
+                 "analysis\n";
+    std::cout << "\t\twhen analyzing daq_multi_cycle buffers.\n";
+    std::cout << "\t-d <offset:format> - Decode the data section, optional\n";
+    std::cout
+        << "\t\tPart of the analyze command.  Decode a specified stretch\n";
+    std::cout << "\t\tof data, with a given format specifier (same as python "
+                 "struct)\n";
     std::cout << "\t-h|--help - This help\n";
     std::cout << "\n";
+    std::cout << "Analysis modes:\n";
+    std::cout << "\trmIpcStr (or rmipcstr) Analyze a models output buffer\n";
+    std::cout
+        << "\tdaq_multi_cycle Analyze the output of a streamer/local_dc\n";
 }
 
 ConfigOpts
@@ -147,6 +74,14 @@ parse_options( int argc, char* argv[] )
     command_lookup.insert( std::make_pair( "create", CREATE ) );
     command_lookup.insert( std::make_pair( "copy", COPY ) );
     command_lookup.insert( std::make_pair( "delete", DELETE ) );
+    command_lookup.insert( std::make_pair( "analyze", ANALYZE ) );
+
+    std::map< std::string, MBufStructures > struct_lookup;
+    struct_lookup.insert( std::make_pair( "rmIpcStr", MBUF_RMIPC ) );
+    struct_lookup.insert( std::make_pair( "rmipcstr", MBUF_RMIPC ) );
+    struct_lookup.insert(
+        std::make_pair( "daq_multi_cycle", MBUF_DAQ_MULTI_DC ) );
+
     std::deque< std::string > args;
     for ( int i = 1; i < argc; ++i )
     {
@@ -176,7 +111,7 @@ parse_options( int argc, char* argv[] )
         {
             if ( args.empty( ) )
             {
-                opts.set_error( "You must specify a size when using -m or -S");
+                opts.set_error( "You must specify a size when using -m or -S" );
                 return opts;
             }
             std::size_t        multiplier = ( arg == "-m" ? 1024 * 1024 : 1 );
@@ -195,6 +130,58 @@ parse_options( int argc, char* argv[] )
             opts.output_fname = args.front( );
             args.pop_front( );
         }
+        else if ( arg == "-d" )
+        {
+            if ( args.empty( ) )
+            {
+                opts.set_error(
+                    "You must specify a format string when using -d" );
+                return opts;
+            }
+            std::string format = args.front( );
+            args.pop_front( );
+            std::string::size_type split = format.find( ':' );
+            if ( split == std::string::npos )
+            {
+                opts.set_error( "You must have a format strip when using -d" );
+                return opts;
+            }
+            std::string        offset_str = format.substr( 0, split );
+            std::string        format_spec = format.substr( split + 1 );
+            std::istringstream is( offset_str );
+            std::size_t        offset = 0;
+            is >> offset;
+            opts.decoder = DataDecoder( offset, format_spec );
+        }
+        else if ( arg == "--struct" )
+        {
+            if ( args.empty( ) )
+            {
+                opts.set_error(
+                    "You must specify a structure type when using --struct" );
+                return opts;
+            }
+            std::map< std::string, MBufStructures >::iterator it;
+            it = struct_lookup.find( args.front( ) );
+            if ( it == struct_lookup.end( ) )
+            {
+                opts.set_error( "Invalid structure type passed to --struct" );
+                return opts;
+            }
+            opts.analysis_type = it->second;
+            args.pop_front( );
+        }
+        else if ( arg == "--dcu" )
+        {
+            if ( args.empty( ) )
+            {
+                opts.set_error( "You must specify a dcu id when using --dcu" );
+                return opts;
+            }
+            std::istringstream is( args.front( ) );
+            is >> opts.dcu_id;
+            args.pop_front( );
+        }
         else
         {
             std::map< std::string, MBufCommands >::iterator it;
@@ -331,6 +318,29 @@ list_shmem_segments( )
     }
 }
 
+int
+handle_analyze( const ConfigOpts& opts )
+{
+    const int OK = 0;
+    const int ERROR = 1;
+
+    volatile void* buffer =
+        shmem_open_segment( opts.buffer_name.c_str( ), opts.buffer_size );
+    switch ( opts.analysis_type )
+    {
+    case MBUF_RMIPC:
+        analyze::analyze_rmipc( buffer, opts.buffer_size, opts );
+        break;
+    case MBUF_DAQ_MULTI_DC:
+        analyze::analyze_multi_dc( buffer, opts.buffer_size, opts );
+        break;
+    default:
+        std::cout << "Unknown analysis type\n";
+        return ERROR;
+    }
+    return OK;
+}
+
 int
 main( int argc, char* argv[] )
 {
@@ -372,6 +382,8 @@ main( int argc, char* argv[] )
     case DELETE:
         shmem_dec_segment_count( opts.buffer_name.c_str( ) );
         break;
+    case ANALYZE:
+        return handle_analyze( opts );
     }
     return 0;
 }
diff --git a/src/drv/mbuf/mbuf_probe/mbuf_probe.hh b/src/drv/mbuf/mbuf_probe/mbuf_probe.hh
new file mode 100644
index 000000000..1c3f8c3d3
--- /dev/null
+++ b/src/drv/mbuf/mbuf_probe/mbuf_probe.hh
@@ -0,0 +1,141 @@
+//
+// Created by jonathan.hanks on 10/10/19.
+//
+
+#ifndef DAQD_MBUF_PROBE_HH
+#define DAQD_MBUF_PROBE_HH
+
+#include <cstddef>
+#include <string>
+#include <vector>
+
+#include <unistd.h>
+
+#include "mbuf_decoders.hh"
+
+enum MBufCommands
+{
+    INVALID,
+    CREATE,
+    LIST,
+    COPY,
+    DELETE,
+    ANALYZE,
+};
+
+enum MBufStructures
+{
+    MBUF_INVALID,
+    MBUF_RMIPC,
+    MBUF_DAQ_MULTI_DC,
+};
+
+struct ConfigOpts
+{
+    ConfigOpts( )
+        : action( INVALID ), buffer_size( 0 ), buffer_name( "" ),
+          output_fname( "probe_out.bin" ), error_msg( "" ), dcu_id( -1 ),
+          decoder( ), analysis_type( MBUF_INVALID )
+    {
+    }
+    MBufCommands   action;
+    std::size_t    buffer_size;
+    std::string    buffer_name;
+    std::string    output_fname;
+    std::string    error_msg;
+    int            dcu_id;
+    DataDecoder    decoder;
+    MBufStructures analysis_type;
+
+    bool
+    select_action( MBufCommands selected_action )
+    {
+        if ( action != INVALID )
+        {
+            set_error( "Please only select one action" );
+            return false;
+        }
+        action = selected_action;
+        return true;
+    }
+
+    void
+    set_error( const std::string& msg )
+    {
+        action = INVALID;
+        error_msg = msg;
+    }
+
+    void
+    validate_options( )
+    {
+        if ( !error_msg.empty( ) )
+        {
+            return;
+        }
+        switch ( action )
+        {
+        case CREATE:
+            if ( buffer_name.empty( ) || buffer_size == 0 )
+            {
+                set_error( "Both a buffer name and buffer size are required to "
+                           "create a buffer" );
+            }
+            break;
+        case LIST:
+            break;
+        case COPY:
+            if ( buffer_name.empty( ) || buffer_size == 0 ||
+                 output_fname.empty( ) )
+            {
+                set_error( "To copy a buffer a buffer name, size, and output "
+                           "filename must be provided" );
+            }
+            break;
+        case DELETE:
+            if ( buffer_name.empty( ) )
+            {
+                set_error( "To delete a buffer you must specify its name" );
+            }
+            break;
+        case ANALYZE:
+            if ( buffer_name.empty( ) || buffer_size == 0 ||
+                 analysis_type == MBUF_INVALID )
+            {
+                set_error( "To analyze a buffer a buffer, size, and structure "
+                           "type must be provided" );
+            }
+            break;
+        case INVALID:
+        default:
+            set_error( "Please select a valid action" );
+        }
+    }
+
+    bool
+    should_show_help( )
+    {
+        return action == INVALID;
+    }
+
+    bool
+    is_in_error( )
+    {
+        return ( should_show_help( ) ? !error_msg.empty( ) : false );
+    }
+};
+
+template < typename T >
+unsigned int
+wait_until_changed( volatile T* counter, T old_counter )
+{
+    T cur_cycle = *counter;
+    do
+    {
+        usleep( 250 );
+        cur_cycle = *counter;
+    } while ( cur_cycle == old_counter );
+    return cur_cycle;
+}
+
+#endif // DAQD_MBUF_PROBE_HH
diff --git a/src/fe_stream_test/fe_multi_stream_test.cc b/src/fe_stream_test/fe_multi_stream_test.cc
index b08056a64..c5d8aaa6d 100644
--- a/src/fe_stream_test/fe_multi_stream_test.cc
+++ b/src/fe_stream_test/fe_multi_stream_test.cc
@@ -286,8 +286,8 @@ public:
 
             std::ostringstream ss;
             ss << name_ << "-" << i;
-            generators_.push_back(
-                GeneratorPtr( new Generators::GPSSecondWithOffset< int >(
+            generators_.push_back( GeneratorPtr(
+                new Generators::GPSMod100kSecWithOffsetAndCycle< int >(
                     SimChannel( ss.str( ), 2, 16, chnum ),
                     ( i + dcu_id_ ) % 21 ) ) );
         }
@@ -297,8 +297,8 @@ public:
 
             std::ostringstream ss;
             ss << name_ << "-" << i;
-            generators_.push_back(
-                GeneratorPtr( new Generators::GPSMod30kSecWithOffset< short >(
+            generators_.push_back( GeneratorPtr(
+                new Generators::GPSMod100SecWithOffsetAndCycle< short >(
                     SimChannel( ss.str( ), 1, 16, chnum ),
                     ( i + dcu_id_ ) % 21 ) ) );
         }
@@ -310,8 +310,8 @@ public:
             std::ostringstream ss;
             ss << name_ << "-TP" << i;
             // TP need truncated
-            tp_generators_.push_back(
-                GeneratorPtr( new Generators::GPSMod100kSecWithOffset< float >(
+            tp_generators_.push_back( GeneratorPtr(
+                new Generators::GPSMod100kSecWithOffsetAndCycle< float >(
                     SimChannel( ss.str( ), 4, model_rate_, chnum, dcu_id_ ),
                     ( i + dcu_id_ ) % 21 ) ) );
         }
diff --git a/src/fe_stream_test/fe_stream_generator.cc b/src/fe_stream_test/fe_stream_generator.cc
index 6f47eb06b..700dce01a 100644
--- a/src/fe_stream_test/fe_stream_generator.cc
+++ b/src/fe_stream_test/fe_stream_generator.cc
@@ -4,79 +4,104 @@
 #include "fe_stream_generator.hh"
 
 bool
-is_data_type_valid(int data_type)
+is_data_type_valid( int data_type )
 {
-    switch (static_cast<daq_data_t>(data_type))
+    switch ( static_cast< daq_data_t >( data_type ) )
     {
-        case _16bit_integer:
-        case _32bit_integer:
-        case _64bit_integer:
-        case _32bit_float:
-        case _64bit_double:
-        case _32bit_uint:
-            return true;
-        case _32bit_complex:
-        default:
-            break;
+    case _16bit_integer:
+    case _32bit_integer:
+    case _64bit_integer:
+    case _32bit_float:
+    case _64bit_double:
+    case _32bit_uint:
+        return true;
+    case _32bit_complex:
+    default:
+        break;
     }
     return false;
 }
 
 GeneratorPtr
-create_generator(const std::string& generator, const SimChannel& ch)
+create_generator( const std::string& generator, const SimChannel& ch )
 {
-  if (ch.data_type() != 2)
-    throw std::runtime_error("Invalid/unsupported data type for a generator");
-  if (generator == "gps_sec") {
-    return GeneratorPtr(new Generators::GPSSecondGenerator(ch));
-  }
-  throw std::runtime_error("Unknown generator type");
+    if ( ch.data_type( ) != 2 )
+        throw std::runtime_error(
+            "Invalid/unsupported data type for a generator" );
+    if ( generator == "gps_sec" )
+    {
+        return GeneratorPtr( new Generators::GPSSecondGenerator( ch ) );
+    }
+    throw std::runtime_error( "Unknown generator type" );
 }
 
 GeneratorPtr
-create_generator(const std::string& channel_name)
+create_generator( const std::string& channel_name )
 {
-  std::vector<std::string> parts = split(channel_name, "--");
-  if (parts.size() < 4)
-    throw std::runtime_error("Generator name has too few parts, invalid input");
-  int data_type = 0;
-  {
-    std::istringstream is (parts[parts.size()-2]);
-    is >> data_type;
-  }
-  int rate = 0;
-  {
-    std::istringstream is (parts[parts.size()-1]);
-    is >> rate;
-  }
-  if (!is_data_type_valid(data_type) || rate < 16)
-    throw std::runtime_error("Invalid data type or rate found");
-  std::string& base = parts[0];
-  std::string& name = parts[1];
-  int arg_count = parts.size()-4; // ignore base channel name, data type, rate
-  if (name == "gpssoff1p" && arg_count == 1)
-  {
-    std::istringstream is(parts[2]);
-    int offset = 0;
-    is >> offset;
-    return create_generic_generator<Generators::GPSSecondWithOffset>(data_type, SimChannel(base, data_type, rate, 0), offset);
-  }
-  else if (name == "gpssmd100koff1p" && arg_count == 1)
-  {
-    std::istringstream is(parts[2]);
-    int offset = 0;
-    is >> offset;
-    return create_generic_generator<Generators::GPSMod100kSecWithOffset>(data_type, SimChannel(base, data_type, rate, 0), offset);
-  }
-  else if (name == "gpssmd30koff1p" && arg_count == 1)
-  {
-      std::istringstream is(parts[2]);
-      int offset = 0;
-      is >> offset;
-      return create_generic_generator<Generators::GPSMod30kSecWithOffset>(data_type, SimChannel(base, data_type, rate, 0), offset);
-  }
-  else
-  {
-    throw std::runtime_error("Unknown generator type");
-  }
+    std::vector< std::string > parts = split( channel_name, "--" );
+    if ( parts.size( ) < 4 )
+        throw std::runtime_error(
+            "Generator name has too few parts, invalid input" );
+    int data_type = 0;
+    {
+        std::istringstream is( parts[ parts.size( ) - 2 ] );
+        is >> data_type;
+    }
+    int rate = 0;
+    {
+        std::istringstream is( parts[ parts.size( ) - 1 ] );
+        is >> rate;
+    }
+    if ( !is_data_type_valid( data_type ) || rate < 16 )
+        throw std::runtime_error( "Invalid data type or rate found" );
+    std::string& base = parts[ 0 ];
+    std::string& name = parts[ 1 ];
+    int          arg_count =
+        parts.size( ) - 4; // ignore base channel name, data type, rate
+    if ( name == "gpssoff1p" && arg_count == 1 )
+    {
+        std::istringstream is( parts[ 2 ] );
+        int                offset = 0;
+        is >> offset;
+        return create_generic_generator< Generators::GPSSecondWithOffset >(
+            data_type, SimChannel( base, data_type, rate, 0 ), offset );
+    }
+    else if ( name == "gpssmd100koff1p" && arg_count == 1 )
+    {
+        std::istringstream is( parts[ 2 ] );
+        int                offset = 0;
+        is >> offset;
+        return create_generic_generator< Generators::GPSMod100kSecWithOffset >(
+            data_type, SimChannel( base, data_type, rate, 0 ), offset );
+    }
+    else if ( name == "gpssmd100koffc1p" && arg_count == 1 )
+    {
+        std::istringstream is( parts[ 2 ] );
+        int                offset = 0;
+        is >> offset;
+        return create_generic_generator<
+            Generators::GPSMod100kSecWithOffsetAndCycle >(
+            data_type, SimChannel( base, data_type, rate, 0 ), offset );
+    }
+    else if ( name == "gpssmd30koff1p" && arg_count == 1 )
+    {
+        std::istringstream is( parts[ 2 ] );
+        int                offset = 0;
+        is >> offset;
+        return create_generic_generator< Generators::GPSMod30kSecWithOffset >(
+            data_type, SimChannel( base, data_type, rate, 0 ), offset );
+    }
+    else if ( name == "gpssmd100offc1p" && arg_count == 1 )
+    {
+        std::istringstream is( parts[ 2 ] );
+        int                offset = 0;
+        is >> offset;
+        return create_generic_generator<
+            Generators::GPSMod100SecWithOffsetAndCycle >(
+            data_type, SimChannel( base, data_type, rate, 0 ), offset );
+    }
+    else
+    {
+        throw std::runtime_error( "Unknown generator type" );
+    }
 }
diff --git a/src/fe_stream_test/fe_stream_generator.hh b/src/fe_stream_test/fe_stream_generator.hh
index d08b81fa3..955a67897 100644
--- a/src/fe_stream_test/fe_stream_generator.hh
+++ b/src/fe_stream_test/fe_stream_generator.hh
@@ -289,6 +289,47 @@ namespace Generators
         }
     };
 
+    template < typename T >
+    class GPSMod100kSecWithOffsetAndCycle : public SimChannelGenerator
+    {
+        int offset_;
+
+    public:
+        GPSMod100kSecWithOffsetAndCycle( const SimChannel& ch, int offset )
+            : SimChannelGenerator( ch ), offset_( offset )
+        {
+        }
+
+        std::string
+        generator_name( ) const
+        {
+            return "gpssmd100koffc1p";
+        }
+
+        std::string
+        other_params( ) const
+        {
+            std::ostringstream os;
+            os << "--" << offset_;
+            return os.str( );
+        }
+
+        char*
+        generate( int gps_sec, int gps_nano, char* out )
+        {
+            int rate = data_rate( ) / 16;
+            T*  out_ = reinterpret_cast< T* >( out );
+            for ( int i = 0; i < rate; ++i )
+            {
+                *out_ =
+                    static_cast< T >( ( gps_sec % 100000 ) + offset_ ) * 100 +
+                    gps_nano;
+                ++out_;
+            }
+            return reinterpret_cast< char* >( out_ );
+        }
+    };
+
     template < typename T >
     class GPSMod30kSecWithOffset : public SimChannelGenerator
     {
@@ -328,6 +369,50 @@ namespace Generators
         }
     };
 
+    template < typename T >
+    class GPSMod100SecWithOffsetAndCycle : public SimChannelGenerator
+    {
+        int offset_;
+
+    public:
+        GPSMod100SecWithOffsetAndCycle( const SimChannel& ch, int offset )
+            : SimChannelGenerator( ch ), offset_( offset )
+        {
+        }
+
+        std::string
+        generator_name( ) const
+        {
+            return "gpssmd100offc1p";
+        }
+
+        std::string
+        other_params( ) const
+        {
+            std::ostringstream os;
+            os << "--" << offset_;
+            return os.str( );
+        }
+
+        char*
+        generate( int gps_sec, int gps_nano, char* out )
+        {
+            int rate = data_rate( ) / 16;
+            T*  out_ = reinterpret_cast< T* >( out );
+            if ( gps_nano > 15 )
+            {
+                gps_nano /= 62500000;
+            }
+            for ( int i = 0; i < rate; ++i )
+            {
+                *out_ = static_cast< T >( ( gps_sec % 100 ) + offset_ ) * 100 +
+                    gps_nano;
+                ++out_;
+            }
+            return reinterpret_cast< char* >( out_ );
+        }
+    };
+
     template < typename T >
     class StaticValue : public SimChannelGenerator
     {
diff --git a/src/fe_stream_test/lookup_chan_from_ini.py b/src/fe_stream_test/lookup_chan_from_ini.py
new file mode 100644
index 000000000..b32764055
--- /dev/null
+++ b/src/fe_stream_test/lookup_chan_from_ini.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python3
+
+import argparse
+import json
+import sys
+
+
+class channel_info(object):
+    def __init__(self, name=None, dcu=-1, datarate=0, datatype=0, offset=0):
+        self.name = name
+        self.dcu = dcu
+        self.datarate = datarate
+        self.datatype = datatype
+        self.offset = offset
+
+    def copy(self):
+        return channel_info(name=self.name, dcu=self.dcu, datarate=self.datarate, datatype=self.datatype, offset=self.offset)
+
+    def size(self):
+        return datatype_size(self.datatype) * (self.datarate // 16)
+
+    def __unicode__(self):
+        data = {
+            'name': self.name,
+            'dcu': self.dcu,
+            'datarate': self.datarate,
+            'datatype': self.datatype,
+            'datatypename': datatypename(self.datatype),
+            'datatypesize': datatype_size(self.datatype),
+            'size': self.size(),
+            'offset': self.offset,
+        }
+        return json.dumps(data)
+
+    def __str__(self):
+        return self.__unicode__()
+
+
+def datatypename(data_type):
+    mapping = {
+        1: 'int16',
+        2: 'int32',
+        3: 'int64',
+        4: 'float32',
+        5: 'float64',
+        6: 'complex',
+        7: 'uint32',
+    }
+    return mapping[data_type]
+
+
+def datatype_size(data_type):
+    if data_type == 1:
+        return 2
+    elif data_type in (2,4,7):
+        return 4
+    elif data_type in (3, 5, 6):
+        return 8
+    raise RuntimeError("Invalid datatype {0}".format(data_type))
+
+
+print(sys.argv)
+
+
+parser = argparse.ArgumentParser(description="Lookup channel information from an ini file")
+parser.add_argument("-i", "--ini", help="Specify the ini file")
+parser.add_argument("-c", "--channel", help="Specify the channel")
+
+args = parser.parse_args()
+
+channels = []
+
+default_settings = channel_info()
+
+with open(args.ini, "rt") as f:
+    cur_info = default_settings.copy()
+    cur_offset = 0
+
+    for line in f:
+        line = line.strip()
+        if line == "":
+            continue
+        if line.startswith("["):
+            if not line.endswith("]"):
+                raise RuntimeError("Unparsable line {0}".format(line))
+
+            # first store the old entry
+            if cur_info.name == 'default':
+                default_settings = cur_info
+                default_settings.offset = 0
+            elif cur_info.name is not None:
+                channels.append(cur_info)
+                cur_offset += cur_info.size()
+
+            cur_info = default_settings.copy()
+            cur_info.name = line[1:-1]
+            cur_info.offset = cur_offset
+        else:
+            parts = line.split("=")
+            if len(parts) != 2:
+                continue
+            if parts[0] == "dcuid":
+                cur_info.dcu = int(parts[1])
+            elif parts[0] == "datatype":
+                cur_info.datatype = int(parts[1])
+            elif parts[0] == "datarate":
+                cur_info.datarate = int(parts[1])
+
+    if cur_info.name is not None:
+        channels.append("{0}".format(cur_info))
+
+for entry in channels:
+    print(entry)
-- 
GitLab