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 }