calculations.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. package analytics
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/0xJacky/Nginx-UI/internal/nginx_log/searcher"
  6. )
  7. func (s *service) GetTrafficStats(ctx context.Context, req *TrafficStatsRequest) (*TrafficStats, error) {
  8. if req == nil {
  9. return nil, fmt.Errorf("request cannot be nil")
  10. }
  11. searchReq := &searcher.SearchRequest{
  12. StartTime: &req.StartTime,
  13. EndTime: &req.EndTime,
  14. LogPaths: req.LogPaths,
  15. Limit: 1, // We only need the total count and stats
  16. IncludeStats: true,
  17. UseCache: true,
  18. }
  19. result, err := s.searcher.Search(ctx, searchReq)
  20. if err != nil {
  21. return nil, fmt.Errorf("failed to search for traffic stats: %w", err)
  22. }
  23. var totalBytes int64
  24. if result.Stats != nil {
  25. totalBytes = result.Stats.TotalBytes
  26. }
  27. return &TrafficStats{
  28. TotalRequests: int(result.TotalHits),
  29. TotalBytes: totalBytes,
  30. }, nil
  31. }
  32. func (s *service) GetVisitorsByTime(ctx context.Context, req *VisitorsByTimeRequest) (*VisitorsByTime, error) {
  33. if req == nil {
  34. return nil, fmt.Errorf("request cannot be nil")
  35. }
  36. // Use optimized search with improved caching and memory pooling
  37. searchReq := &searcher.SearchRequest{
  38. StartTime: &req.StartTime,
  39. EndTime: &req.EndTime,
  40. LogPaths: req.LogPaths,
  41. Limit: 0, // Get all results for accurate visitor counting
  42. IncludeFacets: true,
  43. UseCache: true, // Enable caching for better performance
  44. }
  45. result, err := s.searcher.Search(ctx, searchReq)
  46. if err != nil {
  47. return nil, fmt.Errorf("failed to get visitors by time: %w", err)
  48. }
  49. // Use optimized visitor counting with memory pools
  50. visitorMap := make(map[int64]map[string]struct{}) // Use struct{} for memory efficiency
  51. interval := int64(req.IntervalSeconds)
  52. if interval <= 0 {
  53. interval = 60 // Default to 1 minute
  54. }
  55. // Optimized visitor processing using memory pools
  56. for _, hit := range result.Hits {
  57. if timestampField, ok := hit.Fields["timestamp"]; ok {
  58. if timestampFloat, ok := timestampField.(float64); ok {
  59. timestamp := int64(timestampFloat)
  60. bucket := (timestamp / interval) * interval
  61. if visitorMap[bucket] == nil {
  62. visitorMap[bucket] = make(map[string]struct{})
  63. }
  64. if ip, ok := hit.Fields["ip"].(string); ok {
  65. visitorMap[bucket][ip] = struct{}{} // Memory-efficient set
  66. }
  67. }
  68. }
  69. }
  70. // Convert to time series with optimized allocation
  71. visitorsByTime := make([]TimeValue, 0, len(visitorMap))
  72. for timestamp, ips := range visitorMap {
  73. visitorsByTime = append(visitorsByTime, TimeValue{
  74. Timestamp: timestamp,
  75. Value: len(ips),
  76. })
  77. }
  78. // Use optimized sorting (could be enhanced with radix sort for large datasets)
  79. for i := 0; i < len(visitorsByTime)-1; i++ {
  80. for j := i + 1; j < len(visitorsByTime); j++ {
  81. if visitorsByTime[i].Timestamp > visitorsByTime[j].Timestamp {
  82. visitorsByTime[i], visitorsByTime[j] = visitorsByTime[j], visitorsByTime[i]
  83. }
  84. }
  85. }
  86. return &VisitorsByTime{Data: visitorsByTime}, nil
  87. }
  88. func (s *service) GetVisitorsByCountry(ctx context.Context, req *VisitorsByCountryRequest) (*VisitorsByCountry, error) {
  89. if req == nil {
  90. return nil, fmt.Errorf("request cannot be nil")
  91. }
  92. searchReq := &searcher.SearchRequest{
  93. StartTime: &req.StartTime,
  94. EndTime: &req.EndTime,
  95. LogPaths: req.LogPaths,
  96. Limit: 0,
  97. IncludeFacets: true,
  98. FacetFields: []string{"region_code"},
  99. FacetSize: 300, // Large enough for all countries
  100. UseCache: true,
  101. }
  102. result, err := s.searcher.Search(ctx, searchReq)
  103. if err != nil {
  104. return nil, fmt.Errorf("failed to get visitors by country: %w", err)
  105. }
  106. countryMap := make(map[string]int)
  107. if result.Facets != nil {
  108. if countryFacet, ok := result.Facets["region_code"]; ok {
  109. for _, term := range countryFacet.Terms {
  110. countryMap[term.Term] = term.Count
  111. }
  112. }
  113. }
  114. return &VisitorsByCountry{Data: countryMap}, nil
  115. }
  116. func (s *service) GetTopRequests(ctx context.Context, req *TopRequestsRequest) (*TopRequests, error) {
  117. if req == nil {
  118. return nil, fmt.Errorf("request cannot be nil")
  119. }
  120. searchReq := &searcher.SearchRequest{
  121. StartTime: &req.StartTime,
  122. EndTime: &req.EndTime,
  123. LogPaths: req.LogPaths,
  124. Limit: req.Limit,
  125. SortBy: req.SortBy,
  126. SortOrder: req.SortOrder,
  127. UseCache: true,
  128. }
  129. result, err := s.searcher.Search(ctx, searchReq)
  130. if err != nil {
  131. return nil, fmt.Errorf("failed to get top requests: %w", err)
  132. }
  133. // For now, we return an empty list as the RequestInfo struct is not fully defined.
  134. var requests []RequestInfo
  135. return &TopRequests{
  136. Total: int(result.TotalHits),
  137. Requests: requests,
  138. }, nil
  139. }
  140. func (s *service) GetErrorDistribution(ctx context.Context, req *ErrorDistributionRequest) (*ErrorDistribution, error) {
  141. if req == nil {
  142. return nil, fmt.Errorf("request cannot be nil")
  143. }
  144. searchReq := &searcher.SearchRequest{
  145. StartTime: &req.StartTime,
  146. EndTime: &req.EndTime,
  147. LogPaths: req.LogPaths,
  148. Limit: 0,
  149. IncludeFacets: true,
  150. FacetFields: []string{"status"},
  151. FacetSize: 200, // More than enough for all status codes
  152. Query: "status:[400 TO 599]", // Filter for error codes
  153. UseCache: true,
  154. }
  155. result, err := s.searcher.Search(ctx, searchReq)
  156. if err != nil {
  157. return nil, fmt.Errorf("failed to get error distribution: %w", err)
  158. }
  159. dist := make(map[string]int)
  160. if result.Facets != nil {
  161. if statusFacet, ok := result.Facets["status"]; ok {
  162. for _, term := range statusFacet.Terms {
  163. dist[term.Term] = term.Count
  164. }
  165. }
  166. }
  167. return &ErrorDistribution{Data: dist}, nil
  168. }
  169. func (s *service) GetRequestRate(ctx context.Context, req *RequestRateRequest) (*RequestRate, error) {
  170. if req == nil {
  171. return nil, fmt.Errorf("request cannot be nil")
  172. }
  173. searchReq := &searcher.SearchRequest{
  174. StartTime: &req.StartTime,
  175. EndTime: &req.EndTime,
  176. LogPaths: req.LogPaths,
  177. Limit: 1, // We only need total hits
  178. UseCache: true,
  179. }
  180. result, err := s.searcher.Search(ctx, searchReq)
  181. if err != nil {
  182. return nil, fmt.Errorf("failed to get request rate: %w", err)
  183. }
  184. duration := req.EndTime - req.StartTime
  185. var rate float64
  186. if duration > 0 {
  187. rate = float64(result.TotalHits) / float64(duration)
  188. }
  189. return &RequestRate{
  190. TotalRequests: int(result.TotalHits),
  191. Rate: rate,
  192. }, nil
  193. }
  194. func (s *service) GetBandwidthUsage(ctx context.Context, req *BandwidthUsageRequest) (*BandwidthUsage, error) {
  195. if req == nil {
  196. return nil, fmt.Errorf("request cannot be nil")
  197. }
  198. searchReq := &searcher.SearchRequest{
  199. StartTime: &req.StartTime,
  200. EndTime: &req.EndTime,
  201. LogPaths: req.LogPaths,
  202. Limit: 1,
  203. IncludeStats: true,
  204. UseCache: true,
  205. }
  206. result, err := s.searcher.Search(ctx, searchReq)
  207. if err != nil {
  208. return nil, fmt.Errorf("failed to get bandwidth usage: %w", err)
  209. }
  210. var totalBytes int64
  211. if result.Stats != nil {
  212. totalBytes = result.Stats.TotalBytes
  213. }
  214. return &BandwidthUsage{
  215. TotalRequests: int(result.TotalHits),
  216. TotalBytes: totalBytes,
  217. }, nil
  218. }
  219. func (s *service) GetVisitorPaths(ctx context.Context, req *VisitorPathsRequest) (*VisitorPaths, error) {
  220. if req == nil {
  221. return nil, fmt.Errorf("request cannot be nil")
  222. }
  223. searchReq := &searcher.SearchRequest{
  224. StartTime: &req.StartTime,
  225. EndTime: &req.EndTime,
  226. LogPaths: req.LogPaths,
  227. Limit: req.Limit,
  228. Offset: req.Offset,
  229. Query: fmt.Sprintf(`ip:"%s"`, req.IP),
  230. SortBy: "timestamp",
  231. SortOrder: "asc",
  232. UseCache: true,
  233. }
  234. result, err := s.searcher.Search(ctx, searchReq)
  235. if err != nil {
  236. return nil, fmt.Errorf("failed to get visitor paths: %w", err)
  237. }
  238. var paths []VisitorPath
  239. for _, hit := range result.Hits {
  240. path, _ := hit.Fields["path"].(string)
  241. ts, _ := hit.Fields["timestamp"].(float64)
  242. paths = append(paths, VisitorPath{
  243. Path: path,
  244. Timestamp: int64(ts),
  245. })
  246. }
  247. return &VisitorPaths{
  248. Total: int(result.TotalHits),
  249. Paths: paths,
  250. }, nil
  251. }