github.com/iotexproject/iotex-core@v1.14.1-rc1/server/itx/nodestats/apilocalstats.go (about) 1 package nodestats 2 3 import ( 4 "fmt" 5 "strings" 6 "sync" 7 "time" 8 ) 9 10 // APIReport is the report of an API call 11 type APIReport struct { 12 Method string 13 HandlingTime time.Duration 14 Success bool 15 } 16 17 type apiMethodStats struct { 18 Successes int 19 Errors int 20 AvgTimeOfErrors int64 21 AvgTimeOfSuccesses int64 22 MaxTimeOfError int64 23 MaxTimeOfSuccess int64 24 TotalSize int64 25 } 26 27 // AvgSize returns the average size of the api call 28 func (m *apiMethodStats) AvgSize() int64 { 29 if m.Successes+m.Errors == 0 { 30 return 0 31 } 32 return m.TotalSize / int64(m.Successes+m.Errors) 33 } 34 35 var _ StatsReporter = (*APILocalStats)(nil) 36 37 // APILocalStats is the struct for getting API stats 38 type APILocalStats struct { 39 allTimeStats sync.Map 40 currentStats sync.Map 41 } 42 43 // NewAPILocalStats creates a new APILocalStats 44 func NewAPILocalStats() *APILocalStats { 45 return &APILocalStats{ 46 allTimeStats: sync.Map{}, 47 currentStats: sync.Map{}, 48 } 49 } 50 51 // ReportCall reports a call to the API 52 func (s *APILocalStats) ReportCall(report APIReport, size int64) { 53 if report.Method == "" { 54 return 55 } 56 v, _ := s.currentStats.LoadOrStore(report.Method, &apiMethodStats{}) 57 methodStats := v.(*apiMethodStats) 58 v, _ = s.allTimeStats.LoadOrStore(report.Method, &apiMethodStats{}) 59 allTimeMethodStats := v.(*apiMethodStats) 60 reportHandlingTimeMicroseconds := report.HandlingTime.Microseconds() 61 if report.Success { 62 methodStats.Successes++ 63 methodStats.AvgTimeOfSuccesses = (methodStats.AvgTimeOfSuccesses*int64(methodStats.Successes-1) + reportHandlingTimeMicroseconds) / int64(methodStats.Successes) 64 if reportHandlingTimeMicroseconds > methodStats.MaxTimeOfSuccess { 65 methodStats.MaxTimeOfSuccess = reportHandlingTimeMicroseconds 66 } 67 68 allTimeMethodStats.Successes++ 69 allTimeMethodStats.AvgTimeOfSuccesses = (allTimeMethodStats.AvgTimeOfSuccesses*int64(allTimeMethodStats.Successes-1) + reportHandlingTimeMicroseconds) / int64(allTimeMethodStats.Successes) 70 if reportHandlingTimeMicroseconds > allTimeMethodStats.MaxTimeOfSuccess { 71 allTimeMethodStats.MaxTimeOfSuccess = reportHandlingTimeMicroseconds 72 } 73 } else { 74 methodStats.Errors++ 75 methodStats.AvgTimeOfErrors = (methodStats.AvgTimeOfErrors*int64(methodStats.Errors-1) + reportHandlingTimeMicroseconds) / int64(methodStats.Errors) 76 if reportHandlingTimeMicroseconds > methodStats.MaxTimeOfError { 77 methodStats.MaxTimeOfError = reportHandlingTimeMicroseconds 78 } 79 80 allTimeMethodStats.Errors++ 81 allTimeMethodStats.AvgTimeOfErrors = (allTimeMethodStats.AvgTimeOfErrors*int64(allTimeMethodStats.Errors-1) + reportHandlingTimeMicroseconds) / int64(allTimeMethodStats.Errors) 82 if reportHandlingTimeMicroseconds > allTimeMethodStats.MaxTimeOfError { 83 allTimeMethodStats.MaxTimeOfError = reportHandlingTimeMicroseconds 84 } 85 } 86 methodStats.TotalSize += size 87 allTimeMethodStats.TotalSize += size 88 89 s.currentStats.Store(report.Method, methodStats) 90 s.allTimeStats.Store(report.Method, allTimeMethodStats) 91 } 92 93 // BuildReport builds a report of the API stats 94 func (s *APILocalStats) BuildReport() string { 95 var snapshot sync.Map 96 snapshotLen := 0 97 s.currentStats.Range(func(key, value interface{}) bool { 98 snapshot.Store(key, value) 99 snapshotLen++ 100 s.currentStats.Delete(key) 101 return true 102 }) 103 stringBuilder := strings.Builder{} 104 105 if snapshotLen == 0 { 106 return stringBuilder.String() 107 } 108 const reportHeader = "method | " + 109 "successes | " + 110 " avg time (µs) | " + 111 " max time (µs) | " + 112 " errors | " + 113 " avg time (µs) | " + 114 " max time (µs) |" + 115 " avg size |" + 116 " total size |" 117 stringBuilder.WriteString("***** API CALL report *****\n") 118 divider := strings.Repeat("-", len(reportHeader)-4) 119 stringBuilder.WriteString(divider + "\n") 120 stringBuilder.WriteString(reportHeader + "\n") 121 stringBuilder.WriteString(divider + "\n") 122 total := &apiMethodStats{} 123 snapshot.Range(func(key, val interface{}) bool { 124 value := val.(*apiMethodStats) 125 if total.Successes+value.Successes > 0 { 126 total.AvgTimeOfSuccesses = (total.AvgTimeOfSuccesses*int64(total.Successes) + int64(value.Successes)*value.AvgTimeOfSuccesses) / int64(total.Successes+value.Successes) 127 } else { 128 total.AvgTimeOfSuccesses = 0 129 } 130 if total.Errors+value.Errors > 0 { 131 total.AvgTimeOfErrors = (total.AvgTimeOfErrors*int64(total.Errors) + int64(value.Errors)*value.AvgTimeOfErrors) / int64(total.Errors+value.Errors) 132 } else { 133 total.AvgTimeOfErrors = 0 134 } 135 total.Successes += value.Successes 136 total.Errors += value.Errors 137 if value.MaxTimeOfError > total.MaxTimeOfError { 138 total.MaxTimeOfError = value.MaxTimeOfError 139 } 140 if value.MaxTimeOfSuccess > total.MaxTimeOfSuccess { 141 total.MaxTimeOfSuccess = value.MaxTimeOfSuccess 142 } 143 total.TotalSize += value.TotalSize 144 stringBuilder.WriteString(s.prepareReportLine(key.(string), value) + "\n") 145 return true 146 }) 147 stringBuilder.WriteString(divider + "\n") 148 stringBuilder.WriteString(s.prepareReportLine("TOTAL", total) + "\n") 149 stringBuilder.WriteString(divider + "\n") 150 return stringBuilder.String() 151 152 } 153 154 func (s *APILocalStats) prepareReportLine(method string, stats *apiMethodStats) string { 155 return fmt.Sprintf("%-40s| %9d | %14d | %14d | %9d | %14d | %14d | %8s | %10s |", 156 method, 157 stats.Successes, 158 stats.AvgTimeOfSuccesses, 159 stats.MaxTimeOfSuccess, 160 stats.Errors, 161 stats.AvgTimeOfErrors, 162 stats.MaxTimeOfError, 163 byteCountSI(stats.AvgSize()), 164 byteCountSI(stats.TotalSize), 165 ) 166 } 167 168 func byteCountSI(b int64) string { 169 const unit = 1000 170 if b < unit { 171 return fmt.Sprintf("%d B", b) 172 } 173 div, exp := int64(unit), 0 174 for n := b / unit; n >= unit; n /= unit { 175 div *= unit 176 exp++ 177 } 178 return fmt.Sprintf("%.1f %cB", 179 float64(b)/float64(div), "kMGTPE"[exp]) 180 } 181 182 func byteCountIEC(b uint64) string { 183 const unit = 1024 184 if b < unit { 185 return fmt.Sprintf("%d B", b) 186 } 187 div, exp := int64(unit), 0 188 for n := b / unit; n >= unit; n /= unit { 189 div *= unit 190 exp++ 191 } 192 return fmt.Sprintf("%.1f %ciB", 193 float64(b)/float64(div), "KMGTPE"[exp]) 194 }