Linux

pv コマンドの --rate-limit はどのように転送レート制限をしているのか

ちょっと気になって調べたメモ。バージョンは CentOS 7 の pv-1.4.6-1.el7。

if (state->rate_limit > 0) {
    gettimeofday(&cur_time, NULL);
    if ((cur_time.tv_sec > next_ratecheck.tv_sec)
        || (cur_time.tv_sec == next_ratecheck.tv_sec
        && cur_time.tv_usec >=
        next_ratecheck.tv_usec)) {
        target +=
            ((long double) (state->rate_limit)) /
            (long double) (1000000 /
                   RATE_GRANULARITY);
        pv_timeval_add_usec(&next_ratecheck,
                    RATE_GRANULARITY);
    }
    cansend = target;
}

RATE_GRANULARITY がマイクロ秒単位のレートとチェックする間隔で target に次の RATE_GRANULARITY までに転送出来る量を計算して target に加算して cansend に入れる。

RATE_GRANULARITY の値は 100000 だったので 0.1 秒ごと。

if ((0 < state->size) && (state->stop_at_size)
    && (0 >= cansend) && eof_in && eof_out) {
    written = 0;
} else {
    written =
        pv_transfer(state, fd, &eof_in, &eof_out,
            cansend, &lineswritten);
}

pv_transfer()cansend の値は1回の read/write のサイズとして使われる。

if (state->linemode) {
    since_last += lineswritten;
    total_written += lineswritten;
    if (state->rate_limit > 0)
        target -= lineswritten;
} else {
    since_last += written;
    total_written += written;
    if (state->rate_limit > 0)
        target -= written;
}

転送が終わったら実際に転送した量を target から減算する。


転送開始してから I/O が詰まってレートが期待より小さくなると target の値がどんどん大きくなって、I/O の詰まりが外れたときに一気に流れる。

time (dd 'if=/dev/zero' bs=1M count=10) | pv -L 1M > /dev/null
# 10+0 records in
# 10+0 records out
# 10485760 bytes (10 MB) copied, 9.8449 s, 1.1 MB/s
# 
# real    0m9.941s
# user    0m0.002s
# sys     0m0.021s

time (sleep 10; dd 'if=/dev/zero' bs=1M count=10) | pv -L 1M > /dev/null
# 10+0 records in
# 10+0 records out
# 10485760 bytes (10 MB) copied, 0.00900839 s, 1.2 GB/s
# 
# real    0m10.033s
# user    0m0.004s
# sys     0m0.031s

あくまでもトータルのレートが制限されるだけで、一時的に詰まったりするとめちゃめちゃでかいのが流れることがある。