feat(p2p): gossip free VRAM per node and add testable online check

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
Ettore Di Giacinto
2026-06-01 07:54:42 +00:00
parent c01ed631d6
commit 830f818c58
3 changed files with 56 additions and 6 deletions

View File

@@ -15,6 +15,7 @@ import (
"github.com/libp2p/go-libp2p/core/peer"
"github.com/mudler/LocalAI/core/schema"
"github.com/mudler/LocalAI/pkg/utils"
"github.com/mudler/LocalAI/pkg/xsysinfo"
"github.com/mudler/edgevpn/pkg/config"
"github.com/mudler/edgevpn/pkg/node"
"github.com/mudler/edgevpn/pkg/protocol"
@@ -348,9 +349,10 @@ func ExposeService(ctx context.Context, host, port, token, servicesID string) (*
func() {
updatedMap := map[string]any{}
updatedMap[name] = &schema.NodeData{
Name: name,
LastSeen: time.Now(),
ID: nodeID(name),
Name: name,
LastSeen: time.Now(),
ID: nodeID(name),
AvailableVRAM: xsysinfo.GetGPUAggregateInfo().FreeVRAM,
}
ledger.Add(servicesID, updatedMap)
},

View File

@@ -121,18 +121,32 @@ type StoresFindResponse struct {
Similarities []float32 `json:"similarities" yaml:"similarities"`
}
// NodeOnlineWindow is how long after its last announce a node still counts as
// online. Nodes re-announce into the edgevpn ledger every 20s (see core/p2p
// ExposeService), so 40s tolerates a single missed announce.
const NodeOnlineWindow = 40 * time.Second
type NodeData struct {
Name string
ID string
TunnelAddress string
ServiceID string
LastSeen time.Time
// AvailableVRAM is the node's free GPU VRAM in bytes at its last announce,
// gossiped so federation selection can prefer peers with more headroom.
// Zero for CPU-only nodes and for peers on an older version that does not
// publish it; the routing policy treats zero as the lowest VRAM tier.
AvailableVRAM uint64
}
func (d NodeData) IsOnline() bool {
now := time.Now()
// if the node was seen in the last 40 seconds, it's online
return now.Sub(d.LastSeen) < 40*time.Second
return d.IsOnlineAt(time.Now())
}
// IsOnlineAt reports whether the node counts as online relative to now. It is
// split from IsOnline so selection logic can be exercised with a fixed clock.
func (d NodeData) IsOnlineAt(now time.Time) bool {
return now.Sub(d.LastSeen) < NodeOnlineWindow
}
type P2PNodesResponse struct {

View File

@@ -0,0 +1,34 @@
package schema_test
import (
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/mudler/LocalAI/core/schema"
)
var _ = Describe("NodeData", func() {
ref := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
It("is online within the online window", func() {
nd := schema.NodeData{LastSeen: ref.Add(-30 * time.Second)}
Expect(nd.IsOnlineAt(ref)).To(BeTrue())
})
It("is offline once the online window has elapsed", func() {
nd := schema.NodeData{LastSeen: ref.Add(-50 * time.Second)}
Expect(nd.IsOnlineAt(ref)).To(BeFalse())
})
It("treats exactly the window boundary as offline (strict less-than)", func() {
nd := schema.NodeData{LastSeen: ref.Add(-schema.NodeOnlineWindow)}
Expect(nd.IsOnlineAt(ref)).To(BeFalse())
})
It("carries AvailableVRAM in bytes", func() {
nd := schema.NodeData{AvailableVRAM: 8_000_000_000}
Expect(nd.AvailableVRAM).To(Equal(uint64(8_000_000_000)))
})
})