Files
netris-nestri/packages/relay/internal/common/latency.go
Kristian Ollikainen 6e82eff9e2 feat: Migrate from WebSocket to libp2p for peer-to-peer connectivity (#286)
## Description
Whew, some stuff is still not re-implemented, but it's working!

Rabbit's gonna explode with the amount of changes I reckon 😅



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a peer-to-peer relay system using libp2p with enhanced
stream forwarding, room state synchronization, and mDNS peer discovery.
- Added decentralized room and participant management, metrics
publishing, and safe, size-limited, concurrent message streaming with
robust framing and callback dispatching.
- Implemented asynchronous, callback-driven message handling over custom
libp2p streams replacing WebSocket signaling.
- **Improvements**
- Migrated signaling and stream protocols from WebSocket to libp2p,
improving reliability and scalability.
- Simplified configuration and environment variables, removing
deprecated flags and adding persistent data support.
- Enhanced logging, error handling, and connection management for better
observability and robustness.
- Refined RTP header extension registration and NAT IP handling for
improved WebRTC performance.
- **Bug Fixes**
- Improved ICE candidate buffering and SDP negotiation in WebRTC
connections.
  - Fixed NAT IP and UDP port range configuration issues.
- **Refactor**
- Modularized codebase, reorganized relay and server logic, and removed
deprecated WebSocket-based components.
- Streamlined message structures, removed obsolete enums and message
types, and simplified SafeMap concurrency.
- Replaced WebSocket signaling with libp2p stream protocols in server
and relay components.
- **Chores**
- Updated and cleaned dependencies across Go, Rust, and JavaScript
packages.
  - Added `.gitignore` for persistent data directory in relay package.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
Co-authored-by: Philipp Neumann <3daquawolf@gmail.com>
2025-06-06 16:48:49 +03:00

134 lines
3.4 KiB
Go

package common
import (
"fmt"
gen "relay/internal/proto"
"time"
"google.golang.org/protobuf/types/known/timestamppb"
)
type TimestampEntry struct {
Stage string `json:"stage"`
Time time.Time `json:"time"`
}
// LatencyTracker provides a generic structure for measuring time taken at various stages in message processing.
// It can be embedded in message structs for tracking the flow of data and calculating round-trip latency.
type LatencyTracker struct {
SequenceID string `json:"sequence_id"`
Timestamps []TimestampEntry `json:"timestamps"`
}
// NewLatencyTracker initializes a new LatencyTracker with the given sequence ID
func NewLatencyTracker(sequenceID string) *LatencyTracker {
return &LatencyTracker{
SequenceID: sequenceID,
Timestamps: make([]TimestampEntry, 0),
}
}
// AddTimestamp adds a new timestamp for a specific stage
func (lt *LatencyTracker) AddTimestamp(stage string) {
lt.Timestamps = append(lt.Timestamps, TimestampEntry{
Stage: stage,
// Ensure extremely precise UTC RFC3339 timestamps (down to nanoseconds)
Time: time.Now().UTC(),
})
}
// TotalLatency calculates the total latency from the earliest to the latest timestamp
func (lt *LatencyTracker) TotalLatency() (int64, error) {
if len(lt.Timestamps) < 2 {
return 0, nil // Not enough timestamps to calculate latency
}
var earliest, latest time.Time
for _, ts := range lt.Timestamps {
if earliest.IsZero() || ts.Time.Before(earliest) {
earliest = ts.Time
}
if latest.IsZero() || ts.Time.After(latest) {
latest = ts.Time
}
}
return latest.Sub(earliest).Milliseconds(), nil
}
// PainPoints returns a list of stages where the duration exceeds the given threshold.
func (lt *LatencyTracker) PainPoints(threshold time.Duration) []string {
var painPoints []string
var lastStage string
var lastTime time.Time
for _, ts := range lt.Timestamps {
stage := ts.Stage
if lastStage == "" {
lastStage = stage
lastTime = ts.Time
continue
}
currentTime := ts.Time
if currentTime.Sub(lastTime) > threshold {
painPoints = append(painPoints, fmt.Sprintf("%s -> %s", lastStage, stage))
}
lastStage = stage
lastTime = currentTime
}
return painPoints
}
// StageLatency calculates the time taken between two specific stages.
func (lt *LatencyTracker) StageLatency(startStage, endStage string) (time.Duration, error) {
var startTime, endTime time.Time
for _, ts := range lt.Timestamps {
if ts.Stage == startStage {
startTime = ts.Time
}
if ts.Stage == endStage {
endTime = ts.Time
}
}
/*if startTime == "" || endTime == "" {
return 0, fmt.Errorf("missing timestamps for stages: %s -> %s", startStage, endStage)
}*/
return endTime.Sub(startTime), nil
}
func LatencyTrackerFromProto(protolt *gen.ProtoLatencyTracker) *LatencyTracker {
ret := &LatencyTracker{
SequenceID: protolt.GetSequenceId(),
Timestamps: make([]TimestampEntry, 0),
}
for _, ts := range protolt.GetTimestamps() {
ret.Timestamps = append(ret.Timestamps, TimestampEntry{
Stage: ts.GetStage(),
Time: ts.GetTime().AsTime(),
})
}
return ret
}
func (lt *LatencyTracker) ToProto() *gen.ProtoLatencyTracker {
ret := &gen.ProtoLatencyTracker{
SequenceId: lt.SequenceID,
Timestamps: make([]*gen.ProtoTimestampEntry, len(lt.Timestamps)),
}
for i, timestamp := range lt.Timestamps {
ret.Timestamps[i] = &gen.ProtoTimestampEntry{
Stage: timestamp.Stage,
Time: timestamppb.New(timestamp.Time),
}
}
return ret
}