string ofxTimeMeasurements::getTimeStringForTM(TimeMeasurement* tm) {

	float time;
	if (tm->measuring){
		string anim;
		switch ((int(ofGetFrameNum() * 0.2f))%6) {
			case 0: anim = "   "; break;
			case 1: anim = ".  "; break;
			case 2: anim = ".. "; break;
			case 3: anim = "..."; break;
			case 4: anim = " .."; break;
			case 5: anim = "  ."; break;
		}
		//return "   Running " + anim;
		return string((ofGetFrameNum()% 6 < 3 ) ? " >  " : "    ") +
				formatTime( TM_GET_MICROS() - tm->microsecondsStart, 1) +
				anim;
	}else{

		string timeString;
		char percentChar[64];

		if (!tm->settings.enabled){
			return "   DISABLED!";
		}else{

			if(tm->accumulating){
				timeString = formatTime(tm->microsecondsAccum, msPrecision);
				time = tm->microsecondsAccum / 1000.0f;
			}else{
				timeString = formatTime(tm->avgDuration, msPrecision);
				time = tm->avgDuration / 1000.0f;
			}

			float percent = 100.0f * time / (1000.0f / desiredFrameRate);
			bool over = false;
			if (percent > 100.0f){
				percent = 100.0f;
				over = true;
			}
			int originalLen = timeString.length();

			int expectedLen = 8;
			for(int i = 0; i < expectedLen - originalLen; i++){
				timeString = " " + timeString;
			}

			if (over){
				sprintf(percentChar, int(ofGetFrameNum() * 0.8)%5 < 3  ? " >100": "  100");
			}else{
				sprintf(percentChar, "% 5.1f", percent);
			}
		}
		return timeString + percentChar + "%" ;
	}
}
float ofxTimeMeasurements::stopMeasuring(string ID, bool accumulate){

	float ret = 0.0f;
	if (!enabled) return ret;

	uint64_t timeNow = TM_GET_MICROS(); //get the time before the lock() to avoid affecting
	//the measurement as much as possible

	Poco::Thread * thread = Poco::Thread::current();

	mutex.lock();

	core::tree<string> &tr = threadInfo[thread].tree; //easier to read, tr is our tree from now on
	core::tree<string>::iterator & tit = threadInfo[thread].tit;
	if (tit != tr.begin()){
		if( tit != tr.end()){
			tit = tit.out();
		}
	}

	unordered_map<string,TimeMeasurement*>::iterator it;
	it = times.find(ID);
	
	if ( it == times.end() ){	//not found!
		ofLog( OF_LOG_WARNING, "ID (%s)not found at stopMeasuring(). Make sure you called"
			  " startMeasuring with that ID first.", ID.c_str());
	}else{
		
		TimeMeasurement* t = times[ID];

		if ( times[ID]->measuring ){

			t->measuring = false;
			t->error = false;
			t->acrossFrames = (t->frame != ofGetFrameNum() && thread == NULL); //we only care about across-frames in main thread
			t->microsecondsStop = timeNow;
			ret = t->duration = t->microsecondsStop - t->microsecondsStart;
			t->avgDuration = (1.0f - timeAveragePercent) * t->avgDuration + t->duration * timeAveragePercent;
			if (accumulate){
				t->microsecondsAccum += t->duration;
			}

		}else{	//wrong use, start first, then stop

			t->error = true;
			ofLog( OF_LOG_WARNING, "Can't stopMeasuring(%s). Make sure you called startMeasuring"
				  " with that ID first.", ID.c_str());
		}
	}
	ret = ret / 1000.0f;
	mutex.unlock();
	return ret; //convert to ms
}
void ofxTimeMeasurements::draw(int x, int y) {

	if (!enabled){
		//drawString(ofToString(fr, msPrecision), 10, fontSize);
		return;
	}
	
	float fr = ofGetFrameRate();
	uint64_t timeNow;
	if(internalBenchmark){
		timeNow = TM_GET_MICROS();
	}
	currentFrameNum = ofGetFrameNum();

	drawLines.clear();
	float percentTotal = 0.0f;
	float timePerFrame = 1000.0f / desiredFrameRate;

	mutex.lock();

	vector<TimeMeasurement*> toResetUpdatedLastFrameFlag;

	//update time stuff, build draw lists
	for( unordered_map<string,TimeMeasurement*>::iterator ii = times.begin(); ii != times.end(); ++ii ){
		TimeMeasurement* t = ii->second;
		string key = ii->first;
		if(!t->measuring){
			if (t->life > 0.01){
				t->life *= idleTimeColorDecay; //decrease life
			}else{ //life decays very slow when very low
				t->life *= deadThreadExtendedLifeDecSpeed; //decrease life very slowly
			}
		}
//		if (!t->updatedLastFrame && averaging){ // if we didnt update that time, make it tend to zero slowly
//			t->avgDuration = (1.0f - timeAveragePercent) * t->avgDuration;
//		}
		toResetUpdatedLastFrameFlag.push_back(t);
	}

	unordered_map<ThreadId, ThreadInfo>::iterator ii;
	vector<ThreadId> expiredThreads;

	//lets make sure the Main Thread is always on top
	vector< ThreadContainer > sortedThreadList;

	for( ii = threadInfo.begin(); ii != threadInfo.end(); ++ii ){ //walk all thread trees
		ThreadContainer cont;
		cont.id = ii->first;
		cont.info = &ii->second;
		if (isMainThread(ii->first)){ //is main thread
			sortedThreadList.insert(sortedThreadList.begin(), cont);
		}else{
			sortedThreadList.push_back(cont);
		}
	}
	std::sort(sortedThreadList.begin(), sortedThreadList.end(), compareThreadPairs);

	#if defined(USE_OFX_HISTORYPLOT)
	vector<ofxHistoryPlot*> plotsToDraw;
	#endif

	for( int k = 0; k < sortedThreadList.size(); k++ ){ //walk all thread trees

		ThreadId thread = sortedThreadList[k].id;
		core::tree<string> &tr = sortedThreadList[k].info->tree;

		ThreadInfo & tinfo = threadInfo[thread];
		PrintedLine header;
		header.formattedKey = "+ " + *tr;
		header.color = tinfo.color;
		header.lineBgColor = ofColor(header.color, dimColorA * 2); //header twice as alpha
		header.key = *tr; //key for selection, is thread name
		drawLines.push_back(header); //add header to drawLines

		int numAlive = 0;
		int numAdded = 0;

		core::tree<string>::iterator wholeTreeWalker = tr.in();
		bool finishedWalking = false;
		float winW = ofGetWidth();

		while( !finishedWalking ){

			string key = *wholeTreeWalker;
			TimeMeasurement * t = times[*wholeTreeWalker];
			if(t->thread == thread){

				#if defined(USE_OFX_HISTORYPLOT)
				bool plotActive = false;
				ofxHistoryPlot* plot = plots[key];
				if(plot){
					if(t->settings.plotting){
						if(t->updatedLastFrame){
							//update plot res every now and then
							if(currentFrameNum%120 == 1) plot->setMaxHistory(MIN(maxPlotSamples, winW * plotResolution));
							if (!freeze) {
								if (t->accumulating) {
									plot->update(t->microsecondsAccum / 1000.0f);
								}
								else {
									plot->update(t->avgDuration / 1000.0f);
								}
							}
						}
						plotsToDraw.push_back(plot);
						plotActive = true;
					}
				}
				#endif

				bool visible = t->settings.visible;
				bool alive = t->life > 0.0001;
				if(alive){
					numAlive++;
				}

				if (visible && (removeExpiredTimings ? alive : true)){
					PrintedLine l;
					l.key = key;
					l.tm = t;
					l.lineBgColor = ofColor(tinfo.color, dimColorA);

					int depth = wholeTreeWalker.level();
					for(int i = 0; i < depth; ++i) l.formattedKey += " ";

					if (wholeTreeWalker.size() == 0){
						l.formattedKey += "-";
					}else{
						l.formattedKey += "+";
					}
					l.formattedKey += key + string(t->accumulating ? "[" + ofToString(t->numAccumulations)+ "]" : "" );
					l.isAccum = t->accumulating;
					l.time = getTimeStringForTM(t);
					if(drawPercentageAsGraph){
						l.percentGraph = getPctForTM(t);
					}

					l.color = tinfo.color * ((1.0 - idleTimeColorFadePercent) + idleTimeColorFadePercent * t->life);
					if (!t->settings.enabled){
						if(t->ifClause){
							l.color = disabledTextColor;
						}else{
							l.color = disabledTextColor.getInverted();
						}
					}

					#if defined(USE_OFX_HISTORYPLOT)
					if(plotActive){
						l.plotColor = ofColor(plots[key]->getColor(), 200);
					}
					#endif

					if (menuActive && t->key == selection){
						if(currentFrameNum%5 < 4){
							l.color.invert();
							l.lineBgColor = ofColor(tinfo.color, dimColorA * 1.5);
						}
					}

					drawLines.push_back(l);
					numAdded++;
				}

				//only update() and draw() count to the final %
				if(key == TIME_MEASUREMENTS_DRAW_KEY || key == TIME_MEASUREMENTS_UPDATE_KEY){
					percentTotal += (t->avgDuration * 0.1f) / timePerFrame;
				}
				//reset accumulator
				t->accumulating = false;
				t->numAccumulations = 0;
				t->microsecondsAccum = 0;
			}

			//control the iterator to walk the tree "recursively" without doing so.
			if(wholeTreeWalker.size()){
				wholeTreeWalker = wholeTreeWalker.in();
			}else{
				if ( wholeTreeWalker.next() == wholeTreeWalker.end() ){
					wholeTreeWalker = wholeTreeWalker.out();
					while( wholeTreeWalker.next() == wholeTreeWalker.end() && wholeTreeWalker != tr){
						wholeTreeWalker = wholeTreeWalker.out();
					}
					if(wholeTreeWalker == tr){
						finishedWalking = true;
					}else{
						wholeTreeWalker++;
					}
				}else{
					++wholeTreeWalker;
				}
			}
		}

		#if defined(USE_OFX_HISTORYPLOT)
		numActivePlots = plotsToDraw.size();
		#endif

		if (numAlive == 0 && removeExpiredThreads){
			//drop that whole section if all entries in it are not alive
			for(int i = 0; i < numAdded + 1; i++){
				if(drawLines.size() > 0){
					int delID = drawLines.size() - 1;
					//clear selection if needed
					if (selection == drawLines[delID].key){
						selection = TIME_MEASUREMENTS_UPDATE_KEY;
					}
					drawLines.erase(drawLines.begin() + delID);
				}
			}
			expiredThreads.push_back(thread);
		}
	}

	//delete expired threads
	for(int i = 0; i < expiredThreads.size(); i++){
		unordered_map<ThreadId, ThreadInfo>::iterator treeIt = threadInfo.find(expiredThreads[i]);
		if (treeIt != threadInfo.end()) threadInfo.erase(treeIt);
	}

	mutex.unlock();

	updateLongestLabel();

	//find headers
	int tempMaxW = -1;
	vector<int> headerLocations;
	for( int i = 0; i < drawLines.size(); i++ ){
		if (drawLines[i].tm){ //its a measurement
			//add padding to draw in columns
			for(int j = drawLines[i].formattedKey.length(); j < longestLabel; j++){
				drawLines[i].formattedKey += " ";
			}
			if (!drawLines[i].tm->error){
				drawLines[i].shouldDrawPctGraph = true;
				drawLines[i].fullLine = drawLines[i].formattedKey + " " + drawLines[i].time;
			}else{
				drawLines[i].shouldDrawPctGraph = true;
				drawLines[i].fullLine = drawLines[i].formattedKey + "    Error!" ;
			}
			int len = drawLines[i].fullLine.length();
			if(len > tempMaxW) tempMaxW = len;
			if(drawLines[i].tm->measuring) drawLines[i].shouldDrawPctGraph = false;
		}else{ //its a header
			drawLines[i].fullLine = drawLines[i].formattedKey;
			drawLines[i].shouldDrawPctGraph = false;
			headerLocations.push_back(i);
		}
	}

	int numInstructionLines = 0;
	if(menuActive){ //add instructions line if menu active
		PrintedLine l;
		//title line
		l.color = hilightColor;
		l.lineBgColor = ofColor(hilightColor, dimColorA * 2);
		l.fullLine = " KEYBOARD COMMANDS "; //len = 23
		int numPad = 2 + ceil((getWidth() - charW * (23)) / charW);
		for(int i = 0; i < floor(numPad/2.0); i++ ) l.fullLine = "#" + l.fullLine;
		for(int i = 0; i < ceil(numPad/2.0); i++ ) l.fullLine += "#";
		l.fullLine = " " + l.fullLine;
		drawLines.push_back(l); numInstructionLines++;
		//key command lines
		l.lineBgColor = ofColor(hilightColor, dimColorA);
		l.fullLine = " 'UP/DOWN' select measur."; drawLines.push_back(l); numInstructionLines++;
		l.fullLine = " 'LFT/RGHT' expand/collaps"; drawLines.push_back(l); numInstructionLines++;
		l.fullLine = " 'RET' toggle code section"; drawLines.push_back(l); numInstructionLines++;
		l.fullLine = " 'A' average measurements"; drawLines.push_back(l); numInstructionLines++;
		l.fullLine = " 'F' freeze measurements"; drawLines.push_back(l); numInstructionLines++;
		l.fullLine = " 'L' cycle widget location"; drawLines.push_back(l); numInstructionLines++;
		l.fullLine = " 'PG_DWN' en/disable addon"; drawLines.push_back(l); numInstructionLines++;
		l.fullLine = " 'B' internal benchmark"; drawLines.push_back(l); numInstructionLines++;
		l.fullLine = " 'V' expand all"; drawLines.push_back(l); numInstructionLines++;
		#if defined USE_OFX_HISTORYPLOT
		l.fullLine = " 'P' plot selectd measur."; drawLines.push_back(l); numInstructionLines++;
		#endif
	}

	maxW = tempMaxW;

	ofSetupScreen(); //mmmm----
	ofPushStyle();
	ofSetRectMode(OF_RECTMODE_CORNER);
	ofSetDrawBitmapMode(OF_BITMAPMODE_SIMPLE);
	ofEnableAlphaBlending();

	ofPushMatrix();
	ofScale(uiScale,uiScale);

	ofFill();

	//draw all plots
	#if defined(USE_OFX_HISTORYPLOT)
	//int numCols = plotsToDraw.size()

	float highest = FLT_MIN;
	for(auto plot : plotsToDraw){
		if(allPlotsTogether){ //lets find the range that covers all the plots
			float high = plot->getHigerRange();
			if (high > highest) highest = high;
			plot->setDrawTitle(false);
			plot->setDrawBackground(false);
			plot->setShowSmoothedCurve(false);
		}else{
			plot->setDrawTitle(true);
			plot->setDrawBackground(true);
			plot->setLowerRange(0);
			plot->setShowSmoothedCurve(true);
		}
	}

	float canvasW = ofGetWidth();
	float canvasH = ofGetHeight();

	if(allPlotsTogether && plotsToDraw.size()){
		ofSetColor(0, 230);
		ofDrawRectangle(0, canvasH - plotHeight, canvasW / uiScale, plotHeight);
	}

	for(int i = 0; i < plotsToDraw.size(); i++){
		int y = (plotBaseY == 0 ? canvasH : plotBaseY) / uiScale - plotHeight * (i + 1);
		if(allPlotsTogether){
			plotsToDraw[i]->setRange(0, highest);
			y = ((plotBaseY == 0 ? canvasH : plotBaseY) - plotHeight) / uiScale;
		}
		plotsToDraw[i]->draw(0, y, canvasW / uiScale, plotHeight);
		if(!allPlotsTogether){
			ofSetColor(99);
			if(i != plotsToDraw.size() -1){
				ofLine(0, y, canvasW / uiScale, y );
			}
		}

		if(allPlotsTogether){
			ofSetColor(plotsToDraw[i]->getColor());
			deque<float>& vals = plotsToDraw[i]->getValues();
			float val = 0.0f;
			if(!vals.empty()) val = vals.back();
			string msg = plotsToDraw[i]->getVariableName() + " " + ofToString(val, 2);
			drawString(msg, canvasW - charW * msg.size() - 2, ofGetHeight() - plotHeight - 4 - charH * (plotsToDraw.size() -1 - i));
		}
	}
	#endif

	float totalW = getWidth();
	float totalH = getHeight();

	//draw bg rect
	ofSetColor(bgColor);
	ofDrawRectangle(x, y + 1, totalW, totalH);

	//draw all lines
	for(int i = 0; i < drawLines.size(); i++){
		ofSetColor(drawLines[i].lineBgColor);
		ofRectangle lineRect = ofRectangle(x, y + 2 + i * charH, totalW, charH + (drawLines[i].tm ? 0 : 1));
		ofDrawRectangle(lineRect);
		if(drawLines[i].isAccum && drawLines[i].tm != NULL){
			ofSetColor(drawLines[i].color, 128);
			ofDrawRectangle(x + totalW,
							y + 3 + i * charH,
							-5,
							charH - 1 );
		}
		ofSetColor(drawLines[i].color);
		drawString(drawLines[i].fullLine, x , y + (i + 1) * charH);
		if(drawLines[i].plotColor.a > 0){ //plot highlight on the sides
			ofSetColor(drawLines[i].plotColor);
			float y1 = y + 2.4f + i * charH;
			ofDrawTriangle(	x, y1, 
							x, y1 + charH, 
							x + charW * 0.7f, y1 + charH * 0.5f);
		}

		if(drawPercentageAsGraph && drawLines[i].shouldDrawPctGraph && drawLines[i].percentGraph > 0.02f){
			float ww = charW * 5.5;
			float xx = lineRect.x + lineRect.width - charW * 7;
			float pct = MIN(drawLines[i].percentGraph, 1.0);
			unsigned char a = 64;
			ofColor gC;
			if(drawLines[i].percentGraph > 1.0){
				gC = ofColor(255,0,0, (currentFrameNum%4 > 2) ? 1.5 * a : a);
			}else{
				gC = ofColor(drawLines[i].lineBgColor, a) * (1.0f - pct) + ofColor(255,0,0,a) * pct;
			}

			ofSetColor(gC);
			ofDrawRectangle( xx,
							lineRect.y + 0.2 * lineRect.height ,
							ww * pct,
							lineRect.height * 0.65
							);
		}
	}

	if(internalBenchmark){
		float offset = 0;
		if(drawLocation == TIME_MEASUREMENTS_TOP_LEFT ||
		   drawLocation == TIME_MEASUREMENTS_TOP_RIGHT ||
		   drawLocation == TIME_MEASUREMENTS_CUSTOM_LOCATION ){
			offset = (drawLines.size() + 2.5) * charH;
		}
		ofSetColor(0);
		ofDrawRectangle(x, offset + y - charH, totalW, charH);
		ofSetColor(currentFrameNum%3 ? 255 : 64);
		drawString(" Meas: " + ofToString(wastedTimeAvg / 1000.f, 2) + "ms " +
				   " Draw: " + ofToString(wastedTimeDrawingAvg / 1000.f, 2) + "ms ",
				   x, offset + y - charH * 0.12);
	}

	if (freeze) {
		if(currentFrameNum%5 < 4) ofSetColor(frozenColor);
		else ofSetColor(ofColor::white);
		drawString("Frozen! 'F'", x + totalW - 13 * charW, y + charH );
	}

	{//lines
		ofSetColor(hilightColor);
		ofMesh lines;
		ofSetLineWidth(0.1);
		lines.setMode(OF_PRIMITIVE_LINES);
		float fuzzyFix = 0.5;
		float yy = y+1 + fuzzyFix;
		lines.addVertex(ofVec2f(x, yy));
		lines.addVertex(ofVec2f(x + totalW, yy));
		yy = y + totalH - charH - 3 + fuzzyFix;
		lines.addVertex(ofVec2f(x, yy));
		lines.addVertex(ofVec2f(x + totalW, yy));
		yy = y + totalH + fuzzyFix;
		lines.addVertex(ofVec2f(x, yy));
		lines.addVertex(ofVec2f(x + totalW, yy));
		if(menuActive){
			yy = y + totalH + fuzzyFix - (numInstructionLines + 1) * charH - 3;
			lines.addVertex(ofVec2f(x, yy));
			lines.addVertex(ofVec2f(x + totalW, yy));
			yy = y + totalH + fuzzyFix - (numInstructionLines) * charH - 3;
			lines.addVertex(ofVec2f(x, yy));
			lines.addVertex(ofVec2f(x + totalW, yy));
		}
		lines.draw();
	}//lines

	//print bottom line, fps and stuff
	bool missingFrames = ( fr < desiredFrameRate - 1.0 ); // tolerance of 1 fps TODO!
	static char msg[128];

	sprintf(msg, "%2.1f fps % 5.1f%%", fr, percentTotal );
	if(missingFrames){
		ofSetColor(170,33,33); //reddish fps below desired fps
	}else{
		ofSetColor(hilightColor);
	}
	int len = strlen(msg);
	string pad = " ";
	int diff = (maxW - len) - 1;
	for(int i = 0; i < diff; i++) pad += " ";
	int lastLine = ( drawLines.size() + 1 ) * charH + 2;
	drawString( pad + msg, x, y + lastLine );
	
	//show activate menu key
	if(menuActive ) ofSetColor(hilightColor.getInverted());
	else ofSetColor(hilightColor);
	drawString(" '" + ofToString(char(activateKey)) + "'", x, y + lastLine);

	//show averaging warning
	if (averaging) {
		if (currentFrameNum % 5 < 2) ofSetColor(hilightColor);
		else ofSetColor(ofColor::limeGreen);
		drawString(" avg!", x + charW * 3.5, y + lastLine);
	}

	for(int i = 0; i < toResetUpdatedLastFrameFlag.size(); i++){
		toResetUpdatedLastFrameFlag[i]->updatedLastFrame = false;
	}
	ofPopMatrix();
	ofPopStyle();

	if(internalBenchmark){
		wastedTimeDrawingThisFrame += TM_GET_MICROS() - timeNow;
		wastedTimeAvg = wastedTimeThisFrame * 0.025f + 0.975f * wastedTimeAvg;
		wastedTimeDrawingAvg = wastedTimeDrawingThisFrame * 0.025f + 0.975f * wastedTimeDrawingAvg;
		wastedTimeThisFrame = wastedTimeDrawingThisFrame = 0;
	}
}
float ofxTimeMeasurements::stopMeasuring(const string & ID, bool accumulate){

	if (!enabled) return 0.0f;
	float ret = 0.0f;
	string localID = ID;

	uint64_t timeNow = TM_GET_MICROS(); //get the time before the lock() to avoid affecting

	ThreadId thread = getThreadID();
	bool bIsMainThread = isMainThread(thread);

	mutex.lock();

	unordered_map<ThreadId, ThreadInfo>::iterator threadIt = threadInfo.find(thread);

	if(threadIt == threadInfo.end()){ //thread not found!
		mutex.unlock();
		return 0.0f;
	}

	ThreadInfo & tinfo = threadIt->second;

	if(tinfo.order > 0){
		localID = "T" + ofToString(tinfo.order) + ":" + localID;
	}

	core::tree<string> & tr = tinfo.tree; //easier to read, tr is our tree from now on
	core::tree<string>::iterator & tit = tinfo.tit;
	if (tit.out() != tr.end()){
		tit = tit.out();
	}else{
		ofLogError("ofxTimeMeasurements") << "tree climbing too high up! (" << localID << ")";
	}

	unordered_map<string,TimeMeasurement*>::iterator it;
	it = times.find(localID);

	if ( it == times.end() ){	//not found!
		ofLogWarning("ofxTimeMeasurements") << "ID ("<< localID << ")not found at stopMeasuring(). Make sure you called startMeasuring with that ID first.";
	}else{

		TimeMeasurement* t = it->second;
		if ( t->measuring ){
			t->measuring = false;
			t->thread = thread;
			t->error = false;
			t->acrossFrames = (bIsMainThread && t->frame != currentFrameNum); //we only care about across-frames in main thread
			t->microsecondsStop = timeNow;
			ret = t->duration = timeNow - t->microsecondsStart;
			if (!freeze) {
				if (!averaging) {
					t->avgDuration = t->duration;
				}
				else {
					t->avgDuration = (1.0f - timeAveragePercent) * t->avgDuration + t->duration * timeAveragePercent;
				}
			}
			if (accumulate && !freeze){
				t->microsecondsAccum += t->avgDuration;
			}
		}else{	//wrong use, start first, then stop
			t->error = true;
			ofLogWarning("ofxTimeMeasurements") << "Can't stopMeasuring(" << localID << "). Make sure you called startMeasuring() with that ID first.";
		}
	}

	mutex.unlock();

	if(internalBenchmark){
		wastedTimeThisFrame += TM_GET_MICROS() - timeNow;
	}

	return ret / 1000.0f; //convert to ms
}
bool ofxTimeMeasurements::startMeasuring(const string & ID, bool accumulate, bool ifClause){

	string localID = ID;
	if (!enabled) return true;

	uint64_t wastedTime;
	if(internalBenchmark){
		wastedTime = TM_GET_MICROS();
	}

	if (!settingsLoaded){
		loadSettings();
		settingsLoaded = true;
	}

	string threadName = "Thread";
	ThreadId thread = getThreadID();
	bool bIsMainThread = isMainThread(thread);

	if(!bIsMainThread){
		if(Poco::Thread::current()){
			threadName = Poco::Thread::current()->getName();
		}
	}

	mutex.lock();

	unordered_map<ThreadId, ThreadInfo>::iterator threadIt = threadInfo.find(thread);
	ThreadInfo * tinfo = NULL;
	core::tree<string> *tr = NULL;

	bool newThread = threadIt == threadInfo.end();

	if (newThread){ //new thread!

		//cout << "NewThread! " << ID << " " << &thread << endl;
		threadInfo[thread] = ThreadInfo();
		tinfo = &threadInfo[thread];
		tr = &tinfo->tree; //easier to read, tr is our tree from now on

		if (!bIsMainThread){
			tinfo->color = threadColorTable[numThreads%(threadColorTable.size())];
			numThreads++;
		}else{ //main thread
			tinfo->color = hilightColor;
		}
		tinfo->order = numThreads;

		string tName = bIsMainThread ? "Main Thread" : ("T" + ofToString(tinfo->order) + ": " + threadName);
		//init the iterator
		*tr = tName; //thread name is root
		tinfo->tit = (core::tree<string>::iterator)*tr;

	}else{
		tinfo = &threadIt->second;
		tr = &(tinfo->tree); //easier to read, tr is our tree from now on
	}

	if(tinfo->order > 0){
		localID = "T" + ofToString(tinfo->order) + ":" + localID;
	}

	//see if we had an actual measurement, or its a new one
	unordered_map<string, TimeMeasurement*>::iterator tit = times.find(localID);
	TimeMeasurement* t;

	if(tit == times.end()){ //if it wasnt in the tree, append it
		times[localID] = t = new TimeMeasurement();
		unordered_map<string, TimeMeasurementSettings>::iterator it2 = settings.find(localID);
		if (it2 != settings.end()){
			t->settings = settings[localID];
			if(tinfo->tit.out() == tinfo->tit.end()){ //if we are the tree root - we cant be hidden!
				t->settings.visible = true;
			}
		}
		tinfo->tit = tinfo->tit.push_back(localID);

	}else{
		core::tree<string>::iterator temptit = tr->tree_find_depth(localID);
		if(temptit != tr->end()){
			tinfo->tit = temptit;
		}else{
			//cout << "gotcha!" << endl;
			//this is the rare case where we already had a measurement for this ID,
			//but it must be assigned to another old thread bc we cant find it!
			//so we re-add that ID for this thread and update the tree iterator
			tinfo->tit = tinfo->tit.push_back(localID);
		}
		t = tit->second;
	}

	t->key = localID;
	t->life = 1.0f; //
	t->measuring = true;
	t->ifClause = ifClause;
	t->microsecondsStop = 0;
	t->accumulating = accumulate;
	if(accumulate) t->numAccumulations++;
	t->error = false;
	t->frame = currentFrameNum;
	t->updatedLastFrame = true;
	t->microsecondsStart = TM_GET_MICROS();
	t->thread = thread;

	mutex.unlock();

	if(internalBenchmark){
		wastedTimeThisFrame += t->microsecondsStart - wastedTime;
	}

	return t->settings.enabled;
}
bool ofxTimeMeasurements::startMeasuring(string ID, bool accumulate, ofColor color){

	if (!enabled) return true;
	if (!settingsLoaded){
		loadSettings();
		settingsLoaded = true;
	}

	Poco::Thread * thread = Poco::Thread::current();
	bool isMainThread = (mainThreadID == thread);

	uint64_t timeNow = TM_GET_MICROS(); //get the time before the lock() to avoid affecting
	//the measurement as much as possible

	mutex.lock();

	unordered_map<Poco::Thread*, ThreadInfo>::iterator threadIt = threadInfo.find(thread);
	ThreadInfo & tinfo = threadInfo[thread];
	core::tree<string> &tr = tinfo.tree; //easier to read, tr is our tree from now on

	if (threadIt == threadInfo.end()){ //new thread!

		//string tName = isMainThread ? "mainThread" : string("Thread " + ofToString(threadCounter));
		string tName = isMainThread ? "Main Thread" : string(Poco::Thread::current()->getName() +
															 " (" + ofToString(numThreads) + ")");
		//init the iterator
		*tr = tName; //thread name is root
		tinfo.tit = (core::tree<string>::iterator)tr;
		tinfo.order = numThreads;

		if (thread){
			if(color.a == 0 && color.r == 0 && color.g == 0 && color.b == 0){
				tinfo.color = threadColorTable[numThreads%(threadColorTable.size())];
			}else{
				tinfo.color = color;
			}
			numThreads++;
		}else{
			tinfo.color = hilightColor;
		}
	}

	//see if the new measurement already was in tree
	core::tree<string>::iterator searchIt = tr.tree_find_depth(ID);

	if(searchIt == tr.end()){ //if it wasnt in the tree, append it

		if (ID == TIME_MEASUREMENTS_SETUP_KEY ||
			ID == TIME_MEASUREMENTS_UPDATE_KEY ||
			ID == TIME_MEASUREMENTS_DRAW_KEY
			){ //setup update and draw are always at root level!
			if (tr.size()){
				tinfo.tit = tr.push_back(ID);
			}else{
				tinfo.tit = tr.insert(ID);
			}

		}else{
			if(tinfo.tit == tr.end()){
				ofLogError() << "ofxTimeMeasurements error - unordered tree cant climb back up!"; //TODO handle this better!
				tinfo.tit = tr.push_back(ID);
			}else{
				tinfo.tit = tinfo.tit.push_back(ID);
			}
		}
	}else{
		tinfo.tit = searchIt;
	}

	//see if we had an actual measurement, or its a new one
	unordered_map<string, TimeMeasurement*>::iterator tit = times.find(ID);

	if (tit == times.end()){ //not found, let alloc a new TimeMeasurement
		times[ID] = new TimeMeasurement();

		//keyOrder[ keyOrder.size() ] = ID;
		unordered_map<string, TimeMeasurementSettings>::iterator it2 = settings.find(ID);
		if (it2 != settings.end()){
			times[ID]->settings = settings[ID];
		}
	}

	TimeMeasurement* t = times[ID];
	t->key = ID;
	t->life = 1.0f; //
	t->measuring = true;
	t->microsecondsStart = timeNow;
	t->microsecondsStop = 0;
	t->accumulating = accumulate;
	t->error = false;
	t->frame = ofGetFrameNum();
	t->measuring = true;
	t->updatedLastFrame = true;

	mutex.unlock();

	return t->settings.enabled;
}