Wideband autonomous SDR analysis engine forked from sdr-visual-suite
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

242 строки
6.1KB

  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. HoldMs int `json:"hold_ms"`
  23. HoldSource string `json:"hold_source,omitempty"`
  24. Planned int `json:"planned"`
  25. Admitted int `json:"admitted"`
  26. Skipped int `json:"skipped"`
  27. Displaced int `json:"displaced"`
  28. PriorityCutoff float64 `json:"priority_cutoff,omitempty"`
  29. Reason string `json:"reason,omitempty"`
  30. }
  31. type RefinementAdmissionResult struct {
  32. Plan RefinementPlan
  33. WorkItems []RefinementWorkItem
  34. Admitted []ScheduledCandidate
  35. Admission RefinementAdmission
  36. }
  37. func HoldPolicyFromPolicy(policy Policy) HoldPolicy {
  38. base := policy.DecisionHoldMs
  39. if base < 0 {
  40. base = 0
  41. }
  42. refMult := 1.0
  43. recMult := 1.0
  44. decMult := 1.0
  45. reasons := make([]string, 0, 2)
  46. profile := strings.ToLower(strings.TrimSpace(policy.Profile))
  47. strategy := strings.ToLower(strings.TrimSpace(policy.RefinementStrategy))
  48. if profileContains(profile, "archive") || strategyContains(strategy, "archive") {
  49. recMult *= 1.5
  50. decMult *= 1.1
  51. refMult *= 1.2
  52. reasons = append(reasons, "archive")
  53. }
  54. if profileContains(profile, "digital") || strategyContains(strategy, "digital") {
  55. decMult *= 1.6
  56. recMult *= 0.85
  57. refMult *= 1.1
  58. reasons = append(reasons, "digital")
  59. }
  60. if profileContains(profile, "aggressive") {
  61. refMult *= 1.15
  62. reasons = append(reasons, "aggressive")
  63. }
  64. if strategyContains(strings.ToLower(strings.TrimSpace(policy.SurveillanceStrategy)), "multi") {
  65. refMult *= 1.1
  66. reasons = append(reasons, "multi-resolution")
  67. }
  68. return HoldPolicy{
  69. BaseMs: base,
  70. RefinementMs: scaleHold(base, refMult),
  71. RecordMs: scaleHold(base, recMult),
  72. DecodeMs: scaleHold(base, decMult),
  73. Profile: policy.Profile,
  74. Strategy: policy.RefinementStrategy,
  75. Reasons: reasons,
  76. }
  77. }
  78. func AdmitRefinementPlan(plan RefinementPlan, policy Policy, now time.Time, hold *RefinementHold) RefinementAdmissionResult {
  79. ranked := plan.Ranked
  80. if len(ranked) == 0 {
  81. ranked = plan.Selected
  82. }
  83. workItems := append([]RefinementWorkItem(nil), plan.WorkItems...)
  84. admission := RefinementAdmission{
  85. Budget: plan.Budget,
  86. BudgetSource: plan.BudgetSource,
  87. }
  88. if len(ranked) == 0 {
  89. admission.Reason = "no-candidates"
  90. return RefinementAdmissionResult{Plan: plan, WorkItems: workItems, Admission: admission}
  91. }
  92. holdPolicy := HoldPolicyFromPolicy(policy)
  93. admission.HoldMs = holdPolicy.RefinementMs
  94. admission.HoldSource = "resources.decision_hold_ms"
  95. if len(holdPolicy.Reasons) > 0 {
  96. admission.HoldSource += ":" + strings.Join(holdPolicy.Reasons, ",")
  97. }
  98. planned := len(ranked)
  99. admission.Planned = planned
  100. selected := map[int64]struct{}{}
  101. if hold != nil {
  102. purgeHold(hold.Active, now)
  103. for id := range hold.Active {
  104. if rankedContains(ranked, id) {
  105. selected[id] = struct{}{}
  106. }
  107. }
  108. }
  109. limit := plan.Budget
  110. if limit <= 0 || limit > planned {
  111. limit = planned
  112. }
  113. if len(selected) > limit {
  114. limit = len(selected)
  115. if limit > planned {
  116. limit = planned
  117. }
  118. }
  119. for _, cand := range ranked {
  120. if len(selected) >= limit {
  121. break
  122. }
  123. if _, ok := selected[cand.Candidate.ID]; ok {
  124. continue
  125. }
  126. selected[cand.Candidate.ID] = struct{}{}
  127. }
  128. if hold != nil && admission.HoldMs > 0 {
  129. until := now.Add(time.Duration(admission.HoldMs) * time.Millisecond)
  130. if hold.Active == nil {
  131. hold.Active = map[int64]time.Time{}
  132. }
  133. for id := range selected {
  134. hold.Active[id] = until
  135. }
  136. }
  137. admitted := make([]ScheduledCandidate, 0, len(selected))
  138. for _, cand := range ranked {
  139. if _, ok := selected[cand.Candidate.ID]; ok {
  140. admitted = append(admitted, cand)
  141. }
  142. }
  143. admission.Admitted = len(admitted)
  144. admission.Skipped = planned - admission.Admitted
  145. if admission.Skipped < 0 {
  146. admission.Skipped = 0
  147. }
  148. displaced := map[int64]struct{}{}
  149. if len(admitted) > 0 {
  150. admission.PriorityCutoff = admitted[len(admitted)-1].Priority
  151. for _, cand := range ranked {
  152. if _, ok := selected[cand.Candidate.ID]; ok {
  153. continue
  154. }
  155. if cand.Priority >= admission.PriorityCutoff {
  156. displaced[cand.Candidate.ID] = struct{}{}
  157. }
  158. }
  159. }
  160. admission.Displaced = len(displaced)
  161. plan.Selected = admitted
  162. plan.PriorityCutoff = admission.PriorityCutoff
  163. plan.DroppedByBudget = admission.Skipped
  164. for i := range workItems {
  165. item := &workItems[i]
  166. if item.Status != RefinementStatusPlanned {
  167. continue
  168. }
  169. id := item.Candidate.ID
  170. if _, ok := selected[id]; ok {
  171. item.Status = RefinementStatusAdmitted
  172. item.Reason = RefinementReasonAdmitted
  173. continue
  174. }
  175. if _, ok := displaced[id]; ok {
  176. item.Status = RefinementStatusDisplaced
  177. item.Reason = RefinementReasonDisplaced
  178. continue
  179. }
  180. item.Status = RefinementStatusSkipped
  181. item.Reason = RefinementReasonBudget
  182. }
  183. return RefinementAdmissionResult{
  184. Plan: plan,
  185. WorkItems: workItems,
  186. Admitted: admitted,
  187. Admission: admission,
  188. }
  189. }
  190. func purgeHold(active map[int64]time.Time, now time.Time) {
  191. for id, until := range active {
  192. if now.After(until) {
  193. delete(active, id)
  194. }
  195. }
  196. }
  197. func rankedContains(items []ScheduledCandidate, id int64) bool {
  198. for _, item := range items {
  199. if item.Candidate.ID == id {
  200. return true
  201. }
  202. }
  203. return false
  204. }
  205. func scaleHold(base int, mult float64) int {
  206. if base <= 0 {
  207. return 0
  208. }
  209. return int(math.Round(float64(base) * mult))
  210. }
  211. func profileContains(profile string, token string) bool {
  212. if profile == "" || token == "" {
  213. return false
  214. }
  215. return strings.Contains(profile, strings.ToLower(token))
  216. }
  217. func strategyContains(strategy string, token string) bool {
  218. if strategy == "" || token == "" {
  219. return false
  220. }
  221. return strings.Contains(strategy, strings.ToLower(token))
  222. }