traffic-control: (fixes #2751) Ensure queue discs keep correct statistics
authorStefano Avallone <stavallo@unina.it>
Thu, 14 Sep 2017 18:12:19 +0200
changeset 13060 7670ef916b21
parent 13059 037e9046f940
child 13061 80f47eaeae23
traffic-control: (fixes #2751) Ensure queue discs keep correct statistics
CHANGES.html
RELEASE_NOTES
examples/traffic-control/traffic-control.cc
src/network/utils/queue.h
src/traffic-control/doc/queue-discs.rst
src/traffic-control/model/codel-queue-disc.cc
src/traffic-control/model/fq-codel-queue-disc.cc
src/traffic-control/model/pfifo-fast-queue-disc.cc
src/traffic-control/model/pie-queue-disc.cc
src/traffic-control/model/queue-disc.cc
src/traffic-control/model/queue-disc.h
src/traffic-control/model/red-queue-disc.cc
--- a/CHANGES.html	Wed Sep 13 23:11:38 2017 +0200
+++ b/CHANGES.html	Thu Sep 14 18:12:19 2017 +0200
@@ -95,6 +95,14 @@
 </li>
 <li>MqQueueDisc, a multi-queue aware queue disc modelled after the mq qdisc in Linux, has been introduced.
 </li>
+<li>Added <b>QueueDisc::GetStats()</b> which returns detailed statistics about the operations of a queue disc.
+    Consequently, a number of methods of the QueueDisc class have been removed: <b>GetTotalReceivedPackets()</b>,
+    <b>GetTotalReceivedBytes()</b>, <b>GetTotalDroppedPackets()</b>, <b>GetTotalDroppedBytes()</b>,
+    <b>GetTotalRequeuedPackets()</b>, <b>GetTotalRequeuedBytes()</b>.
+</li>
+<li>Two new methods, <b>QueueDisc::DropBeforeEnqueue()</b> and <b>QueueDisc::DropAfterDequeue()</b> have
+    been introduced to replace <b>QueueDisc::Drop()</b>. Correspondingly, two new trace sources have been added to the QueueDisc class: DropBeforeEnqueue and DropAfterDequeue.
+</li>
 </ul>
 <h2>Changes to existing API:</h2>
 <ul>
--- a/RELEASE_NOTES	Wed Sep 13 23:11:38 2017 +0200
+++ b/RELEASE_NOTES	Thu Sep 14 18:12:19 2017 +0200
@@ -107,6 +107,7 @@
 - Bug 2733 - Ideal wifi manager cannot handle NSS higher than 1
 - Bug 2741 - IPv4 fragmentation fails when last fragment have to be re-fragmented.
 - Bug 2744 - 802.11n/ac with RTS/CTS is crashing for a large number of nodes
+- Bug 2751 - QueueDisc::Enqueue() order of operations
 - Bug 2757 - 802.11n/ac/ax maximum TXOP is not properly enforced
 - Bug 2758 - IPv4 sockets bound to unicast receive also subnet-directed broadcasts
 - Bug 2759 - Packets sent to broadcast address are converted to subnet-directed broadcast
--- a/examples/traffic-control/traffic-control.cc	Wed Sep 13 23:11:38 2017 +0200
+++ b/examples/traffic-control/traffic-control.cc	Thu Sep 14 18:12:19 2017 +0200
@@ -170,11 +170,11 @@
   Ptr<Ipv4FlowClassifier> classifier = DynamicCast<Ipv4FlowClassifier> (flowmon.GetClassifier ());
   std::map<FlowId, FlowMonitor::FlowStats> stats = monitor->GetFlowStats ();
   std::cout << std::endl << "*** Flow monitor statistics ***" << std::endl;
-  std::cout << "  Tx Packets:   " << stats[1].txPackets << std::endl;
-  std::cout << "  Tx Bytes:   " << stats[1].txBytes << std::endl;
+  std::cout << "  Tx Packets/Bytes:   " << stats[1].txPackets
+            << " / " << stats[1].txBytes << std::endl;
   std::cout << "  Offered Load: " << stats[1].txBytes * 8.0 / (stats[1].timeLastTxPacket.GetSeconds () - stats[1].timeFirstTxPacket.GetSeconds ()) / 1000000 << " Mbps" << std::endl;
-  std::cout << "  Rx Packets:   " << stats[1].rxPackets << std::endl;
-  std::cout << "  Rx Bytes:   " << stats[1].rxBytes << std::endl;
+  std::cout << "  Rx Packets/Bytes:   " << stats[1].rxPackets
+            << " / " << stats[1].rxBytes << std::endl;
   uint32_t packetsDroppedByQueueDisc = 0;
   uint64_t bytesDroppedByQueueDisc = 0;
   if (stats[1].packetsDropped.size () > Ipv4FlowProbe::DROP_QUEUE_DISC)
@@ -182,8 +182,8 @@
       packetsDroppedByQueueDisc = stats[1].packetsDropped[Ipv4FlowProbe::DROP_QUEUE_DISC];
       bytesDroppedByQueueDisc = stats[1].bytesDropped[Ipv4FlowProbe::DROP_QUEUE_DISC];
     }
-  std::cout << "  Packets Dropped by Queue Disc:   " << packetsDroppedByQueueDisc << std::endl;
-  std::cout << "  Bytes Dropped by Queue Disc:   " << bytesDroppedByQueueDisc << std::endl;
+  std::cout << "  Packets/Bytes Dropped by Queue Disc:   " << packetsDroppedByQueueDisc
+            << " / " << bytesDroppedByQueueDisc << std::endl;
   uint32_t packetsDroppedByNetDevice = 0;
   uint64_t bytesDroppedByNetDevice = 0;
   if (stats[1].packetsDropped.size () > Ipv4FlowProbe::DROP_QUEUE)
@@ -191,8 +191,8 @@
       packetsDroppedByNetDevice = stats[1].packetsDropped[Ipv4FlowProbe::DROP_QUEUE];
       bytesDroppedByNetDevice = stats[1].bytesDropped[Ipv4FlowProbe::DROP_QUEUE];
     }
-  std::cout << "  Packets Dropped by NetDevice:   " << packetsDroppedByNetDevice << std::endl;
-  std::cout << "  Bytes Dropped by NetDevice:   " << bytesDroppedByNetDevice << std::endl;
+  std::cout << "  Packets/Bytes Dropped by NetDevice:   " << packetsDroppedByNetDevice
+            << " / " << bytesDroppedByNetDevice << std::endl;
   std::cout << "  Throughput: " << stats[1].rxBytes * 8.0 / (stats[1].timeLastRxPacket.GetSeconds () - stats[1].timeFirstRxPacket.GetSeconds ()) / 1000000 << " Mbps" << std::endl;
   std::cout << "  Mean delay:   " << stats[1].delaySum.GetSeconds () / stats[1].rxPackets << std::endl;
   std::cout << "  Mean jitter:   " << stats[1].jitterSum.GetSeconds () / (stats[1].rxPackets - 1) << std::endl;
@@ -212,10 +212,6 @@
   std::cout << "  Rx Bytes: " << totalPacketsThr << std::endl;
   std::cout << "  Average Goodput: " << thr << " Mbit/s" << std::endl;
   std::cout << std::endl << "*** TC Layer statistics ***" << std::endl;
-  std::cout << "  Packets dropped by the TC layer: " << q->GetTotalDroppedPackets () << std::endl;
-  std::cout << "  Bytes dropped by the TC layer: " << q->GetTotalDroppedBytes () << std::endl;
-  std::cout << "  Packets dropped by the netdevice: " << queue->GetTotalDroppedPackets () << std::endl;
-  std::cout << "  Packets requeued by the TC layer: " << q->GetTotalRequeuedPackets () << std::endl;
-
+  std::cout << q->GetStats () << std::endl;
   return 0;
 }
--- a/src/network/utils/queue.h	Wed Sep 13 23:11:38 2017 +0200
+++ b/src/network/utils/queue.h	Thu Sep 14 18:12:19 2017 +0200
@@ -552,6 +552,10 @@
       m_nBytes -= item->GetSize ();
       m_nPackets--;
 
+      // packets are first dequeued and then dropped
+      NS_LOG_LOGIC ("m_traceDequeue (p)");
+      m_traceDequeue (item);
+
       DropAfterDequeue (item);
     }
   return item;
--- a/src/traffic-control/doc/queue-discs.rst	Wed Sep 13 23:11:38 2017 +0200
+++ b/src/traffic-control/doc/queue-discs.rst	Thu Sep 14 18:12:19 2017 +0200
@@ -50,10 +50,26 @@
 The traffic control layer interacts with a queue disc in a simple manner: after requesting
 to enqueue a packet, the traffic control layer requests the qdisc to "run", i.e., to
 dequeue a set of packets, until a predefined number ("quota") of packets is dequeued
-or the netdevice stops the queue disc. A netdevice may stop the queue disc when its
-transmission queue(s) is/are (almost) full. Also, a netdevice may wake the
-queue disc when its transmission queue(s) is/are (almost) empty. Waking a queue disc
-is equivalent to make it run.
+or the netdevice stops the queue disc.  A netdevice shall
+stop the queue disc when its transmission queue does not have room for another
+packet. Also, a netdevice shall wake the queue disc when it detects that there
+is room for another packet in its transmission queue, but the transmission queue
+is stopped. Waking a queue disc is equivalent to make it run.
+
+Every queue disc collects statistics about the total number of packets/bytes
+received from the upper layers (in case of root queue disc) or from the parent
+queue disc (in case of child queue disc), enqueued, dequeued, requeued, dropped,
+dropped before enqueue, dropped after dequeue, stored in the queue disc and
+sent to the netdevice or to the parent queue disc. Note that packets that are
+dequeued may be requeued, i.e., retained by the traffic control infrastructure,
+if the netdevice is not ready to receive them. Requeued packets are not part
+of the queue disc. The following identities hold:
+
+* dropped = dropped before enqueue + dropped after dequeue
+* received = dropped before enqueue + enqueued
+* queued = enqueued - dequeued
+* sent = dequeued - dropped after dequeue (- 1 if there is a requeued packet)
+
 
 Design
 ==========
--- a/src/traffic-control/model/codel-queue-disc.cc	Wed Sep 13 23:11:38 2017 +0200
+++ b/src/traffic-control/model/codel-queue-disc.cc	Thu Sep 14 18:12:19 2017 +0200
@@ -281,7 +281,7 @@
   if (m_mode == QUEUE_DISC_MODE_PACKETS && (GetInternalQueue (0)->GetNPackets () + 1 > m_maxPackets))
     {
       NS_LOG_LOGIC ("Queue full (at max packets) -- droppping pkt");
-      Drop (item);
+      DropBeforeEnqueue (item);
       ++m_dropOverLimit;
       return false;
     }
@@ -289,7 +289,7 @@
   if (m_mode == QUEUE_DISC_MODE_BYTES && (GetInternalQueue (0)->GetNBytes () + item->GetSize () > m_maxBytes))
     {
       NS_LOG_LOGIC ("Queue full (packet would exceed max bytes) -- droppping pkt");
-      Drop (item);
+      DropBeforeEnqueue (item);
       ++m_dropOverLimit;
       return false;
     }
@@ -300,8 +300,8 @@
 
   bool retval = GetInternalQueue (0)->Enqueue (item);
 
