diff --git a/grc/CMakeLists.txt b/grc/CMakeLists.txt
index 2cb52e997313825379a3758515b466f0f08d692d..c5e55511dd9c9d54ddd6148d80c8f31dbc4635f2 100644
--- a/grc/CMakeLists.txt
+++ b/grc/CMakeLists.txt
@@ -11,5 +11,6 @@ install(FILES
     cdc_phy_tx.block.yml
     cdc_phy_rx.block.yml
     cdc_insert_burst_c.block.yml
-    cdc_dsa_pu_scenario_cc.block.yml DESTINATION share/gnuradio/grc/blocks
+    cdc_dsa_pu_scenario_cc.block.yml
+    cdc_dsa_stats.block.yml DESTINATION share/gnuradio/grc/blocks
 )
diff --git a/grc/cdc_dsa_stats.block.yml b/grc/cdc_dsa_stats.block.yml
new file mode 100644
index 0000000000000000000000000000000000000000..766349406ccf727c865003a149e2fa1273def0c7
--- /dev/null
+++ b/grc/cdc_dsa_stats.block.yml
@@ -0,0 +1,43 @@
+id: cdc_dsa_stats
+label: 'CDC DSA Stats'
+category: '[CDC]'
+
+templates:
+  imports: from gnuradio import cdc
+  make: cdc.dsa_stats(${period}, ${primary}, ${tput_offered})
+  callbacks:
+  - reset_stats(${reset})
+  - set_tput_offered(${tput_offered})
+
+parameters:
+- id: period
+  label: Period
+  default: ' 1'
+  dtype: int
+- id: primary
+  label: 'Is primary'
+  default: True
+  dtype: bool
+- id: tput_offered
+  label: 'Offered PU Throughput'
+  default: 0.0
+  dtype: float
+- id: reset
+  label: 'Reset Stats'
+  default: False
+  dtype: bool
+
+inputs:
+- label: pdu
+  domain: message
+  optional: True
+- label: stats_in
+  domain: message
+  optional: True
+
+outputs:
+- label: stats
+  domain: message
+  optional: True
+
+file_format: 1
diff --git a/include/gnuradio/cdc/CMakeLists.txt b/include/gnuradio/cdc/CMakeLists.txt
index 91025a06f98fa23e2f0adeabeaa95dcf8916de8b..266b4c50c49828dd0512d214c6d153ee78fd1e6d 100644
--- a/include/gnuradio/cdc/CMakeLists.txt
+++ b/include/gnuradio/cdc/CMakeLists.txt
@@ -14,5 +14,6 @@ install(FILES
     tag_tx_burst_cc.h
     preamble_detect_cc.h
     insert_burst_c.h
-    dsa_pu_scenario_cc.h DESTINATION include/gnuradio/cdc
+    dsa_pu_scenario_cc.h
+    dsa_stats.h DESTINATION include/gnuradio/cdc
 )
diff --git a/include/gnuradio/cdc/dsa_stats.h b/include/gnuradio/cdc/dsa_stats.h
new file mode 100644
index 0000000000000000000000000000000000000000..8520d3d53de33ba73a99c8753f868fcbb29c9ace
--- /dev/null
+++ b/include/gnuradio/cdc/dsa_stats.h
@@ -0,0 +1,40 @@
+/* -*- c++ -*- */
+/*
+ * Copyright 2023 University of Melbourne.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#ifndef INCLUDED_CDC_DSA_STATS_H
+#define INCLUDED_CDC_DSA_STATS_H
+
+#include <gnuradio/block.h>
+#include <gnuradio/cdc/api.h>
+
+namespace gr {
+namespace cdc {
+
+/*!
+ * \brief Track DSA scenario statistics.
+ * \ingroup cdc
+ *
+ */
+class CDC_API dsa_stats : virtual public gr::block {
+public:
+    typedef std::shared_ptr<dsa_stats> sptr;
+
+    static sptr make(int period = 1,
+                     bool primary = true,
+                     double tput_offered = 0.0);
+
+    virtual void reset_stats(bool reset) = 0;
+
+    virtual void set_tput_offered(double tput_offered) = 0;
+
+    virtual double get_tput_offered(void) = 0;
+};
+
+} // namespace cdc
+} // namespace gr
+
+#endif /* INCLUDED_CDC_DSA_STATS_H */
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 0066e1d356f52a930796a404a695362df4d75b76..32e66d09f9708dcea5a6d80a3e5d71fb2e2a931f 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -14,7 +14,8 @@ list(APPEND cdc_sources
     tag_tx_burst_cc_impl.cc
     preamble_detect_cc_impl.cc
     insert_burst_c_impl.cc
-    dsa_pu_scenario_cc_impl.cc )
+    dsa_pu_scenario_cc_impl.cc
+    dsa_stats_impl.cc )
 
 set(cdc_sources "${cdc_sources}" PARENT_SCOPE)
 if(NOT cdc_sources)
