public void checkProcessTimeAndSpeed(DatanodeInfo datanodeInfo, long packetDataLen,
    long processTimeMs, long lastAckTimestamp, int unfinished) {
    long current = EnvironmentEdgeManager.currentTime();
    // Here are two conditions used to determine whether a datanode is slow,
    // 1. For small packet, we just have a simple time limit, without considering
    // the size of the packet.
    // 2. For large packet, we will calculate the speed, and check if the speed is too slow.
    boolean slow = (packetDataLen <= minLengthForSpeedCheck && processTimeMs > slowPacketAckMs)
      || (packetDataLen > minLengthForSpeedCheck
        && (double) packetDataLen / processTimeMs < minPacketFlushSpeedKBs);
    if (slow) {
      // Check if large diff ack timestamp between replicas,
      // should try to avoid misjudgments that caused by GC STW.
      if (
        (lastAckTimestamp > 0 && current - lastAckTimestamp > slowPacketAckMs / 2)
          || (lastAckTimestamp <= 0 && unfinished == 0)
      ) {
        LOG.info(
          "Slow datanode: {}, data length={}, duration={}ms, unfinishedReplicas={}, "
            + "lastAckTimestamp={}, monitor name: {}",
          datanodeInfo, packetDataLen, processTimeMs, unfinished, lastAckTimestamp, this.name);
        if (addSlowAckData(datanodeInfo, packetDataLen, processTimeMs)) {
          excludeDatanodeManager.tryAddExcludeDN(datanodeInfo, "slow packet ack");
        }
      }
    }
  }
Check if the packet process time shows that the relevant datanode is a slow node.
@param datanodeInfo     the datanode that processed the packet
@param packetDataLen    the data length of the packet (in bytes)
@param processTimeMs    the process time (in ms) of the packet on the datanode,
@param lastAckTimestamp the last acked timestamp of the packet on another datanode
@param unfinished       if the packet is unfinished flushed to the datanode replicas
hbase-asyncfs/src/main/java/org/apache/hadoop/hbase/io/asyncfs/monitor/StreamSlowMonitor.java