statistic.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. package statistic
  2. import (
  3. "strings"
  4. "sync"
  5. "time"
  6. "imuslab.com/zoraxy/mod/database"
  7. )
  8. /*
  9. Statistic Package
  10. This packet is designed to collection information
  11. and store them for future analysis
  12. */
  13. //Faststat, a interval summary for all collected data and avoid
  14. //looping through every data everytime a overview is needed
  15. type DailySummary struct {
  16. TotalRequest int64 //Total request of the day
  17. ErrorRequest int64 //Invalid request of the day, including error or not found
  18. ValidRequest int64 //Valid request of the day
  19. //Type counters
  20. ForwardTypes *sync.Map //Map that hold the forward types
  21. RequestOrigin *sync.Map //Map that hold [country ISO code]: visitor counter
  22. RequestClientIp *sync.Map //Map that hold all unique request IPs
  23. }
  24. type RequestInfo struct {
  25. IpAddr string
  26. RequestOriginalCountryISOCode string
  27. Succ bool
  28. StatusCode int
  29. ForwardType string
  30. }
  31. type CollectorOption struct {
  32. Database *database.Database
  33. }
  34. type Collector struct {
  35. rtdataStopChan chan bool
  36. DailySummary *DailySummary
  37. Option *CollectorOption
  38. }
  39. func NewStatisticCollector(option CollectorOption) (*Collector, error) {
  40. option.Database.NewTable("stats")
  41. //Create the collector object
  42. thisCollector := Collector{
  43. DailySummary: newDailySummary(),
  44. Option: &option,
  45. }
  46. //Load the stat if exists for today
  47. //This will exists if the program was forcefully restarted
  48. year, month, day := time.Now().Date()
  49. summary := thisCollector.LoadSummaryOfDay(year, month, day)
  50. if summary != nil {
  51. thisCollector.DailySummary = summary
  52. }
  53. //Schedule the realtime statistic clearing at midnight everyday
  54. rtstatStopChan := thisCollector.ScheduleResetRealtimeStats()
  55. thisCollector.rtdataStopChan = rtstatStopChan
  56. return &thisCollector, nil
  57. }
  58. //Write the current in-memory summary to database file
  59. func (c *Collector) SaveSummaryOfDay() {
  60. //When it is called in 0:00am, make sure it is stored as yesterday key
  61. t := time.Now().Add(-30 * time.Second)
  62. summaryKey := t.Format("02_01_2006")
  63. saveData := DailySummaryToExport(*c.DailySummary)
  64. c.Option.Database.Write("stats", summaryKey, saveData)
  65. }
  66. //Load the summary of a day given
  67. func (c *Collector) LoadSummaryOfDay(year int, month time.Month, day int) *DailySummary {
  68. date := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local)
  69. summaryKey := date.Format("02_01_2006")
  70. targetSummaryExport := DailySummaryExport{}
  71. c.Option.Database.Read("stats", summaryKey, &targetSummaryExport)
  72. targetSummary := DailySummaryExportToSummary(targetSummaryExport)
  73. return &targetSummary
  74. }
  75. //This function gives the current slot in the 288- 5 minutes interval of the day
  76. func (c *Collector) GetCurrentRealtimeStatIntervalId() int {
  77. now := time.Now()
  78. startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local).Unix()
  79. secondsSinceStartOfDay := now.Unix() - startOfDay
  80. interval := secondsSinceStartOfDay / (5 * 60)
  81. return int(interval)
  82. }
  83. func (c *Collector) Close() {
  84. //Stop the ticker
  85. c.rtdataStopChan <- true
  86. //Write the buffered data into database
  87. c.SaveSummaryOfDay()
  88. }
  89. //Main function to record all the inbound traffics
  90. //Note that this function run in go routine and might have concurrent R/W issue
  91. //Please make sure there is no racing paramters in this function
  92. func (c *Collector) RecordRequest(ri RequestInfo) {
  93. go func() {
  94. c.DailySummary.TotalRequest++
  95. if ri.Succ {
  96. c.DailySummary.ValidRequest++
  97. } else {
  98. c.DailySummary.ErrorRequest++
  99. }
  100. //Store the request info into correct types of maps
  101. ft, ok := c.DailySummary.ForwardTypes.Load(ri.ForwardType)
  102. if !ok {
  103. c.DailySummary.ForwardTypes.Store(ri.ForwardType, 1)
  104. } else {
  105. c.DailySummary.ForwardTypes.Store(ri.ForwardType, ft.(int)+1)
  106. }
  107. originISO := strings.ToLower(ri.RequestOriginalCountryISOCode)
  108. fo, ok := c.DailySummary.RequestOrigin.Load(originISO)
  109. if !ok {
  110. c.DailySummary.RequestOrigin.Store(originISO, 1)
  111. } else {
  112. c.DailySummary.RequestOrigin.Store(originISO, fo.(int)+1)
  113. }
  114. fi, ok := c.DailySummary.RequestClientIp.Load(ri.IpAddr)
  115. if !ok {
  116. c.DailySummary.RequestClientIp.Store(ri.IpAddr, 1)
  117. } else {
  118. c.DailySummary.RequestClientIp.Store(ri.IpAddr, fi.(int)+1)
  119. }
  120. }()
  121. }
  122. //nightly task
  123. func (c *Collector) ScheduleResetRealtimeStats() chan bool {
  124. doneCh := make(chan bool)
  125. go func() {
  126. defer close(doneCh)
  127. for {
  128. // calculate duration until next midnight
  129. now := time.Now()
  130. // Get midnight of the next day in the local time zone
  131. midnight := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
  132. // Calculate the duration until midnight
  133. duration := midnight.Sub(now)
  134. select {
  135. case <-time.After(duration):
  136. // store daily summary to database and reset summary
  137. c.SaveSummaryOfDay()
  138. c.DailySummary = newDailySummary()
  139. case <-doneCh:
  140. // stop the routine
  141. return
  142. }
  143. }
  144. }()
  145. return doneCh
  146. }
  147. func newDailySummary() *DailySummary {
  148. return &DailySummary{
  149. TotalRequest: 0,
  150. ErrorRequest: 0,
  151. ValidRequest: 0,
  152. ForwardTypes: &sync.Map{},
  153. RequestOrigin: &sync.Map{},
  154. RequestClientIp: &sync.Map{},
  155. }
  156. }