github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/system/trace/workload_statistic.go (about)

     1  package trace
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"sync/atomic"
     7  	"time"
     8  )
     9  
    10  var (
    11  	startupTime = time.Now()
    12  
    13  	applyBlockWorkloadStatistic = newWorkloadStatistic(
    14  		[]time.Duration{time.Hour, 2 * time.Hour, 4 * time.Hour, 8 * time.Hour}, []string{LastRun, Persist})
    15  )
    16  
    17  // TODO: think about a very long work which longer than a statistic period.
    18  
    19  // WorkloadStatistic accumulate workload for specific trace tags during some specific period.
    20  // Everytime `Add` or `end` method be called, it record workload on corresponding `summaries` fields,
    21  // and send this workload info to `shrinkLoop`, which will subtract this workload from `summaries`
    22  // when the workload out of statistic period. To do that, `shrinkLoop` will record the workload and it's
    23  // out-of-date timestamp; `shrinkLoop` also has a ticker promote current time once a second.
    24  // If current time is larger or equal than recorded timestamp, it remove that workload and subtract
    25  // it's value from `summaries`.
    26  type WorkloadStatistic struct {
    27  	concernedTags map[string]struct{}
    28  	summaries     []workloadSummary
    29  
    30  	workCh chan singleWorkInfo
    31  }
    32  
    33  type workloadSummary struct {
    34  	period   time.Duration
    35  	workload int64
    36  }
    37  
    38  type singleWorkInfo struct {
    39  	duration int64
    40  	endTime  time.Time
    41  }
    42  
    43  // GetApplyBlockWorkloadSttistic return a global `WorkloadStatistic` object.
    44  // WARNING: if you call `WorkloadStatistic.Add` concurrently, the summary result will be incorrect.
    45  func GetApplyBlockWorkloadSttistic() *WorkloadStatistic {
    46  	return applyBlockWorkloadStatistic
    47  }
    48  
    49  func newWorkloadStatistic(periods []time.Duration, tags []string) *WorkloadStatistic {
    50  	concernedTags := toTagsMap(tags)
    51  
    52  	workloads := make([]workloadSummary, 0, len(periods))
    53  	for _, period := range periods {
    54  		workloads = append(workloads, workloadSummary{period, 0})
    55  	}
    56  
    57  	wls := &WorkloadStatistic{concernedTags: concernedTags, summaries: workloads, workCh: make(chan singleWorkInfo, 1000)}
    58  	go wls.shrinkLoop()
    59  
    60  	return wls
    61  }
    62  
    63  // Add accumulate workload to summary.
    64  // WARNING: if you call `Add` concurrently, the summary result will be incorrect.
    65  func (ws *WorkloadStatistic) Add(tag string, endTime time.Time, duration time.Duration) {
    66  	if _, ok := ws.concernedTags[tag]; !ok {
    67  		return
    68  	}
    69  
    70  	for i := range ws.summaries {
    71  		atomic.AddInt64(&ws.summaries[i].workload, int64(duration))
    72  	}
    73  
    74  	ws.workCh <- singleWorkInfo{int64(duration), endTime}
    75  }
    76  
    77  func (ws *WorkloadStatistic) Format() string {
    78  	var sumItem []string
    79  	for _, summary := range ws.summary() {
    80  		sumItem = append(sumItem, fmt.Sprintf("%.2f", float64(summary.workload)/float64(summary.period)))
    81  	}
    82  
    83  	return strings.Join(sumItem, "|")
    84  }
    85  
    86  type summaryInfo struct {
    87  	period   time.Duration
    88  	workload time.Duration
    89  }
    90  
    91  func (ws *WorkloadStatistic) summary() []summaryInfo {
    92  	startupDuration := time.Now().Sub(startupTime)
    93  	result := make([]summaryInfo, 0, len(ws.summaries))
    94  
    95  	for _, summary := range ws.summaries {
    96  		period := minDuration(startupDuration, summary.period)
    97  		result = append(result, summaryInfo{period, time.Duration(atomic.LoadInt64(&summary.workload))})
    98  	}
    99  	return result
   100  }
   101  
   102  func (ws *WorkloadStatistic) shrinkLoop() {
   103  	shrinkInfos := make([]map[int64]int64, 0, len(ws.summaries))
   104  	for i := 0; i < len(ws.summaries); i++ {
   105  		shrinkInfos = append(shrinkInfos, make(map[int64]int64))
   106  	}
   107  
   108  	var latest int64
   109  	ticker := time.NewTicker(time.Second)
   110  
   111  	for {
   112  		select {
   113  		case singleWork := <-ws.workCh:
   114  			// `earliest` record the expired timestamp which is minimum.
   115  			// It's just for initialize `latest`.
   116  			earliest := int64(^uint64(0) >> 1)
   117  
   118  			for sumIndex, summary := range ws.summaries {
   119  				expiredTS := singleWork.endTime.Add(summary.period).Unix()
   120  				if expiredTS < earliest {
   121  					earliest = expiredTS
   122  				}
   123  
   124  				info := shrinkInfos[sumIndex]
   125  				// TODO: it makes recoding workload larger than actual value
   126  				//       if a work begin before this period and end during this period
   127  				if _, ok := info[expiredTS]; !ok {
   128  					info[expiredTS] = singleWork.duration
   129  				} else {
   130  					info[expiredTS] += singleWork.duration
   131  				}
   132  			}
   133  
   134  			if latest == 0 {
   135  				latest = earliest
   136  			}
   137  		case t := <-ticker.C:
   138  			current := t.Unix()
   139  			if latest == 0 {
   140  				latest = current
   141  			}
   142  
   143  			// try to remove workload of every expired work.
   144  			// `latest` make sure even if ticker is not accurately,
   145  			// we can also remove the expired correctly.
   146  			for index, info := range shrinkInfos {
   147  				for i := latest; i < current+1; i++ {
   148  					w, ok := info[i]
   149  					if ok {
   150  						atomic.AddInt64(&ws.summaries[index].workload, -w)
   151  						delete(info, i)
   152  					}
   153  				}
   154  			}
   155  
   156  			latest = current
   157  		}
   158  	}
   159  
   160  }
   161  
   162  func toTagsMap(keys []string) map[string]struct{} {
   163  	tags := make(map[string]struct{})
   164  	for _, tag := range keys {
   165  		tags[tag] = struct{}{}
   166  	}
   167  	return tags
   168  }
   169  
   170  func minDuration(d1 time.Duration, d2 time.Duration) time.Duration {
   171  	if d1 < d2 {
   172  		return d1
   173  	}
   174  	return d2
   175  }