github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/burst.go (about)

     1  /*
     2   * Copyright (c) 2020, Psiphon Inc.
     3   * All rights reserved.
     4   *
     5   * This program is free software: you can redistribute it and/or modify
     6   * it under the terms of the GNU General Public License as published by
     7   * the Free Software Foundation, either version 3 of the License, or
     8   * (at your option) any later version.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package common
    21  
    22  import (
    23  	"net"
    24  	"sync"
    25  	"time"
    26  )
    27  
    28  // BurstMonitoredConn wraps a net.Conn and monitors for data transfer bursts.
    29  // Upstream (read) and downstream (write) bursts are tracked independently.
    30  //
    31  // A burst is defined as a transfer of "target" bytes, possibly across
    32  // multiple I/O operations, where the total time elapsed does not exceed
    33  // "deadline". Both a non-zero deadline and theshold must be set to enable
    34  // monitoring. Four bursts are reported: the first, the last, the min (by
    35  // rate) and max.
    36  //
    37  // The burst monitoring is heuristical in nature and may not capture all
    38  // bursts. The reported rates will be more accurate for larger target values
    39  // and shorter deadlines, but these settings may fail to register bursts on
    40  // slower connections. Tune the deadline/target as required. The threshold
    41  // should be set to account for buffering (e.g, the local host socket
    42  // send/receive buffer) but this is not enforced by BurstMonitoredConn.
    43  //
    44  // Overhead: BurstMonitoredConn adds mutexes but does not use timers.
    45  type BurstMonitoredConn struct {
    46  	net.Conn
    47  	isServer         bool
    48  	readTargetBytes  int64
    49  	readDeadline     time.Duration
    50  	writeTargetBytes int64
    51  	writeDeadline    time.Duration
    52  
    53  	readMutex        sync.Mutex
    54  	currentReadBurst burst
    55  	readBursts       burstHistory
    56  
    57  	writeMutex        sync.Mutex
    58  	currentWriteBurst burst
    59  	writeBursts       burstHistory
    60  }
    61  
    62  // NewBurstMonitoredConn creates a new BurstMonitoredConn.
    63  func NewBurstMonitoredConn(
    64  	conn net.Conn,
    65  	isServer bool,
    66  	upstreamTargetBytes int64,
    67  	upstreamDeadline time.Duration,
    68  	downstreamTargetBytes int64,
    69  	downstreamDeadline time.Duration) *BurstMonitoredConn {
    70  
    71  	burstConn := &BurstMonitoredConn{
    72  		Conn:     conn,
    73  		isServer: isServer,
    74  	}
    75  
    76  	if isServer {
    77  		burstConn.readTargetBytes = upstreamTargetBytes
    78  		burstConn.readDeadline = upstreamDeadline
    79  		burstConn.writeTargetBytes = downstreamTargetBytes
    80  		burstConn.writeDeadline = downstreamDeadline
    81  	} else {
    82  		burstConn.readTargetBytes = downstreamTargetBytes
    83  		burstConn.readDeadline = downstreamDeadline
    84  		burstConn.writeTargetBytes = upstreamTargetBytes
    85  		burstConn.writeDeadline = upstreamDeadline
    86  	}
    87  
    88  	return burstConn
    89  }
    90  
    91  type burst struct {
    92  	startTime time.Time
    93  	endTime   time.Time
    94  	bytes     int64
    95  }
    96  
    97  func (b *burst) isZero() bool {
    98  	return b.startTime.IsZero()
    99  }
   100  
   101  func (b *burst) offset(baseTime time.Time) time.Duration {
   102  	offset := b.startTime.Sub(baseTime)
   103  	if offset <= 0 {
   104  		return 0
   105  	}
   106  	return offset
   107  }
   108  
   109  func (b *burst) duration() time.Duration {
   110  	duration := b.endTime.Sub(b.startTime)
   111  	if duration <= 0 {
   112  		return 0
   113  	}
   114  	return duration
   115  }
   116  
   117  func (b *burst) rate() int64 {
   118  	duration := b.duration()
   119  	if duration <= 0 {
   120  		return 0
   121  	}
   122  	return int64(
   123  		(float64(b.bytes) * float64(time.Second)) /
   124  			float64(duration))
   125  }
   126  
   127  func (b *burst) reset() {
   128  	b.startTime = time.Time{}
   129  	b.endTime = time.Time{}
   130  	b.bytes = 0
   131  }
   132  
   133  type burstHistory struct {
   134  	first burst
   135  	last  burst
   136  	min   burst
   137  	max   burst
   138  }
   139  
   140  func (conn *BurstMonitoredConn) Read(buffer []byte) (int, error) {
   141  
   142  	if conn.readTargetBytes <= 0 || conn.readDeadline <= 0 {
   143  		return conn.Conn.Read(buffer)
   144  	}
   145  
   146  	start := time.Now()
   147  	n, err := conn.Conn.Read(buffer)
   148  	end := time.Now()
   149  
   150  	if n > 0 {
   151  		conn.readMutex.Lock()
   152  		conn.updateBurst(
   153  			start,
   154  			end,
   155  			int64(n),
   156  			conn.readTargetBytes,
   157  			conn.readDeadline,
   158  			&conn.currentReadBurst,
   159  			&conn.readBursts)
   160  		conn.readMutex.Unlock()
   161  	}
   162  
   163  	// Note: no context error to preserve error type
   164  	return n, err
   165  }
   166  
   167  func (conn *BurstMonitoredConn) Write(buffer []byte) (int, error) {
   168  
   169  	if conn.writeTargetBytes <= 0 || conn.writeDeadline <= 0 {
   170  		return conn.Conn.Write(buffer)
   171  	}
   172  
   173  	start := time.Now()
   174  	n, err := conn.Conn.Write(buffer)
   175  	end := time.Now()
   176  
   177  	if n > 0 {
   178  		conn.writeMutex.Lock()
   179  		conn.updateBurst(
   180  			start,
   181  			end,
   182  			int64(n),
   183  			conn.writeTargetBytes,
   184  			conn.writeDeadline,
   185  			&conn.currentWriteBurst,
   186  			&conn.writeBursts)
   187  		conn.writeMutex.Unlock()
   188  	}
   189  
   190  	// Note: no context error to preserve error type
   191  	return n, err
   192  }
   193  
   194  // IsClosed implements the Closer iterface. The return value indicates whether
   195  // the underlying conn has been closed.
   196  func (conn *BurstMonitoredConn) IsClosed() bool {
   197  	closer, ok := conn.Conn.(Closer)
   198  	if !ok {
   199  		return false
   200  	}
   201  	return closer.IsClosed()
   202  }
   203  
   204  // GetMetrics returns log fields with burst metrics for the first, last, min
   205  // (by rate), and max bursts for this conn. Time/duration values are reported
   206  // in milliseconds. Rate is reported in bytes per second.
   207  func (conn *BurstMonitoredConn) GetMetrics(baseTime time.Time) LogFields {
   208  	logFields := make(LogFields)
   209  
   210  	addFields := func(prefix string, burst *burst) {
   211  		if burst.isZero() {
   212  			return
   213  		}
   214  		logFields[prefix+"offset"] = int64(burst.offset(baseTime) / time.Millisecond)
   215  		logFields[prefix+"duration"] = int64(burst.duration() / time.Millisecond)
   216  		logFields[prefix+"bytes"] = burst.bytes
   217  		logFields[prefix+"rate"] = burst.rate()
   218  	}
   219  
   220  	addHistory := func(prefix string, history *burstHistory) {
   221  		addFields(prefix+"first_", &history.first)
   222  		addFields(prefix+"last_", &history.last)
   223  		addFields(prefix+"min_", &history.min)
   224  		addFields(prefix+"max_", &history.max)
   225  	}
   226  
   227  	var upstreamBursts *burstHistory
   228  	var downstreamBursts *burstHistory
   229  
   230  	if conn.isServer {
   231  		upstreamBursts = &conn.readBursts
   232  		downstreamBursts = &conn.writeBursts
   233  	} else {
   234  		upstreamBursts = &conn.writeBursts
   235  		downstreamBursts = &conn.readBursts
   236  	}
   237  
   238  	addHistory("burst_upstream_", upstreamBursts)
   239  	addHistory("burst_downstream_", downstreamBursts)
   240  
   241  	return logFields
   242  }
   243  
   244  func (conn *BurstMonitoredConn) updateBurst(
   245  	operationStart time.Time,
   246  	operationEnd time.Time,
   247  	operationBytes int64,
   248  	thresholdBytes int64,
   249  	deadline time.Duration,
   250  	currentBurst *burst,
   251  	history *burstHistory) {
   252  
   253  	// Assumes the associated mutex is locked.
   254  
   255  	if !currentBurst.isZero() &&
   256  		operationEnd.Sub(currentBurst.startTime) > deadline {
   257  		// Partial burst failed to reach the target, so discard it.
   258  		currentBurst.reset()
   259  	}
   260  
   261  	if operationEnd.Sub(operationStart) > deadline {
   262  		// Operation exceeded deadline, so no burst.
   263  		return
   264  	}
   265  
   266  	if currentBurst.isZero() {
   267  		// Start a new burst.
   268  		currentBurst.startTime = operationStart
   269  	}
   270  
   271  	currentBurst.bytes += operationBytes
   272  
   273  	if currentBurst.bytes >= thresholdBytes {
   274  
   275  		// Burst completed. Bytes in excess of the target are included in the burst
   276  		// for a more accurate rate calculation: we know, roughly, when the last
   277  		// byte arrived, but not the last target byte. For the same reason, we do
   278  		// not count the excess bytes towards a subsequent burst.
   279  
   280  		currentBurst.endTime = operationEnd
   281  
   282  		if history.first.isZero() {
   283  			history.first = *currentBurst
   284  		}
   285  		history.last = *currentBurst
   286  		rate := currentBurst.rate()
   287  		if history.min.isZero() || history.min.rate() > rate {
   288  			history.min = *currentBurst
   289  		}
   290  		if history.max.isZero() || history.max.rate() < rate {
   291  			history.max = *currentBurst
   292  		}
   293  
   294  		currentBurst.reset()
   295  	}
   296  }