Wideband autonomous SDR analysis engine forked from sdr-visual-suite
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

367 řádky
12KB

  1. package pipeline
  2. import (
  3. "math"
  4. "strings"
  5. "time"
  6. )
  7. type HoldPolicy struct {
  8. BaseMs int `json:"base_ms"`
  9. RefinementMs int `json:"refinement_ms"`
  10. RecordMs int `json:"record_ms"`
  11. DecodeMs int `json:"decode_ms"`
  12. Profile string `json:"profile,omitempty"`
  13. Strategy string `json:"strategy,omitempty"`
  14. Reasons []string `json:"reasons,omitempty"`
  15. }
  16. type RefinementHold struct {
  17. Active map[int64]time.Time
  18. }
  19. type RefinementAdmission struct {
  20. Budget int `json:"budget"`
  21. BudgetSource string `json:"budget_source,omitempty"`
  22. DecisionHoldMs int `json:"decision_hold_ms,omitempty"`
  23. HoldMs int `json:"hold_ms"`
  24. HoldSource string `json:"hold_source,omitempty"`
  25. Planned int `json:"planned"`
  26. Admitted int `json:"admitted"`
  27. Skipped int `json:"skipped"`
  28. Displaced int `json:"displaced"`
  29. DisplacedByHold int `json:"displaced_by_hold,omitempty"`
  30. HoldActive int `json:"hold_active"`
  31. HoldSelected int `json:"hold_selected"`
  32. HoldProtected int `json:"hold_protected"`
  33. HoldExpired int `json:"hold_expired"`
  34. HoldDisplaced int `json:"hold_displaced"`
  35. Opportunistic int `json:"opportunistic"`
  36. PriorityCutoff float64 `json:"priority_cutoff,omitempty"`
  37. PriorityTier string `json:"priority_tier,omitempty"`
  38. Reason string `json:"reason,omitempty"`
  39. Pressure BudgetPressure `json:"pressure,omitempty"`
  40. }
  41. type RefinementAdmissionResult struct {
  42. Plan RefinementPlan
  43. WorkItems []RefinementWorkItem
  44. Admitted []ScheduledCandidate
  45. Admission RefinementAdmission
  46. }
  47. func HoldPolicyFromPolicy(policy Policy) HoldPolicy {
  48. base := policy.DecisionHoldMs
  49. if base < 0 {
  50. base = 0
  51. }
  52. refMult := 1.0
  53. recMult := 1.0
  54. decMult := 1.0
  55. reasons := make([]string, 0, 2)
  56. profile := strings.ToLower(strings.TrimSpace(policy.Profile))
  57. strategy := strings.ToLower(strings.TrimSpace(policy.RefinementStrategy))
  58. archiveProfile := profileContains(profile, "archive")
  59. archiveStrategy := strategyContains(strategy, "archive")
  60. if archiveProfile || archiveStrategy {
  61. recMult *= 1.5
  62. decMult *= 1.1
  63. refMult *= 1.2
  64. if archiveProfile {
  65. reasons = append(reasons, HoldReasonProfileArchive)
  66. }
  67. if archiveStrategy {
  68. reasons = append(reasons, HoldReasonStrategyArchive)
  69. }
  70. }
  71. digitalProfile := profileContains(profile, "digital")
  72. digitalStrategy := strategyContains(strategy, "digital")
  73. if digitalProfile || digitalStrategy {
  74. decMult *= 1.6
  75. recMult *= 0.85
  76. refMult *= 1.1
  77. if digitalProfile {
  78. reasons = append(reasons, HoldReasonProfileDigital)
  79. }
  80. if digitalStrategy {
  81. reasons = append(reasons, HoldReasonStrategyDigital)
  82. }
  83. }
  84. if profileContains(profile, "aggressive") {
  85. refMult *= 1.15
  86. reasons = append(reasons, HoldReasonProfileAggressive)
  87. }
  88. if strategyContains(strings.ToLower(strings.TrimSpace(policy.SurveillanceStrategy)), "multi") {
  89. refMult *= 1.1
  90. reasons = append(reasons, HoldReasonStrategyMultiRes)
  91. }
  92. return HoldPolicy{
  93. BaseMs: base,
  94. RefinementMs: scaleHold(base, refMult),
  95. RecordMs: scaleHold(base, recMult),
  96. DecodeMs: scaleHold(base, decMult),
  97. Profile: policy.Profile,
  98. Strategy: policy.RefinementStrategy,
  99. Reasons: reasons,
  100. }
  101. }
  102. func AdmitRefinementPlan(plan RefinementPlan, policy Policy, now time.Time, hold *RefinementHold) RefinementAdmissionResult {
  103. ranked := plan.Ranked
  104. if len(ranked) == 0 {
  105. ranked = plan.Selected
  106. }
  107. workItems := append([]RefinementWorkItem(nil), plan.WorkItems...)
  108. admission := RefinementAdmission{
  109. Budget: plan.Budget,
  110. BudgetSource: plan.BudgetSource,
  111. }
  112. if len(ranked) == 0 {
  113. admission.Reason = ReasonAdmissionNoCandidates
  114. return RefinementAdmissionResult{Plan: plan, WorkItems: workItems, Admission: admission}
  115. }
  116. holdPolicy := HoldPolicyFromPolicy(policy)
  117. budgetModel := BudgetModelFromPolicy(policy)
  118. admission.DecisionHoldMs = holdPolicy.BaseMs
  119. admission.HoldMs = holdPolicy.RefinementMs
  120. admission.HoldSource = "resources.decision_hold_ms"
  121. if len(holdPolicy.Reasons) > 0 {
  122. admission.HoldSource += ":" + strings.Join(holdPolicy.Reasons, ",")
  123. }
  124. planned := len(ranked)
  125. admission.Planned = planned
  126. selected := map[int64]struct{}{}
  127. held := map[int64]struct{}{}
  128. protected := map[int64]struct{}{}
  129. expired := map[int64]struct{}{}
  130. if hold != nil {
  131. expired = expireHold(hold.Active, now)
  132. for id := range hold.Active {
  133. if rankedContains(ranked, id) {
  134. selected[id] = struct{}{}
  135. held[id] = struct{}{}
  136. }
  137. }
  138. }
  139. limit := plan.Budget
  140. if limit <= 0 || limit > planned {
  141. limit = planned
  142. }
  143. if len(selected) > limit {
  144. limit = len(selected)
  145. if limit > planned {
  146. limit = planned
  147. }
  148. }
  149. tierByID := map[int64]string{}
  150. scoreByID := map[int64]float64{}
  151. for _, cand := range ranked {
  152. tierByID[cand.Candidate.ID] = PriorityTierFromRange(cand.Priority, plan.PriorityMin, plan.PriorityMax)
  153. scoreByID[cand.Candidate.ID] = cand.Priority
  154. }
  155. for id := range held {
  156. if isProtectedTier(tierByID[id]) {
  157. protected[id] = struct{}{}
  158. }
  159. }
  160. displaceable := buildDisplaceableHold(held, protected, tierByID, scoreByID)
  161. opportunistic := map[int64]struct{}{}
  162. displacedHold := map[int64]struct{}{}
  163. for _, cand := range ranked {
  164. if _, ok := selected[cand.Candidate.ID]; ok {
  165. continue
  166. }
  167. if len(selected) < limit {
  168. selected[cand.Candidate.ID] = struct{}{}
  169. continue
  170. }
  171. if len(displaceable) == 0 {
  172. continue
  173. }
  174. target := displaceable[0]
  175. if priorityTierRank(tierByID[cand.Candidate.ID]) <= priorityTierRank(tierByID[target]) {
  176. continue
  177. }
  178. displaceable = displaceable[1:]
  179. delete(selected, target)
  180. displacedHold[target] = struct{}{}
  181. selected[cand.Candidate.ID] = struct{}{}
  182. opportunistic[cand.Candidate.ID] = struct{}{}
  183. }
  184. if hold != nil && admission.HoldMs > 0 {
  185. until := now.Add(time.Duration(admission.HoldMs) * time.Millisecond)
  186. if hold.Active == nil {
  187. hold.Active = map[int64]time.Time{}
  188. }
  189. for id := range displacedHold {
  190. delete(hold.Active, id)
  191. }
  192. for id := range selected {
  193. hold.Active[id] = until
  194. }
  195. }
  196. admitted := make([]ScheduledCandidate, 0, len(selected))
  197. for _, cand := range ranked {
  198. if _, ok := selected[cand.Candidate.ID]; ok {
  199. admitted = append(admitted, cand)
  200. }
  201. }
  202. admission.Admitted = len(admitted)
  203. admission.Skipped = planned - admission.Admitted
  204. if admission.Skipped < 0 {
  205. admission.Skipped = 0
  206. }
  207. if hold != nil {
  208. admission.HoldActive = len(hold.Active)
  209. }
  210. admission.HoldSelected = len(held) - len(displacedHold)
  211. admission.HoldProtected = len(protected)
  212. admission.HoldExpired = len(expired)
  213. admission.HoldDisplaced = len(displacedHold)
  214. admission.Opportunistic = len(opportunistic)
  215. displacedByHold := map[int64]struct{}{}
  216. if len(admitted) > 0 {
  217. admission.PriorityCutoff = admitted[len(admitted)-1].Priority
  218. for _, cand := range ranked {
  219. if _, ok := selected[cand.Candidate.ID]; ok {
  220. continue
  221. }
  222. if cand.Priority >= admission.PriorityCutoff {
  223. if _, ok := displacedHold[cand.Candidate.ID]; ok {
  224. continue
  225. }
  226. displacedByHold[cand.Candidate.ID] = struct{}{}
  227. }
  228. }
  229. }
  230. admission.Displaced = len(displacedByHold) + len(displacedHold)
  231. admission.DisplacedByHold = len(displacedByHold)
  232. admission.PriorityTier = PriorityTierFromRange(admission.PriorityCutoff, plan.PriorityMin, plan.PriorityMax)
  233. admission.Pressure = buildRefinementPressure(budgetModel, admission)
  234. if admission.PriorityCutoff > 0 {
  235. admission.Reason = admissionReason("admission:budget", policy, holdPolicy, pressureReasonTag(admission.Pressure), "budget:"+slugToken(plan.BudgetSource))
  236. }
  237. plan.Selected = admitted
  238. plan.PriorityCutoff = admission.PriorityCutoff
  239. plan.DroppedByBudget = admission.Skipped
  240. for i := range workItems {
  241. item := &workItems[i]
  242. if item.Status != RefinementStatusPlanned {
  243. continue
  244. }
  245. id := item.Candidate.ID
  246. if _, ok := selected[id]; ok {
  247. item.Status = RefinementStatusAdmitted
  248. item.Reason = RefinementReasonAdmitted
  249. class := AdmissionClassAdmit
  250. reason := "refinement:admit:budget"
  251. if _, wasHeld := held[id]; wasHeld {
  252. class = AdmissionClassHold
  253. reason = "refinement:admit:hold"
  254. }
  255. if item.Admission == nil {
  256. item.Admission = &PriorityAdmission{Basis: "refinement"}
  257. }
  258. item.Admission.Class = class
  259. item.Admission.Score = item.Priority
  260. item.Admission.Cutoff = admission.PriorityCutoff
  261. item.Admission.Tier = tierByID[id]
  262. extras := []string{pressureReasonTag(admission.Pressure), "budget:" + slugToken(plan.BudgetSource)}
  263. if _, wasHeld := held[id]; wasHeld {
  264. extras = append(extras, "pressure:hold", ReasonTagHoldActive)
  265. if _, ok := protected[id]; ok {
  266. extras = append(extras, ReasonTagHoldProtected)
  267. }
  268. }
  269. if _, ok := opportunistic[id]; ok {
  270. extras = append(extras, "pressure:hold", ReasonTagDisplaceOpportunist, ReasonTagDisplaceTier, ReasonTagHoldDisplaced)
  271. }
  272. item.Admission.Reason = admissionReason(reason, policy, holdPolicy, extras...)
  273. continue
  274. }
  275. if _, ok := displacedHold[id]; ok {
  276. item.Status = RefinementStatusDisplaced
  277. item.Reason = RefinementReasonDisplaced
  278. if item.Admission == nil {
  279. item.Admission = &PriorityAdmission{Basis: "refinement"}
  280. }
  281. item.Admission.Class = AdmissionClassDisplace
  282. item.Admission.Score = item.Priority
  283. item.Admission.Cutoff = admission.PriorityCutoff
  284. item.Admission.Tier = tierByID[id]
  285. item.Admission.Reason = admissionReason("refinement:displace:hold", policy, holdPolicy, pressureReasonTag(admission.Pressure), "pressure:hold", ReasonTagDisplaceOpportunist, ReasonTagDisplaceTier, ReasonTagHoldDisplaced, "budget:"+slugToken(plan.BudgetSource))
  286. continue
  287. }
  288. if _, ok := displacedByHold[id]; ok {
  289. item.Status = RefinementStatusDisplaced
  290. item.Reason = RefinementReasonDisplaced
  291. if item.Admission == nil {
  292. item.Admission = &PriorityAdmission{Basis: "refinement"}
  293. }
  294. item.Admission.Class = AdmissionClassDisplace
  295. item.Admission.Score = item.Priority
  296. item.Admission.Cutoff = admission.PriorityCutoff
  297. item.Admission.Tier = tierByID[id]
  298. item.Admission.Reason = admissionReason("refinement:displace:hold", policy, holdPolicy, pressureReasonTag(admission.Pressure), "pressure:hold", ReasonTagHoldActive, "budget:"+slugToken(plan.BudgetSource))
  299. continue
  300. }
  301. item.Status = RefinementStatusSkipped
  302. item.Reason = RefinementReasonBudget
  303. if item.Admission == nil {
  304. item.Admission = &PriorityAdmission{Basis: "refinement"}
  305. }
  306. item.Admission.Class = AdmissionClassDefer
  307. item.Admission.Score = item.Priority
  308. item.Admission.Cutoff = admission.PriorityCutoff
  309. item.Admission.Tier = tierByID[id]
  310. extras := []string{pressureReasonTag(admission.Pressure), "pressure:budget", "budget:" + slugToken(plan.BudgetSource)}
  311. if _, ok := expired[id]; ok {
  312. extras = append(extras, ReasonTagHoldExpired)
  313. }
  314. item.Admission.Reason = admissionReason("refinement:skip:budget", policy, holdPolicy, extras...)
  315. }
  316. return RefinementAdmissionResult{
  317. Plan: plan,
  318. WorkItems: workItems,
  319. Admitted: admitted,
  320. Admission: admission,
  321. }
  322. }
  323. func rankedContains(items []ScheduledCandidate, id int64) bool {
  324. for _, item := range items {
  325. if item.Candidate.ID == id {
  326. return true
  327. }
  328. }
  329. return false
  330. }
  331. func scaleHold(base int, mult float64) int {
  332. if base <= 0 {
  333. return 0
  334. }
  335. return int(math.Round(float64(base) * mult))
  336. }
  337. func profileContains(profile string, token string) bool {
  338. if profile == "" || token == "" {
  339. return false
  340. }
  341. return strings.Contains(profile, strings.ToLower(token))
  342. }
  343. func strategyContains(strategy string, token string) bool {
  344. if strategy == "" || token == "" {
  345. return false
  346. }
  347. return strings.Contains(strategy, strings.ToLower(token))
  348. }