package goja
import (
"errors"
"io"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/google/pprof/profile"
)
const profInterval = 10 * time .Millisecond
const profMaxStackDepth = 64
const (
profReqNone int32 = iota
profReqDoSample
profReqSampleReady
profReqStop
)
type _globalProfiler struct {
p profiler
w io .Writer
enabled int32
}
var globalProfiler _globalProfiler
type profTracker struct {
req, finished int32
start, stop time .Time
numFrames int
frames [profMaxStackDepth ]StackFrame
}
type profiler struct {
mu sync .Mutex
trackers []*profTracker
buf *profBuffer
running bool
}
type profFunc struct {
f profile .Function
locs map [int32 ]*profile .Location
}
type profSampleNode struct {
loc *profile .Location
sample *profile .Sample
parent *profSampleNode
children map [*profile .Location ]*profSampleNode
}
type profBuffer struct {
funcs map [*Program ]*profFunc
root profSampleNode
}
func (pb *profBuffer ) addSample (pt *profTracker ) {
sampleFrames := pt .frames [:pt .numFrames ]
n := &pb .root
for j := len (sampleFrames ) - 1 ; j >= 0 ; j -- {
frame := sampleFrames [j ]
if frame .prg == nil {
continue
}
var f *profFunc
if f = pb .funcs [frame .prg ]; f == nil {
f = &profFunc {
locs : make (map [int32 ]*profile .Location ),
}
if pb .funcs == nil {
pb .funcs = make (map [*Program ]*profFunc )
}
pb .funcs [frame .prg ] = f
}
var loc *profile .Location
if loc = f .locs [int32 (frame .pc )]; loc == nil {
loc = &profile .Location {}
f .locs [int32 (frame .pc )] = loc
}
if nn := n .children [loc ]; nn == nil {
if n .children == nil {
n .children = make (map [*profile .Location ]*profSampleNode , 1 )
}
nn = &profSampleNode {
parent : n ,
loc : loc ,
}
n .children [loc ] = nn
n = nn
} else {
n = nn
}
}
smpl := n .sample
if smpl == nil {
locs := make ([]*profile .Location , 0 , len (sampleFrames ))
for n1 := n ; n1 .loc != nil ; n1 = n1 .parent {
locs = append (locs , n1 .loc )
}
smpl = &profile .Sample {
Location : locs ,
Value : make ([]int64 , 2 ),
}
n .sample = smpl
}
smpl .Value [0 ]++
smpl .Value [1 ] += int64 (pt .stop .Sub (pt .start ))
}
func (pb *profBuffer ) profile () *profile .Profile {
pr := profile .Profile {}
pr .SampleType = []*profile .ValueType {
{Type : "samples" , Unit : "count" },
{Type : "cpu" , Unit : "nanoseconds" },
}
pr .PeriodType = pr .SampleType [1 ]
pr .Period = int64 (profInterval )
mapping := &profile .Mapping {
ID : 1 ,
File : "[ECMAScript code]" ,
}
pr .Mapping = make ([]*profile .Mapping , 1 , len (pb .funcs )+1 )
pr .Mapping [0 ] = mapping
pr .Function = make ([]*profile .Function , 0 , len (pb .funcs ))
funcNames := make (map [string ]struct {})
var funcId , locId uint64
for prg , f := range pb .funcs {
fileName := prg .src .Name ()
funcId ++
f .f .ID = funcId
f .f .Filename = fileName
var funcName string
if prg .funcName != "" {
funcName = prg .funcName .String ()
} else {
funcName = "<anonymous>"
}
if _ , exists := funcNames [funcName ]; exists {
funcName += "." + strconv .FormatUint (f .f .ID , 10 )
} else {
funcNames [funcName ] = struct {}{}
}
f .f .Name = funcName
pr .Function = append (pr .Function , &f .f )
for pc , loc := range f .locs {
locId ++
loc .ID = locId
pos := prg .src .Position (prg .sourceOffset (int (pc )))
loc .Line = []profile .Line {
{
Function : &f .f ,
Line : int64 (pos .Line ),
},
}
loc .Mapping = mapping
pr .Location = append (pr .Location , loc )
}
}
pb .addSamples (&pr , &pb .root )
return &pr
}
func (pb *profBuffer ) addSamples (p *profile .Profile , n *profSampleNode ) {
if n .sample != nil {
p .Sample = append (p .Sample , n .sample )
}
for _ , child := range n .children {
pb .addSamples (p , child )
}
}
func (p *profiler ) run () {
ticker := time .NewTicker (profInterval )
counter := 0
for ts := range ticker .C {
p .mu .Lock ()
left := len (p .trackers )
if left == 0 {
break
}
for {
if counter >= len (p .trackers ) {
counter = 0
}
tracker := p .trackers [counter ]
req := atomic .LoadInt32 (&tracker .req )
if req == profReqSampleReady {
p .buf .addSample (tracker )
}
if atomic .LoadInt32 (&tracker .finished ) != 0 {
p .trackers [counter ] = p .trackers [len (p .trackers )-1 ]
p .trackers [len (p .trackers )-1 ] = nil
p .trackers = p .trackers [:len (p .trackers )-1 ]
} else {
counter ++
if req != profReqDoSample {
tracker .start = ts
atomic .StoreInt32 (&tracker .req , profReqDoSample )
break
}
}
left --
if left <= 0 {
break
}
}
p .mu .Unlock ()
}
ticker .Stop ()
p .running = false
p .mu .Unlock ()
}
func (p *profiler ) registerVm () *profTracker {
pt := new (profTracker )
p .mu .Lock ()
if p .buf != nil {
p .trackers = append (p .trackers , pt )
if !p .running {
go p .run ()
p .running = true
}
} else {
pt .req = profReqStop
}
p .mu .Unlock ()
return pt
}
func (p *profiler ) start () error {
p .mu .Lock ()
if p .buf != nil {
p .mu .Unlock ()
return errors .New ("profiler is already active" )
}
p .buf = new (profBuffer )
p .mu .Unlock ()
return nil
}
func (p *profiler ) stop () *profile .Profile {
p .mu .Lock ()
trackers , buf := p .trackers , p .buf
p .trackers , p .buf = nil , nil
p .mu .Unlock ()
if buf != nil {
k := 0
for i , tracker := range trackers {
req := atomic .LoadInt32 (&tracker .req )
if req == profReqSampleReady {
buf .addSample (tracker )
} else if req == profReqDoSample {
if i != k {
trackers [k ] = trackers [i ]
}
k ++
}
atomic .StoreInt32 (&tracker .req , profReqStop )
}
if k > 0 {
trackers = trackers [:k ]
go func () {
for {
k := 0
for i , tracker := range trackers {
req := atomic .LoadInt32 (&tracker .req )
if req != profReqStop {
atomic .StoreInt32 (&tracker .req , profReqStop )
if i != k {
trackers [k ] = trackers [i ]
}
k ++
}
}
if k == 0 {
return
}
trackers = trackers [:k ]
time .Sleep (100 * time .Millisecond )
}
}()
}
return buf .profile ()
}
return nil
}
func StartProfile (w io .Writer ) error {
err := globalProfiler .p .start ()
if err != nil {
return err
}
globalProfiler .w = w
atomic .StoreInt32 (&globalProfiler .enabled , 1 )
return nil
}
func StopProfile () {
atomic .StoreInt32 (&globalProfiler .enabled , 0 )
pr := globalProfiler .p .stop ()
if pr != nil {
_ = pr .Write (globalProfiler .w )
}
globalProfiler .w = nil
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .