prompt_metrics.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. package securityaudit
  2. import (
  3. "sort"
  4. "sync"
  5. "sync/atomic"
  6. "time"
  7. )
  8. const latencySampleCapacity = 2048
  9. type AtomicMetrics struct {
  10. total atomic.Int64
  11. allowed atomic.Int64
  12. flagged atomic.Int64
  13. blocked atomic.Int64
  14. unavailable atomic.Int64
  15. invalid atomic.Int64
  16. timeouts atomic.Int64
  17. failovers atomic.Int64
  18. bulkheadFull atomic.Int64
  19. recordFailed atomic.Int64
  20. latencyTotal atomic.Int64
  21. latencyMax atomic.Int64
  22. enqueued atomic.Int64
  23. dropped atomic.Int64
  24. latencyMu sync.RWMutex
  25. latencies []int64
  26. latencyNext int
  27. }
  28. func NewAtomicMetrics() *AtomicMetrics { return &AtomicMetrics{} }
  29. func (m *AtomicMetrics) Snapshot() GuardMetricsSnapshot {
  30. if m == nil {
  31. return GuardMetricsSnapshot{}
  32. }
  33. snapshot := GuardMetricsSnapshot{
  34. Total: m.total.Load(), Allowed: m.allowed.Load(), Flagged: m.flagged.Load(),
  35. Blocked: m.blocked.Load(), Unavailable: m.unavailable.Load(), Invalid: m.invalid.Load(),
  36. Timeouts: m.timeouts.Load(), Failovers: m.failovers.Load(), BulkheadFull: m.bulkheadFull.Load(),
  37. RecordFailed: m.recordFailed.Load(), LatencyCount: m.total.Load(), LatencyMaxMS: m.latencyMax.Load(),
  38. }
  39. if snapshot.LatencyCount > 0 {
  40. snapshot.LatencyAvgMS = m.latencyTotal.Load() / snapshot.LatencyCount
  41. }
  42. m.latencyMu.RLock()
  43. samples := append([]int64(nil), m.latencies...)
  44. m.latencyMu.RUnlock()
  45. if len(samples) > 0 {
  46. sort.Slice(samples, func(i, j int) bool { return samples[i] < samples[j] })
  47. snapshot.LatencyP50MS = percentile(samples, 0.50)
  48. snapshot.LatencyP95MS = percentile(samples, 0.95)
  49. snapshot.LatencyP99MS = percentile(samples, 0.99)
  50. }
  51. return snapshot
  52. }
  53. func (m *AtomicMetrics) AuditSnapshot() AuditMetricsSnapshot {
  54. if m == nil {
  55. return AuditMetricsSnapshot{}
  56. }
  57. return AuditMetricsSnapshot{Enqueued: m.enqueued.Load(), Dropped: m.dropped.Load()}
  58. }
  59. func (m *AtomicMetrics) Observe(kind DecisionKind, latency time.Duration) {
  60. if m == nil {
  61. return
  62. }
  63. m.total.Add(1)
  64. latencyMS := latency.Milliseconds()
  65. if latencyMS < 0 {
  66. latencyMS = 0
  67. }
  68. m.latencyTotal.Add(latencyMS)
  69. for current := m.latencyMax.Load(); latencyMS > current && !m.latencyMax.CompareAndSwap(current, latencyMS); current = m.latencyMax.Load() {
  70. }
  71. m.latencyMu.Lock()
  72. if len(m.latencies) < latencySampleCapacity {
  73. m.latencies = append(m.latencies, latencyMS)
  74. } else {
  75. m.latencies[m.latencyNext] = latencyMS
  76. m.latencyNext = (m.latencyNext + 1) % latencySampleCapacity
  77. }
  78. m.latencyMu.Unlock()
  79. switch kind {
  80. case DecisionFlag:
  81. m.flagged.Add(1)
  82. case DecisionBlock:
  83. m.blocked.Add(1)
  84. case DecisionUnavailable:
  85. m.unavailable.Add(1)
  86. case DecisionInvalid:
  87. m.invalid.Add(1)
  88. default:
  89. m.allowed.Add(1)
  90. }
  91. }
  92. func percentile(sorted []int64, quantile float64) int64 {
  93. if len(sorted) == 0 {
  94. return 0
  95. }
  96. index := int(float64(len(sorted)-1) * quantile)
  97. if index < 0 {
  98. index = 0
  99. }
  100. if index >= len(sorted) {
  101. index = len(sorted) - 1
  102. }
  103. return sorted[index]
  104. }
  105. func (m *AtomicMetrics) IncEnqueued() {
  106. if m != nil {
  107. m.enqueued.Add(1)
  108. }
  109. }
  110. func (m *AtomicMetrics) IncDropped() {
  111. if m != nil {
  112. m.dropped.Add(1)
  113. }
  114. }
  115. func (m *AtomicMetrics) IncTimeout() {
  116. if m != nil {
  117. m.timeouts.Add(1)
  118. }
  119. }
  120. func (m *AtomicMetrics) IncFailover() {
  121. if m != nil {
  122. m.failovers.Add(1)
  123. }
  124. }
  125. func (m *AtomicMetrics) IncBulkheadFull() {
  126. if m != nil {
  127. m.bulkheadFull.Add(1)
  128. }
  129. }
  130. func (m *AtomicMetrics) IncRecordFailed() {
  131. if m != nil {
  132. m.recordFailed.Add(1)
  133. }
  134. }