github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/fvm/environment/program_logger.go (about)

     1  package environment
     2  
     3  import (
     4  	"time"
     5  
     6  	"github.com/onflow/cadence/runtime/common"
     7  	"github.com/rs/zerolog"
     8  	"go.opentelemetry.io/otel/attribute"
     9  	otelTrace "go.opentelemetry.io/otel/trace"
    10  
    11  	"github.com/onflow/flow-go/fvm/tracing"
    12  	"github.com/onflow/flow-go/module/trace"
    13  )
    14  
    15  // MetricsReporter captures and reports metrics to back to the execution
    16  // environment it is a setup passed to the context.
    17  type MetricsReporter interface {
    18  	RuntimeTransactionParsed(time.Duration)
    19  	RuntimeTransactionChecked(time.Duration)
    20  	RuntimeTransactionInterpreted(time.Duration)
    21  	RuntimeSetNumberOfAccounts(count uint64)
    22  	RuntimeTransactionProgramsCacheMiss()
    23  	RuntimeTransactionProgramsCacheHit()
    24  }
    25  
    26  // NoopMetricsReporter is a MetricReporter that does nothing.
    27  type NoopMetricsReporter struct{}
    28  
    29  // RuntimeTransactionParsed is a noop
    30  func (NoopMetricsReporter) RuntimeTransactionParsed(time.Duration) {}
    31  
    32  // RuntimeTransactionChecked is a noop
    33  func (NoopMetricsReporter) RuntimeTransactionChecked(time.Duration) {}
    34  
    35  // RuntimeTransactionInterpreted is a noop
    36  func (NoopMetricsReporter) RuntimeTransactionInterpreted(time.Duration) {}
    37  
    38  // RuntimeSetNumberOfAccounts is a noop
    39  func (NoopMetricsReporter) RuntimeSetNumberOfAccounts(count uint64) {}
    40  
    41  // RuntimeTransactionProgramsCacheMiss is a noop
    42  func (NoopMetricsReporter) RuntimeTransactionProgramsCacheMiss() {}
    43  
    44  // RuntimeTransactionProgramsCacheHit is a noop
    45  func (NoopMetricsReporter) RuntimeTransactionProgramsCacheHit() {}
    46  
    47  type ProgramLoggerParams struct {
    48  	zerolog.Logger
    49  
    50  	CadenceLoggingEnabled bool
    51  
    52  	MetricsReporter
    53  }
    54  
    55  func DefaultProgramLoggerParams() ProgramLoggerParams {
    56  	return ProgramLoggerParams{
    57  		Logger:                zerolog.Nop(),
    58  		CadenceLoggingEnabled: false,
    59  		MetricsReporter:       NoopMetricsReporter{},
    60  	}
    61  }
    62  
    63  type ProgramLogger struct {
    64  	tracer tracing.TracerSpan
    65  
    66  	ProgramLoggerParams
    67  
    68  	logs []string
    69  }
    70  
    71  func NewProgramLogger(
    72  	tracer tracing.TracerSpan,
    73  	params ProgramLoggerParams,
    74  ) *ProgramLogger {
    75  	return &ProgramLogger{
    76  		tracer:              tracer,
    77  		ProgramLoggerParams: params,
    78  		logs:                nil,
    79  	}
    80  }
    81  
    82  func (logger *ProgramLogger) Logger() zerolog.Logger {
    83  	return logger.ProgramLoggerParams.Logger
    84  }
    85  
    86  func (logger *ProgramLogger) ImplementationDebugLog(message string) error {
    87  	logger.Debug().Msgf("Cadence: %s", message)
    88  	return nil
    89  }
    90  
    91  func (logger *ProgramLogger) ProgramLog(message string) error {
    92  	defer logger.tracer.StartExtensiveTracingChildSpan(
    93  		trace.FVMEnvProgramLog).End()
    94  
    95  	if logger.CadenceLoggingEnabled {
    96  
    97  		// If cadence logging is enabled (which is usually in the
    98  		// emulator or emulator based tools),
    99  		// we log the message to the zerolog logger so that they can be tracked
   100  		// while stepping through a transaction/script.
   101  		logger.
   102  			Debug().
   103  			Msgf("Cadence log: %s", message)
   104  
   105  		logger.logs = append(logger.logs, message)
   106  	}
   107  	return nil
   108  }
   109  
   110  func (logger *ProgramLogger) Logs() []string {
   111  	return logger.logs
   112  }
   113  
   114  func (logger *ProgramLogger) RecordTrace(
   115  	operation string,
   116  	location common.Location,
   117  	duration time.Duration,
   118  	attrs []attribute.KeyValue,
   119  ) {
   120  	if location != nil {
   121  		attrs = append(attrs, attribute.String("location", location.String()))
   122  	}
   123  
   124  	end := time.Now()
   125  
   126  	span := logger.tracer.StartChildSpan(
   127  		trace.FVMCadenceTrace.Child(operation),
   128  		otelTrace.WithAttributes(attrs...),
   129  		otelTrace.WithTimestamp(end.Add(-duration)))
   130  	span.End(otelTrace.WithTimestamp(end))
   131  }
   132  
   133  // ProgramParsed captures time spent on parsing a code at specific location
   134  func (logger *ProgramLogger) ProgramParsed(
   135  	location common.Location,
   136  	duration time.Duration,
   137  ) {
   138  	logger.RecordTrace("parseProgram", location, duration, nil)
   139  
   140  	// These checks prevent re-reporting durations, the metrics collection is
   141  	// a bit counter-intuitive:
   142  	// The three functions (parsing, checking, interpretation) are not called
   143  	// in sequence, but in some cases as part of each other. We basically only
   144  	// measure the durations reported for the entry-point (the transaction),
   145  	// and not for child locations, because they might be already part of the
   146  	// duration for the entry-point.
   147  	if location == nil {
   148  		return
   149  	}
   150  	if _, ok := location.(common.TransactionLocation); ok {
   151  		logger.MetricsReporter.RuntimeTransactionParsed(duration)
   152  	}
   153  }
   154  
   155  // ProgramChecked captures time spent on checking a code at specific location
   156  func (logger *ProgramLogger) ProgramChecked(
   157  	location common.Location,
   158  	duration time.Duration,
   159  ) {
   160  	logger.RecordTrace("checkProgram", location, duration, nil)
   161  
   162  	// see the comment for ProgramParsed
   163  	if location == nil {
   164  		return
   165  	}
   166  	if _, ok := location.(common.TransactionLocation); ok {
   167  		logger.MetricsReporter.RuntimeTransactionChecked(duration)
   168  	}
   169  }
   170  
   171  // ProgramInterpreted captures time spent on interpreting a code at specific location
   172  func (logger *ProgramLogger) ProgramInterpreted(
   173  	location common.Location,
   174  	duration time.Duration,
   175  ) {
   176  	logger.RecordTrace("interpretProgram", location, duration, nil)
   177  
   178  	// see the comment for ProgramInterpreted
   179  	if location == nil {
   180  		return
   181  	}
   182  	if _, ok := location.(common.TransactionLocation); ok {
   183  		logger.MetricsReporter.RuntimeTransactionInterpreted(duration)
   184  	}
   185  }
   186  
   187  // ValueEncoded accumulates time spend on runtime value encoding
   188  func (logger *ProgramLogger) ValueEncoded(duration time.Duration) {
   189  	logger.RecordTrace("encodeValue", nil, duration, nil)
   190  }
   191  
   192  // ValueDecoded accumulates time spend on runtime value decoding
   193  func (logger *ProgramLogger) ValueDecoded(duration time.Duration) {
   194  	logger.RecordTrace("decodeValue", nil, duration, nil)
   195  }