Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
399
backend/sdk/cliproxy/auth/home_in_flight_publisher.go
Normal file
399
backend/sdk/cliproxy/auth/home_in_flight_publisher.go
Normal file
|
|
@ -0,0 +1,399 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/home"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executionregistry"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// HomeInFlightTransport publishes in-flight observation frames for one Home lifetime.
|
||||
type HomeInFlightTransport interface {
|
||||
HeartbeatOK() bool
|
||||
LPushInFlightSnapshot(context.Context, []byte) error
|
||||
}
|
||||
|
||||
// HomeInFlightPublisherConfig bounds in-flight observation frames.
|
||||
type HomeInFlightPublisherConfig struct {
|
||||
SnapshotInterval time.Duration
|
||||
MaxPartBytes int
|
||||
MaxPartCount int
|
||||
MaxRevisionBytes int
|
||||
MaxAggregateGroups int
|
||||
MaxDetails int
|
||||
MaxStringBytes int
|
||||
}
|
||||
|
||||
type homeInFlightAggregateKey struct {
|
||||
CredentialID string
|
||||
Model string
|
||||
Accounted bool
|
||||
}
|
||||
|
||||
func homeInFlightStatus(accounted bool) home.InFlightAccountedStatus {
|
||||
if accounted {
|
||||
return home.InFlightAccounted
|
||||
}
|
||||
return home.InFlightUnaccounted
|
||||
}
|
||||
|
||||
// HomeInFlightPublisherConfigFromConfig converts validated runtime config into publisher bounds.
|
||||
func HomeInFlightPublisherConfigFromConfig(cfg internalconfig.CredentialInFlightConfig) (HomeInFlightPublisherConfig, error) {
|
||||
snapshotInterval, _, _, errDurations := cfg.Durations()
|
||||
if errDurations != nil {
|
||||
return HomeInFlightPublisherConfig{}, errDurations
|
||||
}
|
||||
if errValidate := cfg.Validate(); errValidate != nil {
|
||||
return HomeInFlightPublisherConfig{}, errValidate
|
||||
}
|
||||
return HomeInFlightPublisherConfig{
|
||||
SnapshotInterval: snapshotInterval,
|
||||
MaxPartBytes: cfg.MaxPartBytes,
|
||||
MaxPartCount: cfg.MaxPartCount,
|
||||
MaxRevisionBytes: cfg.MaxRevisionBytes,
|
||||
MaxAggregateGroups: cfg.MaxAggregateGroups,
|
||||
MaxDetails: cfg.MaxDetails,
|
||||
MaxStringBytes: cfg.MaxStringBytes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ApplyHomeInFlightPublisherConfig stores an immutable validated publisher config snapshot.
|
||||
func (m *Manager) ApplyHomeInFlightPublisherConfig(cfg HomeInFlightPublisherConfig) {
|
||||
if m == nil || !validHomeInFlightPublisherConfig(cfg) {
|
||||
return
|
||||
}
|
||||
snapshot := cfg
|
||||
m.homeInFlightPublisherConfig.Store(&snapshot)
|
||||
}
|
||||
|
||||
// HomeInFlightPublisherConfig returns the current immutable publisher config snapshot.
|
||||
func (m *Manager) HomeInFlightPublisherConfig() HomeInFlightPublisherConfig {
|
||||
if m == nil {
|
||||
return HomeInFlightPublisherConfig{}
|
||||
}
|
||||
cfg := m.homeInFlightPublisherConfig.Load()
|
||||
if cfg == nil {
|
||||
return HomeInFlightPublisherConfig{}
|
||||
}
|
||||
return *cfg
|
||||
}
|
||||
|
||||
func validHomeInFlightPublisherConfig(cfg HomeInFlightPublisherConfig) bool {
|
||||
if cfg.SnapshotInterval <= 0 || cfg.MaxPartBytes < 1024 || cfg.MaxPartCount <= 0 || cfg.MaxPartCount > internalconfig.DefaultInFlightMaxPartCount ||
|
||||
cfg.MaxRevisionBytes < cfg.MaxPartBytes || cfg.MaxRevisionBytes > internalconfig.DefaultInFlightMaxRevisionBytes ||
|
||||
cfg.MaxAggregateGroups <= 0 || cfg.MaxAggregateGroups > internalconfig.DefaultInFlightMaxAggregateGroups ||
|
||||
cfg.MaxDetails < 0 || cfg.MaxDetails > internalconfig.DefaultInFlightMaxDetails ||
|
||||
cfg.MaxStringBytes <= 0 || cfg.MaxStringBytes > internalconfig.DefaultInFlightMaxStringBytes {
|
||||
return false
|
||||
}
|
||||
return (cfg.MaxRevisionBytes+cfg.MaxPartBytes-1)/cfg.MaxPartBytes <= cfg.MaxPartCount
|
||||
}
|
||||
|
||||
func validHomeInFlightPublisherBounds(cfg HomeInFlightPublisherConfig) bool {
|
||||
return cfg.MaxPartBytes > 0 && cfg.MaxPartCount > 0 && cfg.MaxRevisionBytes >= cfg.MaxPartBytes &&
|
||||
cfg.MaxAggregateGroups > 0 && cfg.MaxDetails >= 0 && cfg.MaxStringBytes > 0
|
||||
}
|
||||
|
||||
// StartHomeInFlightPublisher publishes periodic snapshots for the supplied lifetime registry.
|
||||
func (m *Manager) StartHomeInFlightPublisher(ctx context.Context, transport HomeInFlightTransport, registry *executionregistry.Registry) {
|
||||
if m == nil || transport == nil || registry == nil {
|
||||
return
|
||||
}
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
timer := time.NewTimer(0)
|
||||
defer timer.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case observedAt := <-timer.C:
|
||||
cfg := m.HomeInFlightPublisherConfig()
|
||||
interval := cfg.SnapshotInterval
|
||||
if interval <= 0 {
|
||||
interval = 2 * time.Second
|
||||
}
|
||||
timer.Reset(interval)
|
||||
if !transport.HeartbeatOK() {
|
||||
continue
|
||||
}
|
||||
freeze := registry.FreezeInFlight(observedAt.UTC())
|
||||
frames := encodeHomeInFlightFreeze(freeze, observedAt.UTC(), cfg)
|
||||
for index := range frames {
|
||||
raw, errMarshal := json.Marshal(frames[index])
|
||||
if errMarshal != nil {
|
||||
log.Warn("failed to encode in-flight snapshot frame")
|
||||
break
|
||||
}
|
||||
if errPush := transport.LPushInFlightSnapshot(ctx, raw); errPush != nil {
|
||||
log.Warn("failed to publish in-flight snapshot frame")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func encodeHomeInFlightFreeze(freeze executionregistry.Freeze, observedAt time.Time, cfg HomeInFlightPublisherConfig) []home.InFlightSnapshotFrame {
|
||||
observedAt = observedAt.UTC()
|
||||
aggregateCounts := make(map[homeInFlightAggregateKey]int64, len(freeze.Executions))
|
||||
aggregateKeysValid := true
|
||||
for _, observation := range freeze.Executions {
|
||||
key := homeInFlightAggregateKey{
|
||||
CredentialID: observation.CredentialID,
|
||||
Model: homeInFlightObservationModel(observation),
|
||||
Accounted: observation.Accounted,
|
||||
}
|
||||
if len(key.CredentialID) > cfg.MaxStringBytes || len(key.Model) > cfg.MaxStringBytes {
|
||||
aggregateKeysValid = false
|
||||
}
|
||||
aggregateCounts[key]++
|
||||
}
|
||||
aggregates := make([]home.InFlightAggregate, 0, len(aggregateCounts))
|
||||
for key, count := range aggregateCounts {
|
||||
aggregates = append(aggregates, home.InFlightAggregate{
|
||||
CredentialID: key.CredentialID,
|
||||
Model: key.Model,
|
||||
Status: homeInFlightStatus(key.Accounted),
|
||||
Count: count,
|
||||
})
|
||||
}
|
||||
sort.Slice(aggregates, func(left, right int) bool {
|
||||
if aggregates[left].CredentialID != aggregates[right].CredentialID {
|
||||
return aggregates[left].CredentialID < aggregates[right].CredentialID
|
||||
}
|
||||
if aggregates[left].Model != aggregates[right].Model {
|
||||
return aggregates[left].Model < aggregates[right].Model
|
||||
}
|
||||
return aggregates[left].Status < aggregates[right].Status
|
||||
})
|
||||
if !validHomeInFlightPublisherBounds(cfg) || !aggregateKeysValid || len(aggregates) > cfg.MaxAggregateGroups {
|
||||
return homeInFlightOverflow(freeze, observedAt, len(aggregates))
|
||||
}
|
||||
|
||||
details := make([]home.InFlightRequestDetail, 0, len(freeze.Executions))
|
||||
detailsTruncated := false
|
||||
for _, observation := range freeze.Executions {
|
||||
detail, bounded := homeInFlightBoundDetail(home.InFlightRequestDetail{
|
||||
RequestID: observation.RequestID,
|
||||
CredentialID: observation.CredentialID,
|
||||
Model: homeInFlightObservationModel(observation),
|
||||
RequestKind: observation.RequestKind,
|
||||
StartedAt: observation.StartedAt.UTC(),
|
||||
}, cfg.MaxStringBytes)
|
||||
if !validHomeInFlightDetail(detail, cfg.MaxStringBytes) {
|
||||
detailsTruncated = true
|
||||
continue
|
||||
}
|
||||
detailsTruncated = detailsTruncated || bounded
|
||||
details = append(details, detail)
|
||||
}
|
||||
sort.Slice(details, func(left, right int) bool {
|
||||
if !details[left].StartedAt.Equal(details[right].StartedAt) {
|
||||
return details[left].StartedAt.Before(details[right].StartedAt)
|
||||
}
|
||||
if details[left].RequestID != details[right].RequestID {
|
||||
return details[left].RequestID < details[right].RequestID
|
||||
}
|
||||
if details[left].CredentialID != details[right].CredentialID {
|
||||
return details[left].CredentialID < details[right].CredentialID
|
||||
}
|
||||
if details[left].Model != details[right].Model {
|
||||
return details[left].Model < details[right].Model
|
||||
}
|
||||
return details[left].RequestKind < details[right].RequestKind
|
||||
})
|
||||
|
||||
if len(details) > cfg.MaxDetails {
|
||||
details = details[:cfg.MaxDetails]
|
||||
detailsTruncated = true
|
||||
}
|
||||
|
||||
for {
|
||||
frames, aggregatesPacked, includedDetails := packHomeInFlightFrames(freeze, observedAt, cfg, aggregates, details, detailsTruncated)
|
||||
if !aggregatesPacked {
|
||||
return homeInFlightOverflow(freeze, observedAt, len(aggregates))
|
||||
}
|
||||
if includedDetails < len(details) {
|
||||
details = details[:includedDetails]
|
||||
detailsTruncated = true
|
||||
continue
|
||||
}
|
||||
if homeInFlightFramesWithinBounds(frames, cfg) {
|
||||
return frames
|
||||
}
|
||||
if len(details) == 0 {
|
||||
return homeInFlightOverflow(freeze, observedAt, len(aggregates))
|
||||
}
|
||||
details = details[:len(details)-1]
|
||||
detailsTruncated = true
|
||||
}
|
||||
}
|
||||
|
||||
func homeInFlightObservationModel(observation executionregistry.Observation) string {
|
||||
if observation.Accounted {
|
||||
return observation.Model
|
||||
}
|
||||
if model, valid := validCanonicalHomeConcurrencyModelKey(observation.Model); valid {
|
||||
return model
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func validHomeInFlightDetail(detail home.InFlightRequestDetail, maxStringBytes int) bool {
|
||||
validString := func(value string) bool {
|
||||
return utf8.ValidString(value) && strings.TrimSpace(value) != "" && len(value) <= maxStringBytes
|
||||
}
|
||||
return validString(detail.RequestID) && validString(detail.CredentialID) && validString(detail.Model) && validString(detail.RequestKind) && !detail.StartedAt.IsZero() && detail.StartedAt.Location() == time.UTC
|
||||
}
|
||||
|
||||
func homeInFlightBoundDetail(detail home.InFlightRequestDetail, maxBytes int) (home.InFlightRequestDetail, bool) {
|
||||
truncated := false
|
||||
bound := func(value string) string {
|
||||
bounded := homeInFlightTruncateString(value, maxBytes)
|
||||
truncated = truncated || bounded != value
|
||||
return bounded
|
||||
}
|
||||
detail.RequestID = bound(detail.RequestID)
|
||||
detail.CredentialID = bound(detail.CredentialID)
|
||||
detail.Model = bound(detail.Model)
|
||||
detail.RequestKind = bound(detail.RequestKind)
|
||||
return detail, truncated
|
||||
}
|
||||
|
||||
func homeInFlightTruncateString(value string, maxBytes int) string {
|
||||
if maxBytes <= 0 || len(value) <= maxBytes {
|
||||
return value
|
||||
}
|
||||
value = value[:maxBytes]
|
||||
for len(value) > 0 && !utf8.ValidString(value) {
|
||||
value = value[:len(value)-1]
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func packHomeInFlightFrames(freeze executionregistry.Freeze, observedAt time.Time, cfg HomeInFlightPublisherConfig, aggregates []home.InFlightAggregate, details []home.InFlightRequestDetail, detailsTruncated bool) ([]home.InFlightSnapshotFrame, bool, int) {
|
||||
frames := make([]home.InFlightSnapshotFrame, 0, cfg.MaxPartCount)
|
||||
current := homeInFlightPartFrame(freeze, observedAt, cfg.MaxPartCount, detailsTruncated)
|
||||
appendCurrent := func() bool {
|
||||
if len(frames) >= cfg.MaxPartCount {
|
||||
return false
|
||||
}
|
||||
frames = append(frames, current)
|
||||
current = homeInFlightPartFrame(freeze, observedAt, cfg.MaxPartCount, detailsTruncated)
|
||||
return true
|
||||
}
|
||||
for _, aggregate := range aggregates {
|
||||
candidate := current
|
||||
candidate.Aggregates = append(candidate.Aggregates, aggregate)
|
||||
if homeInFlightFrameWithinPartLimit(candidate, cfg.MaxPartBytes) {
|
||||
current = candidate
|
||||
continue
|
||||
}
|
||||
if len(current.Aggregates) == 0 && len(current.Details) == 0 {
|
||||
return nil, false, 0
|
||||
}
|
||||
if !appendCurrent() {
|
||||
return nil, false, 0
|
||||
}
|
||||
candidate = current
|
||||
candidate.Aggregates = append(candidate.Aggregates, aggregate)
|
||||
if !homeInFlightFrameWithinPartLimit(candidate, cfg.MaxPartBytes) {
|
||||
return nil, false, 0
|
||||
}
|
||||
current = candidate
|
||||
}
|
||||
|
||||
includedDetails := 0
|
||||
for _, detail := range details {
|
||||
candidate := current
|
||||
candidate.Details = append(candidate.Details, detail)
|
||||
if homeInFlightFrameWithinPartLimit(candidate, cfg.MaxPartBytes) {
|
||||
current = candidate
|
||||
includedDetails++
|
||||
continue
|
||||
}
|
||||
if len(current.Aggregates) == 0 && len(current.Details) == 0 {
|
||||
return frames, true, includedDetails
|
||||
}
|
||||
if !appendCurrent() {
|
||||
return frames, true, includedDetails - len(current.Details)
|
||||
}
|
||||
candidate = current
|
||||
candidate.Details = append(candidate.Details, detail)
|
||||
if !homeInFlightFrameWithinPartLimit(candidate, cfg.MaxPartBytes) {
|
||||
return frames, true, includedDetails
|
||||
}
|
||||
current = candidate
|
||||
includedDetails++
|
||||
}
|
||||
if len(current.Aggregates) != 0 || len(current.Details) != 0 || len(frames) == 0 {
|
||||
if !appendCurrent() {
|
||||
if len(current.Aggregates) != 0 {
|
||||
return nil, false, 0
|
||||
}
|
||||
return frames, true, includedDetails - len(current.Details)
|
||||
}
|
||||
}
|
||||
for index := range frames {
|
||||
partIndex, partCount := index, len(frames)
|
||||
frames[index].PartIndex = &partIndex
|
||||
frames[index].PartCount = &partCount
|
||||
}
|
||||
return frames, true, includedDetails
|
||||
}
|
||||
|
||||
func homeInFlightPartFrame(freeze executionregistry.Freeze, observedAt time.Time, partCount int, detailsTruncated bool) home.InFlightSnapshotFrame {
|
||||
partIndex := 0
|
||||
return home.InFlightSnapshotFrame{
|
||||
Kind: home.InFlightFramePart,
|
||||
Revision: freeze.Revision,
|
||||
ObservedAt: observedAt,
|
||||
BarrierRevision: freeze.BarrierRevision,
|
||||
PartIndex: &partIndex,
|
||||
PartCount: &partCount,
|
||||
DetailsTruncated: detailsTruncated,
|
||||
}
|
||||
}
|
||||
|
||||
func homeInFlightFrameWithinPartLimit(frame home.InFlightSnapshotFrame, maxPartBytes int) bool {
|
||||
raw, errMarshal := json.Marshal(frame)
|
||||
return errMarshal == nil && len(raw) <= maxPartBytes
|
||||
}
|
||||
|
||||
func homeInFlightFramesWithinBounds(frames []home.InFlightSnapshotFrame, cfg HomeInFlightPublisherConfig) bool {
|
||||
if len(frames) == 0 || len(frames) > cfg.MaxPartCount {
|
||||
return false
|
||||
}
|
||||
totalBytes := 0
|
||||
for _, frame := range frames {
|
||||
raw, errMarshal := json.Marshal(frame)
|
||||
if errMarshal != nil || len(raw) > cfg.MaxPartBytes {
|
||||
return false
|
||||
}
|
||||
totalBytes += len(raw)
|
||||
if totalBytes > cfg.MaxRevisionBytes {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func homeInFlightOverflow(freeze executionregistry.Freeze, observedAt time.Time, aggregateGroupCount int) []home.InFlightSnapshotFrame {
|
||||
return []home.InFlightSnapshotFrame{{
|
||||
Kind: home.InFlightFrameOverflow,
|
||||
Revision: freeze.Revision,
|
||||
ObservedAt: observedAt,
|
||||
BarrierRevision: freeze.BarrierRevision,
|
||||
AggregateGroupCount: aggregateGroupCount,
|
||||
}}
|
||||
}
|
||||
Loading…
Reference in a new issue