chore: 并入 iotlcm-worker-svr 历史到 worker-svr/(原提交 96f0f4e)
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
// Package supply holds bounded, per-channel telemetry independent of card volume.
|
||||
package supply
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const SchemaVersion = 1
|
||||
|
||||
type Wait struct {
|
||||
Count int64 `json:"count"`
|
||||
TotalMS int64 `json:"totalMs"`
|
||||
LongestMS int64 `json:"longestMs"`
|
||||
}
|
||||
type Channel struct {
|
||||
Key string `json:"chanKey"`
|
||||
FirstQueryMS *int64 `json:"firstQueryMs"`
|
||||
FirstEnqueueMS *int64 `json:"firstEnqueueMs"`
|
||||
Startup Wait `json:"startupWait"`
|
||||
Confirmed Wait `json:"confirmedWait"`
|
||||
Unconfirmed Wait `json:"unconfirmedIdle"`
|
||||
Consumer Wait `json:"consumerStartupWait"`
|
||||
UnknownMS int64 `json:"unknownMs"`
|
||||
EmptyClaims int64 `json:"emptyClaims"`
|
||||
ValidSamples int64 `json:"validSamples"`
|
||||
MissingSamples int64 `json:"missingSamples"`
|
||||
Stages map[string]int64 `json:"producerStageMs"`
|
||||
Completeness string `json:"completeness"`
|
||||
Termination string `json:"termination,omitempty"`
|
||||
CoverageStart time.Time `json:"coverageStart"`
|
||||
CoverageEnd time.Time `json:"coverageEnd"`
|
||||
}
|
||||
type Summary struct {
|
||||
Targets int `json:"targetChannels"`
|
||||
Valid int `json:"validChannels"`
|
||||
Confirmed int `json:"confirmedChannels"`
|
||||
Unknown int `json:"unknownChannels"`
|
||||
UnknownMS int64 `json:"unknownChannelMs"`
|
||||
UnconfirmedMS int64 `json:"unconfirmedChannelMs"`
|
||||
ConsumerMS int64 `json:"consumerStartupChannelMs"`
|
||||
TotalMS int64 `json:"confirmedChannelMs"`
|
||||
LongestMS int64 `json:"longestMs"`
|
||||
Worst string `json:"worstChannel,omitempty"`
|
||||
}
|
||||
type Snapshot struct {
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
Attempt string `json:"attempt"`
|
||||
ResolutionMS int `json:"resolutionMs"`
|
||||
Partial bool `json:"partial"`
|
||||
Truncated bool `json:"truncated"`
|
||||
Summary Summary `json:"summary"`
|
||||
Channels []Channel `json:"channels"`
|
||||
}
|
||||
type View struct {
|
||||
Version uint64
|
||||
Pending int
|
||||
Processing int
|
||||
Stage string
|
||||
Ended bool
|
||||
}
|
||||
type Sample struct {
|
||||
Ready int64
|
||||
Leases int64
|
||||
Future int64
|
||||
Eligible bool
|
||||
Known bool
|
||||
}
|
||||
type state struct {
|
||||
Channel
|
||||
View
|
||||
last time.Time
|
||||
kind string
|
||||
continuous int64
|
||||
open bool
|
||||
}
|
||||
type Observer struct {
|
||||
mu sync.Mutex
|
||||
started time.Time
|
||||
attempt string
|
||||
partial bool
|
||||
retained Summary
|
||||
truncated bool
|
||||
channels map[string]*state
|
||||
}
|
||||
|
||||
func New(attempt string, started time.Time, keys []string) *Observer {
|
||||
o := &Observer{attempt: attempt, started: started, channels: make(map[string]*state, len(keys))}
|
||||
for _, k := range keys {
|
||||
o.channels[k] = &state{Channel: Channel{Key: k, Stages: map[string]int64{}, CoverageStart: started}, View: View{Stage: "initializing"}}
|
||||
}
|
||||
return o
|
||||
}
|
||||
func (o *Observer) Event(key, stage string, pending int, added bool, ended bool, now time.Time) {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
s := o.channels[key]
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
s.Version++
|
||||
s.Stage = stage
|
||||
s.Pending = pending
|
||||
if stage == "sql_query" && s.FirstQueryMS == nil {
|
||||
v := now.Sub(o.started).Milliseconds()
|
||||
s.FirstQueryMS = &v
|
||||
}
|
||||
if added && s.FirstEnqueueMS == nil {
|
||||
v := now.Sub(o.started).Milliseconds()
|
||||
s.FirstEnqueueMS = &v
|
||||
s.close()
|
||||
}
|
||||
if ended {
|
||||
s.Ended = true
|
||||
s.Termination = stage
|
||||
s.CoverageEnd = now
|
||||
s.close()
|
||||
}
|
||||
}
|
||||
func (o *Observer) Empty(key string) {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
if s := o.channels[key]; s != nil && !s.Ended {
|
||||
s.EmptyClaims++
|
||||
}
|
||||
}
|
||||
func (o *Observer) Processing(key string, delta int) {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
if s := o.channels[key]; s != nil {
|
||||
s.Version++
|
||||
s.Processing = max(0, s.Processing+delta)
|
||||
if delta > 0 {
|
||||
s.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
func (o *Observer) Views() map[string]View {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
out := make(map[string]View, len(o.channels))
|
||||
for k, s := range o.channels {
|
||||
out[k] = s.View
|
||||
}
|
||||
return out
|
||||
}
|
||||
func (s *state) close() { s.kind = ""; s.continuous = 0; s.open = false; s.last = time.Time{} }
|
||||
func (o *Observer) Partial() { o.mu.Lock(); o.partial = true; o.mu.Unlock() }
|
||||
func (o *Observer) Apply(key string, before View, sample Sample, now time.Time) {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
s := o.channels[key]
|
||||
if s == nil || s.Ended {
|
||||
return
|
||||
}
|
||||
kind := "unknown"
|
||||
if sample.Known && before.Version == s.Version {
|
||||
s.ValidSamples++
|
||||
switch {
|
||||
case !sample.Eligible:
|
||||
s.close()
|
||||
s.CoverageEnd = now
|
||||
return
|
||||
case sample.Ready > 0:
|
||||
if s.Processing == 0 && sample.Leases == 0 {
|
||||
kind = "consumer"
|
||||
} else {
|
||||
kind = "stock"
|
||||
}
|
||||
case s.Processing > 0 || sample.Leases > 0:
|
||||
kind = "processing"
|
||||
case sample.Future > 0:
|
||||
kind = "scheduled"
|
||||
case s.Pending > 0:
|
||||
if s.FirstEnqueueMS == nil {
|
||||
kind = "startup"
|
||||
} else {
|
||||
kind = "confirmed"
|
||||
}
|
||||
default:
|
||||
kind = "unconfirmed"
|
||||
}
|
||||
} else {
|
||||
s.MissingSamples++
|
||||
o.partial = true
|
||||
}
|
||||
elapsed := now.Sub(s.last)
|
||||
if !s.last.IsZero() && elapsed > 2*time.Second {
|
||||
s.MissingSamples += int64(elapsed/time.Second) - 1
|
||||
o.partial = true
|
||||
s.close()
|
||||
}
|
||||
if s.kind == kind && !s.last.IsZero() {
|
||||
ms := elapsed.Milliseconds()
|
||||
if ms < 0 {
|
||||
ms = 0
|
||||
}
|
||||
s.continuous += ms
|
||||
var w *Wait
|
||||
switch kind {
|
||||
case "startup":
|
||||
w = &s.Startup
|
||||
case "confirmed":
|
||||
w = &s.Confirmed
|
||||
case "unconfirmed":
|
||||
w = &s.Unconfirmed
|
||||
case "consumer":
|
||||
w = &s.Consumer
|
||||
case "unknown":
|
||||
s.UnknownMS += ms
|
||||
}
|
||||
if w != nil {
|
||||
if !s.open {
|
||||
w.Count++
|
||||
s.open = true
|
||||
}
|
||||
w.TotalMS += ms
|
||||
if s.continuous > w.LongestMS {
|
||||
w.LongestMS = s.continuous
|
||||
}
|
||||
if kind == "startup" || kind == "confirmed" {
|
||||
s.Stages[s.Stage] += ms
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s.continuous = 0
|
||||
s.open = false
|
||||
}
|
||||
s.kind = kind
|
||||
s.last = now
|
||||
s.CoverageEnd = now
|
||||
}
|
||||
func (o *Observer) Snapshot() Snapshot {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
out := Snapshot{SchemaVersion: 1, Attempt: o.attempt, ResolutionMS: 1000, Partial: o.partial, Truncated: o.truncated, Channels: make([]Channel, 0, len(o.channels))}
|
||||
out.Summary.Targets = len(o.channels)
|
||||
for _, s := range o.channels {
|
||||
c := s.Channel
|
||||
c.Completeness = "observing"
|
||||
if s.Ended {
|
||||
c.Completeness = "complete"
|
||||
}
|
||||
if c.MissingSamples > 0 || c.UnknownMS > 0 || c.ValidSamples == 0 || c.Termination == "cancelled" {
|
||||
c.Completeness = "partial"
|
||||
}
|
||||
c.Stages = make(map[string]int64, len(s.Stages))
|
||||
for k, v := range s.Stages {
|
||||
c.Stages[k] = v
|
||||
}
|
||||
out.Channels = append(out.Channels, c)
|
||||
if c.ValidSamples == 0 {
|
||||
out.Partial = true
|
||||
}
|
||||
out.Summary.UnknownMS += c.UnknownMS
|
||||
out.Summary.UnconfirmedMS += c.Unconfirmed.TotalMS
|
||||
out.Summary.ConsumerMS += c.Consumer.TotalMS
|
||||
if c.ValidSamples > 0 {
|
||||
out.Summary.Valid++
|
||||
}
|
||||
total := c.Startup.TotalMS + c.Confirmed.TotalMS
|
||||
longest := max(c.Startup.LongestMS, c.Confirmed.LongestMS)
|
||||
if total > 0 {
|
||||
out.Summary.Confirmed++
|
||||
}
|
||||
if c.MissingSamples > 0 || c.UnknownMS > 0 || c.ValidSamples == 0 {
|
||||
out.Summary.Unknown++
|
||||
}
|
||||
out.Summary.TotalMS += total
|
||||
if longest > out.Summary.LongestMS {
|
||||
out.Summary.LongestMS = longest
|
||||
out.Summary.Worst = c.Key
|
||||
}
|
||||
}
|
||||
out.Summary.Confirmed = min(out.Summary.Targets, out.Summary.Confirmed+o.retained.Confirmed)
|
||||
out.Summary.TotalMS += o.retained.TotalMS
|
||||
out.Summary.UnknownMS += o.retained.UnknownMS
|
||||
out.Summary.UnconfirmedMS += o.retained.UnconfirmedMS
|
||||
out.Summary.ConsumerMS += o.retained.ConsumerMS
|
||||
if o.retained.LongestMS > out.Summary.LongestMS {
|
||||
out.Summary.LongestMS = o.retained.LongestMS
|
||||
out.Summary.Worst = o.retained.Worst
|
||||
}
|
||||
sort.Slice(out.Channels, func(i, j int) bool { return out.Channels[i].Key < out.Channels[j].Key })
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user