-  // If Queue::Enqueue fails, QueueDisc::Drop is called by the internal queue
-  // because QueueDisc::AddInternalQueue sets the drop callback
+  // If Queue::Enqueue fails, QueueDisc::DropBeforeEnqueue is called by the
+  // internal queue because QueueDisc::AddInternalQueue sets the trace callback
 
   NS_LOG_LOGIC ("Number packets " << GetInternalQueue (0)->GetNPackets ());
   NS_LOG_LOGIC ("Number bytes " << GetInternalQueue (0)->GetNBytes ());
@@ -400,7 +400,7 @@
               // rates so high that the next drop should happen now,
               // hence the while loop.
               NS_LOG_LOGIC ("Sojourn time is still above target and it's time for next drop; dropping " << item);
-              Drop (item);
+              DropAfterDequeue (item);
 
               ++m_dropCount;
               ++m_count;
@@ -440,7 +440,7 @@
           // Drop the first packet and enter dropping state unless the queue is empty
           NS_LOG_LOGIC ("Sojourn time goes above target, dropping the first packet " << item << " and entering the dropping state");
           ++m_dropCount;
-          Drop (item);
+          DropAfterDequeue (item);
 
           item = GetInternalQueue (0)->Dequeue ();
 
--- a/src/traffic-control/model/fq-codel-queue-disc.cc	Wed Sep 13 23:11:38 2017 +0200
+++ b/src/traffic-control/model/fq-codel-queue-disc.cc	Thu Sep 14 18:12:19 2017 +0200
@@ -162,7 +162,7 @@
   if (ret == PacketFilter::PF_NO_MATCH)
     {
       NS_LOG_ERROR ("No filter has been able to classify this packet, drop it.");
-      Drop (item);
+      DropBeforeEnqueue (item);
       return false;
     }
 
--- a/src/traffic-control/model/pfifo-fast-queue-disc.cc	Wed Sep 13 23:11:38 2017 +0200
+++ b/src/traffic-control/model/pfifo-fast-queue-disc.cc	Thu Sep 14 18:12:19 2017 +0200
@@ -65,10 +65,10 @@
 {
   NS_LOG_FUNCTION (this << item);
 
-  if (GetNPackets () > m_limit)
+  if (GetNPackets () >= m_limit)
     {
       NS_LOG_LOGIC ("Queue disc limit exceeded -- dropping packet");
-      Drop (item);
+      DropBeforeEnqueue (item);
       return false;
     }
 
@@ -83,8 +83,8 @@
 
   bool retval = GetInternalQueue (band)->Enqueue (item);
 
-  // If Queue::Enqueue fails, QueueDisc::Drop is called by the internal queue
-  // because QueueDisc::AddInternalQueue sets the drop callback
+  // If Queue::Enqueue fails, QueueDisc::DropBeforeEnqueue is called by the
+  // internal queue because QueueDisc::AddInternalQueue sets the trace callback
 
   NS_LOG_LOGIC ("Number packets band " << band << ": " << GetInternalQueue (band)->GetNPackets ());
 
--- a/src/traffic-control/model/pie-queue-disc.cc	Wed Sep 13 23:11:38 2017 +0200
+++ b/src/traffic-control/model/pie-queue-disc.cc	Thu Sep 14 18:12:19 2017 +0200
@@ -197,14 +197,14 @@
       || (GetMode () == QUEUE_DISC_MODE_BYTES && nQueued + item->GetSize () > m_queueLimit))
     {
       // Drops due to queue limit: reactive
-      Drop (item);
+      DropBeforeEnqueue (item);
       m_stats.forcedDrop++;
       return false;
     }
   else if (DropEarly (item, nQueued))
     {
       // Early probability drop: proactive
-      Drop (item);
+      DropBeforeEnqueue (item);
       m_stats.unforcedDrop++;
       return false;
     }
@@ -212,8 +212,8 @@
   // No drop
   bool retval = GetInternalQueue (0)->Enqueue (item);
 
-  // If Queue::Enqueue fails, QueueDisc::Drop is called by the internal queue
-  // because QueueDisc::AddInternalQueue sets the drop callback
+  // If Queue::Enqueue fails, QueueDisc::DropBeforeEnqueue is called by the
+  // internal queue because QueueDisc::AddInternalQueue sets the trace callback
 
   NS_LOG_LOGIC ("\t bytesInQueue  " << GetInternalQueue (0)->GetNBytes ());
   NS_LOG_LOGIC ("\t packetsInQueue  " << GetInternalQueue (0)->GetNPackets ());
--- a/src/traffic-control/model/queue-disc.cc	Wed Sep 13 23:11:38 2017 +0200
+++ b/src/traffic-control/model/queue-disc.cc	Thu Sep 14 18:12:19 2017 +0200
@@ -86,6 +86,61 @@
   m_queueDisc = qd;
 }
 
