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  }