#include #include #include #include #include #include #include #include #include namespace hx { inline unsigned int __hxt_ptr_id(void* obj) { #if defined(HXCPP_M64) size_t h64 = (size_t)obj; // Note, using >> 1 since Strings can be small, down to 2 bytes, causing collisions return (unsigned int)(h64>>1) ^ (unsigned int)(h64>>32); #else // Note, using >> 1 since Strings can be small, down to 2 bytes, causing collisions return ((unsigned int)obj) >> 1; #endif } struct AllocStackIdMapEntry { int terminationStackId; std::map children; }; // Telemetry functionality class Telemetry { public: Telemetry(StackContext *inStack, bool profiler_en, bool allocs_en) : mT0(0) { stack = inStack; names.push_back("1-indexed"); namesStashed = 1; ignoreAllocs = allocs_en ? 0 : 1; allocStacksStashed = 0; allocStackIdNext = 0; allocStackIdMapRoot.terminationStackId = 0; gcTimer = 0; gcTimerTemp = 0; gcOverhead = 0; _last_obj = 0; profiler_enabled = profiler_en; allocations_enabled = profiler_en && allocs_en; samples = 0; allocation_data = 0; // Push a blank (destroyed on first Dump) Stash(); // When a profiler exists, the profiler thread needs to exist gThreadMutex.Lock(); gThreadRefCount += 1; if (gThreadRefCount == 1) { HxCreateDetachedThread(ProfileMainLoop, 0); } gThreadMutex.Unlock(); } ~Telemetry() { gThreadMutex.Lock(); gThreadRefCount -= 1; gThreadMutex.Unlock(); } // todo void attach(StackContext *ctx) { stack = ctx; } void detach() { } void StackUpdate(StackFrame *frame); inline void sampleEnter(StackFrame *frame) { StackUpdate(frame); } inline void sampleExit() { StackUpdate(0); } void HXTAllocation(void* obj, size_t inSize, const char* type=0); void HXTRealloc(void* old_obj, void* new_obj, int new_Size); void Stash() { TelemetryFrame *stash = new TelemetryFrame(); IgnoreAllocs(1); stash->gctime = gcTimer*1000000; // usec gcTimer = 0; stash->gcoverhead = gcOverhead*1000000; // usec gcOverhead = 0; alloc_mutex.Lock(); if (_last_obj!=0) lookup_last_object_type(); stash->allocation_data = allocation_data; stash->samples = samples; samples = profiler_enabled ? new std::vector() : 0; if (allocations_enabled) { allocation_data = new std::vector(); } alloc_mutex.Unlock(); int i,size; stash->names = 0; if (profiler_enabled) { stash->names = new std::vector(); size = names.size(); for (i=namesStashed; inames->push_back(names.at(i)); } //printf("Stash pushed %d names, %d\n", (size-namesStashed), stash->names->size()); namesStashed = names.size(); } stash->stacks = 0; if (allocations_enabled) { stash->stacks = new std::vector(); size = allocStacks.size(); for (i=allocStacksStashed; istacks->push_back(allocStacks.at(i)); } allocStacksStashed = allocStacks.size(); } gStashMutex.Lock(); stashed.push_back(*stash); gStashMutex.Unlock(); IgnoreAllocs(-1); } TelemetryFrame* Dump() { gStashMutex.Lock(); if (stashed.size()<1) { gStashMutex.Unlock(); return 0; } // Destroy item that was dumped last call TelemetryFrame *front = &stashed.front(); if (front->samples!=0) delete front->samples; if (front->names!=0) delete front->names; if (front->allocation_data!=0) delete front->allocation_data; if (front->stacks!=0) delete front->stacks; //delete front; // delete happens via pop_front: stashed.pop_front(); // Destroy item that was Dumped last call front = &stashed.front(); gStashMutex.Unlock(); //printf(" -- dumped stash, allocs=%d, alloc[max]=%d\n", front->allocations->size(), front->allocations->size()>0 ? front->allocations->at(front->allocations->size()-1) : 0); return front; } void IgnoreAllocs(int delta) { ignoreAllocs += delta; } void GCStart() { gcTimerTemp = __hxcpp_time_stamp(); } void GCEnd() { gcTimer += __hxcpp_time_stamp() - gcTimerTemp; } void lookup_last_object_type() { if (_last_obj==0) return; const char* type = "_uninitialized"; int obj_id = __hxt_ptr_id(_last_obj); alloc_mutex.Lock(); std::map::iterator exist = alloc_map.find(_last_obj); if (exist != alloc_map.end() && _last_obj!=(NULL)) { type = "_unknown"; int vtt = _last_obj->__GetType(); if (vtt==vtInt || vtt==vtFloat || vtt==vtBool) type = "_non_class"; else if (vtt==vtObject || vtt==vtString || vtt==vtArray || vtt==vtClass || vtt==vtFunction || vtt==vtEnum || vtt==vtAbstractBase) { //printf("About to resolve...\n"); type = _last_obj->__GetClass()->mName; //__CStr(); //printf("Updating last allocation %016lx type to %s\n", _last_obj, type); } } alloc_mutex.Unlock(); allocation_data->at(_last_loc+2) = GetNameIdx(type); _last_obj = 0; } void reclaim(int id) { if (!allocations_enabled) return; allocation_data->push_back(1); // collect flag allocation_data->push_back(id); } static void HXTReclaimInternal(void* obj) { int obj_id = __hxt_ptr_id(obj); std::map::iterator exist = alloc_map.find(obj); if (exist != alloc_map.end()) { Telemetry* telemetry = exist->second; if (telemetry) { telemetry->reclaim(obj_id); alloc_map.erase(exist); //printf("Tracking collection %016lx, id=%016lx\n", obj, obj_id); } else { printf("HXT ERR: we shouldn't get: Telemetry lookup failed!\n"); } } else { // Ignore, assume object was already reclaimed //printf("HXT ERR: we shouldn't get: Reclaim a non-tracked object %016lx, id=%016lx -- was there an object ID collision?\n", obj, obj_id); } } static void HXTAfterMark(int gByteMarkID, int ENDIAN_MARK_ID_BYTE) { double t0 = __hxcpp_time_stamp(); Telemetry* telemetry = 0; alloc_mutex.Lock(); std::map::iterator iter = alloc_map.begin(); while (iter != alloc_map.end()) { void* obj = iter->first; unsigned char mark = ((unsigned char *)obj)[ENDIAN_MARK_ID_BYTE]; if ( mark!=gByteMarkID ) { // not marked, deallocated telemetry = iter->second; if (telemetry) { int obj_id = __hxt_ptr_id(obj); telemetry->reclaim(obj_id); } alloc_map.erase(iter++); } else { iter++; } } alloc_mutex.Unlock(); // Report overhead on one of the telemetry instances // TODO: something better? if (telemetry) { telemetry->gcOverhead += __hxcpp_time_stamp() - t0; } } private: void push_callstack_ids_into(std::vector *list); int GetNameIdx(const char *fullName); int ComputeCallStackId(); static THREAD_FUNC_TYPE ProfileMainLoop(void *) { int millis = 1; while (gThreadRefCount > 0) { #ifdef HX_WINDOWS Sleep(millis); #else struct timespec t; struct timespec tmp; t.tv_sec = 0; t.tv_nsec = millis * 1000000; nanosleep(&t, &tmp); #endif int count = gProfileClock + 1; gProfileClock = (count < 0) ? 0 : count; } THREAD_FUNC_RET } StackContext *stack; int mT0; std::list stashed; std::map nameMap; std::vector names; std::vector *samples; int namesStashed; bool profiler_enabled; bool allocations_enabled; std::vector allocStacks; int allocStacksStashed; int allocStackIdNext; AllocStackIdMapEntry allocStackIdMapRoot; double gcTimer; double gcTimerTemp; double gcOverhead; int ignoreAllocs; hx::Object* _last_obj; int _last_loc; std::vector *allocation_data; static HxMutex gStashMutex; static HxMutex gThreadMutex; static int gThreadRefCount; static int gProfileClock; static HxMutex alloc_mutex; static std::map alloc_map; }; /* static */ HxMutex Telemetry::gStashMutex; /* static */ HxMutex Telemetry::gThreadMutex; /* static */ int Telemetry::gThreadRefCount; /* static */ int Telemetry::gProfileClock; /* static */ HxMutex Telemetry::alloc_mutex; /* static */ std::map Telemetry::alloc_map; Telemetry *tlmCreate(StackContext *inStack) { return new Telemetry(inStack, true, false); } void tlmDestroy(Telemetry *inTelemetry) { delete inTelemetry; } void tlmAttach(Telemetry *inTelemetry, StackContext *inStack) { inTelemetry->attach(inStack); } void tlmDetach(Telemetry *inTelemetry) { inTelemetry->detach(); } void tlmSampleEnter(Telemetry *inTelemetry, StackFrame *inFrame) { inTelemetry->sampleEnter(inFrame); } void tlmSampleExit(Telemetry *inTelemetry) { inTelemetry->sampleExit(); } void hx::Telemetry::push_callstack_ids_into(std::vector *list) { int size = stack->getDepth(); for (int i = 0; i < size; i++) { const char *fullName = stack->getFullNameAtDepth(i); list->push_back(GetNameIdx(fullName)); } } int hx::Telemetry::GetNameIdx(const char *fullName) { int idx = nameMap[fullName]; if (idx==0) { idx = names.size(); nameMap[fullName] = idx; names.push_back(fullName); } return idx; } int hx::Telemetry::ComputeCallStackId() { std::vector callstack; int stackId; push_callstack_ids_into(&callstack); int size = callstack.size(); AllocStackIdMapEntry *asime = &allocStackIdMapRoot; int i=0; while (i::iterator lb = asime->children.lower_bound(name_id); if (lb != asime->children.end() && !(asime->children.key_comp()(name_id, lb->first))) { // key already exists asime = lb->second; } else { // the key does not exist in the map, add it AllocStackIdMapEntry *newEntry = new AllocStackIdMapEntry(); newEntry->terminationStackId = -1; asime->children.insert(lb, std::map::value_type(name_id, newEntry)); asime = newEntry; } } if (asime->terminationStackId == -1) { // This is a new stackId, store call stack id's in allocStacks stackId = asime->terminationStackId = allocStackIdNext; allocStacks.push_back(size); int i = size-1; while (i>=0) allocStacks.push_back(callstack.at(i--)); //printf("new callstackid %d\n", allocStackIdNext); allocStackIdNext++; } else { stackId = asime->terminationStackId; //printf("existing callstackid %d\n", stackId); } return stackId; } void hx::Telemetry::StackUpdate(StackFrame *pushed_frame) { if (mT0 == gProfileClock || !profiler_enabled) { return; } // Latch the profile clock and calculate the time since the last profile // clock tick int clock = gProfileClock; int delta = clock - mT0; if (delta < 0) { delta = 1; } mT0 = clock; int size = stack->getDepth(); // Collect function names and callstacks (as indexes into the names vector) samples->push_back(size); push_callstack_ids_into(samples); samples->push_back(delta); } void hx::Telemetry::HXTAllocation(void* obj, size_t inSize, const char* type) { if (ignoreAllocs>0 || !allocations_enabled) return; // Optionally ignore from extern::cffi - very expensive to track allocs // for every external call, hashes for every SDL event (Lime's // ExternalInterface.external_handler()), etc #ifndef HXCPP_PROFILE_EXTERNS if (stack->getCurrentStackFrame()->position->className==hx::EXTERN_CLASS_NAME) { alloc_mutex.Unlock(); return; } #endif int obj_id = __hxt_ptr_id(obj); alloc_mutex.Lock(); // HXT debug: Check for id collision #ifdef HXCPP_TELEMETRY_DEBUG std::map::iterator exist = alloc_map.find(obj); if (exist != alloc_map.end()) { printf("HXT ERR: Object id collision! at on %016lx, id=%016lx\n", obj, obj_id); throw "uh oh"; alloc_mutex.Unlock(); return; } #endif int stackid = ComputeCallStackId(); if (_last_obj!=0) lookup_last_object_type(); if (type==0) { _last_obj = (hx::Object*)obj; _last_loc = allocation_data->size(); type = "_unresolved"; } allocation_data->push_back(0); // alloc flag allocation_data->push_back(obj_id); allocation_data->push_back(GetNameIdx(type)); // defer lookup allocation_data->push_back((int)inSize); allocation_data->push_back(stackid); alloc_map[obj] = this; //__hxcpp_set_hxt_finalizer(obj, (void*)Telemetry::HXTReclaim); //printf("Tracking alloc %s at %016lx, id=%016lx, s=%d for telemetry %016lx, ts=%f\n", type, obj, obj_id, inSize, this, __hxcpp_time_stamp()); alloc_mutex.Unlock(); } void hx::Telemetry::HXTRealloc(void* old_obj, void* new_obj, int new_size) { if (!allocations_enabled) return; int old_obj_id = __hxt_ptr_id(old_obj); int new_obj_id = __hxt_ptr_id(new_obj); alloc_mutex.Lock(); // Only track reallocations of objects currently known to be allocated std::map::iterator exist = alloc_map.find(old_obj); if (exist != alloc_map.end()) { Telemetry* t = exist->second; t->allocation_data->push_back(2); // realloc flag (necessary?) t->allocation_data->push_back(old_obj_id); t->allocation_data->push_back(new_obj_id); t->allocation_data->push_back(new_size); //printf("Object at %016lx moving to %016lx, new_size = %d bytes\n", old_obj, new_obj, new_size); // HXT debug: Check for id collision #ifdef HXCPP_TELEMETRY_DEBUG std::map::iterator exist_new = alloc_map.find(new_obj); if (exist_new != alloc_map.end()) { printf("HXT ERR: Object id collision (reloc)! at on %016lx, id=%016lx\n", (unsigned long)new_obj, (unsigned long)new_obj_id); throw "uh oh"; } #endif //__hxcpp_set_hxt_finalizer(old_obj, (void*)0); // remove old finalizer -- should GCInternal.InternalRealloc do this? HXTReclaimInternal(old_obj); // count old as reclaimed } else { //printf("Not tracking re-alloc of untracked %016lx, id=%016lx\n", old_obj, old_obj_id); alloc_mutex.Unlock(); return; } alloc_map[new_obj] = this; //__hxcpp_set_hxt_finalizer(new_obj, (void*)HXTReclaim); //printf("Tracking re-alloc from %016lx, id=%016lx to %016lx, id=%016lx at %f\n", old_obj, old_obj_id, new_obj, new_obj_id, __hxcpp_time_stamp()); alloc_mutex.Unlock(); } } // end namespace hx // These globals are called by HXTelemetry.hx int __hxcpp_hxt_start_telemetry(bool profiler, bool allocations) { #ifdef HXCPP_STACK_TRACE hx::StackContext *stack = hx::StackContext::getCurrent(); delete stack->mTelemetry; stack->mTelemetry = new hx::Telemetry(stack, profiler, allocations); return stack->mThreadId; #else return 0; #endif } void __hxcpp_hxt_stash_telemetry() { #ifdef HXCPP_STACK_TRACE // Operates on the current thread, no mutexes needed hx::StackContext *stack = hx::StackContext::getCurrent(); if (stack->mTelemetry) stack->mTelemetry->Stash(); #endif } // Operates on the socket writer thread TelemetryFrame* __hxcpp_hxt_dump_telemetry(int thread_num) { #ifdef HXCPP_STACK_TRACE hx::StackContext *stack = hx::StackContext::getStackForId(thread_num); if (!stack || !stack->mTelemetry) return 0; return stack->mTelemetry->Dump(); #else return 0; #endif } void __hxcpp_hxt_ignore_allocs(int delta) { #ifdef HXCPP_STACK_TRACE hx::StackContext *stack = hx::StackContext::getCurrent(); if (stack->mTelemetry) stack->mTelemetry->IgnoreAllocs(delta); #endif } // These globals are called by other cpp files void __hxt_new_string(void* obj, int inSize) { #ifdef HXCPP_STACK_TRACE hx::StackContext *stack = hx::StackContext::getCurrent(); if (stack->mTelemetry) stack->mTelemetry->HXTAllocation(obj, inSize, (const char *)"String"); #endif } void __hxt_new_array(void* obj, int inSize) { #ifdef HXCPP_STACK_TRACE hx::StackContext *stack = hx::StackContext::getCurrent(); if (stack->mTelemetry) stack->mTelemetry->HXTAllocation(obj, inSize, (const char *)"Array"); #endif } void __hxt_new_hash(void* obj, int inSize) { #ifdef HXCPP_STACK_TRACE hx::StackContext *stack = hx::StackContext::getCurrent(); if (stack->mTelemetry) stack->mTelemetry->HXTAllocation(obj, inSize, (const char *)"Hash"); #endif } void __hxt_gc_new(hx::StackContext *stack, void* obj, int inSize, const char* name) { #ifdef HXCPP_STACK_TRACE if (stack->mTelemetry) stack->mTelemetry->HXTAllocation(obj, inSize, name); #endif } void __hxt_gc_realloc(void* old_obj, void* new_obj, int new_size) { #ifdef HXCPP_STACK_TRACE hx::StackContext *stack = hx::StackContext::getCurrent(); if (stack->mTelemetry) stack->mTelemetry->HXTRealloc(old_obj, new_obj, new_size); #endif } void __hxt_gc_start() { hx::StackContext *stack = hx::StackContext::getCurrent(); if (stack->mTelemetry) stack->mTelemetry->GCStart(); } void __hxt_gc_end() { hx::StackContext *stack = hx::StackContext::getCurrent(); if (stack->mTelemetry) stack->mTelemetry->GCEnd(); } void __hxt_gc_after_mark(int gByteMarkID, int ENDIAN_MARK_ID_BYTE) { hx::Telemetry::HXTAfterMark(gByteMarkID, ENDIAN_MARK_ID_BYTE); }