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 }