mirror of
https://github.com/hackdiaz-dev/free-turn-proxy.git
synced 2026-10-12 09:41:53 +02:00
perf: меньше фоновых таймеров при поднятом туннеле и опрос метрик только при видимом окне
This commit is contained in:
@@ -94,7 +94,6 @@ func Run(ctx context.Context, dtlsDialer *dtlsdial.Dialer, auth AuthHandler, log
|
||||
wg.Go(func() {
|
||||
runListener(runCtx, listenConn, &activeLocalPeer, inboundChan)
|
||||
})
|
||||
t := time.Tick(200 * time.Millisecond)
|
||||
|
||||
// Стрим 1 стартует первым для прогрева кэша учетных данных.
|
||||
okchan := make(chan struct{}, 1)
|
||||
@@ -104,7 +103,7 @@ func Run(ctx context.Context, dtlsDialer *dtlsdial.Dialer, auth AuthHandler, log
|
||||
DTLSLoop(runCtx, deps, params, peer, listenConn, inboundChan, cchan, okchan, 1)
|
||||
})
|
||||
wg.Go(func() {
|
||||
TURNLoop(runCtx, deps, params, peer, cchan, t, 1)
|
||||
TURNLoop(runCtx, deps, params, peer, cchan, 1)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -121,7 +120,7 @@ func Run(ctx context.Context, dtlsDialer *dtlsdial.Dialer, auth AuthHandler, log
|
||||
DTLSLoop(runCtx, deps, params, peer, listenConn, inboundChan, cchan, nil, streamID)
|
||||
})
|
||||
wg.Go(func() {
|
||||
TURNLoop(runCtx, deps, params, peer, cchan, t, streamID)
|
||||
TURNLoop(runCtx, deps, params, peer, cchan, streamID)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -40,12 +40,13 @@ var (
|
||||
)
|
||||
|
||||
const (
|
||||
defaultStatusInterval = 500 * time.Millisecond
|
||||
defaultRateInterval = time.Second
|
||||
defaultStatusInterval = 2 * time.Second
|
||||
defaultUDPHandshakeTimeout = 20 * time.Second
|
||||
defaultHandshakeConcurrency = 3
|
||||
|
||||
wakeTick = 5 * time.Second
|
||||
// Порог вдвое больше тика: сон короче порога всё равно пропускаем, а тик почаще
|
||||
// стоил бы пробуждений процесса на всё время сессии.
|
||||
wakeTick = 30 * time.Second
|
||||
wakeThreshold = 60 * time.Second
|
||||
)
|
||||
|
||||
@@ -53,7 +54,6 @@ type Options struct {
|
||||
ConnectTimeout time.Duration
|
||||
|
||||
StatusInterval time.Duration
|
||||
RateInterval time.Duration
|
||||
UDPHandshakeTimeout time.Duration
|
||||
HandshakeConcurrency int
|
||||
|
||||
@@ -64,9 +64,6 @@ func (o Options) withDefaults() Options {
|
||||
if o.StatusInterval <= 0 {
|
||||
o.StatusInterval = defaultStatusInterval
|
||||
}
|
||||
if o.RateInterval <= 0 {
|
||||
o.RateInterval = defaultRateInterval
|
||||
}
|
||||
if o.UDPHandshakeTimeout <= 0 {
|
||||
o.UDPHandshakeTimeout = defaultUDPHandshakeTimeout
|
||||
}
|
||||
@@ -184,28 +181,16 @@ func (s *Session) Run(ctx context.Context) (err error) {
|
||||
defer cancel()
|
||||
|
||||
var bg sync.WaitGroup
|
||||
if s.traffic != nil {
|
||||
bg.Add(1)
|
||||
go func() {
|
||||
defer bg.Done()
|
||||
s.traffic.rateMeter(runCtx, s.opts.RateInterval)
|
||||
}()
|
||||
}
|
||||
|
||||
bg.Add(1)
|
||||
go func() {
|
||||
defer bg.Done()
|
||||
bg.Go(func() {
|
||||
s.wake.Watch(runCtx, wakeTick, wakeThreshold, func(gap time.Duration) {
|
||||
log.Warnf("device slept for %s - recycling TURN allocations", gap.Truncate(time.Second))
|
||||
})
|
||||
}()
|
||||
})
|
||||
|
||||
var watchdogErr error
|
||||
bg.Add(1)
|
||||
go func() {
|
||||
defer bg.Done()
|
||||
bg.Go(func() {
|
||||
watchdogErr = s.watch(runCtx, cancel)
|
||||
}()
|
||||
})
|
||||
|
||||
relayErr := s.relay(runCtx, prov, peer)
|
||||
stopped := runCtx.Err() != nil
|
||||
@@ -230,8 +215,7 @@ func (s *Session) Snapshot() Snapshot {
|
||||
snap := Snapshot{Phase: st.phase, Streams: st.streams, Total: st.total, Err: st.err}
|
||||
if s.traffic != nil {
|
||||
snap.TxTotal, snap.RxTotal = s.traffic.stats.Counters()
|
||||
snap.TxRate = s.traffic.txRate.Load()
|
||||
snap.RxRate = s.traffic.rxRate.Load()
|
||||
snap.TxRate, snap.RxRate = s.traffic.rates()
|
||||
}
|
||||
return snap
|
||||
}
|
||||
|
||||
@@ -283,21 +283,25 @@ func TestSnapshotWithoutTrafficStaysZero(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrafficRateMeter(t *testing.T) {
|
||||
func TestTrafficRates(t *testing.T) {
|
||||
tr := newTraffic()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
go tr.rateMeter(ctx, 10*time.Millisecond)
|
||||
// Первый вызов только запоминает отметку - скорость считать не с чего.
|
||||
if tx, rx := tr.rates(); tx != 0 || rx != 0 {
|
||||
t.Fatalf("first rates = tx %d rx %d, want zero", tx, rx)
|
||||
}
|
||||
|
||||
tr.stats.AddTx(1000)
|
||||
tr.stats.AddRx(500)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
deadline := time.Now().Add(2 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
if tr.txRate.Load() == 1000 && tr.rxRate.Load() == 500 {
|
||||
return
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
tx, rx := tr.rates()
|
||||
if tx < 1000 || rx < 500 || rx >= tx {
|
||||
t.Fatalf("rates = tx %d rx %d, want tx >= 1000 > rx >= 500", tx, rx)
|
||||
}
|
||||
|
||||
// Без нового трафика скорость обязана упасть в ноль.
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
if tx, rx := tr.rates(); tx != 0 || rx != 0 {
|
||||
t.Fatalf("idle rates = tx %d rx %d, want zero", tx, rx)
|
||||
}
|
||||
t.Fatalf("rates = tx %d rx %d, want tx 1000 rx 500", tr.txRate.Load(), tr.rxRate.Load())
|
||||
}
|
||||
|
||||
+29
-26
@@ -1,48 +1,51 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"sync/atomic"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/samosvalishe/free-turn-proxy/internal/stats"
|
||||
)
|
||||
|
||||
// traffic агрегирует счётчики и мгновенную скорость передачи.
|
||||
// traffic агрегирует счётчики и считает скорость передачи.
|
||||
type traffic struct {
|
||||
stats *stats.Stats
|
||||
txRate atomic.Int64
|
||||
rxRate atomic.Int64
|
||||
stats *stats.Stats
|
||||
|
||||
mu sync.Mutex
|
||||
prevTx uint64
|
||||
prevRx uint64
|
||||
prevAt time.Time
|
||||
}
|
||||
|
||||
func newTraffic() *traffic {
|
||||
return &traffic{stats: stats.New(true)}
|
||||
}
|
||||
|
||||
func (t *traffic) rateMeter(ctx context.Context, interval time.Duration) {
|
||||
tick := time.NewTicker(interval)
|
||||
defer tick.Stop()
|
||||
var prevTx, prevRx uint64
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.txRate.Store(0)
|
||||
t.rxRate.Store(0)
|
||||
return
|
||||
case <-tick.C:
|
||||
tx, rx := t.stats.Counters()
|
||||
t.txRate.Store(clampInt64(tx - prevTx))
|
||||
t.rxRate.Store(clampInt64(rx - prevRx))
|
||||
prevTx, prevRx = tx, rx
|
||||
}
|
||||
// rates - средняя скорость (байт/с) с прошлого вызова. Считается на запрос, а не
|
||||
// фоновым тикером: спрашивает только открытый экран, и будить процесс ради цифры,
|
||||
// которую никто не читает, незачем.
|
||||
func (t *traffic) rates() (txRate, rxRate int64) {
|
||||
tx, rx := t.stats.Counters()
|
||||
now := time.Now()
|
||||
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
prevTx, prevRx, prevAt := t.prevTx, t.prevRx, t.prevAt
|
||||
t.prevTx, t.prevRx, t.prevAt = tx, rx, now
|
||||
|
||||
elapsed := now.Sub(prevAt)
|
||||
if prevAt.IsZero() || elapsed <= 0 {
|
||||
return 0, 0
|
||||
}
|
||||
sec := elapsed.Seconds()
|
||||
return perSecond(tx-prevTx, sec), perSecond(rx-prevRx, sec)
|
||||
}
|
||||
|
||||
// clampInt64 безопасно приводит uint64 к int64 с насыщением при переполнении.
|
||||
func clampInt64(u uint64) int64 {
|
||||
if u > math.MaxInt64 {
|
||||
func perSecond(delta uint64, sec float64) int64 {
|
||||
rate := float64(delta) / sec
|
||||
if rate >= math.MaxInt64 {
|
||||
return math.MaxInt64
|
||||
}
|
||||
return int64(u)
|
||||
return int64(rate)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user