+QueueDisc::Stats::Stats ()
+  : nTotalReceivedPackets (0),
+    nTotalReceivedBytes (0),
+    nTotalSentPackets (0),
+    nTotalSentBytes (0),
+    nTotalEnqueuedPackets (0),
+    nTotalEnqueuedBytes (0),
+    nTotalDequeuedPackets (0),
+    nTotalDequeuedBytes (0),
+    nTotalDroppedPackets (0),
+    nTotalDroppedPacketsBeforeEnqueue (0),
+    nTotalDroppedPacketsAfterDequeue (0),
+    nTotalDroppedBytes (0),
+    nTotalDroppedBytesBeforeEnqueue (0),
+    nTotalDroppedBytesAfterDequeue (0),
+    nTotalRequeuedPackets (0),
+    nTotalRequeuedBytes (0)
+{
+}
+
+void
+QueueDisc::Stats::Print (std::ostream &os) const
+{
+  os << std::endl << "Packets/Bytes received: "
+                  << nTotalReceivedPackets << " / "
+                  << nTotalReceivedBytes
+     << std::endl << "Packets/Bytes enqueued: "
+                  << nTotalEnqueuedPackets << " / "
+                  << nTotalEnqueuedBytes
+     << std::endl << "Packets/Bytes dequeued: "
+                  << nTotalDequeuedPackets << " / "
+                  << nTotalDequeuedBytes
+     << std::endl << "Packets/Bytes requeued: "
+                  << nTotalRequeuedPackets << " / "
+                  << nTotalRequeuedBytes
+     << std::endl << "Packets/Bytes dropped: "
+                  << nTotalDroppedPackets << " / "
+                  << nTotalDroppedBytes
+     << std::endl << "Packets/Bytes dropped before enqueue: "
+                  << nTotalDroppedPacketsBeforeEnqueue << " / "
+                  << nTotalDroppedBytesBeforeEnqueue
+     << std::endl << "Packets/Bytes dropped after dequeue: "
+                  << nTotalDroppedPacketsAfterDequeue << " / "
+                  << nTotalDroppedBytesAfterDequeue
+     << std::endl << "Packets/Bytes sent: "
+                  << nTotalSentPackets << " / "
+                  << nTotalSentBytes
+     << std::endl;
+}
+
+std::ostream & operator << (std::ostream &os, const QueueDisc::Stats &stats)
+{
+  stats.Print (os);
+  return os;
+}
 
 NS_OBJECT_ENSURE_REGISTERED (QueueDisc);
 
@@ -123,6 +178,12 @@
     .AddTraceSource ("Drop", "Drop a packet stored in the queue disc",
                      MakeTraceSourceAccessor (&QueueDisc::m_traceDrop),
                      "ns3::QueueDiscItem::TracedCallback")
+    .AddTraceSource ("DropBeforeEnqueue", "Drop a packet before enqueue",
+                     MakeTraceSourceAccessor (&QueueDisc::m_traceDropBeforeEnqueue),
+                     "ns3::QueueDiscItem::TracedCallback")
+    .AddTraceSource ("DropAfterDequeue", "Drop a packet after dequeue",
+                     MakeTraceSourceAccessor (&QueueDisc::m_traceDropAfterDequeue),
+                     "ns3::QueueDiscItem::TracedCallback")
     .AddTraceSource ("PacketsInQueue",
                      "Number of packets currently stored in the queue disc",
                      MakeTraceSourceAccessor (&QueueDisc::m_nPackets),
@@ -138,12 +199,6 @@
 QueueDisc::QueueDisc ()
   :  m_nPackets (0),
      m_nBytes (0),
-     m_nTotalReceivedPackets (0),
-     m_nTotalReceivedBytes (0),
-     m_nTotalDroppedPackets (0),
-     m_nTotalDroppedBytes (0),
-     m_nTotalRequeuedPackets (0),
-     m_nTotalRequeuedBytes (0),
      m_running (false)
 {
   NS_LOG_FUNCTION (this);
@@ -194,6 +249,25 @@
   Object::DoInitialize ();
 }
 
+const QueueDisc::Stats&
+QueueDisc::GetStats (void)
+{
+  NS_ASSERT (m_stats.nTotalDroppedPackets == m_stats.nTotalDroppedPacketsBeforeEnqueue
+             + m_stats.nTotalDroppedPacketsAfterDequeue);
+  NS_ASSERT (m_stats.nTotalDroppedBytes == m_stats.nTotalDroppedBytesBeforeEnqueue
+             + m_stats.nTotalDroppedBytesAfterDequeue);
+
+  // the total number of sent packets is only updated here to avoid to increase it
+  // after a dequeue and then having to decrease it if the packet is dropped after
+  // dequeue or requeued
+  m_stats.nTotalSentPackets = m_stats.nTotalDequeuedPackets - (m_requeued ? 1 : 0)
+                              - m_stats.nTotalDroppedPacketsAfterDequeue;
+  m_stats.nTotalSentBytes = m_stats.nTotalDequeuedBytes - (m_requeued ? m_requeued->GetSize () : 0)
+                            - m_stats.nTotalDroppedBytesAfterDequeue;
+
+  return m_stats;
+}
+
 uint32_t
 QueueDisc::GetNPackets () const
 {
@@ -208,48 +282,6 @@
   return m_nBytes;
 }
 
-uint32_t
-QueueDisc::GetTotalReceivedPackets (void) const
-{
-  NS_LOG_FUNCTION (this);
-  return m_nTotalReceivedPackets;
-}
-
-uint32_t
-QueueDisc::GetTotalReceivedBytes (void) const
-{
-  NS_LOG_FUNCTION (this);
-  return m_nTotalReceivedBytes;
-}
-
-uint32_t
-QueueDisc::GetTotalDroppedPackets (void) const
-{
-  NS_LOG_FUNCTION (this);
-  return m_nTotalDroppedPackets;
-}
-
-uint32_t
-QueueDisc:: GetTotalDroppedBytes (void) const
-{
-  NS_LOG_FUNCTION (this);
-  return m_nTotalDroppedBytes;
-}
-
-uint32_t
-QueueDisc::GetTotalRequeuedPackets (void) const
-{
-  NS_LOG_FUNCTION (this);
-  return m_nTotalRequeuedPackets;
-}
-
-uint32_t
-QueueDisc:: GetTotalRequeuedBytes (void) const
-{
-  NS_LOG_FUNCTION (this);
-  return m_nTotalRequeuedBytes;
-}
-
 void
 QueueDisc::SetNetDevice (Ptr<NetDevice> device)
 {
@@ -282,9 +314,16 @@
 QueueDisc::AddInternalQueue (Ptr<InternalQueue> queue)
 {
   NS_LOG_FUNCTION (this);
-  // set the drop callback on the internal queue, so that the queue disc is
-  // notified of packets dropped by the internal queue
-  queue->TraceConnectWithoutContext ("Drop", MakeCallback (&QueueDisc::Drop, this));
+  // set various callbacks on the internal queue, so that the queue disc is
+  // notified of packets enqueued, dequeued or dropped by the internal queue
+  queue->TraceConnectWithoutContext ("Enqueue",
+                                     MakeCallback (&QueueDisc::PacketEnqueued, this));
+  queue->TraceConnectWithoutContext ("Dequeue",
+                                     MakeCallback (&QueueDisc::PacketDequeued, this));
+  queue->TraceConnectWithoutContext ("DropBeforeEnqueue",
+                                     MakeCallback (&QueueDisc::DropBeforeEnqueue, this));
+  queue->TraceConnectWithoutContext ("DropAfterDequeue",
+                                     MakeCallback (&QueueDisc::DropAfterDequeue, this));
   m_queues.push_back (queue);
 }
 
@@ -330,9 +369,16 @@
   // such queue discs do not implement the enqueue/dequeue methods
   NS_ABORT_MSG_IF (qdClass->GetQueueDisc ()->GetWakeMode () == WAKE_CHILD,
                    "A queue disc with WAKE_CHILD as wake mode can only be a root queue disc");
-  // set the parent drop callback on the child queue disc, so that it can notify
-  // packet drops to the parent queue disc
-  qdClass->GetQueueDisc ()->SetParentDropCallback (MakeCallback (&QueueDisc::Drop, this));
+  // set the parent callbacks on the child queue disc, so that it can notify
+  // the parent queue disc of packets enqueued, dequeued or dropped
+  qdClass->GetQueueDisc ()->TraceConnectWithoutContext ("Enqueue",
+                                     MakeCallback (&QueueDisc::PacketEnqueued, this));
+  qdClass->GetQueueDisc ()->TraceConnectWithoutContext ("Dequeue",
+                                     MakeCallback (&QueueDisc::PacketDequeued, this));
+  qdClass->GetQueueDisc ()->TraceConnectWithoutContext ("DropBeforeEnqueue",
+                                     MakeCallback (&QueueDisc::DropBeforeEnqueue, this));
+  qdClass->GetQueueDisc ()->TraceConnectWithoutContext ("DropAfterDequeue",
+                                     MakeCallback (&QueueDisc::DropAfterDequeue, this));
   m_classes.push_back (qdClass);
 }
 
@@ -370,49 +416,57 @@
 }
 
 void
