calculations.go 7.2 KB

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