diff --git a/algo/detectors/sts/UnpackSts.cxx b/algo/detectors/sts/UnpackSts.cxx
index e1dd3dc806e695d898a71debcd8dde8e68aae02f..eeb33bf047390c30084cfea30bf66dd66a32ef05 100644
--- a/algo/detectors/sts/UnpackSts.cxx
+++ b/algo/detectors/sts/UnpackSts.cxx
@@ -24,39 +24,40 @@ namespace cbm::algo
   {
 
     // --- Output data
-    vector<CbmStsDigi> digiVec   = {};
-    UnpackStsMonitorData monitor = {};
+    resultType result = {};
 
     // --- Current Timeslice time and TS_MSB epoch cycle
     fCurrentTsTime = tTimeslice;
     auto msTime    = msDescr.idx;  // Unix time of MS in ns
     fCurrentCycle  = std::ldiv(msTime, fkCycleLength).quot;
 
+    // --- Number of messages in microslice
+    auto msSize = msDescr.size;
+    if (msSize % sizeof(stsxyter::Message) != 0) {
+      result.second.fNumErrInvalidMsSize++;
+      return result;
+    }
+    const uint32_t numMessages = msSize / sizeof(stsxyter::Message);
+    if (numMessages < 2) {
+      result.second.fNumErrInvalidMsSize++;
+      return result;
+    }
+
     // --- Interpret MS content as sequence of SMX messages
     auto message = reinterpret_cast<const stsxyter::Message*>(msContent);
 
     // --- The first message in the MS is expected to be of type EPOCH and can be ignored
     if (message[0].GetMessType() != stsxyter::MessType::Epoch) {
-      std::cout << "Error: UnpackSts: First message type is " << uint16_t(message[0].GetMessType()) << std::endl;
-      monitor.fNumErrInvalidFirstMessage++;
-      return std::make_pair(digiVec, monitor);
+      result.second.fNumErrInvalidFirstMessage++;
+      return result;
     }
 
     // --- The second message must be of type ts_msb
     if (message[1].GetMessType() != stsxyter::MessType::TsMsb) {
-      std::cout << "Error: UnpackSts: Second message type is " << uint16_t(message[0].GetMessType()) << std::endl;
-      monitor.fNumErrInvalidFirstMessage++;
-      return std::make_pair(digiVec, monitor);
+      result.second.fNumErrInvalidFirstMessage++;
+      return result;
     }
-    ProcessTsmsbMessage(message[1], monitor);
-
-    // --- Number of messages in microslice
-    auto msSize = msDescr.size;
-    if (msSize % sizeof(stsxyter::Message) != 0) {
-      monitor.fNumErrInvalidMsSize++;
-      return std::make_pair(digiVec, monitor);
-    }
-    const uint32_t numMessages = msSize / sizeof(stsxyter::Message);
+    ProcessTsmsbMessage(message[1], result.second);
 
     // --- Message loop
     for (uint32_t messageNr = 2; messageNr < numMessages; messageNr++) {
@@ -65,15 +66,15 @@ namespace cbm::algo
       switch (message[messageNr].GetMessType()) {
 
         case stsxyter::MessType::Hit: {
-          ProcessHitMessage(message[messageNr], digiVec, monitor);
+          ProcessHitMessage(message[messageNr], result.first, result.second);
           break;
         }
         case stsxyter::MessType::TsMsb: {
-          ProcessTsmsbMessage(message[messageNr], monitor);
+          ProcessTsmsbMessage(message[messageNr], result.second);
           break;
         }
         default: {
-          monitor.fNumNonHitOrTsbMessage++;
+          result.second.fNumNonHitOrTsbMessage++;
           break;
         }
 
@@ -81,14 +82,14 @@ namespace cbm::algo
 
     }  //# Messages
 
-    return std::make_pair(digiVec, monitor);
+    return result;
   }
   // --------------------------------------------------------------------------
 
 
   // -----   Process hit message   --------------------------------------------
-  void UnpackSts::ProcessHitMessage(const stsxyter::Message& message, vector<CbmStsDigi>& digiVec,
-                                    UnpackStsMonitorData& monitor) const
+  inline void UnpackSts::ProcessHitMessage(const stsxyter::Message& message, vector<CbmStsDigi>& digiVec,
+                                           UnpackStsMonitorData& monitor) const
   {
 
     // --- Check eLink and get parameters
@@ -129,7 +130,7 @@ namespace cbm::algo
 
 
   // -----   Process an epoch (TS_MSB) message   ------------------------------
-  void UnpackSts::ProcessTsmsbMessage(const stsxyter::Message& message, UnpackStsMonitorData& monitor)
+  inline void UnpackSts::ProcessTsmsbMessage(const stsxyter::Message& message, UnpackStsMonitorData& monitor)
   {
 
     auto epoch = message.GetTsMsbValBinning();
diff --git a/algo/detectors/sts/UnpackSts.h b/algo/detectors/sts/UnpackSts.h
index 96e3dd483448b11d642a7926870c3977758171aa..05989d375367dcc617c39b117e3a59c0f97c263f 100644
--- a/algo/detectors/sts/UnpackSts.h
+++ b/algo/detectors/sts/UnpackSts.h
@@ -57,7 +57,7 @@ namespace cbm::algo
   struct UnpackStsMonitorData {
     uint32_t fNumNonHitOrTsbMessage     = 0;
     uint32_t fNumErrElinkOutOfRange     = 0;  ///< Elink not contained in parameters
-    uint32_t fNumErrInvalidFirstMessage = 0;  ///< First message is not TS_MSB
+    uint32_t fNumErrInvalidFirstMessage = 0;  ///< First message is not TS_MSB or second is not EPOCH
     uint32_t fNumErrInvalidMsSize       = 0;  ///< Microslice size is not multiple of message size
     uint32_t fNumErrTimestampOverflow   = 0;  ///< Overflow in 64 bit time stamp
     bool HasErrors()
diff --git a/reco/tasks/CbmTaskUnpack.cxx b/reco/tasks/CbmTaskUnpack.cxx
index ecca0e5e8884e0354ca259ed18d1faab93ddb8e3..727dca5bd5e9af56aca051c32d65ea5dd5473736 100644
--- a/reco/tasks/CbmTaskUnpack.cxx
+++ b/reco/tasks/CbmTaskUnpack.cxx
@@ -66,33 +66,42 @@ void CbmTaskUnpack::Exec(Option_t*)
   TStopwatch compTimer;
   timer.Start();
   size_t numMs    = 0;
+  size_t numBytes = 0;
   size_t numDigis = 0;
 
   // --- Timeslice properties
-  uint64_t tsIndex     = timeslice->index();
-  uint64_t tsTime      = timeslice->start_time();
-  uint64_t numComp     = timeslice->num_components();
-  uint64_t numCompUsed = 0;
+  const uint64_t tsIndex = timeslice->index();
+  const uint64_t tsTime  = timeslice->start_time();
+  const uint64_t numComp = timeslice->num_components();
+  uint64_t numCompUsed   = 0;
 
   // ---  Component loop
   for (uint64_t comp = 0; comp < numComp; comp++) {
 
-    uint8_t systemId = timeslice->descriptor(comp, 0).sys_id;
-    if (systemId == static_cast<uint8_t>(fles::SubsystemIdentifier::STS)) {
-      uint16_t equipmentId = timeslice->descriptor(comp, 0).eq_id;
-      auto algoIt          = fAlgoSts.find(equipmentId);
+    auto systemId = static_cast<fles::SubsystemIdentifier>(timeslice->descriptor(comp, 0).sys_id);
+    if (systemId == fles::SubsystemIdentifier::STS) {
+      const uint16_t equipmentId = timeslice->descriptor(comp, 0).eq_id;
+      const auto algoIt          = fAlgoSts.find(equipmentId);
       assert(algoIt != fAlgoSts.end());
 
+      // The current algorithm works for the STS data format version 0x20 used in 2021.
+      // Other versions are not yet supported.
+      // In the future, different data formats will be supported by instantiating different
+      // algorithms depending on the version.
+      assert(timeslice->descriptor(comp, 0).sys_ver == 0x20);
+
       // --- Component log
+      size_t numBytesInComp = 0;
       size_t numDigisInComp = 0;
       compTimer.Start();
 
       // --- Microslice loop
       uint64_t numMsInComp = timeslice->num_microslices(comp);
       for (uint64_t mslice = 0; mslice < numMsInComp; mslice++) {
-        auto msDescriptor = timeslice->descriptor(comp, mslice);
-        auto msContent    = timeslice->content(comp, mslice);
-        auto result       = (algoIt->second)(msContent, msDescriptor, tsTime);
+        const auto msDescriptor = timeslice->descriptor(comp, mslice);
+        const auto msContent    = timeslice->content(comp, mslice);
+        numBytesInComp += msDescriptor.size;
+        auto result = (algoIt->second)(msContent, msDescriptor, tsTime);
         LOG(debug1) << GetName() << ": Component " << comp << ", microslice " << mslice << ", digis "
                     << result.first.size() << ", errors " << result.second.fNumNonHitOrTsbMessage << " | "
                     << result.second.fNumErrElinkOutOfRange << " | " << result.second.fNumErrInvalidFirstMessage
@@ -100,15 +109,17 @@ void CbmTaskUnpack::Exec(Option_t*)
                     << " | ";
         //std::move(result.first.begin(), result.first.end(), fTimeslice->fData.fSts.fDigis.end());
         // TODO: The above usage of std::move does not work (seg. fault). Would need advice.
-        auto it = fTimeslice->fData.fSts.fDigis.end();
+        const auto it = fTimeslice->fData.fSts.fDigis.end();
         fTimeslice->fData.fSts.fDigis.insert(it, result.first.begin(), result.first.end());
         numDigisInComp += result.first.size();
       }  //# microslice
 
       compTimer.Stop();
-      LOG(debug) << GetName() << ": Component " << comp << ", microslices " << numMsInComp << ", digis "
-                 << numDigisInComp << ", CPU time " << compTimer.CpuTime() * 1000. << " ms";
+      LOG(debug) << GetName() << ": Component " << comp << ", microslices " << numMsInComp << " input size "
+                 << numBytesInComp << " bytes, "
+                 << ", digis " << numDigisInComp << ", CPU time " << compTimer.CpuTime() * 1000. << " ms";
       numCompUsed++;
+      numBytes += numBytesInComp;
       numDigis += numDigisInComp;
       numMs += numMsInComp;
 
@@ -120,16 +131,18 @@ void CbmTaskUnpack::Exec(Option_t*)
   // --- Timeslice log
   timer.Stop();
   stringstream logOut;
-  logOut << setw(20) << left << GetName() << " [";
+  logOut << setw(15) << left << GetName() << " [";
   logOut << fixed << setw(8) << setprecision(1) << right << timer.RealTime() * 1000. << " ms] ";
   logOut << "TS " << fNumTs << " (index " << tsIndex << ")";
   logOut << ", components " << numCompUsed << " / " << numComp << ", microslices " << numMs;
+  logOut << ", input rate " << double(numBytes) / timer.RealTime() / 1.e6 << " MB/s";
   logOut << ", digis " << numDigis;
   LOG(info) << logOut.str();
 
   // --- Run statistics
   fNumTs++;
   fNumMs += numMs;
+  fNumBytes += numBytes;
   fNumDigis += numDigis;
   fTime += timer.RealTime();
 }
@@ -140,12 +153,15 @@ void CbmTaskUnpack::Exec(Option_t*)
 void CbmTaskUnpack::Finish()
 {
   std::cout << std::endl;
+  double timePerTs = 1000. * fTime / double(fNumTs);  // in ms
+  double rate      = fNumBytes / 1.e6 / fTime;        // in MB/s
   LOG(info) << "=====================================";
   LOG(info) << GetName() << ": Run summary";
-  LOG(info) << "Timeslices         : " << fNumTs;
-  LOG(info) << "Microslices        : " << fNumMs;
-  LOG(info) << "Digis              : " << fNumDigis;
-  LOG(info) << "Time  / TS         : " << fixed << setprecision(2) << 1000. * fTime / double(fNumTs) << " ms";
+  LOG(info) << "Timeslices     : " << fNumTs;
+  LOG(info) << "Microslices    : " << fNumMs;
+  LOG(info) << "Digis          : " << fNumDigis;
+  LOG(info) << "Av. input rate : " << fixed << setprecision(2) << rate << " MB/s";
+  LOG(info) << "Time / TS      : " << fixed << setprecision(2) << timePerTs << " ms";
   LOG(info) << "=====================================";
 }
 // ----------------------------------------------------------------------------
@@ -193,7 +209,7 @@ InitStatus CbmTaskUnpack::Init()
     std::unique_ptr<UnpackStsPar> par(new UnpackStsPar());
     par->fNumChansPerAsic   = numChansPerAsic;
     par->fNumAsicsPerModule = numAsicsPerModule;
-    size_t numElinks        = fStsConfig.GetNumElinks(equip);
+    const size_t numElinks  = fStsConfig.GetNumElinks(equip);
     for (size_t elink = 0; elink < numElinks; elink++) {
       UnpackStsElinkPar elinkPar;
       auto mapEntry        = fStsConfig.Map(equip, elink);
diff --git a/reco/tasks/CbmTaskUnpack.h b/reco/tasks/CbmTaskUnpack.h
index 9d5dbbd924c502e162302460457ba6bf9ae0e3b6..541a749a7bbbcc286d98b8e339aadf5362d498e3 100644
--- a/reco/tasks/CbmTaskUnpack.h
+++ b/reco/tasks/CbmTaskUnpack.h
@@ -72,6 +72,7 @@ private:  // members
   cbm::algo::StsReadoutConfig fStsConfig {};
   size_t fNumTs                = 0;
   size_t fNumMs                = 0;
+  size_t fNumBytes             = 0;
   size_t fNumDigis             = 0;
   double fTime                 = 0.;
   CbmDigiTimeslice* fTimeslice = nullptr;  ///< Output data