346 lines
8.9 KiB
Go
346 lines
8.9 KiB
Go
package service
|
|
|
|
import (
|
|
edgenode "iop/apps/edge/internal/node"
|
|
"iop/packages/go/config"
|
|
iop "iop/proto/gen/iop"
|
|
)
|
|
|
|
func (m *modelQueueManager) getSnapshotForNode(nodeID string, rec *edgenode.NodeRecord) []*iop.ProviderSnapshot {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
var snaps []*iop.ProviderSnapshot
|
|
|
|
// Catalog-first: nodes with a providers[] catalog emit only catalog snapshots.
|
|
// Adapter snapshots (sections 1-4) are omitted to prevent duplicate metrics
|
|
// when a node has both adapter instances and provider-pool catalog entries.
|
|
if len(rec.Providers) > 0 {
|
|
for _, prov := range rec.Providers {
|
|
if prov.ID == "" {
|
|
continue
|
|
}
|
|
|
|
servedModels := make([]string, len(prov.Models))
|
|
copy(servedModels, prov.Models)
|
|
lifecycleCaps := make([]string, len(prov.LifecycleCapabilities))
|
|
copy(lifecycleCaps, prov.LifecycleCapabilities)
|
|
|
|
// Disabled providers appear in the snapshot with status=disabled and
|
|
// effective capacity 0 so operators can see the switch state.
|
|
if !config.ProviderEnabled(prov) {
|
|
snaps = append(snaps, &iop.ProviderSnapshot{
|
|
Adapter: prov.Adapter,
|
|
Status: "disabled",
|
|
Capacity: 0,
|
|
InFlight: 0,
|
|
Queued: 0,
|
|
Id: prov.ID,
|
|
Type: prov.Type,
|
|
Category: string(prov.Category),
|
|
ServedModels: servedModels,
|
|
Health: "disabled",
|
|
LoadRatio: 0,
|
|
LifecycleCapabilities: lifecycleCaps,
|
|
LongContextCapacity: 0,
|
|
LongInFlight: 0,
|
|
LongQueued: 0,
|
|
})
|
|
continue
|
|
}
|
|
|
|
capVal := prov.Capacity
|
|
inflight, queued := m.getStatsForProviderLocked(nodeID, prov.ID)
|
|
longInflight, longQueued := m.getLongStatsForProviderLocked(nodeID, prov.ID)
|
|
|
|
var loadRatio float32
|
|
if capVal > 0 {
|
|
loadRatio = float32(inflight) / float32(capVal)
|
|
}
|
|
|
|
snaps = append(snaps, &iop.ProviderSnapshot{
|
|
Adapter: prov.Adapter,
|
|
Status: "available",
|
|
Capacity: int32(capVal),
|
|
InFlight: int32(inflight),
|
|
Queued: int32(queued),
|
|
Id: prov.ID,
|
|
Type: prov.Type,
|
|
Category: string(prov.Category),
|
|
ServedModels: servedModels,
|
|
Health: prov.Health,
|
|
LoadRatio: loadRatio,
|
|
LifecycleCapabilities: lifecycleCaps,
|
|
LongContextCapacity: int32(prov.LongContextCapacity),
|
|
LongInFlight: int32(longInflight),
|
|
LongQueued: int32(longQueued),
|
|
})
|
|
}
|
|
return snaps
|
|
}
|
|
|
|
// Legacy adapter snapshots for nodes with no providers catalog.
|
|
concurrencyFallback := 1
|
|
if rec.Runtime.Concurrency > 0 {
|
|
concurrencyFallback = rec.Runtime.Concurrency
|
|
}
|
|
|
|
// 1. CLI
|
|
if rec.Adapters.CLI.Enabled {
|
|
capVal := concurrencyFallback
|
|
inflight, queued := m.getStatsForAdapterLocked(nodeID, rec, "cli")
|
|
snaps = append(snaps, &iop.ProviderSnapshot{
|
|
Adapter: "cli",
|
|
Status: "available",
|
|
Capacity: int32(capVal),
|
|
InFlight: int32(inflight),
|
|
Queued: int32(queued),
|
|
})
|
|
}
|
|
|
|
// 2. Ollama
|
|
for _, inst := range rec.Adapters.OllamaInstances {
|
|
if !inst.Enabled {
|
|
continue
|
|
}
|
|
name := inst.Name
|
|
if name == "" {
|
|
name = "ollama"
|
|
}
|
|
capVal := inst.Capacity
|
|
if capVal <= 0 {
|
|
capVal = concurrencyFallback
|
|
}
|
|
inflight, queued := m.getStatsForAdapterLocked(nodeID, rec, name)
|
|
snaps = append(snaps, &iop.ProviderSnapshot{
|
|
Adapter: name,
|
|
Status: "available",
|
|
Capacity: int32(capVal),
|
|
InFlight: int32(inflight),
|
|
Queued: int32(queued),
|
|
})
|
|
}
|
|
|
|
// 3. vLLM
|
|
for _, inst := range rec.Adapters.VllmInstances {
|
|
if !inst.Enabled {
|
|
continue
|
|
}
|
|
name := inst.Name
|
|
if name == "" {
|
|
name = "vllm"
|
|
}
|
|
capVal := inst.Capacity
|
|
if capVal <= 0 {
|
|
capVal = concurrencyFallback
|
|
}
|
|
inflight, queued := m.getStatsForAdapterLocked(nodeID, rec, name)
|
|
snaps = append(snaps, &iop.ProviderSnapshot{
|
|
Adapter: name,
|
|
Status: "available",
|
|
Capacity: int32(capVal),
|
|
InFlight: int32(inflight),
|
|
Queued: int32(queued),
|
|
})
|
|
}
|
|
|
|
// 4. OpenAI Compat
|
|
for _, inst := range rec.Adapters.OpenAICompatInstances {
|
|
if !inst.Enabled {
|
|
continue
|
|
}
|
|
name := inst.Name
|
|
if name == "" {
|
|
name = "openai_compat"
|
|
}
|
|
capVal := inst.Capacity
|
|
if capVal <= 0 {
|
|
capVal = concurrencyFallback
|
|
}
|
|
inflight, queued := m.getStatsForAdapterLocked(nodeID, rec, name)
|
|
snaps = append(snaps, &iop.ProviderSnapshot{
|
|
Adapter: name,
|
|
Status: "available",
|
|
Capacity: int32(capVal),
|
|
InFlight: int32(inflight),
|
|
Queued: int32(queued),
|
|
})
|
|
}
|
|
|
|
return snaps
|
|
}
|
|
|
|
// getStatsForProviderLocked returns in-flight and queued counts for a
|
|
// provider-pool provider identified by (nodeID, providerID). Must be called
|
|
// with m.mu held.
|
|
func (m *modelQueueManager) getStatsForProviderLocked(nodeID, providerID string) (inFlight, queued int) {
|
|
for _, rec := range m.inflightByRun {
|
|
if rec.nodeID == nodeID && rec.providerID == providerID {
|
|
inFlight++
|
|
}
|
|
}
|
|
for _, group := range m.groups {
|
|
for _, item := range group.queue {
|
|
for _, c := range item.candidates {
|
|
if c.entry.NodeID == nodeID && c.providerID == providerID {
|
|
queued++
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// getLongStatsForProviderLocked returns the long-context in-flight and queued count for a
|
|
// provider-pool provider identified by (nodeID, providerID), derived from tracked
|
|
// runs. Must be called with m.mu held.
|
|
func (m *modelQueueManager) getLongStatsForProviderLocked(nodeID, providerID string) (longInFlight, longQueued int) {
|
|
for _, rec := range m.inflightByRun {
|
|
if rec.nodeID == nodeID && rec.providerID == providerID && rec.long {
|
|
longInFlight++
|
|
}
|
|
}
|
|
for _, group := range m.groups {
|
|
for _, item := range group.queue {
|
|
if item.long {
|
|
for _, c := range item.candidates {
|
|
if c.entry.NodeID == nodeID && c.providerID == providerID {
|
|
longQueued++
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// longInFlightForProvider returns the long-context in-flight count for a provider,
|
|
// acquiring the manager lock. Exposed for status/snapshot reporting and tests.
|
|
func (m *modelQueueManager) longInFlightForProvider(nodeID, providerID string) int {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
longInFlight, _ := m.getLongStatsForProviderLocked(nodeID, providerID)
|
|
return longInFlight
|
|
}
|
|
|
|
func (m *modelQueueManager) getStatsForAdapterLocked(nodeID string, rec *edgenode.NodeRecord, adapterName string) (inFlight, queued int) {
|
|
for _, group := range m.groups {
|
|
canonical, ok := resolveSnapshotAdapterName(rec, group.adapter, group.target)
|
|
if ok && canonical == adapterName {
|
|
if val, ok := group.inflight[nodeID]; ok {
|
|
inFlight += val
|
|
}
|
|
for _, item := range group.queue {
|
|
for _, c := range item.candidates {
|
|
if c.entry.NodeID == nodeID {
|
|
queued++
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// resolveSnapshotAdapterName returns the adapter/instance key used to match
|
|
// queue state for a provider snapshot.
|
|
// When adapterType is empty it falls back to the provider id (target) so that
|
|
// queue stats are still resolved instead of being dropped on an empty key.
|
|
func resolveSnapshotAdapterName(rec *edgenode.NodeRecord, adapterType, target string) (string, bool) {
|
|
// Fallback to provider id when adapter is not set.
|
|
if adapterType == "" {
|
|
adapterType = target
|
|
}
|
|
if rec == nil {
|
|
return adapterType, true
|
|
}
|
|
|
|
// 1. Exact instance Name match (highest priority).
|
|
for _, inst := range rec.Adapters.OllamaInstances {
|
|
if inst.Name == adapterType {
|
|
return adapterType, true
|
|
}
|
|
}
|
|
for _, inst := range rec.Adapters.VllmInstances {
|
|
if inst.Name == adapterType {
|
|
return adapterType, true
|
|
}
|
|
}
|
|
for _, inst := range rec.Adapters.OpenAICompatInstances {
|
|
if inst.Name == adapterType {
|
|
return adapterType, true
|
|
}
|
|
}
|
|
|
|
// 2. Type-name route.
|
|
switch adapterType {
|
|
case "ollama":
|
|
var enabled []string
|
|
for _, inst := range rec.Adapters.OllamaInstances {
|
|
if inst.Enabled {
|
|
name := inst.Name
|
|
if name == "" {
|
|
name = "ollama"
|
|
}
|
|
enabled = append(enabled, name)
|
|
}
|
|
}
|
|
switch len(enabled) {
|
|
case 0:
|
|
return "ollama", true
|
|
case 1:
|
|
return enabled[0], true
|
|
default:
|
|
return "", false
|
|
}
|
|
|
|
case "vllm":
|
|
var enabled []string
|
|
for _, inst := range rec.Adapters.VllmInstances {
|
|
if inst.Enabled {
|
|
name := inst.Name
|
|
if name == "" {
|
|
name = "vllm"
|
|
}
|
|
enabled = append(enabled, name)
|
|
}
|
|
}
|
|
switch len(enabled) {
|
|
case 0:
|
|
return "vllm", true
|
|
case 1:
|
|
return enabled[0], true
|
|
default:
|
|
return "", false
|
|
}
|
|
|
|
case "openai_compat":
|
|
var enabled []string
|
|
for _, inst := range rec.Adapters.OpenAICompatInstances {
|
|
if inst.Enabled {
|
|
name := inst.Name
|
|
if name == "" {
|
|
name = "openai_compat"
|
|
}
|
|
enabled = append(enabled, name)
|
|
}
|
|
}
|
|
switch len(enabled) {
|
|
case 0:
|
|
return "openai_compat", true
|
|
case 1:
|
|
return enabled[0], true
|
|
default:
|
|
return "", false
|
|
}
|
|
|
|
case "cli":
|
|
return "cli", true
|
|
|
|
default:
|
|
return adapterType, true
|
|
}
|
|
}
|