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 }