Skip to content

Instantly share code, notes, and snippets.

@NhanHo
Created December 1, 2016 11:31
Show Gist options
  • Save NhanHo/ff90f71e5f029a0a973ff09976d49b2d to your computer and use it in GitHub Desktop.
Save NhanHo/ff90f71e5f029a0a973ff09976d49b2d to your computer and use it in GitHub Desktop.
let usedOnStart = 0;
let enabled = false;
let depth = 0;
declare var Source: any;
function setupProfiler() {
depth = 0; // reset depth, this needs to be done each tick.
Game.profiler = {
stream(duration, filter) {
setupMemory('stream', duration || 10, filter);
},
email(duration, filter) {
setupMemory('email', duration || 100, filter);
},
profile(duration, filter) {
setupMemory('profile', duration || 100, filter);
},
reset: resetMemory,
};
overloadCPUCalc();
}
function setupMemory(profileType, duration, filter) {
resetMemory();
if (!Memory.profiler) {
Memory.profiler = {
map: {},
totalTime: 0,
enabledTick: Game.time + 1,
disableTick: Game.time + duration,
type: profileType,
filter,
};
}
}
function resetMemory() {
Memory.profiler = null;
}
function overloadCPUCalc() {
/*if (false) {
usedOnStart = 0; // This needs to be reset, but only in the sim.
Game.cpu.getUsed = function getUsed() {
return performance.now() - usedOnStart;
};
}*/
}
function getFilter() {
return Memory.profiler.filter;
}
function wrapFunction(name, originalFunction) {
return function wrappedFunction() {
if (Profiler.isProfiling()) {
const nameMatchesFilter = name === getFilter();
const start = Game.cpu.getUsed();
if (nameMatchesFilter) {
depth++;
}
const result = originalFunction.apply(this, arguments);
if (depth > 0 || !getFilter()) {
const end = Game.cpu.getUsed();
Profiler.record(name, end - start);
}
if (nameMatchesFilter) {
depth--;
}
return result;
}
return originalFunction.apply(this, arguments);
};
}
function hookUpPrototypes() {
Profiler.prototypes.forEach(proto => {
profileObjectFunctions(proto.val, proto.name);
});
}
function profileObjectFunctions(object, label) {
const objectToWrap = object.prototype ? object.prototype : object;
Object.keys(objectToWrap).forEach(functionName => {
const extendedLabel = `${label}.${functionName}`;
try {
if (typeof objectToWrap[functionName] === 'function' && functionName !== 'getUsed') {
const originalFunction = objectToWrap[functionName];
objectToWrap[functionName] = profileFunction(originalFunction, extendedLabel);
}
} catch (e) { } /* eslint no-empty:0 */
});
return objectToWrap;
}
function profileFunction(fn, functionName) {
const fnName = functionName || fn.name;
if (!fnName) {
console.log('Couldn\'t find a function name for - ', fn);
console.log('Will not profile this function.');
return fn;
}
return wrapFunction(fnName, fn);
}
const Profiler = {
printProfile() {
console.log(Profiler.output());
},
emailProfile() {
//Game.notify(Profiler.output());
},
output() {
const elapsedTicks = Game.time - Memory.profiler.enabledTick + 1;
const header = 'calls\t\ttime\t\tavg\t\tfunction';
const footer = [
`Avg: ${(Memory.profiler.totalTime / elapsedTicks).toFixed(2)}`,
`Total: ${Memory.profiler.totalTime.toFixed(2)}`,
`Ticks: ${elapsedTicks}`,
].join('\t');
return ([] as string[]).concat(header, Profiler.lines().slice(0, 20), footer).join('\n');
},
lines() {
const stats = Object.keys(Memory.profiler.map).map(functionName => {
const functionCalls = Memory.profiler.map[functionName];
return {
name: functionName,
calls: functionCalls.calls,
totalTime: functionCalls.time,
averageTime: functionCalls.time / functionCalls.calls,
};
}).sort((val1, val2) => {
return val2.totalTime - val1.totalTime;
});
const lines = stats.map(data => {
return [
data.calls,
data.totalTime.toFixed(1),
data.averageTime.toFixed(3),
data.name,
].join('\t\t');
});
return lines;
},
prototypes: [
{ name: 'Game', val: Game },
{ name: 'Room', val: Room },
{ name: 'Structure', val: Structure },
{ name: 'Spawn', val: Spawn },
{ name: 'Creep', val: Creep },
{ name: 'RoomPosition', val: RoomPosition },
{ name: 'Source', val: Source },
{ name: 'Flag', val: Flag },
],
record(functionName, time) {
if (!Memory.profiler.map[functionName]) {
Memory.profiler.map[functionName] = {
time: 0,
calls: 0,
};
}
Memory.profiler.map[functionName].calls++;
Memory.profiler.map[functionName].time += time;
},
endTick() {
if (Game.time >= Memory.profiler.enabledTick) {
const cpuUsed = Game.cpu.getUsed();
Memory.profiler.totalTime += cpuUsed;
Profiler.report();
}
},
report() {
if (Profiler.shouldPrint()) {
Profiler.printProfile();
} else if (Profiler.shouldEmail()) {
Profiler.emailProfile();
}
},
isProfiling() {
return enabled && !!Memory.profiler && Game.time <= Memory.profiler.disableTick;
},
type() {
return Memory.profiler.type;
},
shouldPrint() {
const streaming = Profiler.type() === 'stream';
const profiling = Profiler.type() === 'profile';
const onEndingTick = Memory.profiler.disableTick === Game.time;
return streaming || (profiling && onEndingTick);
},
shouldEmail() {
return Profiler.type() === 'email' && Memory.profiler.disableTick === Game.time;
},
};
export let profiler = {
wrap(callback) {
if (enabled) {
setupProfiler();
}
if (Profiler.isProfiling()) {
usedOnStart = Game.cpu.getUsed();
// Commented lines are part of an on going experiment to keep the profiler
// performant, and measure certain types of overhead.
// var callbackStart = Game.cpu.getUsed();
const returnVal = callback();
// var callbackEnd = Game.cpu.getUsed();
Profiler.endTick();
// var end = Game.cpu.getUsed();
// var profilerTime = (end - start) - (callbackEnd - callbackStart);
// var callbackTime = callbackEnd - callbackStart;
// var unaccounted = end - profilerTime - callbackTime;
// console.log('total-', end, 'profiler-', profilerTime, 'callbacktime-',
// callbackTime, 'start-', start, 'unaccounted', unaccounted);
return returnVal;
}
return callback();
},
enable() {
enabled = true;
hookUpPrototypes();
},
registerObject(object, label) {
return profileObjectFunctions(object, label);
},
registerFN(fn, functionName) {
return profileFunction(fn, functionName);
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment