Skip to content

Instantly share code, notes, and snippets.

@Szpadel
Last active June 12, 2020 11:17
Show Gist options
  • Save Szpadel/fcd47a8e8dd846a2e84801a658e06928 to your computer and use it in GitHub Desktop.
Save Szpadel/fcd47a8e8dd846a2e84801a658e06928 to your computer and use it in GitHub Desktop.
screeps-tracer

Note: Currently this only works via Steam client

Usage:

Trace single function/method

@trace
function something() {
}

Trace whole class

@trace
class Something{
}

Trace code fragment

const t = (global as any).Tracing.startCall('something');
myCode();
t.endCall();

It works also with generators

@trace
function *generator() {
}

Trace engine code or js code

for(const m in Creep.prototype) {
    wrapFunction(Creep.prototype, m, 'Creep');
}

JS codebases: copy those typescript files into: https://www.typescriptlang.org/play/index.html And use generated js equivalent

To start tracing type: trace(10) where 10 is number of next ticks you want to trace. After that you will get request to save trace.json. Next load that file in chrome dev tools on performance tab;

(global as any).trace = (ticks: number) => {
(global as any).Tracing.enableTracing(ticks);
console.log(`Enabled tracing for ${ticks} ticks`);
};
(global as any).Tracing = new Tracing();
wrapEngine();
export function loop() {
yourCodeLoop();
(global as any).Tracing.postTick();
}
function wrappedCall(
name: string, originalFunction: Function, that: any, args: any[]) {
let result: any;
(global as any).Tracing.traceCall(name, () => {
result = originalFunction.apply(that, args);
// wrap iterators
if (!!result && typeof(result.next) === 'function' &&
result.__wrapped === undefined) {
result.__wrapped = true;
const orgNext = result.next;
Reflect.set(result, 'next', function(this: any, ...args: any[]) {
return wrappedCall(name, orgNext, this, args);
});
}
},
() => {
// Uncomment this to trace arguments and return values
// const r: {[k:string]:string} = {};
// for(let i = 0;i<args.length;i++) {
// r[`arg${i}`] = String(args[i]);
// }
// r[`ret`] = String(result);
// return r;
return {};
},
);
return result;
}
export function wrapFunction(obj: object, _key: PropertyKey, className?: string) {
const descriptor = Reflect.getOwnPropertyDescriptor(obj, _key);
const key = String(_key);
if (!descriptor || descriptor.get || descriptor.set) {
return;
}
if (key === 'constructor') {
return;
}
const originalFunction = descriptor.value;
if (!originalFunction || typeof originalFunction !== 'function') {
return;
}
// set a key for the object in memory
if (!className) {
className = obj.constructor ? `${obj.constructor.name}` : '';
}
const memKey = className + `:${key}`;
// set a tag so we don't wrap a function twice
const savedName = `__${key}__`;
if (Reflect.has(obj, savedName)) {
return;
}
Reflect.set(obj, savedName, originalFunction);
Reflect.set(obj, _key, function(this: any, ...args: any[]) {
return wrappedCall(memKey, originalFunction, this, args);
});
}
export function wrapEngine() {
for(const m in Creep.prototype) {
wrapFunction(Creep.prototype, m, 'Creep');
}
for(const m in Room.prototype) {
wrapFunction(Room.prototype, m, 'Room');
}
}
export function trace(target: any): void;
export function trace(
target: object, key: string|symbol,
_descriptor: TypedPropertyDescriptor<any>): void;
export function trace(
target: object|Function,
key?: string|symbol,
_descriptor?: TypedPropertyDescriptor<Function>,
): void {
if (key) {
// case of method decorator
wrapFunction(target, key);
return;
}
// case of class decorator
const ctor = target as any;
if (!ctor.prototype) {
return;
}
const className = ctor.name;
Reflect.ownKeys(ctor.prototype).forEach((k) => {
wrapFunction(ctor.prototype, k, className);
});
}
enum EventPhase {
Start = 'B',
End = 'E',
}
interface Event {
name: string;
ph: string;
pid: number;
tid: number;
ts: number;
args?: {[k:string]:string};
}
export interface TracingCall {
endCall: () => void;
}
export class Tracing {
protected serializedEvents: string = "";
protected events: Event[] = [];
protected isEnabled = false;
protected ticksLeft = 0;
enableTracing(ticks: number) {
this.events = [];
this.isEnabled = true;
this.ticksLeft = ticks;
this.serializedEvents = '';
}
traceCall(name: string, fn: () => void, argsFn?: () => {[k:string]:string}) {
if (this.isEnabled) {
const start = {
name,
ph: EventPhase.Start,
pid: 0,
tid: Game.time,
ts: Game.cpu.getUsed() * 1000,
};
fn();
this.events.push(start, {
name,
ph: EventPhase.End,
pid: 0,
tid: Game.time,
ts: Game.cpu.getUsed() * 1000,
args: argsFn ? argsFn() : undefined,
});
} else {
fn();
}
}
startCall(name: string): TracingCall {
if (this.isEnabled) {
const start = {
name,
ph: EventPhase.Start,
pid: 0,
tid: Game.time,
ts: Game.cpu.getUsed() * 1000
};
return {
endCall: () => {
this.events.push(start, {
name,
ph: EventPhase.End,
pid: 0,
tid: Game.time,
ts: Game.cpu.getUsed() * 1000
});
}
}
} else {
return {
endCall: () => {},
}
}
}
protected downloadTrace() {
const code = 'Download trace hook <script>' +
`(() => {` +
`angular.element("section.console").scope().Console.clear();` +
`var filename = 'trace.json';` +
`var text = JSON.stringify({ traceEvents: [${this.serializedEvents}], displayTimeUnit: 'ms'});` +
`const element = document.createElement('input');` +
`element.nwsaveas = 'trace.json';` +
`element.type='file';` +
`document.body.appendChild(element);` +
`element.click();` +
`document.body.removeChild(element);` +
`element.addEventListener('change', () => {` +
` const fs = nw.require('fs');` +
` fs.writeFileSync(element.value, text);` +
`});` +
`})();` +
`</script>`;
console.log(code.replace("\n", ""));
}
protected serializeEvents() {
const serial = JSON.stringify(this.events);
this.events = [];
if(this.serializedEvents.length > 0) {
this.serializedEvents += ',';
}
this.serializedEvents += serial.substring(1, serial.length -1);
}
postTick() {
if (this.isEnabled) {
this.ticksLeft--;
this.serializeEvents();
if (this.ticksLeft <= 0) {
this.isEnabled = false;
this.downloadTrace();
this.events = [];
this.serializedEvents = '';
} else {
console.log(`Tracing ${this.ticksLeft}...`);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment