refactor(common): 重构 Conn 实体并优化地图进入逻辑
- 优化 Conn 实体的 SendPack 方法,提高代码复用性 - 添加 goja 模块到 go.work 文件 - 重构地图进入逻辑,增加玩家广播和刷怪功能 - 调整 OutInfo 结构中的 Vip 和 Viped 字段类型 - 简化 MonsterRefresh 结构体定义
This commit is contained in:
350
common/utils/goja/profiler.go
Normal file
350
common/utils/goja/profiler.go
Normal file
@@ -0,0 +1,350 @@
|
||||
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>"
|
||||
}
|
||||
// Make sure the function name is unique, otherwise the graph display merges them into one node, even
|
||||
// if they are in different mappings.
|
||||
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 {
|
||||
// This loop runs until either one of the VMs is signalled or all of the VMs are scanned and found
|
||||
// busy or deleted.
|
||||
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 {
|
||||
// signal the VM to take a sample
|
||||
tracker.start = ts
|
||||
atomic.StoreInt32(&tracker.req, profReqDoSample)
|
||||
break
|
||||
}
|
||||
}
|
||||
left--
|
||||
if left <= 0 {
|
||||
// all VMs are busy
|
||||
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 {
|
||||
// In case the VM is requested to do a sample, there is a small chance of a race
|
||||
// where we set profReqStop in between the read and the write, so that the req
|
||||
// ends up being set to profReqSampleReady. It's no such a big deal if we do nothing,
|
||||
// it just means the VM remains in tracing mode until it finishes the current run,
|
||||
// but we do an extra cleanup step later just in case.
|
||||
if i != k {
|
||||
trackers[k] = trackers[i]
|
||||
}
|
||||
k++
|
||||
}
|
||||
atomic.StoreInt32(&tracker.req, profReqStop)
|
||||
}
|
||||
|
||||
if k > 0 {
|
||||
trackers = trackers[:k]
|
||||
go func() {
|
||||
// Make sure all VMs are requested to stop tracing.
|
||||
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
|
||||
}
|
||||
|
||||
/*
|
||||
StartProfile enables execution time profiling for all Runtimes within the current process.
|
||||
This works similar to pprof.StartCPUProfile and produces the same format which can be consumed by `go tool pprof`.
|
||||
There are, however, a few notable differences. Firstly, it's not a CPU profile, rather "execution time" profile.
|
||||
It measures the time the VM spends executing an instruction. If this instruction happens to be a call to a
|
||||
blocking Go function, the waiting time will be measured. Secondly, the 'cpu' sample isn't simply `count*period`,
|
||||
it's the time interval between when sampling was requested and when the instruction has finished. If a VM is still
|
||||
executing the same instruction when the time comes for the next sample, the sampling is skipped (i.e. `count` doesn't
|
||||
grow).
|
||||
|
||||
If there are multiple functions with the same name, their names get a '.N' suffix, where N is a unique number,
|
||||
because otherwise the graph view merges them together (even if they are in different mappings). This includes
|
||||
"<anonymous>" functions.
|
||||
|
||||
The sampling period is set to 10ms.
|
||||
|
||||
It returns an error if profiling is already active.
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
/*
|
||||
StopProfile stops the current profile initiated by StartProfile, if any.
|
||||
*/
|
||||
func StopProfile() {
|
||||
atomic.StoreInt32(&globalProfiler.enabled, 0)
|
||||
pr := globalProfiler.p.stop()
|
||||
if pr != nil {
|
||||
_ = pr.Write(globalProfiler.w)
|
||||
}
|
||||
globalProfiler.w = nil
|
||||
}
|
||||
Reference in New Issue
Block a user