parent
f0f9cf7499
commit
4087d18989
@ -0,0 +1,227 @@ |
||||
(function (global) { |
||||
'use strict'; |
||||
|
||||
/** |
||||
* 游戏侧性能剖析(零侵入,按需采样): |
||||
* - 函数耗时 Top(Error.stack 采样聚合) |
||||
* - 慢帧堆栈 Top(帧耗时超阈值时抓栈) |
||||
* - 真实 FPS / 内存 |
||||
*/ |
||||
function StackProfiler() { |
||||
this.enabled = false; |
||||
this.intervalMs = 8; |
||||
this._timer = 0; |
||||
this._fnStats = {}; // name -> {count, ms}
|
||||
this._slowFrames = []; |
||||
this._maxSlow = 20; |
||||
this._lastTick = 0; |
||||
this._tickHooked = false; |
||||
this._origTick = null; |
||||
this._cc = null; |
||||
} |
||||
|
||||
StackProfiler.prototype.bindEngine = function (cc) { |
||||
this._cc = cc; |
||||
this._hookTick(); |
||||
}; |
||||
|
||||
StackProfiler.prototype._hookTick = function () { |
||||
var self = this; |
||||
var cc = this._cc; |
||||
if (!cc || !cc.director || this._tickHooked) return; |
||||
var dir = cc.director; |
||||
var proto = Object.getPrototypeOf(dir) || dir; |
||||
if (typeof proto.tick !== 'function') return; |
||||
this._origTick = proto.tick; |
||||
this._tickHooked = true; |
||||
proto.tick = function () { |
||||
if (!self.enabled) { |
||||
return self._origTick.apply(this, arguments); |
||||
} |
||||
var t0 = performance.now(); |
||||
var ret = self._origTick.apply(this, arguments); |
||||
var dt = performance.now() - t0; |
||||
self._noteFrame(dt); |
||||
return ret; |
||||
}; |
||||
}; |
||||
|
||||
StackProfiler.prototype._noteFrame = function (ms) { |
||||
if (ms < 20) return; |
||||
var stack = ''; |
||||
try { |
||||
stack = new Error('pf').stack || ''; |
||||
} catch (e) { /* */ } |
||||
this._slowFrames.push({ |
||||
t: Date.now(), |
||||
ms: Math.round(ms * 100) / 100, |
||||
stack: this._cleanStack(stack), |
||||
}); |
||||
if (this._slowFrames.length > this._maxSlow * 3) { |
||||
this._slowFrames.sort(function (a, b) { return b.ms - a.ms; }); |
||||
this._slowFrames.length = this._maxSlow; |
||||
} |
||||
}; |
||||
|
||||
StackProfiler.prototype._cleanStack = function (stack) { |
||||
if (!stack) return ''; |
||||
var lines = String(stack).split('\n'); |
||||
var out = []; |
||||
for (var i = 0; i < lines.length && out.length < 12; i++) { |
||||
var s = lines[i].trim(); |
||||
if (!s || /^error/i.test(s) || s.indexOf('Error') === 0 && s.indexOf('at ') < 0) continue; |
||||
if (/StackProfiler|_cleanStack|_noteFrame|agent-profiler|workbench/.test(s)) continue; |
||||
out.push(s); |
||||
} |
||||
return out.join('\n'); |
||||
}; |
||||
|
||||
StackProfiler.prototype.start = function () { |
||||
if (this.enabled) return; |
||||
this.enabled = true; |
||||
this._fnStats = {}; |
||||
this._slowFrames = []; |
||||
var self = this; |
||||
this._timer = setInterval(function () { |
||||
self._sampleStack(); |
||||
}, this.intervalMs); |
||||
}; |
||||
|
||||
StackProfiler.prototype.stop = function () { |
||||
this.enabled = false; |
||||
if (this._timer) { |
||||
clearInterval(this._timer); |
||||
this._timer = 0; |
||||
} |
||||
}; |
||||
|
||||
StackProfiler.prototype.clear = function () { |
||||
this._fnStats = {}; |
||||
this._slowFrames = []; |
||||
}; |
||||
|
||||
/** 调用栈采样:每 tick 记一帧到最热函数 */ |
||||
StackProfiler.prototype._sampleStack = function () { |
||||
if (!this.enabled) return; |
||||
var stack = ''; |
||||
try { |
||||
stack = new Error('s').stack || ''; |
||||
} catch (e) { |
||||
return; |
||||
} |
||||
var lines = String(stack).split('\n'); |
||||
var name = ''; |
||||
for (var i = 1; i < lines.length; i++) { |
||||
var s = lines[i].trim(); |
||||
if (!s) continue; |
||||
if (/StackProfiler|_sampleStack|agent-profiler|workbench|setInterval/.test(s)) continue; |
||||
// at fnName (url:line:col) 或 at url:line:col
|
||||
var m = s.match(/at\s+([^\s(]+)/); |
||||
name = (m && m[1]) || s; |
||||
break; |
||||
} |
||||
if (!name) return; |
||||
if (!this._fnStats[name]) this._fnStats[name] = { count: 0, ms: 0 }; |
||||
this._fnStats[name].count += 1; |
||||
this._fnStats[name].ms += this.intervalMs; |
||||
}; |
||||
|
||||
StackProfiler.prototype.topFunctions = function (limit) { |
||||
var arr = []; |
||||
for (var k in this._fnStats) { |
||||
if (!Object.prototype.hasOwnProperty.call(this._fnStats, k)) continue; |
||||
arr.push({ |
||||
name: k, |
||||
count: this._fnStats[k].count, |
||||
ms: Math.round(this._fnStats[k].ms * 10) / 10, |
||||
}); |
||||
} |
||||
arr.sort(function (a, b) { return b.ms - a.ms; }); |
||||
return arr.slice(0, limit || 20); |
||||
}; |
||||
|
||||
StackProfiler.prototype.topSlowFrames = function (limit) { |
||||
var arr = this._slowFrames.slice(); |
||||
arr.sort(function (a, b) { return b.ms - a.ms; }); |
||||
return arr.slice(0, limit || 10); |
||||
}; |
||||
|
||||
/** 当前真实帧率 / 内存 */ |
||||
StackProfiler.prototype.liveStats = function () { |
||||
var cc = this._cc; |
||||
var fps = 0; |
||||
var tex = 0; |
||||
var buf = 0; |
||||
try { |
||||
var st = cc && cc.profiler && cc.profiler.stats; |
||||
if (st) { |
||||
if (st.fps && st.fps.counter) fps = st.fps.counter.value || 0; |
||||
if (st.textureMemory && st.textureMemory.counter) tex = st.textureMemory.counter.value || 0; |
||||
if (st.bufferMemory && st.bufferMemory.counter) buf = st.bufferMemory.counter.value || 0; |
||||
} |
||||
} catch (e) { /* */ } |
||||
if (!fps && cc && cc.director && typeof cc.director.getTotalFrames === 'function') { |
||||
var now = performance.now(); |
||||
var frames = cc.director.getTotalFrames(); |
||||
if (this._fpsLastFrames != null && this._fpsLastTime) { |
||||
var dt = now - this._fpsLastTime; |
||||
if (dt > 0) fps = ((frames - this._fpsLastFrames) * 1000) / dt; |
||||
} |
||||
this._fpsLastFrames = frames; |
||||
this._fpsLastTime = now; |
||||
} |
||||
var heap = 0; |
||||
try { |
||||
if (performance.memory && performance.memory.usedJSHeapSize) { |
||||
heap = performance.memory.usedJSHeapSize / (1024 * 1024); |
||||
} |
||||
} catch (e2) { /* */ } |
||||
return { |
||||
fps: fps, |
||||
textureMemory: tex, |
||||
bufferMemory: buf, |
||||
jsHeap: heap, |
||||
totalMemory: tex + buf + heap, |
||||
}; |
||||
}; |
||||
|
||||
global.PWStackProfiler = StackProfiler; |
||||
|
||||
// —— 挂到 Agent ——
|
||||
if (global.PWAgent) { |
||||
var Agent = global.PWAgent; |
||||
if (!Agent.prototype._profiler) { |
||||
Agent.prototype._ensureProfiler = function () { |
||||
if (!this.__stackProfiler) { |
||||
this.__stackProfiler = new StackProfiler(); |
||||
if (this.cc) this.__stackProfiler.bindEngine(this.cc); |
||||
} else if (this.cc && !this.__stackProfiler._cc) { |
||||
this.__stackProfiler.bindEngine(this.cc); |
||||
} |
||||
return this.__stackProfiler; |
||||
}; |
||||
Agent.prototype._profStart = function () { |
||||
var p = this._ensureProfiler(); |
||||
p.start(); |
||||
return true; |
||||
}; |
||||
Agent.prototype._profStop = function () { |
||||
if (this.__stackProfiler) this.__stackProfiler.stop(); |
||||
return true; |
||||
}; |
||||
Agent.prototype._profClear = function () { |
||||
if (this.__stackProfiler) this.__stackProfiler.clear(); |
||||
return true; |
||||
}; |
||||
Agent.prototype._profReport = function () { |
||||
var p = this._ensureProfiler(); |
||||
return { |
||||
enabled: p.enabled, |
||||
functions: p.topFunctions(20), |
||||
slowFrames: p.topSlowFrames(10), |
||||
live: p.liveStats(), |
||||
}; |
||||
}; |
||||
} |
||||
} |
||||
})(window); |
||||
Binary file not shown.
Loading…
Reference in new issue