diff --git a/lib/dsa_pu_scenario_cc_impl.cc b/lib/dsa_pu_scenario_cc_impl.cc
index c8e8ae51555cafc56b53d0cb2bb743c88073b5c1..556f3b525cace5203455ae70fd45d9a7c92413c1 100644
--- a/lib/dsa_pu_scenario_cc_impl.cc
+++ b/lib/dsa_pu_scenario_cc_impl.cc
@@ -32,9 +32,9 @@ dsa_pu_scenario_cc_impl::dsa_pu_scenario_cc_impl(int samp_rate,
           gr::io_signature::make(1, -1, sizeof(gr_complex))),
       d_samp_rate(samp_rate),
       d_duration_ms(duration_ms),
-      d_engine(seed),
       d_random(random),
-      d_scenario(scenario)
+      d_scenario(scenario),
+      d_engine(seed)
 {
     message_port_register_out(pmt::mp("mode"));
 }
@@ -83,7 +83,7 @@ dsa_pu_scenario_cc_impl::work(int noutput_items,
         if (d_active[i_chan])
             memcpy(out, in, n_samps * sizeof(gr_complex));
         else
-            memset(out, 0x0, n_samps * sizeof(gr_complex));
+            memset((void *)out, 0x0, n_samps * sizeof(gr_complex));
     }
     d_samp_cnt -= n_samps;
 
diff --git a/lib/dsa_pu_scenario_cc_impl.h b/lib/dsa_pu_scenario_cc_impl.h
index 8dcd25aad3908e60e8f84c2dc488484eb2ef1605..77afc6e89526e0753af1703c60ada689579ed55d 100644
--- a/lib/dsa_pu_scenario_cc_impl.h
+++ b/lib/dsa_pu_scenario_cc_impl.h
@@ -44,7 +44,7 @@ public:
     
     void set_random(bool random) { d_random = random; };
     
-    bool get_random(void) { return random; };
+    bool get_random(void) { return d_random; };
     
     void set_duration_ms(float duration_ms) { d_duration_ms = duration_ms; };
     
