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

211 рядки
5.0KB

  1. package main
  2. import (
  3. "sort"
  4. "time"
  5. "sdr-wideband-suite/internal/pipeline"
  6. )
  7. type decisionQueueStats struct {
  8. RecordQueued int `json:"record_queued"`
  9. DecodeQueued int `json:"decode_queued"`
  10. RecordSelected int `json:"record_selected"`
  11. DecodeSelected int `json:"decode_selected"`
  12. RecordActive int `json:"record_active"`
  13. DecodeActive int `json:"decode_active"`
  14. RecordOldestS float64 `json:"record_oldest_sec"`
  15. DecodeOldestS float64 `json:"decode_oldest_sec"`
  16. }
  17. type queuedDecision struct {
  18. ID int64
  19. SNRDb float64
  20. Hint string
  21. Class string
  22. FirstSeen time.Time
  23. LastSeen time.Time
  24. }
  25. type decisionQueues struct {
  26. record map[int64]*queuedDecision
  27. decode map[int64]*queuedDecision
  28. recordHold map[int64]time.Time
  29. decodeHold map[int64]time.Time
  30. }
  31. func newDecisionQueues() *decisionQueues {
  32. return &decisionQueues{
  33. record: map[int64]*queuedDecision{},
  34. decode: map[int64]*queuedDecision{},
  35. recordHold: map[int64]time.Time{},
  36. decodeHold: map[int64]time.Time{},
  37. }
  38. }
  39. func (dq *decisionQueues) Apply(decisions []pipeline.SignalDecision, maxRecord int, maxDecode int, hold time.Duration, now time.Time, policy pipeline.Policy) decisionQueueStats {
  40. if dq == nil {
  41. return decisionQueueStats{}
  42. }
  43. recSeen := map[int64]bool{}
  44. decSeen := map[int64]bool{}
  45. for i := range decisions {
  46. id := decisions[i].Candidate.ID
  47. if id == 0 {
  48. continue
  49. }
  50. if decisions[i].ShouldRecord {
  51. qd := dq.record[id]
  52. if qd == nil {
  53. qd = &queuedDecision{ID: id, FirstSeen: now}
  54. dq.record[id] = qd
  55. }
  56. qd.SNRDb = decisions[i].Candidate.SNRDb
  57. qd.Hint = decisions[i].Candidate.Hint
  58. qd.Class = decisions[i].Class
  59. qd.LastSeen = now
  60. recSeen[id] = true
  61. }
  62. if decisions[i].ShouldAutoDecode {
  63. qd := dq.decode[id]
  64. if qd == nil {
  65. qd = &queuedDecision{ID: id, FirstSeen: now}
  66. dq.decode[id] = qd
  67. }
  68. qd.SNRDb = decisions[i].Candidate.SNRDb
  69. qd.Hint = decisions[i].Candidate.Hint
  70. qd.Class = decisions[i].Class
  71. qd.LastSeen = now
  72. decSeen[id] = true
  73. }
  74. }
  75. for id := range dq.record {
  76. if !recSeen[id] {
  77. delete(dq.record, id)
  78. }
  79. }
  80. for id := range dq.decode {
  81. if !decSeen[id] {
  82. delete(dq.decode, id)
  83. }
  84. }
  85. purgeExpired(dq.recordHold, now)
  86. purgeExpired(dq.decodeHold, now)
  87. recSelected := selectQueued("record", dq.record, dq.recordHold, maxRecord, hold, now, policy)
  88. decSelected := selectQueued("decode", dq.decode, dq.decodeHold, maxDecode, hold, now, policy)
  89. stats := decisionQueueStats{
  90. RecordQueued: len(dq.record),
  91. DecodeQueued: len(dq.decode),
  92. RecordSelected: len(recSelected),
  93. DecodeSelected: len(decSelected),
  94. RecordActive: len(dq.recordHold),
  95. DecodeActive: len(dq.decodeHold),
  96. RecordOldestS: oldestAge(dq.record, now),
  97. DecodeOldestS: oldestAge(dq.decode, now),
  98. }
  99. for i := range decisions {
  100. id := decisions[i].Candidate.ID
  101. if decisions[i].ShouldRecord {
  102. if _, ok := recSelected[id]; !ok {
  103. decisions[i].ShouldRecord = false
  104. decisions[i].Reason = "queued: record budget"
  105. }
  106. }
  107. if decisions[i].ShouldAutoDecode {
  108. if _, ok := decSelected[id]; !ok {
  109. decisions[i].ShouldAutoDecode = false
  110. if decisions[i].Reason == "" {
  111. decisions[i].Reason = "queued: decode budget"
  112. }
  113. }
  114. }
  115. }
  116. return stats
  117. }
  118. func selectQueued(queueName string, queue map[int64]*queuedDecision, hold map[int64]time.Time, max int, holdDur time.Duration, now time.Time, policy pipeline.Policy) map[int64]struct{} {
  119. selected := map[int64]struct{}{}
  120. if len(queue) == 0 {
  121. return selected
  122. }
  123. type scored struct {
  124. id int64
  125. score float64
  126. }
  127. scoredList := make([]scored, 0, len(queue))
  128. for id, qd := range queue {
  129. age := now.Sub(qd.FirstSeen).Seconds()
  130. boost := age / 2.0
  131. if boost > 5 {
  132. boost = 5
  133. }
  134. hint := qd.Hint
  135. if hint == "" {
  136. hint = qd.Class
  137. }
  138. policyBoost := pipeline.DecisionPriorityBoost(policy, hint, qd.Class, queueName)
  139. scoredList = append(scoredList, scored{id: id, score: qd.SNRDb + boost + policyBoost})
  140. }
  141. sort.Slice(scoredList, func(i, j int) bool {
  142. return scoredList[i].score > scoredList[j].score
  143. })
  144. limit := max
  145. if limit <= 0 || limit > len(scoredList) {
  146. limit = len(scoredList)
  147. }
  148. if len(hold) > 0 && len(hold) > limit {
  149. limit = len(hold)
  150. if limit > len(scoredList) {
  151. limit = len(scoredList)
  152. }
  153. }
  154. for id := range hold {
  155. if _, ok := queue[id]; ok {
  156. selected[id] = struct{}{}
  157. }
  158. }
  159. for _, s := range scoredList {
  160. if len(selected) >= limit {
  161. break
  162. }
  163. if _, ok := selected[s.id]; ok {
  164. continue
  165. }
  166. selected[s.id] = struct{}{}
  167. }
  168. if holdDur > 0 {
  169. for id := range selected {
  170. hold[id] = now.Add(holdDur)
  171. }
  172. }
  173. return selected
  174. }
  175. func purgeExpired(hold map[int64]time.Time, now time.Time) {
  176. for id, until := range hold {
  177. if now.After(until) {
  178. delete(hold, id)
  179. }
  180. }
  181. }
  182. func oldestAge(queue map[int64]*queuedDecision, now time.Time) float64 {
  183. oldest := 0.0
  184. first := true
  185. for _, qd := range queue {
  186. age := now.Sub(qd.FirstSeen).Seconds()
  187. if first || age > oldest {
  188. oldest = age
  189. first = false
  190. }
  191. }
  192. if first {
  193. return 0
  194. }
  195. return oldest
  196. }