-QueueDisc::SetParentDropCallback (ParentDropCallback cb)
+QueueDisc::PacketEnqueued (Ptr<const QueueDiscItem> item)
 {
-  m_parentDropCallback = cb;
+  m_nPackets++;
+  m_nBytes += item->GetSize ();
+  m_stats.nTotalEnqueuedPackets++;
+  m_stats.nTotalEnqueuedBytes += item->GetSize ();
+
+  NS_LOG_LOGIC ("m_traceEnqueue (p)");
+  m_traceEnqueue (item);
 }
 
 void
-QueueDisc::Drop (Ptr<const QueueDiscItem> item)
+QueueDisc::PacketDequeued (Ptr<const QueueDiscItem> item)
+{
+  m_nPackets--;
+  m_nBytes -= item->GetSize ();
+  m_stats.nTotalDequeuedPackets++;
+  m_stats.nTotalDequeuedBytes += item->GetSize ();
+
+  NS_LOG_LOGIC ("m_traceDequeue (p)");
+  m_traceDequeue (item);
+}
+
+void
+QueueDisc::DropBeforeEnqueue (Ptr<const QueueDiscItem> item)
 {
   NS_LOG_FUNCTION (this << item);
 
-  // if the wake mode of this queue disc is WAKE_CHILD, packets are directly
-  // enqueued/dequeued from the child queue discs, thus this queue disc does not
-  // keep valid packets/bytes counters and no actions need to be performed.
-  if (this->GetWakeMode () == WAKE_CHILD)
-    {
-      return;
-    }
+  m_stats.nTotalDroppedPackets++;
+  m_stats.nTotalDroppedBytes += item->GetSize ();
+  m_stats.nTotalDroppedPacketsBeforeEnqueue++;
+  m_stats.nTotalDroppedBytesBeforeEnqueue += item->GetSize ();
 
-  NS_ASSERT_MSG (m_nPackets >= 1u, "No packet in the queue disc, cannot drop");
-  NS_ASSERT_MSG (m_nBytes >= item->GetSize (), "The size of the packet that"
-                 << " is reported to be dropped is greater than the amount of bytes"
-                 << "stored in the queue disc");
-
-  m_nPackets--;
-  m_nBytes -= item->GetSize ();
-  m_nTotalDroppedPackets++;
-  m_nTotalDroppedBytes += item->GetSize ();
-
-  NS_LOG_LOGIC ("m_traceDrop (p)");
+  NS_LOG_LOGIC ("m_traceDropBeforeEnqueue (p)");
   m_traceDrop (item);
-
-  NotifyParentDrop (item);
+  m_traceDropBeforeEnqueue (item);
 }
 
 void
-QueueDisc::NotifyParentDrop (Ptr<const QueueDiscItem> item)
+QueueDisc::DropAfterDequeue (Ptr<const QueueDiscItem> item)
 {
   NS_LOG_FUNCTION (this << item);
-  // the parent drop callback is clearly null on root queue discs
-  if (!m_parentDropCallback.IsNull ())
-    {
-      m_parentDropCallback (item);
-    }
+
+  m_stats.nTotalDroppedPackets++;
+  m_stats.nTotalDroppedBytes += item->GetSize ();
+  m_stats.nTotalDroppedPacketsAfterDequeue++;
+  m_stats.nTotalDroppedBytesAfterDequeue += item->GetSize ();
+
+  NS_LOG_LOGIC ("m_traceDropAfterDequeue (p)");
+  m_traceDrop (item);
+  m_traceDropAfterDequeue (item);
 }
 
 bool