diff --git a/lib/dsa_stats_impl.cc b/lib/dsa_stats_impl.cc
new file mode 100644
index 0000000000000000000000000000000000000000..8f07e32942b21a39c34ebe5b042ad08c86b7ef76
--- /dev/null
+++ b/lib/dsa_stats_impl.cc
@@ -0,0 +1,154 @@
+/* -*- c++ -*- */
+/*
+ * Copyright 2023 University of Melbourne.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "dsa_stats_impl.h"
+#include <gnuradio/io_signature.h>
+
+using std::chrono::steady_clock;
+using std::chrono::duration;
+
+namespace gr {
+namespace cdc {
+
+dsa_stats::sptr dsa_stats::make(int period,
+                                bool primary,
+                                double tput_offered)
+{
+    return gnuradio::make_block_sptr<dsa_stats_impl>(
+        period, primary, tput_offered);
+}
+
+dsa_stats_impl::dsa_stats_impl(int period,
+                               bool primary,
+                               double tput_offered)
+    : gr::block("dsa_stats",
+                gr::io_signature::make(0, 0, 0),
+                gr::io_signature::make(0, 0, 0)),
+      d_period(period),
+      d_primary(primary),
+      d_tput_offered(tput_offered)
+{
+    message_port_register_in(pmt::mp("pdu"));
+    set_msg_handler(pmt::mp("pdu"),
+                    [this](const pmt::pmt_t& msg) { this->handle_pdu(msg); });
+    message_port_register_in(pmt::mp("stats_in"));
+    set_msg_handler(pmt::mp("stats_in"),
+                    [this](const pmt::pmt_t& msg) { this->handle_stats_in(msg); });
+    message_port_register_out(pmt::mp("stats"));
+}
+
+dsa_stats_impl::~dsa_stats_impl() {}
+
+bool dsa_stats_impl::start()
+{
+    d_finished = false;
+
+    d_thread = std::shared_ptr<gr::thread::thread>(
+        new gr::thread::thread(boost::bind(&dsa_stats_impl::run, this)));
+
+    return block::start();
+}
+
+bool dsa_stats_impl::stop()
+{
+    // Shut down the thread
+    {
+        gr::thread::scoped_lock lock(d_mtx);
+        d_finished = true;
+    }
+    d_thread->interrupt();
+    d_thread->join();
+
+    return block::stop();
+}
+
+void dsa_stats_impl::run()
+{
+    bool finished = false;
+    reset_stats();
+    
+    while (!finished) {
+        // elapsed time
+        auto now = steady_clock::now();
+        duration<double> dur_cur = (now - d_last_tp);
+        duration<double> dur_avg = (now - d_start_tp);
+        d_last_tp = now;
+
+        // get current stats
+        double bytes_new = 0;
+        double tput_remote = 0;
+        {
+            gr::thread::scoped_lock lock(d_mtx);
+            
+            bytes_new = d_bytes_new;
+            tput_remote = d_tput_remote;
+            d_bytes_new = 0;
+        }
+        d_bytes += bytes_new;
+        double tput_cur = 8.0*bytes_new / dur_cur.count();
+        double tput_avg = 8.0*d_bytes / dur_avg.count();
+        
+        double tput_pu = d_primary ? tput_avg : tput_remote;
+        double tput_su = d_primary ? tput_remote : tput_avg;
+        double score = 0.0;
+        if (tput_pu > 0) {
+            score = exp(-10.0*(d_tput_offered - tput_pu)/tput_pu) * tput_su;
+        }
+
+        // publish stats message
+        pmt::pmt_t stats = pmt::make_dict();
+        stats = pmt::dict_add(stats,
+                              pmt::mp("tput_cur"),
+                              pmt::from_double(tput_cur));
+        stats = pmt::dict_add(stats,
+                              pmt::mp("tput_avg"),
+                              pmt::from_double(tput_avg));
+        stats = pmt::dict_add(stats,
+                              pmt::mp("score"),
+                              pmt::from_double(score));
+        message_port_pub(pmt::mp("stats"), stats);
+        
+        // sleep for 'period' second
+        boost::this_thread::sleep(
+            boost::posix_time::seconds(static_cast<long>(d_period)));
+    
+        {
+            gr::thread::scoped_lock lock(d_mtx);
+            finished = d_finished;
+        }
+    }
+}
+
+void dsa_stats_impl::handle_pdu(const pmt::pmt_t& msg)
+{
+    gr::thread::scoped_lock lock(d_mtx);
+    d_bytes_new += pmt::blob_length(pmt::cdr(msg));
+}
+
+void dsa_stats_impl::handle_stats_in(const pmt::pmt_t& msg)
+{
+    gr::thread::scoped_lock lock(d_mtx);
+    d_tput_remote = pmt::to_double(pmt::dict_ref(msg,
+                                                 pmt::mp("tput_avg"),
+                                                 pmt::from_double(0.0)));
+}
+
+void dsa_stats_impl::reset_stats(bool reset)
+{
+    if (reset)
+    {
+        gr::thread::scoped_lock lock(d_mtx);
+
+        d_bytes_new = 0.0;
+        d_bytes = 0.0;
+        d_start_tp = steady_clock::now();
+        d_last_tp = d_start_tp;
+    }
+}
+
+} /* namespace cdc */
+} /* namespace gr */
diff --git a/lib/dsa_stats_impl.h b/lib/dsa_stats_impl.h
new file mode 100644
index 0000000000000000000000000000000000000000..76412852fa5c6738fa40446c013c62f2bca83a47
--- /dev/null
+++ b/lib/dsa_stats_impl.h
@@ -0,0 +1,64 @@
+/* -*- c++ -*- */
+/*
+ * Copyright 2023 University of Melbourne.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#ifndef INCLUDED_CDC_DSA_STATS_IMPL_H
+#define INCLUDED_CDC_DSA_STATS_IMPL_H
+
+#include <gnuradio/cdc/dsa_stats.h>
+#include <chrono>
+
+using std::chrono::time_point;
+using std::chrono::steady_clock;
+
+namespace gr {
+namespace cdc {
+
+class dsa_stats_impl : public dsa_stats
+{
+private:
+    std::shared_ptr<gr::thread::thread> d_thread;
+    bool d_finished;
+    long d_period;
+    bool d_primary;
+    double d_tput_offered;
+
+    // stat variables
+    gr::thread::mutex d_mtx;
+    time_point<steady_clock> d_start_tp;
+    time_point<steady_clock> d_last_tp;
+    double d_bytes_new = 0.0;
+    double d_bytes = 0.0;
+    double d_tput_remote = 0.0;
+
+    void run();
+
+    void handle_pdu(const pmt::pmt_t& pdu);
+
+    void handle_stats_in(const pmt::pmt_t& pdu);
+
+public:
+    dsa_stats_impl(int period,
+                   bool primary,
+                   double tput_offered);
+
+    ~dsa_stats_impl();
+
+    void reset_stats(bool reset=true);
+
+    double get_tput_offered(void) { return d_tput_offered; }
+
+    void set_tput_offered(double tput_offered) { d_tput_offered = tput_offered; }
+
+    // Overload gr::block start/stop to start internal stats thread
+    bool start();
+    bool stop();
+};
+
+} // namespace cdc
+} // namespace gr
+
+#endif /* INCLUDED_CDC_DSA_STATS_IMPL_H */
diff --git a/python/cdc/bindings/CMakeLists.txt b/python/cdc/bindings/CMakeLists.txt
index a955da9cde1118b906c2b700ba85fe85df10d797..557fceb8f0134b81ec2170a621e7bbb4dad84622 100644
--- a/python/cdc/bindings/CMakeLists.txt
+++ b/python/cdc/bindings/CMakeLists.txt
@@ -31,7 +31,8 @@ list(APPEND cdc_python_files
     tag_tx_burst_cc_python.cc
     preamble_detect_cc_python.cc
     insert_burst_c_python.cc
-    dsa_pu_scenario_cc_python.cc python_bindings.cc)
+    dsa_pu_scenario_cc_python.cc
+    dsa_stats_python.cc python_bindings.cc)
 
 GR_PYBIND_MAKE_OOT(cdc
    ../../..
diff --git a/python/cdc/bindings/docstrings/dsa_stats_pydoc_template.h b/python/cdc/bindings/docstrings/dsa_stats_pydoc_template.h
new file mode 100644
index 0000000000000000000000000000000000000000..5a8455aa3a94597c4c3d6ca524973946be420d44
--- /dev/null
+++ b/python/cdc/bindings/docstrings/dsa_stats_pydoc_template.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 Free Software Foundation, Inc.
+ *
+ * This file is part of GNU Radio
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+#include "pydoc_macros.h"
+#define D(...) DOC(gr,cdc, __VA_ARGS__ )
+/*
+  This file contains placeholders for docstrings for the Python bindings.
+  Do not edit! These were automatically extracted during the binding process
+  and will be overwritten during the build process
+ */
+
+
+ 
+ static const char *__doc_gr_cdc_dsa_stats = R"doc()doc";
+
+
+ static const char *__doc_gr_cdc_dsa_stats_dsa_stats_0 = R"doc()doc";
+
+
+ static const char *__doc_gr_cdc_dsa_stats_dsa_stats_1 = R"doc()doc";
+
+
+ static const char *__doc_gr_cdc_dsa_stats_make = R"doc()doc";
+
+
+ static const char *__doc_gr_cdc_dsa_stats_reset_stats = R"doc()doc";
+
+
+ static const char *__doc_gr_cdc_dsa_stats_set_tput_offered = R"doc()doc";
+
+
+ static const char *__doc_gr_cdc_dsa_stats_get_tput_offered = R"doc()doc";
+
+  
diff --git a/python/cdc/bindings/dsa_stats_python.cc b/python/cdc/bindings/dsa_stats_python.cc
new file mode 100644
index 0000000000000000000000000000000000000000..3e09297ac0cee89a65fbba6135f876b6542a8ed2
--- /dev/null
+++ b/python/cdc/bindings/dsa_stats_python.cc
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2023 Free Software Foundation, Inc.
+ *
+ * This file is part of GNU Radio
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+/***********************************************************************************/
+/* This file is automatically generated using bindtool and can be manually edited  */
+/* The following lines can be configured to regenerate this file during cmake      */
+/* If manual edits are made, the following tags should be modified accordingly.    */
+/* BINDTOOL_GEN_AUTOMATIC(0)                                                       */
+/* BINDTOOL_USE_PYGCCXML(0)                                                        */
+/* BINDTOOL_HEADER_FILE(dsa_stats.h)                                        */
+/* BINDTOOL_HEADER_FILE_HASH(9bbbbaa16987fedac4730cab7eb42ec5)                     */
+/***********************************************************************************/
+
+#include <pybind11/complex.h>
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+
+namespace py = pybind11;
+
+#include <gnuradio/cdc/dsa_stats.h>
+// pydoc.h is automatically generated in the build directory
+#include <dsa_stats_pydoc.h>
+
+void bind_dsa_stats(py::module& m)
+{
+
+    using dsa_stats    = ::gr::cdc::dsa_stats;
+
+
+    py::class_<dsa_stats, gr::block, gr::basic_block,
+        std::shared_ptr<dsa_stats>>(m, "dsa_stats", D(dsa_stats))
+
+        .def(py::init(&dsa_stats::make),
+           py::arg("period") = 1,
+           py::arg("primary") = true,
+           py::arg("tput_offered") = 0.,
+           D(dsa_stats,make)
+        )
+        
+
+
+
+
+        
+        .def("reset_stats",&dsa_stats::reset_stats,       
+            py::arg("reset"),
+            D(dsa_stats,reset_stats)
+        )
+
+
+        
+        .def("set_tput_offered",&dsa_stats::set_tput_offered,       
+            py::arg("tput_offered"),
+            D(dsa_stats,set_tput_offered)
+        )
+
+
+        
+        .def("get_tput_offered",&dsa_stats::get_tput_offered,       
+            D(dsa_stats,get_tput_offered)
+        )
+
+        ;
+
+
+
+
+}
+
+
+
+
+
+
+
+
diff --git a/python/cdc/bindings/python_bindings.cc b/python/cdc/bindings/python_bindings.cc
index 9f463592a128d75d3cf6e20c6b95d8a144917e29..8e0aa7aba74571e317a2467b93d5b2542140e30e 100644
--- a/python/cdc/bindings/python_bindings.cc
+++ b/python/cdc/bindings/python_bindings.cc
@@ -25,6 +25,7 @@ namespace py = pybind11;
     void bind_preamble_detect_cc(py::module& m);
     void bind_insert_burst_c(py::module& m);
     void bind_dsa_pu_scenario_cc(py::module& m);
+    void bind_dsa_stats(py::module& m);
 // ) END BINDING_FUNCTION_PROTOTYPES
 
 
@@ -57,5 +58,6 @@ PYBIND11_MODULE(cdc_python, m)
     bind_preamble_detect_cc(m);
     bind_insert_burst_c(m);
     bind_dsa_pu_scenario_cc(m);
+    bind_dsa_stats(m);
     // ) END BINDING_FUNCTION_CALLS
 }
\ No newline at end of file