lib/connections, lib/model: Track last connection duration (ref #7223) (#7242)

This adds a statistic to track the last connection duration per device.
It isn't used for much in this PR, but it's available for #7223 to use
in deciding how to order device connection attempts (deprioritizing
devices that just dropped our connection the last time).
This commit is contained in:
Jakob Borg 2021-01-05 17:45:07 +01:00 committed by GitHub
parent c48eb4241a
commit b13b15758d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 121 additions and 28 deletions

View File

@ -87,7 +87,7 @@ func (d *quicDialer) Dial(ctx context.Context, _ protocol.DeviceID, uri *url.URL
return internalConn{}, errors.Wrap(err, "open stream")
}
return internalConn{&quicTlsConn{session, stream, createdConn}, connTypeQUICClient, quicPriority}, nil
return newInternalConn(&quicTlsConn{session, stream, createdConn}, connTypeQUICClient, quicPriority), nil
}
type quicDialerFactory struct {

View File

@ -150,7 +150,7 @@ func (t *quicListener) serve(ctx context.Context) error {
continue
}
t.conns <- internalConn{&quicTlsConn{session, stream, nil}, connTypeQUICServer, quicPriority}
t.conns <- newInternalConn(&quicTlsConn{session, stream, nil}, connTypeQUICServer, quicPriority)
}
}

View File

@ -63,7 +63,7 @@ func (d *relayDialer) Dial(ctx context.Context, id protocol.DeviceID, uri *url.U
return internalConn{}, err
}
return internalConn{tc, connTypeRelayClient, relayPriority}, nil
return newInternalConn(tc, connTypeRelayClient, relayPriority), nil
}
type relayDialerFactory struct{}

View File

@ -105,7 +105,7 @@ func (t *relayListener) serve(ctx context.Context) error {
continue
}
t.conns <- internalConn{tc, connTypeRelayServer, relayPriority}
t.conns <- newInternalConn(tc, connTypeRelayServer, relayPriority)
// Poor mans notifier that informs the connection service that the
// relay URI has changed. This can only happen when we connect to a

View File

@ -35,8 +35,9 @@ type tlsConn interface {
// came from (type, priority).
type internalConn struct {
tlsConn
connType connType
priority int
connType connType
priority int
establishedAt time.Time
}
type connType int
@ -82,6 +83,15 @@ func (t connType) Transport() string {
}
}
func newInternalConn(tc tlsConn, connType connType, priority int) internalConn {
return internalConn{
tlsConn: tc,
connType: connType,
priority: priority,
establishedAt: time.Now(),
}
}
func (c internalConn) Close() error {
// *tls.Conn.Close() does more than it says on the tin. Specifically, it
// sends a TLS alert message, which might block forever if the
@ -119,6 +129,10 @@ func (c internalConn) Transport() string {
return transport + "6"
}
func (c internalConn) EstablishedAt() time.Time {
return c.establishedAt
}
func (c internalConn) String() string {
return fmt.Sprintf("%s-%s/%s/%s", c.LocalAddr(), c.RemoteAddr(), c.Type(), c.Crypto())
}

View File

@ -57,7 +57,7 @@ func (d *tcpDialer) Dial(ctx context.Context, _ protocol.DeviceID, uri *url.URL)
return internalConn{}, err
}
return internalConn{tc, connTypeTCPClient, tcpPriority}, nil
return newInternalConn(tc, connTypeTCPClient, tcpPriority), nil
}
type tcpDialerFactory struct{}

View File

@ -137,7 +137,7 @@ func (t *tcpListener) serve(ctx context.Context) error {
continue
}
t.conns <- internalConn{tc, connTypeTCPServer, tcpPriority}
t.conns <- newInternalConn(tc, connTypeTCPServer, tcpPriority)
}
}

View File