@@ -420,15 +474,29 @@
 {
   NS_LOG_FUNCTION (this << item);
 
-  m_nPackets++;
-  m_nBytes += item->GetSize ();
-  m_nTotalReceivedPackets++;
-  m_nTotalReceivedBytes += item->GetSize ();
+  m_stats.nTotalReceivedPackets++;
+  m_stats.nTotalReceivedBytes += item->GetSize ();
+
+  bool retval = DoEnqueue (item);
 
-  NS_LOG_LOGIC ("m_traceEnqueue (p)");
-  m_traceEnqueue (item);
+  // DoEnqueue may return false because:
+  // 1) the internal queue is full
+  //    -> the DropBeforeEnqueue method of this queue disc is automatically called
+  //       because QueueDisc::AddInternalQueue sets the trace callback
+  // 2) the child queue disc dropped the packet
+  //    -> the DropBeforeEnqueue method of this queue disc is automatically called
+  //       because QueueDisc::AddQueueDiscClass sets the trace callback
+  // 3) it dropped the packet
+  //    -> DoEnqueue has to explicitly call DropBeforeEnqueue
+  // Thus, we do not have to call DropBeforeEnqueue here.
 
-  return DoEnqueue (item);
+  // check that the received packet was either enqueued or dropped
+  NS_ASSERT (m_stats.nTotalReceivedPackets == m_stats.nTotalDroppedPacketsBeforeEnqueue +
+             m_stats.nTotalEnqueuedPackets);
+  NS_ASSERT (m_stats.nTotalReceivedBytes == m_stats.nTotalDroppedBytesBeforeEnqueue +
+             m_stats.nTotalEnqueuedBytes);
+
+  return retval;
 }
 
 Ptr<QueueDiscItem>
@@ -436,17 +504,10 @@
 {
   NS_LOG_FUNCTION (this);
 
-  Ptr<QueueDiscItem> item;
-  item = DoDequeue ();
+  Ptr<QueueDiscItem> item = DoDequeue ();
 
-  if (item != 0)
-    {
-      m_nPackets--;
-      m_nBytes -= item->GetSize ();
-
-      NS_LOG_LOGIC ("m_traceDequeue (p)");
-      m_traceDequeue (item);
-    }
+  NS_ASSERT (m_nPackets == m_stats.nTotalEnqueuedPackets - m_stats.nTotalDequeuedPackets);
+  NS_ASSERT (m_nBytes == m_stats.nTotalEnqueuedBytes - m_stats.nTotalDequeuedBytes);
 
   return item;
 }
@@ -530,12 +591,6 @@
           {
             item = m_requeued;
             m_requeued = 0;
-
-            m_nPackets--;
-            m_nBytes -= item->GetSize ();
-
-            NS_LOG_LOGIC ("m_traceDequeue (p)");
-            m_traceDequeue (item);
           }
     }
   else
@@ -566,10 +621,8 @@
   m_requeued = item;
   /// \todo netif_schedule (q);
 
-  m_nPackets++;       // it's still part of the queue
-  m_nBytes += item->GetSize ();
-  m_nTotalRequeuedPackets++;
-  m_nTotalRequeuedBytes += item->GetSize ();
+  m_stats.nTotalRequeuedPackets++;
+  m_stats.nTotalRequeuedBytes += item->GetSize ();
 
   NS_LOG_LOGIC ("m_traceRequeue (p)");
   m_traceRequeue (item);
--- a/src/traffic-control/model/queue-disc.h	Wed Sep 13 23:11:38 2017 +0200
+++ b/src/traffic-control/model/queue-disc.h	Thu Sep 14 18:12:19 2017 +0200
@@ -107,19 +107,80 @@
  * An attempt is made, also, to enqueue each packet in the "same" queue both within the
  * queue disc and within the device.
  *
