| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125 |
- #!/usr/bin/env python
- # Copyright 2012 Google Inc. All Rights Reserved.
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- """Simulate network characteristics directly in Python.
- Allows running replay without dummynet.
- """
- import logging
- import platformsettings
- import re
- import time
- TIMER = platformsettings.timer
- class ProxyShaperError(Exception):
- """Module catch-all error."""
- pass
- class BandwidthValueError(ProxyShaperError):
- """Raised for unexpected dummynet-style bandwidth value."""
- pass
- class RateLimitedFile(object):
- """Wrap a file like object with rate limiting.
- TODO(slamm): Simulate slow-start.
- Each RateLimitedFile corresponds to one-direction of a
- bidirectional socket. Slow-start can be added here (algorithm needed).
- Will consider changing this class to take read and write files and
- corresponding bit rates for each.
- """
- BYTES_PER_WRITE = 1460
- def __init__(self, request_counter, f, bps):
- """Initialize a RateLimiter.
- Args:
- request_counter: callable to see how many requests share the limit.
- f: file-like object to wrap.
- bps: an integer of bits per second.
- """
- self.request_counter = request_counter
- self.original_file = f
- self.bps = bps
- def transfer_seconds(self, num_bytes):
- """Seconds to read/write |num_bytes| with |self.bps|."""
- return 8.0 * num_bytes / self.bps
- def write(self, data):
- num_bytes = len(data)
- num_sent_bytes = 0
- while num_sent_bytes < num_bytes:
- num_write_bytes = min(self.BYTES_PER_WRITE, num_bytes - num_sent_bytes)
- num_requests = self.request_counter()
- wait = self.transfer_seconds(num_write_bytes) * num_requests
- logging.debug('write sleep: %0.4fs (%d requests)', wait, num_requests)
- time.sleep(wait)
- self.original_file.write(
- data[num_sent_bytes:num_sent_bytes + num_write_bytes])
- num_sent_bytes += num_write_bytes
- def _read(self, read_func, size):
- start = TIMER()
- data = read_func(size)
- read_seconds = TIMER() - start
- num_bytes = len(data)
- num_requests = self.request_counter()
- wait = self.transfer_seconds(num_bytes) * num_requests - read_seconds
- if wait > 0:
- logging.debug('read sleep: %0.4fs %d requests)', wait, num_requests)
- time.sleep(wait)
- return data
- def readline(self, size=-1):
- return self._read(self.original_file.readline, size)
- def read(self, size=-1):
- return self._read(self.original_file.read, size)
- def __getattr__(self, name):
- """Forward any non-overriden calls."""
- return getattr(self.original_file, name)
- def GetBitsPerSecond(bandwidth):
- """Return bits per second represented by dummynet bandwidth option.
- See ipfw/dummynet.c:read_bandwidth for how it is really done.
- Args:
- bandwidth: a dummynet-style bandwidth specification (e.g. "10Kbit/s")
- """
- if bandwidth == '0':
- return 0
- bw_re = r'^(\d+)(?:([KM])?(bit|Byte)/s)?$'
- match = re.match(bw_re, str(bandwidth))
- if not match:
- raise BandwidthValueError('Value, "%s", does not match regex: %s' % (
- bandwidth, bw_re))
- bw = int(match.group(1))
- if match.group(2) == 'K':
- bw *= 1000
- if match.group(2) == 'M':
- bw *= 1000000
- if match.group(3) == 'Byte':
- bw *= 8
- return bw
|