github.com/consensys/gnark@v0.11.0/profile/profile_worker.go (about)

     1  package profile
     2  
     3  import (
     4  	"runtime"
     5  	"strings"
     6  	"sync"
     7  	"sync/atomic"
     8  	"unicode"
     9  
    10  	"github.com/google/pprof/profile"
    11  )
    12  
    13  // since we are assuming usage of this package from a single go routine, this channel only has
    14  // one "producer", and one "consumer". it's purpose is to guarantee the order of execution of
    15  // adding / removing a profiling session and sampling events, while enabling the caller
    16  // (frontend.Compile) to sample the events asynchronously.
    17  var chCommands = make(chan command, 100)
    18  var onceInit sync.Once
    19  
    20  type command struct {
    21  	p      *Profile
    22  	pc     []uintptr
    23  	remove bool
    24  }
    25  
    26  func worker() {
    27  	for c := range chCommands {
    28  		if c.p != nil {
    29  			if c.remove {
    30  				for i := 0; i < len(sessions); i++ {
    31  					if sessions[i] == c.p {
    32  						sessions[i] = sessions[len(sessions)-1]
    33  						sessions = sessions[:len(sessions)-1]
    34  						break
    35  					}
    36  				}
    37  				close(c.p.chDone)
    38  
    39  				// decrement active sessions
    40  				atomic.AddUint32(&activeSessions, ^uint32(0))
    41  			} else {
    42  				sessions = append(sessions, c.p)
    43  			}
    44  			continue
    45  		}
    46  
    47  		// it's a sampling of event
    48  		collectSample(c.pc)
    49  	}
    50  
    51  }
    52  
    53  // collectSample must be called from the worker go routine
    54  func collectSample(pc []uintptr) {
    55  	// for each session we may have a distinct sample, since ids of functions and locations may mismatch
    56  	samples := make([]*profile.Sample, len(sessions))
    57  	for i := 0; i < len(samples); i++ {
    58  		samples[i] = &profile.Sample{Value: []int64{1}} // for now, we just collect new constraints count
    59  	}
    60  
    61  	frames := runtime.CallersFrames(pc)
    62  	// Loop to get frames.
    63  	// A fixed number of pcs can expand to an indefinite number of Frames.
    64  	for {
    65  		frame, more := frames.Next()
    66  
    67  		if strings.Contains(frame.Function, "frontend.parseCircuit") {
    68  			// we stop; previous frame was the .Define definition of the circuit
    69  			break
    70  		}
    71  
    72  		if strings.HasSuffix(frame.Function, ".func1") {
    73  			// TODO @gbotrel filter anonymous func better
    74  			//
    75  			// ivokub: relevant comment - if we have many anonymous functions in package, then the name of the anonymous function has different suffices.
    76  			continue
    77  		}
    78  
    79  		// filter internal builder functions
    80  		if filterSCSPrivateFunc(frame.Function) || filterR1CSPrivateFunc(frame.Function) {
    81  			continue
    82  		}
    83  
    84  		// TODO @gbotrel [...] -> from generics display poorly in pprof
    85  		// https://github.com/golang/go/issues/54105
    86  		frame.Function = strings.ReplaceAll(frame.Function, "[...]", "[T]")
    87  
    88  		for i := 0; i < len(samples); i++ {
    89  			samples[i].Location = append(samples[i].Location, sessions[i].getLocation(&frame))
    90  		}
    91  
    92  		if !more {
    93  			break
    94  		}
    95  		if strings.HasSuffix(frame.Function, ".Define") {
    96  			for i := 0; i < len(sessions); i++ {
    97  				sessions[i].onceSetName.Do(func() {
    98  					// once per profile session, we set the "name of the binary"
    99  					// here we grep the struct name where "Define" exist: hopefully the circuit Name
   100  					// note: this won't work well for nested Define calls.
   101  					fe := strings.Split(frame.Function, "/")
   102  					circuitName := strings.TrimSuffix(fe[len(fe)-1], ".Define")
   103  					sessions[i].pprof.Mapping = []*profile.Mapping{
   104  						{ID: 1, File: circuitName},
   105  					}
   106  				})
   107  			}
   108  			// break --> we break when we hit frontend.parseCircuit; in case we have nested Define calls in the stack.
   109  		}
   110  	}
   111  
   112  	for i := 0; i < len(sessions); i++ {
   113  		sessions[i].pprof.Sample = append(sessions[i].pprof.Sample, samples[i])
   114  	}
   115  
   116  }
   117  
   118  func filterSCSPrivateFunc(f string) bool {
   119  	const scsPrefix = "github.com/consensys/gnark/frontend/cs/scs.(*builder)."
   120  	if strings.HasPrefix(f, scsPrefix) && len(f) > len(scsPrefix) {
   121  		// filter plonk frontend private APIs from the trace.
   122  		c := []rune(f)[len(scsPrefix)]
   123  		if unicode.IsLower(c) {
   124  			return true
   125  		}
   126  	}
   127  	return false
   128  }
   129  
   130  func filterR1CSPrivateFunc(f string) bool {
   131  	const r1csPrefix = "github.com/consensys/gnark/frontend/cs/r1cs.(*builder)."
   132  	if strings.HasPrefix(f, r1csPrefix) && len(f) > len(r1csPrefix) {
   133  		// filter r1cs frontend private APIs from the trace.
   134  		c := []rune(f)[len(r1csPrefix)]
   135  		if unicode.IsLower(c) {
   136  			return true
   137  		}
   138  	}
   139  	return false
   140  }