- * The traffic control layer interacts with a queue disc in a simple manner: after requesting
- * to enqueue a packet, the traffic control layer requests the qdisc to "run", i.e., to
- * dequeue a set of packets, until a predefined number ("quota") of packets is dequeued
- * or the netdevice stops the queue disc. A netdevice may stop the queue disc when its
- * transmission queue(s) is/are (almost) full. Also, a netdevice may wake the
- * queue disc when its transmission queue(s) is/are (almost) empty. Waking a queue disc
- * is equivalent to make it run.
+ * The traffic control layer interacts with a queue disc in a simple manner: after
+ * requesting to enqueue a packet, the traffic control layer requests the qdisc to
+ * "run", i.e., to dequeue a set of packets, until a predefined number ("quota")
+ * of packets is dequeued or the netdevice stops the queue disc. A netdevice shall
+ * stop the queue disc when its transmission queue does not have room for another
+ * packet. Also, a netdevice shall wake the queue disc when it detects that there
+ * is room for another packet in its transmission queue, but the transmission queue
+ * is stopped. Waking a queue disc is equivalent to make it run.
+ *
+ * Every queue disc collects statistics about the total number of packets/bytes
+ * received from the upper layers (in case of root queue disc) or from the parent
+ * queue disc (in case of child queue disc), enqueued, dequeued, requeued, dropped,
+ * dropped before enqueue, dropped after dequeue, queued in the queue disc and
+ * sent to the netdevice or to the parent queue disc. Note that packets that are
+ * dequeued may be requeued, i.e., retained by the traffic control infrastructure,
+ * if the netdevice is not ready to receive them. Requeued packets are not part
+ * of the queue disc. The following identities hold:
+ * - dropped = dropped before enqueue + dropped after dequeue
+ * - received = dropped before enqueue + enqueued
+ * - queued = enqueued - dequeued
+ * - sent = dequeued - dropped after dequeue (- 1 if there is a requeued packet)
  *
  * The design and implementation of this class is heavily inspired by Linux.
  * For more details, see the traffic-control model page.
  */
 class QueueDisc : public Object {
 public:
+
+  /// \brief Structure that keeps the queue disc statistics
+  struct Stats
+  {
+    /// Total received packets
+    uint32_t nTotalReceivedPackets;
+    /// Total received bytes
+    uint64_t nTotalReceivedBytes;
+    /// Total sent packets -- this value is not kept up to date, call GetStats first
+    uint32_t nTotalSentPackets;
+    /// Total sent bytes -- this value is not kept up to date, call GetStats first
+    uint64_t nTotalSentBytes;
+    /// Total enqueued packets
+    uint32_t nTotalEnqueuedPackets;
+    /// Total enqueued bytes
+    uint64_t nTotalEnqueuedBytes;
+    /// Total dequeued packets
+    uint32_t nTotalDequeuedPackets;
+    /// Total dequeued bytes
+    uint64_t nTotalDequeuedBytes;
+    /// Total dropped packets
+    uint32_t nTotalDroppedPackets;
+    /// Total packets dropped before enqueue
+    uint32_t nTotalDroppedPacketsBeforeEnqueue;
+    /// Total packets dropped after dequeue
+    uint32_t nTotalDroppedPacketsAfterDequeue;
+    /// Total dropped bytes
+    uint64_t nTotalDroppedBytes;
+    /// Total bytes dropped before enqueue
+    uint64_t nTotalDroppedBytesBeforeEnqueue;
+    /// Total bytes dropped after dequeue
+    uint64_t nTotalDroppedBytesAfterDequeue;
+    /// Total requeued packets
+    uint32_t nTotalRequeuedPackets;
+    /// Total requeued bytes
+    uint64_t nTotalRequeuedBytes;
+
+    /// constructor
+    Stats ();
+
+    /**
+     * \brief Print the statistics.
+     * \param os output stream in which the data should be printed.
+     */
+    void Print (std::ostream &os) const;
+  };
+
   /**
    * \brief Get the type ID.
    * \return the object TypeId
@@ -133,12 +194,7 @@
    * \brief Get the number of packets stored by the queue disc
    * \return the number of packets stored by the queue disc.
    *
-   * Note that the number of packets stored by the queue disc is updated as soon
-   * as a packet is received by the queue disc and before actually enqueuing the
-   * packet (i.e., before calling DoEnqueue). Thus, while implementing the DoEnqueue
-   * method of a subclass, keep in mind that GetNPackets returns the number of
-   * packets stored in the queue disc, including the packet that we are trying
-   * to enqueue.
+   * The requeued packet, if any, is counted.
    */
   uint32_t GetNPackets (void) const;
 
@@ -146,50 +202,15 @@
    * \brief Get the amount of bytes stored by the queue disc
    * \return the amount of bytes stored by the queue disc.
    *
-   * Note that the amount of bytes stored by the queue disc is updated as soon
-   * as a packet is received by the queue disc and before actually enqueuing the
-   * packet (i.e., before calling DoEnqueue). Thus, while implementing the DoEnqueue
-   * method of a subclass, keep in mind that GetNBytes returns the amount of
-   * bytes stored in the queue disc, including the size of the packet that we are
-   * trying to enqueue.
+   * The requeued packet, if any, is counted.
    */
   uint32_t GetNBytes (void) const;
 
   /**
-   * \brief Get the total number of received packets
-   * \return the total number of received packets.
-   */
-  uint32_t GetTotalReceivedPackets (void) const;
-
-  /**
-   * \brief Get the total amount of received bytes
-   * \return the total amount of received bytes.
-   */
-  uint32_t GetTotalReceivedBytes (void) const;
-
-  /**
-   * \brief Get the total number of dropped packets
-   * \return the total number of dropped packets.
+   * \brief Retrieve all the collected statistics.
+   * \return the collected statistics.
    */
-  uint32_t GetTotalDroppedPackets (void) const;
-
-  /**
-   * \brief Get the total amount of dropped bytes
-   * \return the total amount of dropped bytes.
-   */
-  uint32_t GetTotalDroppedBytes (void) const;
-
-  /**
-   * \brief Get the total number of requeued packets
-   * \return the total number of requeued packets.
-   */
-  uint32_t GetTotalRequeuedPackets (void) const;
-
-  /**
-   * \brief Get the total amount of requeued bytes
-   * \return the total amount of requeued bytes.
-   */
-  uint32_t GetTotalRequeuedBytes (void) const;
+  const Stats& GetStats (void);
 
   /**
    * \brief Set the NetDevice on which this queue discipline is installed.
@@ -341,18 +362,6 @@
    */
   virtual WakeMode GetWakeMode (void) const;
 
-  /// Callback invoked by a child queue disc to notify the parent of a packet drop
-  typedef Callback<void, Ptr<const QueueDiscItem> > ParentDropCallback;
-
-  /**
-   * \brief Set the parent drop callback
-   * \param cb the callback to set
-   *
-   * Called when a queue disc class is added to a queue disc in order to set a
-   * callback to the Drop method of the parent queue disc.
-   */
-  virtual void SetParentDropCallback (ParentDropCallback cb);
-
 protected:
   /**
    * \brief Dispose of the object
@@ -369,11 +378,22 @@
   void DoInitialize (void);
 
   /**
-   *  \brief Drop a packet
+   *  \brief Perform the actions required when the queue disc is notified of
+   *         a packet dropped before enqueue
    *  \param item item that was dropped
-   *  This method is called by subclasses to notify parent (this class) of packet drops.
+   *  This method must be called by subclasses to notify parent (this class) of a packet
+   *  dropped before enqueue
    */
-  void Drop (Ptr<const QueueDiscItem> item);
+  void DropBeforeEnqueue (Ptr<const QueueDiscItem> item);
+
+  /**
+   *  \brief Perform the actions required when the queue disc is notified of
+   *         a packet dropped after dequeue
+   *  \param item item that was dropped
+   *  This method must be called by subclasses to notify parent (this class) of a packet
+   *  dropped after dequeue
+   */
+  void DropAfterDequeue (Ptr<const QueueDiscItem> item);
 
 private:
   /**
@@ -394,12 +414,6 @@
   QueueDisc &operator = (const QueueDisc &o);
 
   /**
-   *  \brief Notify the parent queue disc of a packet drop
-   *  \param item item that was dropped
-   */
-  void NotifyParentDrop (Ptr<const QueueDiscItem> item);
-
-  /**
    * This function actually enqueues a packet into the queue disc.
    * \param item item to enqueue
    * \return True if the operation was successful; false otherwise
@@ -472,6 +486,20 @@
    */
   bool Transmit (Ptr<QueueDiscItem> item);
 
+  /**
+   *  \brief Perform the actions required when the queue disc is notified of
+   *         a packet enqueue
+   *  \param item item that was enqueued
+   */
+  void PacketEnqueued (Ptr<const QueueDiscItem> item);
+
+  /**
+   *  \brief Perform the actions required when the queue disc is notified of
+   *         a packet dequeue
+   *  \param item item that was dequeued
+   */
+  void PacketDequeued (Ptr<const QueueDiscItem> item);
+
   static const uint32_t DEFAULT_QUOTA = 64; //!< Default quota (as in /proc/sys/net/core/dev_weight)
 
   std::vector<Ptr<InternalQueue> > m_queues;    //!< Internal queues
@@ -481,29 +509,36 @@
   TracedValue<uint32_t> m_nPackets; //!< Number of packets in the queue
   TracedValue<uint32_t> m_nBytes;   //!< Number of bytes in the queue
 
-  uint32_t m_nTotalReceivedPackets; //!< Total received packets
-  uint32_t m_nTotalReceivedBytes;   //!< Total received bytes
-  uint32_t m_nTotalDroppedPackets;  //!< Total dropped packets
-  uint32_t m_nTotalDroppedBytes;    //!< Total dropped bytes
-  uint32_t m_nTotalRequeuedPackets; //!< Total requeued packets
-  uint32_t m_nTotalRequeuedBytes;   //!< Total requeued bytes
+  Stats m_stats;                    //!< The collected statistics
   uint32_t m_quota;                 //!< Maximum number of packets dequeued in a qdisc run
   Ptr<NetDevice> m_device;          //!< The NetDevice on which this queue discipline is installed
   Ptr<NetDeviceQueueInterface> m_devQueueIface;   //!< NetDevice queue interface
   bool m_running;                   //!< The queue disc is performing multiple dequeue operations
   Ptr<QueueDiscItem> m_requeued;    //!< The last packet that failed to be transmitted
-  ParentDropCallback m_parentDropCallback;   //!< Parent drop callback
 
   /// Traced callback: fired when a packet is enqueued
   TracedCallback<Ptr<const QueueDiscItem> > m_traceEnqueue;
     /// Traced callback: fired when a packet is dequeued
   TracedCallback<Ptr<const QueueDiscItem> > m_traceDequeue;
-    /// Traced callback: fired when a packet is requeued
+  /// Traced callback: fired when a packet is requeued
   TracedCallback<Ptr<const QueueDiscItem> > m_traceRequeue;
   /// Traced callback: fired when a packet is dropped
   TracedCallback<Ptr<const QueueDiscItem> > m_traceDrop;
+  /// Traced callback: fired when a packet is dropped before enqueue
+  TracedCallback<Ptr<const QueueDiscItem> > m_traceDropBeforeEnqueue;
+  /// Traced callback: fired when a packet is dropped after dequeue
+  TracedCallback<Ptr<const QueueDiscItem> > m_traceDropAfterDequeue;
 };
 
