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  }