@ -246,7 +246,7 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersio
started: make(chan struct{}),
}
for devID := range cfg.Devices() {
m.deviceStatRefs[devID] = stats.NewDeviceStatisticsReference(m.db, devID.String())
m.deviceStatRefs[devID] = stats.NewDeviceStatisticsReference(m.db, devID)
}
m.Add(m.progressEmitter)
m.Add(svcutil.AsService(m.serve, m.String()))
@ -1684,6 +1684,7 @@ func (m *model) Closed(conn protocol.Connection, err error) {
m.pmut.Unlock()
return
}
delete(m.conn, device)
delete(m.connRequestLimiters, device)
delete(m.helloMessages, device)
@ -1695,6 +1696,7 @@ func (m *model) Closed(conn protocol.Connection, err error) {
m.pmut.Unlock()
m.progressEmitter.temporaryIndexUnsubscribe(conn)
m.deviceDidClose(device, time.Since(conn.EstablishedAt()))
l.Infof("Connection to %s at %s closed: %v", device, conn, err)
m.evLogger.Log(events.DeviceDisconnected, map[string]string{
@ -2175,7 +2177,16 @@ func (m *model) deviceWasSeen(deviceID protocol.DeviceID) {
sr, ok := m.deviceStatRefs[deviceID]
m.fmut.RUnlock()
if ok {
sr.WasSeen()
_ = sr.WasSeen()
}
}
func (m *model) deviceDidClose(deviceID protocol.DeviceID, duration time.Duration) {
m.fmut.RLock()
sr, ok := m.deviceStatRefs[deviceID]
m.fmut.RUnlock()
if ok {
_ = sr.LastConnectionDuration(duration)
}
}
@ -2674,7 +2685,7 @@ func (m *model) CommitConfiguration(from, to config.Configuration) bool {
for deviceID, toCfg := range toDevices {
fromCfg, ok := fromDevices[deviceID]
if !ok {
sr := stats.NewDeviceStatisticsReference(m.db, deviceID.String())
sr := stats.NewDeviceStatisticsReference(m.db, deviceID)
m.fmut.Lock()
m.deviceStatRefs[deviceID] = sr
m.fmut.Unlock()

View File

@ -152,6 +152,7 @@ type ConnectionInfo interface {
Priority() int
String() string
Crypto() string
EstablishedAt() time.Time
}
type rawConnection struct {

View File

@ -10,26 +10,34 @@ import (
"time"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/protocol"
)
const (
lastSeenKey = "lastSeen"
connDurationKey = "lastConnDuration"
)
type DeviceStatistics struct {
LastSeen time.Time `json:"lastSeen"`
LastSeen time.Time `json:"lastSeen"`
LastConnectionDurationS float64 `json:"lastConnectionDurationS"`
}
type DeviceStatisticsReference struct {
ns *db.NamespacedKV
device string
device protocol.DeviceID
}
func NewDeviceStatisticsReference(ldb *db.Lowlevel, device string) *DeviceStatisticsReference {
func NewDeviceStatisticsReference(dba backend.Backend, device protocol.DeviceID) *DeviceStatisticsReference {
return &DeviceStatisticsReference{
ns: db.NewDeviceStatisticsNamespace(ldb, device),
ns: db.NewDeviceStatisticsNamespace(dba, device.String()),
device: device,
}
}
func (s *DeviceStatisticsReference) GetLastSeen() (time.Time, error) {
t, ok, err := s.ns.Time("lastSeen")
t, ok, err := s.ns.Time(lastSeenKey)
if err != nil {
return time.Time{}, err
} else if !ok {
@ -41,9 +49,25 @@ func (s *DeviceStatisticsReference) GetLastSeen() (time.Time, error) {
return t, nil
}
func (s *DeviceStatisticsReference) GetLastConnectionDuration() (time.Duration, error) {
d, ok, err := s.ns.Int64(connDurationKey)
if err != nil {
return 0, err
} else if !ok {
return 0, nil
}
l.Debugln("stats.DeviceStatisticsReference.GetLastConnectionDuration:", s.device, d)
return time.Duration(d), nil
}
func (s *DeviceStatisticsReference) WasSeen() error {
l.Debugln("stats.DeviceStatisticsReference.WasSeen:", s.device)
return s.ns.PutTime("lastSeen", time.Now())
return s.ns.PutTime(lastSeenKey, time.Now())
}
func (s *DeviceStatisticsReference) LastConnectionDuration(d time.Duration) error {
l.Debugln("stats.DeviceStatisticsReference.LastConnectionDuration:", s.device, d)
return s.ns.PutInt64(connDurationKey, d.Nanoseconds())
}
func (s *DeviceStatisticsReference) GetStatistics() (DeviceStatistics, error) {
@ -51,7 +75,12 @@ func (s *DeviceStatisticsReference) GetStatistics() (DeviceStatistics, error) {
if err != nil {
return DeviceStatistics{}, err
}
lastConnDuration, err := s.GetLastConnectionDuration()
if err != nil {
return DeviceStatistics{}, err
}
return DeviceStatistics{
LastSeen: lastSeen,
LastSeen: lastSeen,
LastConnectionDurationS: lastConnDuration.Seconds(),
}, nil
}

View File

@ -1,10 +0,0 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
// The existence of this file means we get 0% test coverage rather than no
// test coverage at all. Remove when implementing an actual test.
package stats

43
lib/stats/stats_test.go Normal file
View File

@ -0,0 +1,43 @@
// Copyright (C) 2020 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
// The existence of this file means we get 0% test coverage rather than no
// test coverage at all. Remove when implementing an actual test.
package stats
import (
"testing"
"time"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/protocol"
)
func TestDeviceStat(t *testing.T) {
db := backend.OpenLevelDBMemory()
defer db.Close()
sr := NewDeviceStatisticsReference(db, protocol.LocalDeviceID)
if err := sr.WasSeen(); err != nil {
t.Fatal(err)
}
if err := sr.LastConnectionDuration(42 * time.Second); err != nil {
t.Fatal(err)
}
stat, err := sr.GetStatistics()
if err != nil {
t.Fatal(err)
}
if d := time.Since(stat.LastSeen); d > 5*time.Second {
t.Error("Last seen far in the past:", d)
}
if d := stat.LastConnectionDurationS; d != 42 {
t.Error("Bad last duration:", d)
}
}

View File

@ -10,6 +10,7 @@ import (
"errors"
"net"
"sync"
"time"
)
var ErrClosed = errors.New("closed")
@ -90,6 +91,10 @@ func (f *FakeConnectionInfo) String() string {
return ""
}
func (f *FakeConnectionInfo) EstablishedAt() time.Time {
return time.Time{}
}
type FakeAddr struct{}
func (FakeAddr) Network() string {