analytics_service_calculations.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. package nginx_log
  2. import (
  3. "sort"
  4. "time"
  5. )
  6. // calculateHourlyStats calculates UV/PV statistics for each hour of the day
  7. func (s *AnalyticsService) calculateHourlyStats(entries []*AccessLogEntry, startTime, endTime time.Time) []HourlyAccessStats {
  8. // Create map to aggregate stats by hour (0-23)
  9. hourStats := make(map[int]map[string]bool) // hour -> set of unique IPs
  10. hourPV := make(map[int]int) // hour -> page view count
  11. // Initialize all 24 hours
  12. for i := 0; i < 24; i++ {
  13. hourStats[i] = make(map[string]bool)
  14. hourPV[i] = 0
  15. }
  16. // Process entries
  17. for _, entry := range entries {
  18. hour := entry.Timestamp.Hour()
  19. // Count unique visitors (UV)
  20. hourStats[hour][entry.IP] = true
  21. // Count page views (PV)
  22. hourPV[hour]++
  23. }
  24. // Convert to result format - always return 24 hours
  25. result := make([]HourlyAccessStats, 0, 24)
  26. for hour := 0; hour < 24; hour++ {
  27. // Create timestamp for this hour today
  28. now := time.Now()
  29. hourTime := time.Date(now.Year(), now.Month(), now.Day(), hour, 0, 0, 0, now.Location())
  30. result = append(result, HourlyAccessStats{
  31. Hour: hour,
  32. UV: len(hourStats[hour]),
  33. PV: hourPV[hour],
  34. Timestamp: hourTime.Unix(),
  35. })
  36. }
  37. return result
  38. }
  39. // calculateDailyStats calculates daily UV/PV statistics for the time range with padding
  40. func (s *AnalyticsService) calculateDailyStats(entries []*AccessLogEntry, startTime, endTime time.Time) []DailyAccessStats {
  41. // Create map to aggregate stats by date
  42. dailyStats := make(map[string]map[string]bool) // date -> set of unique IPs
  43. dailyPV := make(map[string]int) // date -> page view count
  44. // Process entries
  45. for _, entry := range entries {
  46. date := entry.Timestamp.Format("2006-01-02")
  47. if dailyStats[date] == nil {
  48. dailyStats[date] = make(map[string]bool)
  49. }
  50. // Count unique visitors
  51. dailyStats[date][entry.IP] = true
  52. // Count page views
  53. dailyPV[date]++
  54. }
  55. // Generate complete date range with padding
  56. result := make([]DailyAccessStats, 0)
  57. // Use default time range if not provided
  58. if startTime.IsZero() || endTime.IsZero() {
  59. endTime = time.Now()
  60. startTime = endTime.AddDate(0, 0, -30) // 30 days ago
  61. }
  62. currentDate := startTime.Truncate(24 * time.Hour)
  63. for currentDate.Before(endTime) || currentDate.Equal(endTime.Truncate(24*time.Hour)) {
  64. dateKey := currentDate.Format("2006-01-02")
  65. if ips, exists := dailyStats[dateKey]; exists {
  66. result = append(result, DailyAccessStats{
  67. Date: dateKey,
  68. UV: len(ips),
  69. PV: dailyPV[dateKey],
  70. Timestamp: currentDate.Unix(),
  71. })
  72. } else {
  73. // Pad with zeros for dates without data
  74. result = append(result, DailyAccessStats{
  75. Date: dateKey,
  76. UV: 0,
  77. PV: 0,
  78. Timestamp: currentDate.Unix(),
  79. })
  80. }
  81. currentDate = currentDate.AddDate(0, 0, 1)
  82. }
  83. return result
  84. }
  85. // calculateTopURLs calculates the most visited URLs
  86. func (s *AnalyticsService) calculateTopURLs(entries []*AccessLogEntry) []URLAccessStats {
  87. urlCount := make(map[string]int)
  88. totalRequests := len(entries)
  89. // Count URL visits
  90. for _, entry := range entries {
  91. urlCount[entry.Path]++
  92. }
  93. // Convert to slice and sort
  94. var urlStats []URLAccessStats
  95. for url, count := range urlCount {
  96. percent := 0.0
  97. if totalRequests > 0 {
  98. percent = float64(count) * 100.0 / float64(totalRequests)
  99. }
  100. urlStats = append(urlStats, URLAccessStats{
  101. URL: url,
  102. Visits: count,
  103. Percent: percent,
  104. })
  105. }
  106. // Sort by visits (descending)
  107. sort.Slice(urlStats, func(i, j int) bool {
  108. return urlStats[i].Visits > urlStats[j].Visits
  109. })
  110. // Limit to top 10
  111. if len(urlStats) > 10 {
  112. urlStats = urlStats[:10]
  113. }
  114. return urlStats
  115. }
  116. // calculateBrowserStats calculates browser usage statistics
  117. func (s *AnalyticsService) calculateBrowserStats(entries []*AccessLogEntry) []BrowserAccessStats {
  118. commonStats := calculateCommonStats(entries, func(entry *AccessLogEntry) string {
  119. return entry.Browser
  120. })
  121. // Convert to BrowserAccessStats format
  122. result := make([]BrowserAccessStats, len(commonStats))
  123. for i, stat := range commonStats {
  124. result[i] = BrowserAccessStats{
  125. Browser: stat.Name,
  126. Count: stat.Count,
  127. Percent: stat.Percent,
  128. }
  129. }
  130. return result
  131. }
  132. // calculateOSStats calculates operating system usage statistics
  133. func (s *AnalyticsService) calculateOSStats(entries []*AccessLogEntry) []OSAccessStats {
  134. commonStats := calculateCommonStats(entries, func(entry *AccessLogEntry) string {
  135. return entry.OS
  136. })
  137. // Convert to OSAccessStats format
  138. result := make([]OSAccessStats, len(commonStats))
  139. for i, stat := range commonStats {
  140. result[i] = OSAccessStats{
  141. OS: stat.Name,
  142. Count: stat.Count,
  143. Percent: stat.Percent,
  144. }
  145. }
  146. return result
  147. }
  148. // calculateDeviceStats calculates device type usage statistics
  149. func (s *AnalyticsService) calculateDeviceStats(entries []*AccessLogEntry) []DeviceAccessStats {
  150. commonStats := calculateCommonStats(entries, func(entry *AccessLogEntry) string {
  151. return entry.DeviceType
  152. })
  153. // Convert to DeviceAccessStats format
  154. result := make([]DeviceAccessStats, len(commonStats))
  155. for i, stat := range commonStats {
  156. result[i] = DeviceAccessStats{
  157. Device: stat.Name,
  158. Count: stat.Count,
  159. Percent: stat.Percent,
  160. }
  161. }
  162. return result
  163. }