246 lines
6.7 KiB
Go
246 lines
6.7 KiB
Go
/*
|
|
* Copyright 2023 The Dragonfly Authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
//go:generate mockgen -destination mocks/probes_mock.go -source probes.go -package mocks
|
|
|
|
package networktopology
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"github.com/go-redis/redis/v8"
|
|
|
|
pkgredis "d7y.io/dragonfly/v2/pkg/redis"
|
|
"d7y.io/dragonfly/v2/scheduler/config"
|
|
"d7y.io/dragonfly/v2/scheduler/resource"
|
|
)
|
|
|
|
const (
|
|
// defaultMovingAverageWeight is the default weight of moving average.
|
|
defaultMovingAverageWeight = 0.1
|
|
)
|
|
|
|
// Probe is the probe metadata.
|
|
type Probe struct {
|
|
// Host metadata.
|
|
Host *resource.Host `json:"host"`
|
|
|
|
// RTT is the round-trip time sent via this pinger.
|
|
RTT time.Duration `json:"rtt"`
|
|
|
|
// CreatedAt is the time to create probe.
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
}
|
|
|
|
// Probes is the interface to store probes.
|
|
type Probes interface {
|
|
// Peek returns the oldest probe without removing it.
|
|
Peek() (*Probe, error)
|
|
|
|
// Enqueue enqueues probe into the queue.
|
|
Enqueue(*Probe) error
|
|
|
|
// Len gets the length of probes.
|
|
Len() (int64, error)
|
|
|
|
// CreatedAt is the creation time of probes.
|
|
CreatedAt() (time.Time, error)
|
|
|
|
// UpdatedAt is the updated time to store probe.
|
|
UpdatedAt() (time.Time, error)
|
|
|
|
// AverageRTT is the moving average round-trip time of probes.
|
|
AverageRTT() (time.Duration, error)
|
|
}
|
|
|
|
// probes is the implementation of Probes.
|
|
type probes struct {
|
|
// config is the probe config.
|
|
config config.ProbeConfig
|
|
|
|
// rdb is redis universal client interface.
|
|
rdb redis.UniversalClient
|
|
|
|
// srcHostID is the source host id.
|
|
srcHostID string
|
|
|
|
// destHostID is the destination host id.
|
|
destHostID string
|
|
}
|
|
|
|
// NewProbes creates a probes interface.
|
|
func NewProbes(cfg config.ProbeConfig, rdb redis.UniversalClient, srcHostID string, destHostID string) Probes {
|
|
return &probes{
|
|
config: cfg,
|
|
rdb: rdb,
|
|
srcHostID: srcHostID,
|
|
destHostID: destHostID,
|
|
}
|
|
}
|
|
|
|
// Peek returns the oldest probe without removing it.
|
|
func (p *probes) Peek() (*Probe, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), contextTimeout)
|
|
defer cancel()
|
|
|
|
rawProbe, err := p.rdb.LIndex(ctx, pkgredis.MakeProbesKeyInScheduler(p.srcHostID, p.destHostID), 0).Bytes()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
probe := &Probe{}
|
|
if err = json.Unmarshal(rawProbe, probe); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return probe, nil
|
|
}
|
|
|
|
// Enqueue enqueues probe into the queue.
|
|
func (p *probes) Enqueue(probe *Probe) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), contextTimeout)
|
|
defer cancel()
|
|
|
|
// Get the length of the queue.
|
|
length, err := p.Len()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If the queue is full, remove the oldest probe.
|
|
if length >= int64(p.config.QueueLength) {
|
|
if _, err := p.dequeue(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Push the probe into the queue.
|
|
data, err := json.Marshal(probe)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := p.rdb.RPush(ctx, pkgredis.MakeProbesKeyInScheduler(p.srcHostID, p.destHostID), data).Err(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Calculate the moving average round-trip time.
|
|
var averageRTT time.Duration
|
|
if length > 0 {
|
|
// If the queue is not empty, calculate the
|
|
// moving average round-trip time.
|
|
rawProbes, err := p.rdb.LRange(context.Background(), pkgredis.MakeProbesKeyInScheduler(p.srcHostID, p.destHostID), 0, -1).Result()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for index, rawProbe := range rawProbes {
|
|
probe := &Probe{}
|
|
if err = json.Unmarshal([]byte(rawProbe), probe); err != nil {
|
|
return err
|
|
}
|
|
|
|
if index == 0 {
|
|
averageRTT = probe.RTT
|
|
continue
|
|
}
|
|
|
|
averageRTT = time.Duration(float64(averageRTT)*defaultMovingAverageWeight +
|
|
float64(probe.RTT)*(1-defaultMovingAverageWeight))
|
|
}
|
|
} else {
|
|
// If the queue is empty, use the probe round-trip time as
|
|
// the moving average round-trip time.
|
|
averageRTT = probe.RTT
|
|
}
|
|
|
|
// Update the moving average round-trip time and updated time.
|
|
if err := p.rdb.HSet(ctx, pkgredis.MakeNetworkTopologyKeyInScheduler(p.srcHostID, p.destHostID), "averageRTT", averageRTT.Nanoseconds()).Err(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := p.rdb.HSet(ctx, pkgredis.MakeNetworkTopologyKeyInScheduler(p.srcHostID, p.destHostID), "updatedAt", probe.CreatedAt.Format(time.RFC3339Nano)).Err(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := p.rdb.Set(ctx, pkgredis.MakeProbedAtKeyInScheduler(p.destHostID), probe.CreatedAt.Format(time.RFC3339Nano), 0).Err(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := p.rdb.Incr(ctx, pkgredis.MakeProbedCountKeyInScheduler(p.destHostID)).Err(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Length gets the length of probes.
|
|
func (p *probes) Len() (int64, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), contextTimeout)
|
|
defer cancel()
|
|
|
|
return p.rdb.LLen(ctx, pkgredis.MakeProbesKeyInScheduler(p.srcHostID, p.destHostID)).Result()
|
|
}
|
|
|
|
// CreatedAt is the creation time of probes.
|
|
func (p *probes) CreatedAt() (time.Time, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), contextTimeout)
|
|
defer cancel()
|
|
|
|
return p.rdb.HGet(ctx, pkgredis.MakeNetworkTopologyKeyInScheduler(p.srcHostID, p.destHostID), "createdAt").Time()
|
|
}
|
|
|
|
// UpdatedAt is the updated time to store probe.
|
|
func (p *probes) UpdatedAt() (time.Time, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), contextTimeout)
|
|
defer cancel()
|
|
|
|
return p.rdb.HGet(ctx, pkgredis.MakeNetworkTopologyKeyInScheduler(p.srcHostID, p.destHostID), "updatedAt").Time()
|
|
}
|
|
|
|
// AverageRTT is the moving average round-trip time of probes.
|
|
func (p *probes) AverageRTT() (time.Duration, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), contextTimeout)
|
|
defer cancel()
|
|
|
|
averageRTT, err := p.rdb.HGet(ctx, pkgredis.MakeNetworkTopologyKeyInScheduler(p.srcHostID, p.destHostID), "averageRTT").Int64()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return time.Duration(averageRTT), nil
|
|
}
|
|
|
|
// dequeue removes and returns the oldest probe.
|
|
func (p *probes) dequeue() (*Probe, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), contextTimeout)
|
|
defer cancel()
|
|
|
|
rawProbe, err := p.rdb.LPop(ctx, pkgredis.MakeProbesKeyInScheduler(p.srcHostID, p.destHostID)).Bytes()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
probe := &Probe{}
|
|
if err = json.Unmarshal(rawProbe, probe); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return probe, nil
|
|
}
|