github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/integration/metric.go (about) 1 package integration 2 3 import ( 4 "fmt" 5 "regexp" 6 "strings" 7 "sync" 8 "time" 9 10 "github.com/unicornultrafoundation/go-helios/u2udb" 11 "github.com/unicornultrafoundation/go-u2u/log" 12 "github.com/unicornultrafoundation/go-u2u/metrics" 13 ) 14 15 const ( 16 // metricsGatheringInterval specifies the interval to retrieve leveldb database 17 // compaction, io and pause stats to report to the user. 18 metricsGatheringInterval = 3 * time.Second 19 ) 20 21 type DBProducerWithMetrics struct { 22 u2udb.IterableDBProducer 23 } 24 25 type StoreWithMetrics struct { 26 u2udb.Store 27 28 diskSizeGauge metrics.Gauge // Gauge for tracking the size of all the levels in the database 29 diskReadMeter metrics.Meter // Meter for measuring the effective amount of data read 30 diskWriteMeter metrics.Meter // Meter for measuring the effective amount of data written 31 32 quitLock sync.Mutex // Mutex protecting the quit channel access 33 quitChan chan chan error // Quit channel to stop the metrics collection before closing the database 34 35 log log.Logger // Contextual logger tracking the database path 36 } 37 38 func WrapDatabaseWithMetrics(db u2udb.IterableDBProducer) u2udb.IterableDBProducer { 39 wrapper := &DBProducerWithMetrics{db} 40 return wrapper 41 } 42 43 func WrapStoreWithMetrics(ds u2udb.Store) *StoreWithMetrics { 44 wrapper := &StoreWithMetrics{ 45 Store: ds, 46 quitChan: make(chan chan error), 47 } 48 return wrapper 49 } 50 51 func (ds *StoreWithMetrics) Close() error { 52 ds.quitLock.Lock() 53 defer ds.quitLock.Unlock() 54 55 if ds.quitChan != nil { 56 errc := make(chan error) 57 ds.quitChan <- errc 58 if err := <-errc; err != nil { 59 ds.log.Error("Metrics collection failed", "err", err) 60 } 61 ds.quitChan = nil 62 } 63 return ds.Store.Close() 64 } 65 66 func (ds *StoreWithMetrics) meter(refresh time.Duration) { 67 // Create storage for iostats. 68 var iostats [2]float64 69 70 var ( 71 errc chan error 72 merr error 73 ) 74 75 timer := time.NewTimer(refresh) 76 defer timer.Stop() 77 // Iterate ad infinitum and collect the stats 78 for i := 1; errc == nil && merr == nil; i++ { 79 // Retrieve the database size 80 diskSize, err := ds.Stat("disk.size") 81 if err != nil { 82 ds.log.Error("Failed to read database stats", "err", err) 83 merr = err 84 continue 85 } 86 var nDiskSize int64 87 if n, err := fmt.Sscanf(diskSize, "%d", &nDiskSize); n != 1 || err != nil { 88 ds.log.Error("Bad syntax of disk size entry", "size", diskSize) 89 merr = err 90 continue 91 } 92 // Update all the disk size meters 93 if ds.diskSizeGauge != nil { 94 ds.diskSizeGauge.Update(nDiskSize) 95 } 96 97 // Retrieve the database iostats. 98 ioStats, err := ds.Stat("iostats") 99 if err != nil { 100 ds.log.Error("Failed to read database iostats", "err", err) 101 merr = err 102 continue 103 } 104 var nRead, nWrite float64 105 parts := strings.Split(ioStats, " ") 106 if len(parts) < 2 { 107 ds.log.Error("Bad syntax of ioStats", "ioStats", ioStats) 108 merr = fmt.Errorf("bad syntax of ioStats %s", ioStats) 109 continue 110 } 111 if n, err := fmt.Sscanf(parts[0], "Read(MB):%f", &nRead); n != 1 || err != nil { 112 ds.log.Error("Bad syntax of read entry", "entry", parts[0]) 113 merr = err 114 continue 115 } 116 if n, err := fmt.Sscanf(parts[1], "Write(MB):%f", &nWrite); n != 1 || err != nil { 117 log.Error("Bad syntax of write entry", "entry", parts[1]) 118 merr = err 119 continue 120 } 121 if ds.diskReadMeter != nil { 122 ds.diskReadMeter.Mark(int64((nRead - iostats[0]) * 1024 * 1024)) 123 } 124 if ds.diskWriteMeter != nil { 125 ds.diskWriteMeter.Mark(int64((nWrite - iostats[1]) * 1024 * 1024)) 126 } 127 iostats[0], iostats[1] = nRead, nWrite 128 129 // Sleep a bit, then repeat the stats collection 130 select { 131 case errc = <-ds.quitChan: 132 // Quit requesting, stop hammering the database 133 case <-timer.C: 134 timer.Reset(refresh) 135 // Timeout, gather a new set of stats 136 } 137 } 138 if errc == nil { 139 errc = <-ds.quitChan 140 } 141 errc <- merr 142 } 143 144 var tmpDbNameMask = regexp.MustCompile("^([A-z]+)(-[0-9]+)$") 145 146 func genericNameOfTmpDB(name string) string { 147 match := tmpDbNameMask.FindStringSubmatch(name) 148 if len(match) == 3 { 149 return match[1] + "-tmp" 150 } else { 151 return name 152 } 153 } 154 155 func (db *DBProducerWithMetrics) OpenDB(name string) (u2udb.Store, error) { 156 ds, err := db.IterableDBProducer.OpenDB(name) 157 if err != nil { 158 return nil, err 159 } 160 dm := WrapStoreWithMetrics(ds) 161 162 name = genericNameOfTmpDB(name) 163 164 dm.log = log.New("database", name) 165 166 metric := "u2u/chaindata/" + strings.ReplaceAll(name, "-", "_") 167 dm.diskReadMeter = metrics.GetOrRegisterMeter(metric+"/disk/read", nil) 168 dm.diskWriteMeter = metrics.GetOrRegisterMeter(metric+"/disk/write", nil) 169 // reset size metric as far as previous db will be dropped soon 170 metrics.Unregister(metric + "/disk/size") 171 dm.diskSizeGauge = metrics.NewRegisteredGauge(metric+"/disk/size", nil) 172 173 // Start up the metrics gathering and return 174 go dm.meter(metricsGatheringInterval) 175 return dm, nil 176 }