001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hdfs.util;
019
020import static org.apache.hadoop.util.Time.monotonicNow;
021
022/** 
023 * a class to throttle the data transfers.
024 * This class is thread safe. It can be shared by multiple threads.
025 * The parameter bandwidthPerSec specifies the total bandwidth shared by
026 * threads.
027 */
028public class DataTransferThrottler {
029  private long period;          // period over which bw is imposed
030  private long periodExtension; // Max period over which bw accumulates.
031  private long bytesPerPeriod;  // total number of bytes can be sent in each period
032  private long curPeriodStart;  // current period starting time
033  private long curReserve;      // remaining bytes can be sent in the period
034  private long bytesAlreadyUsed;
035
036  /** Constructor 
037   * @param bandwidthPerSec bandwidth allowed in bytes per second. 
038   */
039  public DataTransferThrottler(long bandwidthPerSec) {
040    this(500, bandwidthPerSec);  // by default throttling period is 500ms 
041  }
042
043  /**
044   * Constructor
045   * @param period in milliseconds. Bandwidth is enforced over this
046   *        period.
047   * @param bandwidthPerSec bandwidth allowed in bytes per second. 
048   */
049  public DataTransferThrottler(long period, long bandwidthPerSec) {
050    this.curPeriodStart = monotonicNow();
051    this.period = period;
052    this.curReserve = this.bytesPerPeriod = bandwidthPerSec*period/1000;
053    this.periodExtension = period*3;
054  }
055
056  /**
057   * @return current throttle bandwidth in bytes per second.
058   */
059  public synchronized long getBandwidth() {
060    return bytesPerPeriod*1000/period;
061  }
062  
063  /**
064   * Sets throttle bandwidth. This takes affect latest by the end of current
065   * period.
066   * 
067   * @param bytesPerSecond 
068   */
069  public synchronized void setBandwidth(long bytesPerSecond) {
070    if ( bytesPerSecond <= 0 ) {
071      throw new IllegalArgumentException("" + bytesPerSecond);
072    }
073    bytesPerPeriod = bytesPerSecond*period/1000;
074  }
075  
076  /** Given the numOfBytes sent/received since last time throttle was called,
077   * make the current thread sleep if I/O rate is too fast
078   * compared to the given bandwidth.
079   *
080   * @param numOfBytes
081   *     number of bytes sent/received since last time throttle was called
082   */
083  public synchronized void throttle(long numOfBytes) {
084    if ( numOfBytes <= 0 ) {
085      return;
086    }
087
088    curReserve -= numOfBytes;
089    bytesAlreadyUsed += numOfBytes;
090
091    while (curReserve <= 0) {
092      long now = monotonicNow();
093      long curPeriodEnd = curPeriodStart + period;
094
095      if ( now < curPeriodEnd ) {
096        // Wait for next period so that curReserve can be increased.
097        try {
098          wait( curPeriodEnd - now );
099        } catch (InterruptedException e) {
100          // Abort throttle and reset interrupted status to make sure other
101          // interrupt handling higher in the call stack executes.
102          Thread.currentThread().interrupt();
103          break;
104        }
105      } else if ( now <  (curPeriodStart + periodExtension)) {
106        curPeriodStart = curPeriodEnd;
107        curReserve += bytesPerPeriod;
108      } else {
109        // discard the prev period. Throttler might not have
110        // been used for a long time.
111        curPeriodStart = now;
112        curReserve = bytesPerPeriod - bytesAlreadyUsed;
113      }
114    }
115
116    bytesAlreadyUsed -= numOfBytes;
117  }
118}