1*4947cdc7SCole Faust// Copyright 2019 The Bazel Authors. All rights reserved. 2*4947cdc7SCole Faust// Use of this source code is governed by a BSD-style 3*4947cdc7SCole Faust// license that can be found in the LICENSE file. 4*4947cdc7SCole Faust 5*4947cdc7SCole Faustpackage starlark 6*4947cdc7SCole Faust 7*4947cdc7SCole Faust// This file defines a simple execution-time profiler for Starlark. 8*4947cdc7SCole Faust// It measures the wall time spent executing Starlark code, and emits a 9*4947cdc7SCole Faust// gzipped protocol message in pprof format (github.com/google/pprof). 10*4947cdc7SCole Faust// 11*4947cdc7SCole Faust// When profiling is enabled, the interpreter calls the profiler to 12*4947cdc7SCole Faust// indicate the start and end of each "span" or time interval. A leaf 13*4947cdc7SCole Faust// function (whether Go or Starlark) has a single span. A function that 14*4947cdc7SCole Faust// calls another function has spans for each interval in which it is the 15*4947cdc7SCole Faust// top of the stack. (A LOAD instruction also ends a span.) 16*4947cdc7SCole Faust// 17*4947cdc7SCole Faust// At the start of a span, the interpreter records the current time in 18*4947cdc7SCole Faust// the thread's topmost frame. At the end of the span, it obtains the 19*4947cdc7SCole Faust// time again and subtracts the span start time. The difference is added 20*4947cdc7SCole Faust// to an accumulator variable in the thread. If the accumulator exceeds 21*4947cdc7SCole Faust// some fixed quantum (10ms, say), the profiler records the current call 22*4947cdc7SCole Faust// stack and sends it to the profiler goroutine, along with the number 23*4947cdc7SCole Faust// of quanta, which are subtracted. For example, if the accumulator 24*4947cdc7SCole Faust// holds 3ms and then a completed span adds 25ms to it, its value is 28ms, 25*4947cdc7SCole Faust// which exceeeds 10ms. The profiler records a stack with the value 20ms 26*4947cdc7SCole Faust// (2 quanta), and the accumulator is left with 8ms. 27*4947cdc7SCole Faust// 28*4947cdc7SCole Faust// The profiler goroutine converts the stacks into the pprof format and 29*4947cdc7SCole Faust// emits a gzip-compressed protocol message to the designated output 30*4947cdc7SCole Faust// file. We use a hand-written streaming proto encoder to avoid 31*4947cdc7SCole Faust// dependencies on pprof and proto, and to avoid the need to 32*4947cdc7SCole Faust// materialize the profile data structure in memory. 33*4947cdc7SCole Faust// 34*4947cdc7SCole Faust// A limitation of this profiler is that it measures wall time, which 35*4947cdc7SCole Faust// does not necessarily correspond to CPU time. A CPU profiler requires 36*4947cdc7SCole Faust// that only running (not runnable) threads are sampled; this is 37*4947cdc7SCole Faust// commonly achieved by having the kernel deliver a (PROF) signal to an 38*4947cdc7SCole Faust// arbitrary running thread, through setitimer(2). The CPU profiler in the 39*4947cdc7SCole Faust// Go runtime uses this mechanism, but it is not possible for a Go 40*4947cdc7SCole Faust// application to register a SIGPROF handler, nor is it possible for a 41*4947cdc7SCole Faust// Go handler for some other signal to read the stack pointer of 42*4947cdc7SCole Faust// the interrupted thread. 43*4947cdc7SCole Faust// 44*4947cdc7SCole Faust// Two caveats: 45*4947cdc7SCole Faust// (1) it is tempting to send the leaf Frame directly to the profiler 46*4947cdc7SCole Faust// goroutine instead of making a copy of the stack, since a Frame is a 47*4947cdc7SCole Faust// spaghetti stack--a linked list. However, as soon as execution 48*4947cdc7SCole Faust// resumes, the stack's Frame.pc values may be mutated, so Frames are 49*4947cdc7SCole Faust// not safe to share with the asynchronous profiler goroutine. 50*4947cdc7SCole Faust// (2) it is tempting to use Callables as keys in a map when tabulating 51*4947cdc7SCole Faust// the pprof protocols's Function entities. However, we cannot assume 52*4947cdc7SCole Faust// that Callables are valid map keys, and furthermore we must not 53*4947cdc7SCole Faust// pin function values in memory indefinitely as this may cause lambda 54*4947cdc7SCole Faust// values to keep their free variables live much longer than necessary. 55*4947cdc7SCole Faust 56*4947cdc7SCole Faust// TODO(adonovan): 57*4947cdc7SCole Faust// - make Start/Stop fully thread-safe. 58*4947cdc7SCole Faust// - fix the pc hack. 59*4947cdc7SCole Faust// - experiment with other values of quantum. 60*4947cdc7SCole Faust 61*4947cdc7SCole Faustimport ( 62*4947cdc7SCole Faust "bufio" 63*4947cdc7SCole Faust "bytes" 64*4947cdc7SCole Faust "compress/gzip" 65*4947cdc7SCole Faust "encoding/binary" 66*4947cdc7SCole Faust "fmt" 67*4947cdc7SCole Faust "io" 68*4947cdc7SCole Faust "log" 69*4947cdc7SCole Faust "reflect" 70*4947cdc7SCole Faust "sync/atomic" 71*4947cdc7SCole Faust "time" 72*4947cdc7SCole Faust "unsafe" 73*4947cdc7SCole Faust 74*4947cdc7SCole Faust "go.starlark.net/syntax" 75*4947cdc7SCole Faust) 76*4947cdc7SCole Faust 77*4947cdc7SCole Faust// StartProfile enables time profiling of all Starlark threads, 78*4947cdc7SCole Faust// and writes a profile in pprof format to w. 79*4947cdc7SCole Faust// It must be followed by a call to StopProfiler to stop 80*4947cdc7SCole Faust// the profiler and finalize the profile. 81*4947cdc7SCole Faust// 82*4947cdc7SCole Faust// StartProfile returns an error if profiling was already enabled. 83*4947cdc7SCole Faust// 84*4947cdc7SCole Faust// StartProfile must not be called concurrently with Starlark execution. 85*4947cdc7SCole Faustfunc StartProfile(w io.Writer) error { 86*4947cdc7SCole Faust if !atomic.CompareAndSwapUint32(&profiler.on, 0, 1) { 87*4947cdc7SCole Faust return fmt.Errorf("profiler already running") 88*4947cdc7SCole Faust } 89*4947cdc7SCole Faust 90*4947cdc7SCole Faust // TODO(adonovan): make the API fully concurrency-safe. 91*4947cdc7SCole Faust // The main challenge is racy reads/writes of profiler.events, 92*4947cdc7SCole Faust // and of send/close races on the channel it refers to. 93*4947cdc7SCole Faust // It's easy to solve them with a mutex but harder to do 94*4947cdc7SCole Faust // it efficiently. 95*4947cdc7SCole Faust 96*4947cdc7SCole Faust profiler.events = make(chan *profEvent, 1) 97*4947cdc7SCole Faust profiler.done = make(chan error) 98*4947cdc7SCole Faust 99*4947cdc7SCole Faust go profile(w) 100*4947cdc7SCole Faust 101*4947cdc7SCole Faust return nil 102*4947cdc7SCole Faust} 103*4947cdc7SCole Faust 104*4947cdc7SCole Faust// StopProfiler stops the profiler started by a prior call to 105*4947cdc7SCole Faust// StartProfile and finalizes the profile. It returns an error if the 106*4947cdc7SCole Faust// profile could not be completed. 107*4947cdc7SCole Faust// 108*4947cdc7SCole Faust// StopProfiler must not be called concurrently with Starlark execution. 109*4947cdc7SCole Faustfunc StopProfile() error { 110*4947cdc7SCole Faust // Terminate the profiler goroutine and get its result. 111*4947cdc7SCole Faust close(profiler.events) 112*4947cdc7SCole Faust err := <-profiler.done 113*4947cdc7SCole Faust 114*4947cdc7SCole Faust profiler.done = nil 115*4947cdc7SCole Faust profiler.events = nil 116*4947cdc7SCole Faust atomic.StoreUint32(&profiler.on, 0) 117*4947cdc7SCole Faust 118*4947cdc7SCole Faust return err 119*4947cdc7SCole Faust} 120*4947cdc7SCole Faust 121*4947cdc7SCole Faust// globals 122*4947cdc7SCole Faustvar profiler struct { 123*4947cdc7SCole Faust on uint32 // nonzero => profiler running 124*4947cdc7SCole Faust events chan *profEvent // profile events from interpreter threads 125*4947cdc7SCole Faust done chan error // indicates profiler goroutine is ready 126*4947cdc7SCole Faust} 127*4947cdc7SCole Faust 128*4947cdc7SCole Faustfunc (thread *Thread) beginProfSpan() { 129*4947cdc7SCole Faust if profiler.events == nil { 130*4947cdc7SCole Faust return // profiling not enabled 131*4947cdc7SCole Faust } 132*4947cdc7SCole Faust 133*4947cdc7SCole Faust thread.frameAt(0).spanStart = nanotime() 134*4947cdc7SCole Faust} 135*4947cdc7SCole Faust 136*4947cdc7SCole Faust// TODO(adonovan): experiment with smaller values, 137*4947cdc7SCole Faust// which trade space and time for greater precision. 138*4947cdc7SCole Faustconst quantum = 10 * time.Millisecond 139*4947cdc7SCole Faust 140*4947cdc7SCole Faustfunc (thread *Thread) endProfSpan() { 141*4947cdc7SCole Faust if profiler.events == nil { 142*4947cdc7SCole Faust return // profiling not enabled 143*4947cdc7SCole Faust } 144*4947cdc7SCole Faust 145*4947cdc7SCole Faust // Add the span to the thread's accumulator. 146*4947cdc7SCole Faust thread.proftime += time.Duration(nanotime() - thread.frameAt(0).spanStart) 147*4947cdc7SCole Faust if thread.proftime < quantum { 148*4947cdc7SCole Faust return 149*4947cdc7SCole Faust } 150*4947cdc7SCole Faust 151*4947cdc7SCole Faust // Only record complete quanta. 152*4947cdc7SCole Faust n := thread.proftime / quantum 153*4947cdc7SCole Faust thread.proftime -= n * quantum 154*4947cdc7SCole Faust 155*4947cdc7SCole Faust // Copy the stack. 156*4947cdc7SCole Faust // (We can't save thread.frame because its pc will change.) 157*4947cdc7SCole Faust ev := &profEvent{ 158*4947cdc7SCole Faust thread: thread, 159*4947cdc7SCole Faust time: n * quantum, 160*4947cdc7SCole Faust } 161*4947cdc7SCole Faust ev.stack = ev.stackSpace[:0] 162*4947cdc7SCole Faust for i := range thread.stack { 163*4947cdc7SCole Faust fr := thread.frameAt(i) 164*4947cdc7SCole Faust ev.stack = append(ev.stack, profFrame{ 165*4947cdc7SCole Faust pos: fr.Position(), 166*4947cdc7SCole Faust fn: fr.Callable(), 167*4947cdc7SCole Faust pc: fr.pc, 168*4947cdc7SCole Faust }) 169*4947cdc7SCole Faust } 170*4947cdc7SCole Faust 171*4947cdc7SCole Faust profiler.events <- ev 172*4947cdc7SCole Faust} 173*4947cdc7SCole Faust 174*4947cdc7SCole Fausttype profEvent struct { 175*4947cdc7SCole Faust thread *Thread // currently unused 176*4947cdc7SCole Faust time time.Duration 177*4947cdc7SCole Faust stack []profFrame 178*4947cdc7SCole Faust stackSpace [8]profFrame // initial space for stack 179*4947cdc7SCole Faust} 180*4947cdc7SCole Faust 181*4947cdc7SCole Fausttype profFrame struct { 182*4947cdc7SCole Faust fn Callable // don't hold this live for too long (prevents GC of lambdas) 183*4947cdc7SCole Faust pc uint32 // program counter (Starlark frames only) 184*4947cdc7SCole Faust pos syntax.Position // position of pc within this frame 185*4947cdc7SCole Faust} 186*4947cdc7SCole Faust 187*4947cdc7SCole Faust// profile is the profiler goroutine. 188*4947cdc7SCole Faust// It runs until StopProfiler is called. 189*4947cdc7SCole Faustfunc profile(w io.Writer) { 190*4947cdc7SCole Faust // Field numbers from pprof protocol. 191*4947cdc7SCole Faust // See https://github.com/google/pprof/blob/master/proto/profile.proto 192*4947cdc7SCole Faust const ( 193*4947cdc7SCole Faust Profile_sample_type = 1 // repeated ValueType 194*4947cdc7SCole Faust Profile_sample = 2 // repeated Sample 195*4947cdc7SCole Faust Profile_mapping = 3 // repeated Mapping 196*4947cdc7SCole Faust Profile_location = 4 // repeated Location 197*4947cdc7SCole Faust Profile_function = 5 // repeated Function 198*4947cdc7SCole Faust Profile_string_table = 6 // repeated string 199*4947cdc7SCole Faust Profile_time_nanos = 9 // int64 200*4947cdc7SCole Faust Profile_duration_nanos = 10 // int64 201*4947cdc7SCole Faust Profile_period_type = 11 // ValueType 202*4947cdc7SCole Faust Profile_period = 12 // int64 203*4947cdc7SCole Faust 204*4947cdc7SCole Faust ValueType_type = 1 // int64 205*4947cdc7SCole Faust ValueType_unit = 2 // int64 206*4947cdc7SCole Faust 207*4947cdc7SCole Faust Sample_location_id = 1 // repeated uint64 208*4947cdc7SCole Faust Sample_value = 2 // repeated int64 209*4947cdc7SCole Faust Sample_label = 3 // repeated Label 210*4947cdc7SCole Faust 211*4947cdc7SCole Faust Label_key = 1 // int64 212*4947cdc7SCole Faust Label_str = 2 // int64 213*4947cdc7SCole Faust Label_num = 3 // int64 214*4947cdc7SCole Faust Label_num_unit = 4 // int64 215*4947cdc7SCole Faust 216*4947cdc7SCole Faust Location_id = 1 // uint64 217*4947cdc7SCole Faust Location_mapping_id = 2 // uint64 218*4947cdc7SCole Faust Location_address = 3 // uint64 219*4947cdc7SCole Faust Location_line = 4 // repeated Line 220*4947cdc7SCole Faust 221*4947cdc7SCole Faust Line_function_id = 1 // uint64 222*4947cdc7SCole Faust Line_line = 2 // int64 223*4947cdc7SCole Faust 224*4947cdc7SCole Faust Function_id = 1 // uint64 225*4947cdc7SCole Faust Function_name = 2 // int64 226*4947cdc7SCole Faust Function_system_name = 3 // int64 227*4947cdc7SCole Faust Function_filename = 4 // int64 228*4947cdc7SCole Faust Function_start_line = 5 // int64 229*4947cdc7SCole Faust ) 230*4947cdc7SCole Faust 231*4947cdc7SCole Faust bufw := bufio.NewWriter(w) // write file in 4KB (not 240B flate-sized) chunks 232*4947cdc7SCole Faust gz := gzip.NewWriter(bufw) 233*4947cdc7SCole Faust enc := protoEncoder{w: gz} 234*4947cdc7SCole Faust 235*4947cdc7SCole Faust // strings 236*4947cdc7SCole Faust stringIndex := make(map[string]int64) 237*4947cdc7SCole Faust str := func(s string) int64 { 238*4947cdc7SCole Faust i, ok := stringIndex[s] 239*4947cdc7SCole Faust if !ok { 240*4947cdc7SCole Faust i = int64(len(stringIndex)) 241*4947cdc7SCole Faust enc.string(Profile_string_table, s) 242*4947cdc7SCole Faust stringIndex[s] = i 243*4947cdc7SCole Faust } 244*4947cdc7SCole Faust return i 245*4947cdc7SCole Faust } 246*4947cdc7SCole Faust str("") // entry 0 247*4947cdc7SCole Faust 248*4947cdc7SCole Faust // functions 249*4947cdc7SCole Faust // 250*4947cdc7SCole Faust // function returns the ID of a Callable for use in Line.FunctionId. 251*4947cdc7SCole Faust // The ID is the same as the function's logical address, 252*4947cdc7SCole Faust // which is supplied by the caller to avoid the need to recompute it. 253*4947cdc7SCole Faust functionId := make(map[uintptr]uint64) 254*4947cdc7SCole Faust function := func(fn Callable, addr uintptr) uint64 { 255*4947cdc7SCole Faust id, ok := functionId[addr] 256*4947cdc7SCole Faust if !ok { 257*4947cdc7SCole Faust id = uint64(addr) 258*4947cdc7SCole Faust 259*4947cdc7SCole Faust var pos syntax.Position 260*4947cdc7SCole Faust if fn, ok := fn.(callableWithPosition); ok { 261*4947cdc7SCole Faust pos = fn.Position() 262*4947cdc7SCole Faust } 263*4947cdc7SCole Faust 264*4947cdc7SCole Faust name := fn.Name() 265*4947cdc7SCole Faust if name == "<toplevel>" { 266*4947cdc7SCole Faust name = pos.Filename() 267*4947cdc7SCole Faust } 268*4947cdc7SCole Faust 269*4947cdc7SCole Faust nameIndex := str(name) 270*4947cdc7SCole Faust 271*4947cdc7SCole Faust fun := new(bytes.Buffer) 272*4947cdc7SCole Faust funenc := protoEncoder{w: fun} 273*4947cdc7SCole Faust funenc.uint(Function_id, id) 274*4947cdc7SCole Faust funenc.int(Function_name, nameIndex) 275*4947cdc7SCole Faust funenc.int(Function_system_name, nameIndex) 276*4947cdc7SCole Faust funenc.int(Function_filename, str(pos.Filename())) 277*4947cdc7SCole Faust funenc.int(Function_start_line, int64(pos.Line)) 278*4947cdc7SCole Faust enc.bytes(Profile_function, fun.Bytes()) 279*4947cdc7SCole Faust 280*4947cdc7SCole Faust functionId[addr] = id 281*4947cdc7SCole Faust } 282*4947cdc7SCole Faust return id 283*4947cdc7SCole Faust } 284*4947cdc7SCole Faust 285*4947cdc7SCole Faust // locations 286*4947cdc7SCole Faust // 287*4947cdc7SCole Faust // location returns the ID of the location denoted by fr. 288*4947cdc7SCole Faust // For Starlark frames, this is the Frame pc. 289*4947cdc7SCole Faust locationId := make(map[uintptr]uint64) 290*4947cdc7SCole Faust location := func(fr profFrame) uint64 { 291*4947cdc7SCole Faust fnAddr := profFuncAddr(fr.fn) 292*4947cdc7SCole Faust 293*4947cdc7SCole Faust // For Starlark functions, the frame position 294*4947cdc7SCole Faust // represents the current PC value. 295*4947cdc7SCole Faust // Mix it into the low bits of the address. 296*4947cdc7SCole Faust // This is super hacky and may result in collisions 297*4947cdc7SCole Faust // in large functions or if functions are numerous. 298*4947cdc7SCole Faust // TODO(adonovan): fix: try making this cleaner by treating 299*4947cdc7SCole Faust // each bytecode segment as a Profile.Mapping. 300*4947cdc7SCole Faust pcAddr := fnAddr 301*4947cdc7SCole Faust if _, ok := fr.fn.(*Function); ok { 302*4947cdc7SCole Faust pcAddr = (pcAddr << 16) ^ uintptr(fr.pc) 303*4947cdc7SCole Faust } 304*4947cdc7SCole Faust 305*4947cdc7SCole Faust id, ok := locationId[pcAddr] 306*4947cdc7SCole Faust if !ok { 307*4947cdc7SCole Faust id = uint64(pcAddr) 308*4947cdc7SCole Faust 309*4947cdc7SCole Faust line := new(bytes.Buffer) 310*4947cdc7SCole Faust lineenc := protoEncoder{w: line} 311*4947cdc7SCole Faust lineenc.uint(Line_function_id, function(fr.fn, fnAddr)) 312*4947cdc7SCole Faust lineenc.int(Line_line, int64(fr.pos.Line)) 313*4947cdc7SCole Faust loc := new(bytes.Buffer) 314*4947cdc7SCole Faust locenc := protoEncoder{w: loc} 315*4947cdc7SCole Faust locenc.uint(Location_id, id) 316*4947cdc7SCole Faust locenc.uint(Location_address, uint64(pcAddr)) 317*4947cdc7SCole Faust locenc.bytes(Location_line, line.Bytes()) 318*4947cdc7SCole Faust enc.bytes(Profile_location, loc.Bytes()) 319*4947cdc7SCole Faust 320*4947cdc7SCole Faust locationId[pcAddr] = id 321*4947cdc7SCole Faust } 322*4947cdc7SCole Faust return id 323*4947cdc7SCole Faust } 324*4947cdc7SCole Faust 325*4947cdc7SCole Faust wallNanos := new(bytes.Buffer) 326*4947cdc7SCole Faust wnenc := protoEncoder{w: wallNanos} 327*4947cdc7SCole Faust wnenc.int(ValueType_type, str("wall")) 328*4947cdc7SCole Faust wnenc.int(ValueType_unit, str("nanoseconds")) 329*4947cdc7SCole Faust 330*4947cdc7SCole Faust // informational fields of Profile 331*4947cdc7SCole Faust enc.bytes(Profile_sample_type, wallNanos.Bytes()) 332*4947cdc7SCole Faust enc.int(Profile_period, quantum.Nanoseconds()) // magnitude of sampling period 333*4947cdc7SCole Faust enc.bytes(Profile_period_type, wallNanos.Bytes()) // dimension and unit of period 334*4947cdc7SCole Faust enc.int(Profile_time_nanos, time.Now().UnixNano()) // start (real) time of profile 335*4947cdc7SCole Faust 336*4947cdc7SCole Faust startNano := nanotime() 337*4947cdc7SCole Faust 338*4947cdc7SCole Faust // Read profile events from the channel 339*4947cdc7SCole Faust // until it is closed by StopProfiler. 340*4947cdc7SCole Faust for e := range profiler.events { 341*4947cdc7SCole Faust sample := new(bytes.Buffer) 342*4947cdc7SCole Faust sampleenc := protoEncoder{w: sample} 343*4947cdc7SCole Faust sampleenc.int(Sample_value, e.time.Nanoseconds()) // wall nanoseconds 344*4947cdc7SCole Faust for _, fr := range e.stack { 345*4947cdc7SCole Faust sampleenc.uint(Sample_location_id, location(fr)) 346*4947cdc7SCole Faust } 347*4947cdc7SCole Faust enc.bytes(Profile_sample, sample.Bytes()) 348*4947cdc7SCole Faust } 349*4947cdc7SCole Faust 350*4947cdc7SCole Faust endNano := nanotime() 351*4947cdc7SCole Faust enc.int(Profile_duration_nanos, endNano-startNano) 352*4947cdc7SCole Faust 353*4947cdc7SCole Faust err := gz.Close() // Close reports any prior write error 354*4947cdc7SCole Faust if flushErr := bufw.Flush(); err == nil { 355*4947cdc7SCole Faust err = flushErr 356*4947cdc7SCole Faust } 357*4947cdc7SCole Faust profiler.done <- err 358*4947cdc7SCole Faust} 359*4947cdc7SCole Faust 360*4947cdc7SCole Faust// nanotime returns the time in nanoseconds since epoch. 361*4947cdc7SCole Faust// It is implemented by runtime.nanotime using the linkname hack; 362*4947cdc7SCole Faust// runtime.nanotime is defined for all OSs/ARCHS and uses the 363*4947cdc7SCole Faust// monotonic system clock, which there is no portable way to access. 364*4947cdc7SCole Faust// Should that function ever go away, these alternatives exist: 365*4947cdc7SCole Faust// 366*4947cdc7SCole Faust// // POSIX only. REALTIME not MONOTONIC. 17ns. 367*4947cdc7SCole Faust// var tv syscall.Timeval 368*4947cdc7SCole Faust// syscall.Gettimeofday(&tv) // can't fail 369*4947cdc7SCole Faust// return tv.Nano() 370*4947cdc7SCole Faust// 371*4947cdc7SCole Faust// // Portable. REALTIME not MONOTONIC. 46ns. 372*4947cdc7SCole Faust// return time.Now().Nanoseconds() 373*4947cdc7SCole Faust// 374*4947cdc7SCole Faust// // POSIX only. Adds a dependency. 375*4947cdc7SCole Faust// import "golang.org/x/sys/unix" 376*4947cdc7SCole Faust// var ts unix.Timespec 377*4947cdc7SCole Faust// unix.ClockGettime(CLOCK_MONOTONIC, &ts) // can't fail 378*4947cdc7SCole Faust// return unix.TimespecToNsec(ts) 379*4947cdc7SCole Faust// 380*4947cdc7SCole Faust//go:linkname nanotime runtime.nanotime 381*4947cdc7SCole Faustfunc nanotime() int64 382*4947cdc7SCole Faust 383*4947cdc7SCole Faust// profFuncAddr returns the canonical "address" 384*4947cdc7SCole Faust// of a Callable for use by the profiler. 385*4947cdc7SCole Faustfunc profFuncAddr(fn Callable) uintptr { 386*4947cdc7SCole Faust switch fn := fn.(type) { 387*4947cdc7SCole Faust case *Builtin: 388*4947cdc7SCole Faust return reflect.ValueOf(fn.fn).Pointer() 389*4947cdc7SCole Faust case *Function: 390*4947cdc7SCole Faust return uintptr(unsafe.Pointer(fn.funcode)) 391*4947cdc7SCole Faust } 392*4947cdc7SCole Faust 393*4947cdc7SCole Faust // User-defined callable types are typically of 394*4947cdc7SCole Faust // of kind pointer-to-struct. Handle them specially. 395*4947cdc7SCole Faust if v := reflect.ValueOf(fn); v.Type().Kind() == reflect.Ptr { 396*4947cdc7SCole Faust return v.Pointer() 397*4947cdc7SCole Faust } 398*4947cdc7SCole Faust 399*4947cdc7SCole Faust // Address zero is reserved by the protocol. 400*4947cdc7SCole Faust // Use 1 for callables we don't recognize. 401*4947cdc7SCole Faust log.Printf("Starlark profiler: no address for Callable %T", fn) 402*4947cdc7SCole Faust return 1 403*4947cdc7SCole Faust} 404*4947cdc7SCole Faust 405*4947cdc7SCole Faust// We encode the protocol message by hand to avoid making 406*4947cdc7SCole Faust// the interpreter depend on both github.com/google/pprof 407*4947cdc7SCole Faust// and github.com/golang/protobuf. 408*4947cdc7SCole Faust// 409*4947cdc7SCole Faust// This also avoids the need to materialize a protocol message object 410*4947cdc7SCole Faust// tree of unbounded size and serialize it all at the end. 411*4947cdc7SCole Faust// The pprof format appears to have been designed to 412*4947cdc7SCole Faust// permit streaming implementations such as this one. 413*4947cdc7SCole Faust// 414*4947cdc7SCole Faust// See https://developers.google.com/protocol-buffers/docs/encoding. 415*4947cdc7SCole Fausttype protoEncoder struct { 416*4947cdc7SCole Faust w io.Writer // *bytes.Buffer or *gzip.Writer 417*4947cdc7SCole Faust tmp [binary.MaxVarintLen64]byte 418*4947cdc7SCole Faust} 419*4947cdc7SCole Faust 420*4947cdc7SCole Faustfunc (e *protoEncoder) uvarint(x uint64) { 421*4947cdc7SCole Faust n := binary.PutUvarint(e.tmp[:], x) 422*4947cdc7SCole Faust e.w.Write(e.tmp[:n]) 423*4947cdc7SCole Faust} 424*4947cdc7SCole Faust 425*4947cdc7SCole Faustfunc (e *protoEncoder) tag(field, wire uint) { 426*4947cdc7SCole Faust e.uvarint(uint64(field<<3 | wire)) 427*4947cdc7SCole Faust} 428*4947cdc7SCole Faust 429*4947cdc7SCole Faustfunc (e *protoEncoder) string(field uint, s string) { 430*4947cdc7SCole Faust e.tag(field, 2) // length-delimited 431*4947cdc7SCole Faust e.uvarint(uint64(len(s))) 432*4947cdc7SCole Faust io.WriteString(e.w, s) 433*4947cdc7SCole Faust} 434*4947cdc7SCole Faust 435*4947cdc7SCole Faustfunc (e *protoEncoder) bytes(field uint, b []byte) { 436*4947cdc7SCole Faust e.tag(field, 2) // length-delimited 437*4947cdc7SCole Faust e.uvarint(uint64(len(b))) 438*4947cdc7SCole Faust e.w.Write(b) 439*4947cdc7SCole Faust} 440*4947cdc7SCole Faust 441*4947cdc7SCole Faustfunc (e *protoEncoder) uint(field uint, x uint64) { 442*4947cdc7SCole Faust e.tag(field, 0) // varint 443*4947cdc7SCole Faust e.uvarint(x) 444*4947cdc7SCole Faust} 445*4947cdc7SCole Faust 446*4947cdc7SCole Faustfunc (e *protoEncoder) int(field uint, x int64) { 447*4947cdc7SCole Faust e.tag(field, 0) // varint 448*4947cdc7SCole Faust e.uvarint(uint64(x)) 449*4947cdc7SCole Faust} 450