+/**
+ * \brief Stream insertion operator.
+ *
+ * \param os the stream
+ * \param stats the queue disc statistics
+ * \returns a reference to the stream
+ */
+std::ostream& operator<< (std::ostream& os, const QueueDisc::Stats &stats);
+
 } // namespace ns3
 
 #endif /* QueueDisc */
--- a/src/traffic-control/model/red-queue-disc.cc	Wed Sep 13 23:11:38 2017 +0200
+++ b/src/traffic-control/model/red-queue-disc.cc	Thu Sep 14 18:12:19 2017 +0200
@@ -457,7 +457,7 @@
         {
           NS_LOG_DEBUG ("\t Dropping due to Prob Mark " << m_qAvg);
           m_stats.unforcedDrop++;
-          Drop (item);
+          DropBeforeEnqueue (item);
           return false;
         }
       NS_LOG_DEBUG ("\t Marking due to Prob Mark " << m_qAvg);
@@ -469,7 +469,7 @@
         {
           NS_LOG_DEBUG ("\t Dropping due to Hard Mark " << m_qAvg);
           m_stats.forcedDrop++;
-          Drop (item);
+          DropBeforeEnqueue (item);
           if (m_isNs1Compat)
             {
               m_count = 0;
@@ -488,8 +488,8 @@
       m_stats.qLimDrop++;
     }
 
-  // If Queue::Enqueue fails, QueueDisc::Drop is called by the internal queue
-  // because QueueDisc::AddInternalQueue sets the drop callback
+  // If Queue::Enqueue fails, QueueDisc::DropBeforeEnqueue is called by the
+  // internal queue because QueueDisc::AddInternalQueue sets the trace callback
 
   NS_LOG_LOGIC ("Number packets " << GetInternalQueue (0)->GetNPackets ());
   NS_LOG_LOGIC ("Number bytes " << GetInternalQueue (0)->GetNBytes ());