github.com/koko1123/flow-go-1@v0.29.6/module/metrics/transaction.go (about) 1 package metrics 2 3 import ( 4 "time" 5 6 "github.com/prometheus/client_golang/prometheus" 7 "github.com/prometheus/client_golang/prometheus/promauto" 8 "github.com/rs/zerolog" 9 10 "github.com/koko1123/flow-go-1/engine/consensus/sealing/counters" 11 "github.com/koko1123/flow-go-1/model/flow" 12 "github.com/koko1123/flow-go-1/module/mempool" 13 ) 14 15 type TransactionCollector struct { 16 transactionTimings mempool.TransactionTimings 17 log zerolog.Logger 18 logTimeToFinalized bool 19 logTimeToExecuted bool 20 logTimeToFinalizedExecuted bool 21 timeToFinalized prometheus.Summary 22 timeToExecuted prometheus.Summary 23 timeToFinalizedExecuted prometheus.Summary 24 transactionSubmission *prometheus.CounterVec 25 scriptExecutedDuration *prometheus.HistogramVec 26 transactionResultDuration *prometheus.HistogramVec 27 scriptSize prometheus.Histogram 28 transactionSize prometheus.Histogram 29 maxReceiptHeight prometheus.Gauge 30 31 // used to skip heights that are lower than the current max height 32 maxReceiptHeightValue counters.StrictMonotonousCounter 33 } 34 35 func NewTransactionCollector(transactionTimings mempool.TransactionTimings, log zerolog.Logger, 36 logTimeToFinalized bool, logTimeToExecuted bool, logTimeToFinalizedExecuted bool) *TransactionCollector { 37 38 tc := &TransactionCollector{ 39 transactionTimings: transactionTimings, 40 log: log, 41 logTimeToFinalized: logTimeToFinalized, 42 logTimeToExecuted: logTimeToExecuted, 43 logTimeToFinalizedExecuted: logTimeToFinalizedExecuted, 44 timeToFinalized: promauto.NewSummary(prometheus.SummaryOpts{ 45 Name: "time_to_finalized_seconds", 46 Namespace: namespaceAccess, 47 Subsystem: subsystemTransactionTiming, 48 Help: "the duration of how long it took between the transaction was received until it was finalized", 49 Objectives: map[float64]float64{ 50 0.01: 0.001, 51 0.5: 0.05, 52 0.99: 0.001, 53 }, 54 MaxAge: 10 * time.Minute, 55 AgeBuckets: 5, 56 BufCap: 500, 57 }), 58 timeToExecuted: promauto.NewSummary(prometheus.SummaryOpts{ 59 Name: "time_to_executed_seconds", 60 Namespace: namespaceAccess, 61 Subsystem: subsystemTransactionTiming, 62 Help: "the duration of how long it took between the transaction was received until it was executed", 63 Objectives: map[float64]float64{ 64 0.01: 0.001, 65 0.5: 0.05, 66 0.99: 0.001, 67 }, 68 MaxAge: 10 * time.Minute, 69 AgeBuckets: 5, 70 BufCap: 500, 71 }), 72 timeToFinalizedExecuted: promauto.NewSummary(prometheus.SummaryOpts{ 73 Name: "time_to_finalized_executed_seconds", 74 Namespace: namespaceAccess, 75 Subsystem: subsystemTransactionTiming, 76 Help: "the duration of how long it took between the transaction was received until it was both " + 77 "finalized and executed", 78 Objectives: map[float64]float64{ 79 0.01: 0.001, 80 0.5: 0.05, 81 0.99: 0.001, 82 }, 83 MaxAge: 10 * time.Minute, 84 AgeBuckets: 5, 85 BufCap: 500, 86 }), 87 transactionSubmission: promauto.NewCounterVec(prometheus.CounterOpts{ 88 Name: "transaction_submission", 89 Namespace: namespaceAccess, 90 Subsystem: subsystemTransactionSubmission, 91 Help: "counter for the success/failure of transaction submissions", 92 }, []string{"result"}), 93 scriptExecutedDuration: promauto.NewHistogramVec(prometheus.HistogramOpts{ 94 Name: "script_executed_duration", 95 Namespace: namespaceAccess, 96 Subsystem: subsystemTransactionSubmission, 97 Help: "histogram for the duration in ms of the round trip time for executing a script", 98 Buckets: []float64{1, 100, 500, 1000, 2000, 5000}, 99 }, []string{"script_size"}), 100 transactionResultDuration: promauto.NewHistogramVec(prometheus.HistogramOpts{ 101 Name: "transaction_result_fetched_duration", 102 Namespace: namespaceAccess, 103 Subsystem: subsystemTransactionSubmission, 104 Help: "histogram for the duration in ms of the round trip time for getting a transaction result", 105 Buckets: []float64{1, 100, 500, 1000, 2000, 5000}, 106 }, []string{"payload_size"}), 107 scriptSize: promauto.NewHistogram(prometheus.HistogramOpts{ 108 Name: "script_size", 109 Namespace: namespaceAccess, 110 Subsystem: subsystemTransactionSubmission, 111 Help: "histogram for the script size in kb of scripts used in ExecuteScript", 112 }), 113 transactionSize: promauto.NewHistogram(prometheus.HistogramOpts{ 114 Name: "transaction_size", 115 Namespace: namespaceAccess, 116 Subsystem: subsystemTransactionSubmission, 117 Help: "histogram for the transaction size in kb of scripts used in GetTransactionResult", 118 }), 119 maxReceiptHeight: promauto.NewGauge(prometheus.GaugeOpts{ 120 Name: "max_receipt_height", 121 Namespace: namespaceAccess, 122 Subsystem: subsystemIngestion, 123 Help: "gauge to track the maximum block height of execution receipts received", 124 }), 125 maxReceiptHeightValue: counters.NewMonotonousCounter(0), 126 } 127 128 return tc 129 } 130 131 func (tc *TransactionCollector) ScriptExecuted(dur time.Duration, size int) { 132 // record the execute script duration and script size 133 tc.scriptSize.Observe(float64(size / 1024)) 134 tc.scriptExecutedDuration.With(prometheus.Labels{ 135 "script_size": tc.sizeLabel(size), 136 }).Observe(float64(dur.Milliseconds())) 137 } 138 139 func (tc *TransactionCollector) TransactionResultFetched(dur time.Duration, size int) { 140 // record the transaction result duration and transaction script/payload size 141 tc.transactionSize.Observe(float64(size / 1024)) 142 tc.transactionResultDuration.With(prometheus.Labels{ 143 "payload_size": tc.sizeLabel(size), 144 }).Observe(float64(dur.Milliseconds())) 145 } 146 147 func (tc *TransactionCollector) sizeLabel(size int) string { 148 // determine the script size label using the size in bytes 149 sizeKb := size / 1024 150 sizeLabel := "100+kb" //"1kb,10kb,100kb, 100+kb" -> [0,1] [2,10] [11,100] [100, +inf] 151 152 if sizeKb <= 1 { 153 sizeLabel = "1kb" 154 } else if sizeKb <= 10 { 155 sizeLabel = "10kb" 156 } else if sizeKb <= 100 { 157 sizeLabel = "100kb" 158 } 159 return sizeLabel 160 } 161 162 func (tc *TransactionCollector) TransactionReceived(txID flow.Identifier, when time.Time) { 163 // we don't need to check whether the transaction timing already exists, it will not be overwritten by the mempool 164 added := tc.transactionTimings.Add(&flow.TransactionTiming{TransactionID: txID, Received: when}) 165 if !added { 166 tc.log.Warn(). 167 Str("transaction_id", txID.String()). 168 Msg("failed to add TransactionReceived metric") 169 } 170 } 171 172 func (tc *TransactionCollector) TransactionFinalized(txID flow.Identifier, when time.Time) { 173 // Count as submitted as long as it's finalized 174 tc.transactionSubmission.WithLabelValues("success").Inc() 175 176 t, updated := tc.transactionTimings.Adjust(txID, func(t *flow.TransactionTiming) *flow.TransactionTiming { 177 t.Finalized = when 178 return t 179 }) 180 181 // the AN may not have received the original transaction sent by the client in which case the finalized metric 182 // is not updated 183 if !updated { 184 tc.log.Debug(). 185 Str("transaction_id", txID.String()). 186 Msg("failed to update TransactionFinalized metric") 187 return 188 } 189 190 tc.trackTTF(t, tc.logTimeToFinalized) 191 tc.trackTTFE(t, tc.logTimeToFinalizedExecuted) 192 193 // remove transaction timing from mempool if finalized and executed 194 if !t.Finalized.IsZero() && !t.Executed.IsZero() { 195 tc.transactionTimings.Remove(txID) 196 } 197 } 198 199 func (tc *TransactionCollector) TransactionExecuted(txID flow.Identifier, when time.Time) { 200 t, updated := tc.transactionTimings.Adjust(txID, func(t *flow.TransactionTiming) *flow.TransactionTiming { 201 t.Executed = when 202 return t 203 }) 204 205 if !updated { 206 tc.log.Debug(). 207 Str("transaction_id", txID.String()). 208 Msg("failed to update TransactionExecuted metric") 209 return 210 } 211 212 tc.trackTTE(t, tc.logTimeToExecuted) 213 tc.trackTTFE(t, tc.logTimeToFinalizedExecuted) 214 215 // remove transaction timing from mempool if finalized and executed 216 if !t.Finalized.IsZero() && !t.Executed.IsZero() { 217 tc.transactionTimings.Remove(txID) 218 } 219 } 220 221 func (tc *TransactionCollector) trackTTF(t *flow.TransactionTiming, log bool) { 222 if t.Received.IsZero() || t.Finalized.IsZero() { 223 return 224 } 225 226 duration := t.Finalized.Sub(t.Received).Seconds() 227 228 tc.timeToFinalized.Observe(duration) 229 230 if log { 231 tc.log.Info().Str("transaction_id", t.TransactionID.String()).Float64("duration", duration). 232 Msg("transaction time to finalized") 233 } 234 } 235 236 func (tc *TransactionCollector) trackTTE(t *flow.TransactionTiming, log bool) { 237 if t.Received.IsZero() || t.Executed.IsZero() { 238 return 239 } 240 241 duration := t.Executed.Sub(t.Received).Seconds() 242 243 tc.timeToExecuted.Observe(duration) 244 245 if log { 246 tc.log.Info().Str("transaction_id", t.TransactionID.String()).Float64("duration", duration). 247 Msg("transaction time to executed") 248 } 249 } 250 251 func (tc *TransactionCollector) trackTTFE(t *flow.TransactionTiming, log bool) { 252 if t.Received.IsZero() || t.Finalized.IsZero() || t.Executed.IsZero() { 253 return 254 } 255 256 duration := t.Finalized.Sub(t.Received).Seconds() 257 if t.Executed.After(t.Finalized) { 258 duration = t.Executed.Sub(t.Received).Seconds() 259 } 260 261 tc.timeToFinalizedExecuted.Observe(duration) 262 263 if log { 264 tc.log.Info().Str("transaction_id", t.TransactionID.String()).Float64("duration", duration). 265 Msg("transaction time to finalized and executed") 266 } 267 } 268 269 func (tc *TransactionCollector) TransactionSubmissionFailed() { 270 tc.transactionSubmission.WithLabelValues("failed").Inc() 271 } 272 273 func (tc *TransactionCollector) TransactionExpired(txID flow.Identifier) { 274 _, exist := tc.transactionTimings.ByID(txID) 275 276 if !exist { 277 // likely previously removed, either executed or expired 278 return 279 } 280 tc.transactionSubmission.WithLabelValues("expired").Inc() 281 tc.transactionTimings.Remove(txID) 282 } 283 284 func (tc *TransactionCollector) UpdateExecutionReceiptMaxHeight(height uint64) { 285 if tc.maxReceiptHeightValue.Set(height) { 286 tc.maxReceiptHeight.Set(float64(height)) 287 } 288 }