├── .gitignore ├── example ├── addons.make ├── src │ ├── ofApp.h │ ├── main.cpp │ └── ofApp.cpp └── config.make ├── ofxaddons_thumbnail.png ├── scripts └── ci │ └── install.sh ├── src ├── ofxTimeMeasurementsScoped.h ├── ofxTimeMeasurementsScoped.cpp ├── MinimalTree.h ├── GL_Measurement.h ├── MinimalTree.cpp ├── ofxTimeMeasurementsMacros.h ├── ofxTimeMeasurements.h └── ofxTimeMeasurements.cpp ├── appveyor.yml ├── License.md ├── addon_config.mk ├── .travis.yml └── ReadMe.md /.gitignore: -------------------------------------------------------------------------------- 1 | .AppleDouble 2 | 3 | -------------------------------------------------------------------------------- /example/addons.make: -------------------------------------------------------------------------------- 1 | ofxTimeMeasurements 2 | -------------------------------------------------------------------------------- /ofxaddons_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armadillu/ofxTimeMeasurements/HEAD/ofxaddons_thumbnail.png -------------------------------------------------------------------------------- /scripts/ci/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | echo "Executing ci/linux/install.sh" 5 | 6 | export OF_ROOT=~/openFrameworks 7 | export OF_ADDONS=$OF_ROOT/addons 8 | 9 | ADDONS="armadillu/ofxHistoryPlot armadillu/ofxFontStash" 10 | 11 | cd $OF_ADDONS 12 | 13 | for ADDON in $ADDONS 14 | do 15 | echo "Cloning addon '$ADDON' to " `pwd` 16 | git clone --depth=1 --branch=$OF_BRANCH https://github.com/$ADDON.git 17 | done 18 | 19 | cd - -------------------------------------------------------------------------------- /src/ofxTimeMeasurementsScoped.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ofxTimeMeasurements.h 3 | * emptyExample 4 | * 5 | * Created by Oriol Ferrer Mesià on 28/01/11. 6 | * Copyright 2011 uri.cat. All rights reserved. 7 | * 8 | */ 9 | 10 | #pragma once 11 | 12 | 13 | class ofxTimeMeasurementsScoped { 14 | 15 | public: 16 | ofxTimeMeasurementsScoped(std::string key, bool acc); 17 | ~ofxTimeMeasurementsScoped(); 18 | 19 | protected: 20 | 21 | ofxTimeMeasurementsScoped(){} 22 | 23 | bool acc; 24 | std::string key; 25 | }; 26 | -------------------------------------------------------------------------------- /example/src/ofApp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ofMain.h" 4 | #include "ofxTimeMeasurements.h" 5 | 6 | static int tcount = 1; 7 | 8 | class MyThread: public ofThread{ 9 | 10 | void threadedFunction(){ 11 | 12 | string ID = ofToString(tcount); 13 | tcount++; 14 | TS_START("task_" + ID); 15 | ofSleepMillis(ofRandom(1,500)); //sleep for a bit 16 | TS_STOP("task_" + ID); 17 | } 18 | }; 19 | 20 | 21 | class ofApp : public ofBaseApp, ofThread{ 22 | 23 | public: 24 | void setup(); 25 | void update(); 26 | void draw(); 27 | void exit(); 28 | 29 | void keyPressed( ofKeyEventArgs & key ); 30 | 31 | void threadedFunction(); 32 | vector myThreads; 33 | }; 34 | -------------------------------------------------------------------------------- /src/ofxTimeMeasurementsScoped.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ofxTimeMeasurements.h 3 | * emptyExample 4 | * 5 | * Created by Oriol Ferrer Mesià on 28/01/11. 6 | * Copyright 2011 uri.cat. All rights reserved. 7 | * 8 | */ 9 | 10 | #include "ofxTimeMeasurements.h" 11 | #include "ofxTimeMeasurementsMacros.h" 12 | 13 | ofxTimeMeasurementsScoped::ofxTimeMeasurementsScoped(std::string key, bool acc) { 14 | 15 | this->key = key; 16 | this->acc = acc; 17 | 18 | if(acc) { 19 | TS_START_ACC_NIF(key); 20 | } else { 21 | TS_START_NIF(key); 22 | } 23 | } 24 | 25 | ofxTimeMeasurementsScoped::~ofxTimeMeasurementsScoped(){ 26 | if(acc) { 27 | TS_STOP_ACC_NIF(key); 28 | } else { 29 | TS_STOP_NIF(key); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | os: Visual Studio 2015 RC 3 | 4 | environment: 5 | global: 6 | APPVEYOR_OS_NAME: windows 7 | matrix: 8 | #MSYS2 Building 9 | # - platform: x86 10 | # BUILDER: MSYS2 11 | 12 | #VisualStudio Building 13 | # - platform: x86 14 | # BUILDER : VS 15 | # BITS: 32 16 | - platform: x64 17 | BUILDER : VS 18 | BITS: 64 19 | 20 | configuration: Debug 21 | shallow_clone: true 22 | clone_depth: 10 23 | init: 24 | - set MSYS2_PATH=c:\msys64 25 | - set CHERE_INVOKING=1 26 | - if "%BUILDER%_%PLATFORM%"=="MSYS2_x86" set MSYSTEM=MINGW32 27 | - if "%BUILDER%_%PLATFORM%"=="MSYS2_x64" set MSYSTEM=MINGW64 28 | - if "%BUILDER%"=="VS" set PATH=C:\Program Files (x86)\MSBuild\14.0\Bin;%PATH% 29 | 30 | install: 31 | - cd .. 32 | - git clone --depth=1 --branch=master https://github.com/openframeworks/openFrameworks 33 | - call openFrameworks\scripts\ci\addons\install.cmd 34 | 35 | build_script: 36 | - cd %OF_PATH% 37 | - scripts\ci\addons\build.cmd 38 | 39 | 40 | -------------------------------------------------------------------------------- /example/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "ofMain.h" 2 | #include "ofApp.h" 3 | #include "ofAppGLFWWindow.h" 4 | #include "ofxTimeMeasurements.h" 5 | 6 | int main( ){ 7 | 8 | ofGLFWWindowSettings settings; 9 | 10 | #ifdef TARGET_OPENGLES 11 | settings.setGLESVersion(2); 12 | #else 13 | #ifdef NANOVG_GL2_IMPLEMENTATION 14 | settings.setGLVersion(2, 1); // Fixed pipeline 15 | #endif 16 | 17 | #ifdef NANOVG_GL3_IMPLEMENTATION 18 | settings.setGLVersion(3, 2); // Programmable pipeline >> you need to define GL_VERSION_3 in you pre-processor macros! 19 | #endif 20 | #endif 21 | 22 | settings.stencilBits = 0; 23 | int w = 800; 24 | int h = 600; 25 | #if OF_VERSION_MINOR < 10 26 | settings.width = w; 27 | settings.height = w; 28 | #else 29 | settings.setSize(w, h); 30 | #endif 31 | 32 | ofCreateWindow(settings); 33 | 34 | TIME_SAMPLE_ADD_SETUP_HOOKS(); 35 | TIME_SAMPLE_SET_FRAMERATE(60); //if you want setup() to get automaticall measured, 36 | //you need to create the instance once before setup() is called; 37 | //just call any TIME_SAMPLE_* method to do so 38 | 39 | ofRunApp(new ofApp()); 40 | } 41 | 42 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | The code in this repository is available under the [MIT License](https://secure.wikimedia.org/wikipedia/en/wiki/Mit_license). 2 | 3 | Copyright (c) 2017- Oriol Ferrer Mesià 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/MinimalTree.h: -------------------------------------------------------------------------------- 1 | // 2 | // MinimalTree.hpp 3 | // ofxTimeMeasurements_example 4 | // 5 | // Created by Oriol Ferrer Mesià on 07/11/2020. 6 | // 7 | 8 | #ifndef Tree_hpp 9 | #define Tree_hpp 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #define TREE_DATA_TYPE std::string 16 | 17 | class MinimalTree{ 18 | public: 19 | 20 | //////////////////////////////////////////////////////////////////////////////////////////////////// 21 | 22 | class Node{ 23 | 24 | friend class MinimalTree; 25 | 26 | public: 27 | 28 | Node(const TREE_DATA_TYPE & d); 29 | const TREE_DATA_TYPE & getData(); 30 | 31 | Node* addChildren(const TREE_DATA_TYPE & d); 32 | Node* addSibling(const TREE_DATA_TYPE & d); 33 | 34 | Node * getParent() const; 35 | 36 | Node* getFirstChild() const; 37 | Node* getLastChild() const; 38 | Node* getNextChildren() const; 39 | const std::vector & getChildren() const; 40 | int getNumChildren() const; 41 | 42 | void getAllData(std::vector> & data) const; 43 | 44 | Node* findInChildren(const TREE_DATA_TYPE & d); 45 | 46 | int level() const; 47 | 48 | protected: 49 | 50 | Node(const TREE_DATA_TYPE & d, Node* parent); 51 | 52 | Node* parent = nullptr; 53 | std::vector children; 54 | TREE_DATA_TYPE data; 55 | 56 | void walkAndStore(std::vector> & data, bool isRoot = true) const; 57 | }; 58 | 59 | //////////////////////////////////////////////////////////////////////////////////////////////////// 60 | 61 | Node* getRoot(); 62 | Node* setup(const TREE_DATA_TYPE & d); 63 | Node* find(const TREE_DATA_TYPE & d); 64 | 65 | 66 | protected: 67 | 68 | Node* root = nullptr; 69 | 70 | }; 71 | 72 | #endif /* Tree_hpp */ 73 | -------------------------------------------------------------------------------- /addon_config.mk: -------------------------------------------------------------------------------- 1 | meta: 2 | ADDON_NAME = ofxTimeMeasurements 3 | ADDON_DESCRIPTION = Easily measure execution times across parts of your code 4 | ADDON_AUTHOR = Oriol Ferrer Mesià 5 | ADDON_TAGS = "time" "performance" "measurements" "benchmark" "profiling" 6 | ADDON_URL = https://github.com/armadillu/ofxTimeMeasurements 7 | 8 | common: 9 | 10 | # dependencies with other addons, a list of them separated by spaces 11 | # or use += in several lines 12 | # ADDON_DEPENDENCIES = 13 | 14 | # include search paths, this will be usually parsed from the file system 15 | # but if the addon or addon libraries need special search paths they can be 16 | # specified here separated by spaces or one per line using += 17 | # ADDON_INCLUDES = 18 | 19 | # any special flag that should be passed to the compiler when using this 20 | # addon 21 | # ADDON_CFLAGS = 22 | 23 | # any special flag that should be passed to the linker when using this 24 | # addon, also used for system libraries with -lname 25 | # ADDON_LDFLAGS = 26 | 27 | # linux only, any library that should be included in the project using 28 | # pkg-config 29 | # ADDON_PKG_CONFIG_LIBRARIES = 30 | 31 | # osx/iOS only, any framework that should be included in the project 32 | # ADDON_FRAMEWORKS = 33 | 34 | # source files, these will be usually parsed from the file system looking 35 | # in the src folders in libs and the root of the addon. if your addon needs 36 | # to include files in different places or a different set of files per platform 37 | # they can be specified here 38 | # ADDON_SOURCES = 39 | 40 | # some addons need resources to be copied to the bin/data folder of the project 41 | # specify here any files that need to be copied, you can use wildcards like * and ? 42 | # ADDON_DATA = 43 | 44 | # when parsing the file system looking for libraries exclude this for all or 45 | # a specific platform 46 | # ADDON_LIBS_EXCLUDE = -------------------------------------------------------------------------------- /src/GL_Measurement.h: -------------------------------------------------------------------------------- 1 | // 2 | // GL_Measurement.h 3 | // BaseApp 4 | // 5 | // Created by Oriol Ferrer Mesià on 20/01/2017. 6 | // 7 | // 8 | 9 | #pragma once 10 | #include "ofMain.h" 11 | 12 | #ifndef TARGET_OPENGLES 13 | 14 | class GL_Measurement{ 15 | 16 | public: 17 | 18 | void init(){ 19 | glGenQueries(1, queryID); 20 | state = INITED; 21 | } 22 | 23 | void start(){ 24 | glBeginQuery(GL_TIME_ELAPSED_EXT, queryID[0]); 25 | state = MEASURING; 26 | } 27 | 28 | void stop(){ 29 | glEndQuery(GL_TIME_ELAPSED_EXT); 30 | state = MEASURED; 31 | measuredFrame = ofGetFrameNum(); 32 | } 33 | 34 | void update(){ 35 | if (state == MEASURED && ofGetFrameNum() - measuredFrame > 2 ){ 36 | GLint available = false; 37 | glGetQueryObjectiv(queryID[0], GL_QUERY_RESULT_AVAILABLE, &available); 38 | if (available){ 39 | GLuint64 measurementResult; 40 | state = RESULT_READY; 41 | glGetQueryObjectui64vEXT(queryID[0], GL_QUERY_RESULT, &measurementResult); 42 | this->measurementResult = measurementResult * 0.000001; //convert to msec 43 | }else{ 44 | ofLogError("ofxTimeMeasurements") << "GL measurement not ready yet!"; 45 | } 46 | } 47 | } 48 | 49 | bool isMeasurementReady(){return state == RESULT_READY;} 50 | double getMeasurement(){return measurementResult;} 51 | void acknowledgeMeasurement(){ state = INITED;} // after you successfully read the measurement, ack it 52 | //so that the timer can be started again. 53 | 54 | 55 | bool canStartMeasuring(){return state == INITED || state == RESULT_READY;} 56 | bool canStopMeasuring(){return state == MEASURING;} 57 | 58 | ~GL_Measurement(){ 59 | if (state > UNINITED){ 60 | glDeleteQueries(1, queryID); 61 | } 62 | } 63 | 64 | protected: 65 | 66 | enum State{ 67 | UNINITED, 68 | INITED, 69 | MEASURING, 70 | MEASURED, 71 | RESULT_READY 72 | }; 73 | 74 | int measuredFrame; 75 | double measurementResult = 0.0; //msec 76 | State state = UNINITED; 77 | GLuint queryID[1]; 78 | }; 79 | 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /src/MinimalTree.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // MinimalTree.cpp 3 | // ofxTimeMeasurements_example 4 | // 5 | // Created by Oriol Ferrer Mesià on 07/11/2020. 6 | // 7 | 8 | #include // for std::sort 9 | 10 | #include "MinimalTree.h" 11 | #include "ofLog.h" 12 | 13 | MinimalTree::Node::Node(const TREE_DATA_TYPE & d){ 14 | data = d; 15 | parent = nullptr; 16 | } 17 | 18 | MinimalTree::Node::Node(const TREE_DATA_TYPE & d, MinimalTree::Node* parent){ 19 | data = d; 20 | this->parent = parent; 21 | } 22 | 23 | const TREE_DATA_TYPE & MinimalTree::Node::getData(){ 24 | return data; 25 | } 26 | 27 | MinimalTree::Node* MinimalTree::Node::addChildren(const TREE_DATA_TYPE & d){ 28 | MinimalTree::Node * n = new MinimalTree::Node(d, this); 29 | children.push_back(n); 30 | return n; 31 | } 32 | 33 | 34 | MinimalTree::Node* MinimalTree::Node::addSibling(const TREE_DATA_TYPE & d){ 35 | if(this->parent){ 36 | return this->parent->addChildren(d); 37 | }else{ 38 | ofLogError("MinimalTree::Node") << "can't add sibling to root node!"; 39 | } 40 | return nullptr; 41 | } 42 | 43 | 44 | MinimalTree::Node * MinimalTree::Node::getParent() const{ 45 | return parent; 46 | } 47 | 48 | 49 | MinimalTree::Node* MinimalTree::Node::getFirstChild() const{ 50 | if(children.size()) return children[0]; 51 | return nullptr; 52 | } 53 | 54 | MinimalTree::Node* MinimalTree::Node::getLastChild() const{ 55 | if(children.size()) return children[children.size()-1]; 56 | return nullptr; 57 | } 58 | 59 | MinimalTree::Node* MinimalTree::Node::getNextChildren() const{ 60 | if(parent){ 61 | if(parent->children.size()){ 62 | auto it = std::find(parent->children.begin(), parent->children.end(), this); 63 | if(it != parent->children.end()){ 64 | int index = it - parent->children.begin(); 65 | if(index + 1 < parent->children.size()){ 66 | return parent->children[index + 1]; 67 | } 68 | } 69 | } 70 | } 71 | return nullptr; 72 | } 73 | 74 | const std::vector & MinimalTree::Node::getChildren() const{ 75 | return children; 76 | } 77 | 78 | 79 | int MinimalTree::Node::getNumChildren() const{ 80 | return children.size(); 81 | } 82 | 83 | 84 | MinimalTree::Node* MinimalTree::Node::findInChildren(const TREE_DATA_TYPE & d){ 85 | for(auto & n : children){ 86 | if(n->data == d) return n; 87 | } 88 | for(auto & n : children){ 89 | MinimalTree::Node* res = n->findInChildren(d); 90 | if(res){ 91 | return res; 92 | } 93 | } 94 | return nullptr; 95 | } 96 | 97 | int MinimalTree::Node::level() const{ 98 | 99 | int l = 0; 100 | const MinimalTree::Node * it = this; 101 | while(it->getParent()){ 102 | it = it->getParent(); 103 | l++; 104 | } 105 | return l; 106 | } 107 | 108 | 109 | void MinimalTree::Node::getAllData(std::vector> & data) const{ 110 | walkAndStore(data, true); 111 | } 112 | 113 | void MinimalTree::Node::walkAndStore(std::vector> & data, bool isRoot) const{ 114 | if(!isRoot) data.push_back(std::make_pair((Node*)this, this->level())); 115 | for(auto & n : children){ 116 | n->walkAndStore(data, false); 117 | } 118 | } 119 | 120 | 121 | /////////////////////////////////////////////////////////////////// 122 | 123 | MinimalTree::Node* MinimalTree::setup(const TREE_DATA_TYPE & d){ 124 | root = new Node(d); 125 | return root; 126 | } 127 | 128 | 129 | MinimalTree::Node* MinimalTree::getRoot(){ 130 | return root; 131 | } 132 | 133 | MinimalTree::Node* MinimalTree::find(const TREE_DATA_TYPE & d){ 134 | if(root) return root->findInChildren(d); 135 | return nullptr; 136 | } 137 | -------------------------------------------------------------------------------- /example/src/ofApp.cpp: -------------------------------------------------------------------------------- 1 | #include "ofApp.h" 2 | #include "MinimalTree.h" 3 | 4 | void ofApp::setup(){ 5 | 6 | 7 | MinimalTree t; 8 | MinimalTree::Node * root = t.setup("root"); 9 | 10 | MinimalTree::Node * a = root->addChildren("a"); 11 | MinimalTree::Node * b = root->addChildren("b"); 12 | a->addChildren("aa"); 13 | a->addChildren("ab"); 14 | 15 | auto ba = b->addChildren("ba"); 16 | auto bb = b->addChildren("bb"); 17 | auto bc = b->addChildren("bc"); 18 | 19 | auto bac = ba->addChildren("bac"); 20 | 21 | std::vector> allData1; 22 | root->getAllData(allData1); 23 | 24 | string list1; 25 | for(auto & l : allData1){ 26 | list1 += l.first->getData() + ", "; 27 | } 28 | ofLogNotice() << "allData1:" << list1; 29 | 30 | auto findRes = t.find("bac"); 31 | 32 | string fullPath; 33 | while(findRes->getParent()){ 34 | fullPath = findRes->getData() + "/" + fullPath; 35 | findRes = findRes->getParent(); 36 | } 37 | ofLogNotice() << "fullPath: " << fullPath; 38 | 39 | ofBackground(22); 40 | 41 | //specify where the widget is to be drawn 42 | TIME_SAMPLE_SET_DRAW_LOCATION( TIME_MEASUREMENTS_TOP_RIGHT ); //specify a drawing location (OPTIONAL) 43 | 44 | 45 | TIME_SAMPLE_SET_AVERAGE_RATE(0.1); //averaging samples, (0..1], 46 | //1.0 gets you no averaging at all 47 | //use lower values to get steadier readings 48 | TIME_SAMPLE_DISABLE_AVERAGE(); //disable averaging 49 | 50 | TIME_SAMPLE_SET_REMOVE_EXPIRED_THREADS(true); //inactive threads will be dropped from the table 51 | //customize color 52 | //TIME_SAMPLE_GET_INSTANCE()->setHighlightColor(ofColor::yellow); 53 | 54 | //TIME_SAMPLE_GET_INSTANCE()->drawUiWithFontStash("fonts/UbuntuMono-R.ttf"); 55 | //TIME_SAMPLE_GET_INSTANCE()->drawUiWithFontStash2("fonts/UbuntuMono-R.ttf"); 56 | startThread(); 57 | 58 | 59 | } 60 | 61 | 62 | void ofApp::threadedFunction(){ 63 | 64 | while(isThreadRunning()){ 65 | TS_START("task"); 66 | ofSleepMillis(30); 67 | TS_START("subtask1"); 68 | ofSleepMillis(300); 69 | TS_STOP("subtask1"); 70 | TS_STOP("task"); 71 | ofSleepMillis(100); 72 | } 73 | } 74 | 75 | 76 | void ofApp::update(){ 77 | 78 | TS_START("simple measurement"); 79 | ofSleepMillis(1); 80 | TS_STOP("simple measurement"); 81 | 82 | TS_START("nested measurement1"); 83 | TS_START("nested measurement11"); 84 | TS_START("nested measurement111"); 85 | ofSleepMillis(1); 86 | TS_STOP("nested measurement111"); 87 | TS_STOP("nested measurement11"); 88 | TS_START("nested measurement12"); 89 | ofSleepMillis(1); 90 | TS_STOP("nested measurement12"); 91 | TS_STOP("nested measurement1"); 92 | 93 | 94 | if (ofGetFrameNum()%60 == 1){ 95 | TS_START_NIF("sample across frames"); 96 | } 97 | 98 | if (ofGetFrameNum()%60 == 3){ 99 | TS_STOP_NIF("sample across frames"); 100 | } 101 | 102 | if (ofGetFrameNum()%600 == 30 || ofGetFrameNum() == 1){ 103 | TS_START("some uncommon method") 104 | ofSleepMillis(ofRandom(3)); 105 | TS_STOP("some uncommon method"); 106 | } 107 | 108 | //test accumulation time sampling 109 | for(int i = 0; i < 3; i++){ 110 | TS_START_ACC("accum test"); 111 | ofSleepMillis(1); 112 | TS_STOP_ACC("accum test"); 113 | { 114 | TS_SCOPE_ACC("scope measurement acc"); 115 | ofSleepMillis(1); 116 | } 117 | } 118 | 119 | for(int i = myThreads.size() - 1; i >= 0 ; i--){ 120 | if (!myThreads[i]->isThreadRunning()){ 121 | delete myThreads[i]; 122 | myThreads.erase(myThreads.begin() + i); 123 | } 124 | } 125 | 126 | { 127 | TS_SCOPE("scope measurement"); 128 | ofSleepMillis(1); 129 | } 130 | } 131 | 132 | 133 | void ofApp::draw(){ 134 | 135 | TSGL_START("drawDotsGL"); 136 | TS_START("draw dots") /////////////////////////////// START MEASURING /// 137 | for(int i = 0; i < ofGetMouseX() * 5; i++){ 138 | ofSetColor( ofRandom(96) ); 139 | ofRect( ofRandom( ofGetWidth()), ofRandom( ofGetHeight()), 4, 4); 140 | } 141 | TS_STOP("draw dots"); /////////////////////////////// STOP MEASURING /// 142 | TSGL_STOP("drawDotsGL"); 143 | 144 | 145 | //testing late samples 146 | if (ofGetFrameNum() > 10){ 147 | TS_START("Nested Test L2"); 148 | TS_STOP("Nested Test L2"); 149 | } 150 | 151 | ofSetColor(255); 152 | ofDrawBitmapString("Move mouse to the right to incrase drawing complexity\n" 153 | "Notice how drawing more cubes takes longer\n" 154 | "Press 'SPACE' to spawn a new thread\n" 155 | "Press 'T' to toggle interactive mode\n\n" 156 | "*instructions are show in the widget when active", 157 | 10, 158 | 20); 159 | } 160 | 161 | 162 | void ofApp::keyPressed( ofKeyEventArgs & key ){ 163 | 164 | TS_START("keyDown"); 165 | if(key.key == ' '){ 166 | if(myThreads.size() < 10){ 167 | MyThread *t = new MyThread(); 168 | t->startThread(); 169 | myThreads.push_back(t); 170 | } 171 | } 172 | TS_STOP("keyDown"); 173 | } 174 | 175 | void ofApp::exit(){ 176 | waitForThread(true); 177 | for(auto t : myThreads){ 178 | t->waitForThread(true); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # This file allows testing your addon using travis CI servers to use it you'll need to 2 | # create an account in travis.org and enable your addon there. 3 | # 4 | # By default it will test linux 64bit and osx against the master and stable OF branches. 5 | # Other platforms can be enabled by uncommenting the corresponding sections. 6 | # 7 | # If any extra install is needed to use the addon it can be included in the corresponding 8 | # install script in: 9 | # 10 | # scripts/ci/$TARGET/install.sh 11 | # 12 | 13 | 14 | language: c++ 15 | compiler: gcc 16 | matrix: 17 | include: 18 | # fully specify builds, include can't dynamically expand matrix entries 19 | # relative order of sudo and env is important so that addons: is recognized 20 | 21 | # Linux 64bit, OF master 22 | - os: linux 23 | dist: trusty 24 | sudo: required 25 | env: TARGET="linux64" OF_BRANCH="main" 26 | cache: ccache 27 | addons: 28 | apt: 29 | sources: 30 | - ubuntu-toolchain-r-test 31 | packages: 32 | - gcc-4.9 33 | - g++-4.9 34 | - gdb 35 | 36 | # Linux 64bit, OF stable: Not supported yet 37 | # - os: linux 38 | # dist: trusty 39 | # sudo: required 40 | # env: TARGET="linux64" OF_BRANCH="stable" 41 | # addons: 42 | # apt: 43 | # sources: 44 | # - ubuntu-toolchain-r-test 45 | # packages: 46 | # - gcc-4.9 47 | # - g++-4.9 48 | # - gdb 49 | 50 | # OSX, OF master 51 | - os: osx 52 | osx_image: xcode9.4 53 | compiler: clang 54 | env: TARGET="osx" OF_BRANCH="main" 55 | cache: ccache 56 | 57 | # OSX, OF stable: Not supported yet 58 | # - os: osx 59 | # osx_image: xcode8 60 | # compiler: clang 61 | # env: TARGET="osx" OF_BRANCH="stable" 62 | 63 | # Linux ARM6, OF master: Uncomment following lines to enable 64 | # - os: linux 65 | # sudo: required 66 | # dist: trusty 67 | # env: TARGET="linuxarmv6l" OF_BRANCH="master" 68 | 69 | 70 | # Linux ARM6, OF stable: Not supported yet 71 | # - os: linux 72 | # sudo: required 73 | # dist: trusty 74 | # env: TARGET="linuxarmv6l" OF_BRANCH="stable" 75 | 76 | # Linux ARM7, OF master: Uncomment following lines to enable 77 | # - os: linux 78 | # sudo: false 79 | # env: TARGET="linuxarmv7l" OF_BRANCH="master" 80 | # cache: 81 | # directories: 82 | # - ~/rpi2_toolchain 83 | # - ~/firmware-master 84 | # - ~/archlinux 85 | 86 | # Linux ARM7, OF stable: Not supported yet 87 | # - os: linux 88 | # sudo: false 89 | # env: TARGET="linuxarmv7l" OF_BRANCH="stable" 90 | # cache: 91 | # directories: 92 | # - ~/rpi2_toolchain 93 | # - ~/firmware-master 94 | # - ~/archlinux 95 | 96 | 97 | # Emscripten, OF master: Uncomment following lines to enable 98 | # - os: linux 99 | # sudo: false 100 | # env: TARGET="emscripten" OF_BRANCH="master" 101 | # addons: 102 | # apt: 103 | # sources: 104 | # - ubuntu-toolchain-r-test 105 | # packages: 106 | # - libstdc++6 107 | 108 | 109 | # Emscripten, OF stable: Not supported yet 110 | # - os: linux 111 | # sudo: false 112 | # env: TARGET="emscripten" OF_BRANCH="stable" 113 | # addons: 114 | # apt: 115 | # sources: 116 | # - ubuntu-toolchain-r-test 117 | # packages: 118 | # - libstdc++6 119 | 120 | 121 | # iOS, OF master: Not supported yet 122 | # - os: osx 123 | # osx_image: xcode8 124 | # compiler: clang 125 | # env: TARGET="ios" OF_BRANCH="master" 126 | 127 | 128 | # iOS, OF stable: Not supported yet 129 | # - os: osx 130 | # osx_image: xcode8 131 | # compiler: clang 132 | # env: TARGET="ios" OF_BRANCH="stable" 133 | 134 | 135 | # tvOS, OF master: Not supported yet 136 | # - os: osx 137 | # osx_image: xcode8 138 | # compiler: clang 139 | # env: TARGET="tvos" OF_BRANCH="master" 140 | 141 | 142 | # tvOS, OF stable: Not supported yet 143 | # - os: osx 144 | # osx_image: xcode8 145 | # compiler: clang 146 | # env: TARGET="tvos" OF_BRANCH="stable" 147 | 148 | 149 | # Android armv7, OF master: Uncomment following lines to enable 150 | # - os: linux 151 | # sudo: false 152 | # env: TARGET="android" OPT="armv7" OF_BRANCH="master" 153 | # cache: 154 | # directories: 155 | # - ~/android-ndk-r12b 156 | 157 | 158 | # Android armv7, OF stable: Not supported yet 159 | # - os: linux 160 | # sudo: false 161 | # env: TARGET="android" OPT="armv7" OF_BRANCH="stable" 162 | # cache: 163 | # directories: 164 | # - ~/android-ndk-r12b 165 | 166 | 167 | # Android x86, OF master: Uncomment following lines to enable 168 | # - os: linux 169 | # sudo: false 170 | # env: TARGET="android" OPT="x86" OF_BRANCH="master" 171 | # cache: 172 | # directories: 173 | # - ~/android-ndk-r12b 174 | 175 | 176 | # Android x86, OF stable: Not supported yet 177 | # - os: linux 178 | # sudo: false 179 | # env: TARGET="android" OPT="x86" OF_BRANCH="stable" 180 | # cache: 181 | # directories: 182 | # - ~/android-ndk-r12b 183 | 184 | 185 | install: 186 | - cd ~ 187 | - git clone --depth=1 --branch=feature/uri.cat https://github.com/armadillu/openFrameworks 188 | - cd openFrameworks 189 | - travis_wait 45 scripts/ci/addons/install.sh 190 | 191 | script: 192 | - ccache -z 193 | - ccache -s 194 | - if [ "$TARGET" = "osx" ]; then 195 | CXX="ccache clang++"; 196 | CC="ccache clang"; 197 | else 198 | CXX="ccache g++"; 199 | CC="ccache gcc"; 200 | fi 201 | - ccache -z 202 | - ccache -s 203 | - if [ "$TARGET" = "osx" ]; then 204 | CXX="ccache clang++"; 205 | CC="ccache clang"; 206 | else 207 | CXX="ccache g++"; 208 | CC="ccache gcc"; 209 | fi 210 | - travis_wait 30 scripts/ci/addons/build.sh 211 | - ccache -s 212 | 213 | - ccache -s 214 | 215 | git: 216 | depth: 10 217 | -------------------------------------------------------------------------------- /example/config.make: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # CONFIGURE PROJECT MAKEFILE (optional) 3 | # This file is where we make project specific configurations. 4 | ################################################################################ 5 | 6 | ################################################################################ 7 | # OF ROOT 8 | # The location of your root openFrameworks installation 9 | # (default) OF_ROOT = ../../.. 10 | ################################################################################ 11 | # OF_ROOT = ../../.. 12 | 13 | ################################################################################ 14 | # PROJECT ROOT 15 | # The location of the project - a starting place for searching for files 16 | # (default) PROJECT_ROOT = . (this directory) 17 | # 18 | ################################################################################ 19 | # PROJECT_ROOT = . 20 | 21 | ################################################################################ 22 | # PROJECT SPECIFIC CHECKS 23 | # This is a project defined section to create internal makefile flags to 24 | # conditionally enable or disable the addition of various features within 25 | # this makefile. For instance, if you want to make changes based on whether 26 | # GTK is installed, one might test that here and create a variable to check. 27 | ################################################################################ 28 | # None 29 | 30 | ################################################################################ 31 | # PROJECT EXTERNAL SOURCE PATHS 32 | # These are fully qualified paths that are not within the PROJECT_ROOT folder. 33 | # Like source folders in the PROJECT_ROOT, these paths are subject to 34 | # exlclusion via the PROJECT_EXLCUSIONS list. 35 | # 36 | # (default) PROJECT_EXTERNAL_SOURCE_PATHS = (blank) 37 | # 38 | # Note: Leave a leading space when adding list items with the += operator 39 | ################################################################################ 40 | # PROJECT_EXTERNAL_SOURCE_PATHS = 41 | 42 | ################################################################################ 43 | # PROJECT EXCLUSIONS 44 | # These makefiles assume that all folders in your current project directory 45 | # and any listed in the PROJECT_EXTERNAL_SOURCH_PATHS are are valid locations 46 | # to look for source code. The any folders or files that match any of the 47 | # items in the PROJECT_EXCLUSIONS list below will be ignored. 48 | # 49 | # Each item in the PROJECT_EXCLUSIONS list will be treated as a complete 50 | # string unless teh user adds a wildcard (%) operator to match subdirectories. 51 | # GNU make only allows one wildcard for matching. The second wildcard (%) is 52 | # treated literally. 53 | # 54 | # (default) PROJECT_EXCLUSIONS = (blank) 55 | # 56 | # Will automatically exclude the following: 57 | # 58 | # $(PROJECT_ROOT)/bin% 59 | # $(PROJECT_ROOT)/obj% 60 | # $(PROJECT_ROOT)/%.xcodeproj 61 | # 62 | # Note: Leave a leading space when adding list items with the += operator 63 | ################################################################################ 64 | # PROJECT_EXCLUSIONS = 65 | 66 | ################################################################################ 67 | # PROJECT LINKER FLAGS 68 | # These flags will be sent to the linker when compiling the executable. 69 | # 70 | # (default) PROJECT_LDFLAGS = -Wl,-rpath=./libs 71 | # 72 | # Note: Leave a leading space when adding list items with the += operator 73 | ################################################################################ 74 | 75 | # Currently, shared libraries that are needed are copied to the 76 | # $(PROJECT_ROOT)/bin/libs directory. The following LDFLAGS tell the linker to 77 | # add a runtime path to search for those shared libraries, since they aren't 78 | # incorporated directly into the final executable application binary. 79 | # TODO: should this be a default setting? 80 | # PROJECT_LDFLAGS=-Wl,-rpath=./libs 81 | 82 | ################################################################################ 83 | # PROJECT DEFINES 84 | # Create a space-delimited list of DEFINES. The list will be converted into 85 | # CFLAGS with the "-D" flag later in the makefile. 86 | # 87 | # (default) PROJECT_DEFINES = (blank) 88 | # 89 | # Note: Leave a leading space when adding list items with the += operator 90 | ################################################################################ 91 | # PROJECT_DEFINES = 92 | # PROJECT_DEFINES = USE_OFX_HISTORYPLOT 93 | # PROJECT_DEFINES += USE_OFX_FONTSTASH 94 | 95 | ################################################################################ 96 | # PROJECT CFLAGS 97 | # This is a list of fully qualified CFLAGS required when compiling for this 98 | # project. These CFLAGS will be used IN ADDITION TO the PLATFORM_CFLAGS 99 | # defined in your platform specific core configuration files. These flags are 100 | # presented to the compiler BEFORE the PROJECT_OPTIMIZATION_CFLAGS below. 101 | # 102 | # (default) PROJECT_CFLAGS = (blank) 103 | # 104 | # Note: Before adding PROJECT_CFLAGS, note that the PLATFORM_CFLAGS defined in 105 | # your platform specific configuration file will be applied by default and 106 | # further flags here may not be needed. 107 | # 108 | # Note: Leave a leading space when adding list items with the += operator 109 | ################################################################################ 110 | # PROJECT_CFLAGS = 111 | 112 | ################################################################################ 113 | # PROJECT OPTIMIZATION CFLAGS 114 | # These are lists of CFLAGS that are target-specific. While any flags could 115 | # be conditionally added, they are usually limited to optimization flags. 116 | # These flags are added BEFORE the PROJECT_CFLAGS. 117 | # 118 | # PROJECT_OPTIMIZATION_CFLAGS_RELEASE flags are only applied to RELEASE targets. 119 | # 120 | # (default) PROJECT_OPTIMIZATION_CFLAGS_RELEASE = (blank) 121 | # 122 | # PROJECT_OPTIMIZATION_CFLAGS_DEBUG flags are only applied to DEBUG targets. 123 | # 124 | # (default) PROJECT_OPTIMIZATION_CFLAGS_DEBUG = (blank) 125 | # 126 | # Note: Before adding PROJECT_OPTIMIZATION_CFLAGS, please note that the 127 | # PLATFORM_OPTIMIZATION_CFLAGS defined in your platform specific configuration 128 | # file will be applied by default and further optimization flags here may not 129 | # be needed. 130 | # 131 | # Note: Leave a leading space when adding list items with the += operator 132 | ################################################################################ 133 | # PROJECT_OPTIMIZATION_CFLAGS_RELEASE = 134 | # PROJECT_OPTIMIZATION_CFLAGS_DEBUG = 135 | 136 | ################################################################################ 137 | # PROJECT COMPILERS 138 | # Custom compilers can be set for CC and CXX 139 | # (default) PROJECT_CXX = (blank) 140 | # (default) PROJECT_CC = (blank) 141 | # Note: Leave a leading space when adding list items with the += operator 142 | ################################################################################ 143 | # PROJECT_CXX = 144 | # PROJECT_CC = 145 | -------------------------------------------------------------------------------- /src/ofxTimeMeasurementsMacros.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ofxTimeMeasurementsMacros.h 3 | * emptyExample 4 | * 5 | * Created by Oriol Ferrer Mesià on 28/01/11. 6 | * Copyright 2011 uri.cat. All rights reserved. 7 | * 8 | */ 9 | 10 | #pragma once 11 | 12 | #if( defined(TARGET_OSX) || defined(TARGET_LINUX)) 13 | #include 14 | #endif 15 | 16 | #ifdef TARGET_WIN32 17 | #define AUTOMAGICAL_FUNC_NAME __FUNCTION__ 18 | #else 19 | #define AUTOMAGICAL_FUNC_NAME (demangledTypeInfoName(typeid(this)) + "::" + __FUNCTION__) 20 | #endif 21 | 22 | 23 | //you can define "TIME_MEASUREMENTS_DISABLED" in your project pre-processor macros to ENTIRELY disable time measurements 24 | #ifndef TIME_MEASUREMENTS_DISABLED 25 | 26 | #define TIME_SAMPLE_SETUP() (ofxTimeMeasurements::instance()->setup()) 27 | #define TIME_SAMPLE_SET_CONFIG_DIR(x) (ofxTimeMeasurements::instance()->setConfigsDir(x)) 28 | #define TIME_SAMPLE_SET_FRAMERATE(x) (ofxTimeMeasurements::instance()->setDesiredFrameRate(x)) 29 | 30 | #define TIME_SAMPLE_START(x, ...) if(ofxTimeMeasurements::instance()->startMeasuring(x, false, true)){ 31 | #define TIME_SAMPLE_STOP(x) }ofxTimeMeasurements::instance()->stopMeasuring(x, false) 32 | 33 | #define TIME_SAMPLE_START_ACC(x, ...) if(ofxTimeMeasurements::instance()->startMeasuring(x, true, true)){ 34 | #define TIME_SAMPLE_STOP_ACC(x) }ofxTimeMeasurements::instance()->stopMeasuring(x, true) 35 | 36 | #define TIME_SAMPLE_START_NOIF(x, ...) ofxTimeMeasurements::instance()->startMeasuring(x, false, false) 37 | #define TIME_SAMPLE_STOP_NOIF(x) ofxTimeMeasurements::instance()->stopMeasuring(x, false) 38 | 39 | #define TIME_SAMPLE_START_ACC_NOIF(x, ...) ofxTimeMeasurements::instance()->startMeasuring(x, true, false) 40 | #define TIME_SAMPLE_STOP_ACC_NOIF(x) ofxTimeMeasurements::instance()->stopMeasuring(x, true) 41 | 42 | #define TIME_SAMPLE_SET_DRAW_LOCATION(x,...)(ofxTimeMeasurements::instance()->setDrawLocation(x, ##__VA_ARGS__)) 43 | #define TIME_SAMPLE_GET_ENABLED() (ofxTimeMeasurements::instance()->getEnabled()) 44 | #define TIME_SAMPLE_SET_ENABLED(e) (ofxTimeMeasurements::instance()->setEnabled(e)) 45 | #define TIME_SAMPLE_ENABLE() (ofxTimeMeasurements::instance()->setEnabled(true)) 46 | #define TIME_SAMPLE_DISABLE() (ofxTimeMeasurements::instance()->setEnabled(false)) 47 | #define TIME_SAMPLE_SET_AVERAGE_RATE(x) (ofxTimeMeasurements::instance()->setTimeAveragePercent(x)) /* 1.0 means no averaging, 0.01 means each new sample only affects 1% on previous sample */ 48 | #define TIME_SAMPLE_DISABLE_AVERAGE() (ofxTimeMeasurements::instance()->setTimeAveragePercent(1)) 49 | #define TIME_SAMPLE_SET_PRECISION(x) (ofxTimeMeasurements::instance()->setMsPrecision(x)) /* how many precion digits to show on time measurements */ 50 | #define TIME_SAMPLE_GET_LAST_DURATION(x)(ofxTimeMeasurements::instance()->getLastDurationFor(x)) /* ms it took for last frame*/ 51 | #define TIME_SAMPLE_GET_AVG_DURATION(x) (ofxTimeMeasurements::instance()->getAvgDurationFor(x)) /* ms it took for last frame avgd*/ 52 | #define TIME_SAMPLE_SET_REMOVE_EXPIRED_THREADS(x) (ofxTimeMeasurements::instance()->setRemoveExpiredThreads(x)) 53 | #define TIME_SAMPLE_GET_INSTANCE() (ofxTimeMeasurements::instance()) 54 | 55 | //this is to add auto measurements on setup() 56 | //only to be used in OF >0.9, and in this case, only to be called from main(); 57 | #define TIME_SAMPLE_ADD_SETUP_HOOKS() (ofxTimeMeasurements::instance()->addSetupHooks()) 58 | 59 | //shorter macros for most of the above! 60 | 61 | #define TS_SETUP() (ofxTimeMeasurements::instance()->setup()) 62 | 63 | //time sample a whole method - ie TS(drawMonkeys()); 64 | #define TS(x) if(ofxTimeMeasurements::instance()->startMeasuring(#x, false)){ x; }ofxTimeMeasurements::instance()->stopMeasuring(#x, false) 65 | #define TS_ACC(x) if(ofxTimeMeasurements::instance()->startMeasuring(#x, true)){ x; }ofxTimeMeasurements::instance()->stopMeasuring(#x, true) 66 | 67 | //includes if(){ } caluse for you 68 | #define TS_START(x, ...) TIME_SAMPLE_START(x, ##__VA_ARGS__) 69 | #define TS_STOP(x) TIME_SAMPLE_STOP(x) 70 | #define TS_START_ACC(x, ... ) TIME_SAMPLE_START_ACC(x, ##__VA_ARGS__) 71 | #define TS_STOP_ACC(x) TIME_SAMPLE_STOP_ACC(x) 72 | 73 | 74 | //"no if clause" (NIF) timings, to be used in timings that are measured across several methods / frames 75 | #define TS_START_NIF(x, ...) TIME_SAMPLE_START_NOIF(x, ##__VA_ARGS__) 76 | #define TS_STOP_NIF(x) TIME_SAMPLE_STOP_NOIF(x) 77 | #define TS_START_ACC_NIF(x, ...) TIME_SAMPLE_START_ACC_NOIF(x, ##__VA_ARGS__) 78 | #define TS_STOP_ACC_NIF(x) TIME_SAMPLE_STOP_ACC_NOIF(x) 79 | 80 | //scope timings 81 | #define TS_SCOPE(x) auto scopeTiming = ofxTimeMeasurementsScoped(x, false) 82 | #define TS_SCOPE_ACC(x) auto scopeTiming = ofxTimeMeasurementsScoped(x, true) 83 | //automatic naming for this scoped time - will look like "Class::method()" 84 | #define TS_ASCOPE() auto scopeTiming = ofxTimeMeasurementsScoped(AUTOMAGICAL_FUNC_NAME, false) 85 | 86 | //auto-named timings 87 | #define TS_ASTART() if(ofxTimeMeasurements::instance()->startMeasuring(AUTOMAGICAL_FUNC_NAME, false, true)){ 88 | #define TS_ASTOP() }ofxTimeMeasurements::instance()->stopMeasuring(AUTOMAGICAL_FUNC_NAME, false) 89 | #define TS_ASTART_NIF() ofxTimeMeasurements::instance()->startMeasuring(AUTOMAGICAL_FUNC_NAME, false, false) 90 | #define TS_ASTOP_NIF() ofxTimeMeasurements::instance()->stopMeasuring(AUTOMAGICAL_FUNC_NAME, false) 91 | #define TS_ASTART_ACC() if(ofxTimeMeasurements::instance()->startMeasuring(AUTOMAGICAL_FUNC_NAME, true, true)){ 92 | #define TS_ASTOP_ACC() }ofxTimeMeasurements::instance()->stopMeasuring(AUTOMAGICAL_FUNC_NAME, true) 93 | #define TS_ASTART_ACC_NIF() ofxTimeMeasurements::instance()->startMeasuring(AUTOMAGICAL_FUNC_NAME, true, false) 94 | #define TS_ASTOP_ACC_NIF() ofxTimeMeasurements::instance()->stopMeasuring(AUTOMAGICAL_FUNC_NAME, true) 95 | 96 | //GL 97 | #ifndef TARGET_OPENGLES 98 | #define TSGL_START(x) if(ofxTimeMeasurements::instance()->startMeasuringGL(x)){ 99 | #define TSGL_STOP(x) }ofxTimeMeasurements::instance()->stopMeasuringGL(x); 100 | #define TSGL_START_NIF(x) ofxTimeMeasurements::instance()->startMeasuringGL(x); 101 | #define TSGL_STOP_NIF(x) ofxTimeMeasurements::instance()->stopMeasuringGL(x); 102 | #else 103 | #define TSGL_START(x) 104 | #define TSGL_STOP(x) 105 | #define TSGL_START_NIF(x) 106 | #define TSGL_STOP_NIF(x) 107 | #endif 108 | 109 | #else 110 | 111 | #define TIME_SAMPLE_SETUP() 112 | #define TIME_SAMPLE_SET_CONFIG_DIR(x) 113 | #define TIME_SAMPLE_SET_FRAMERATE(x) 114 | 115 | #define TIME_SAMPLE_START(x, ...) 116 | #define TIME_SAMPLE_STOP(x) 117 | 118 | #define TIME_SAMPLE_START_ACC(x, ...) 119 | #define TIME_SAMPLE_STOP_ACC(x) 120 | 121 | #define TIME_SAMPLE_START_NOIF(x, ...) 122 | #define TIME_SAMPLE_STOP_NOIF(x) 123 | 124 | #define TIME_SAMPLE_START_ACC_NOIF(x, ...) 125 | #define TIME_SAMPLE_STOP_ACC_NOIF(x) 126 | 127 | #define TIME_SAMPLE_SET_DRAW_LOCATION(x,...) 128 | #define TIME_SAMPLE_GET_ENABLED() 129 | #define TIME_SAMPLE_SET_ENABLED(e) 130 | #define TIME_SAMPLE_ENABLE() 131 | #define TIME_SAMPLE_DISABLE() 132 | #define TIME_SAMPLE_SET_AVERAGE_RATE(x) 133 | #define TIME_SAMPLE_DISABLE_AVERAGE() 134 | #define TIME_SAMPLE_SET_PRECISION(x) 135 | #define TIME_SAMPLE_GET_LAST_DURATION(x) 136 | #define TIME_SAMPLE_GET_AVG_DURATION(x) 137 | #define TIME_SAMPLE_SET_REMOVE_EXPIRED_THREADS(x) 138 | #define TIME_SAMPLE_GET_INSTANCE() 139 | 140 | #define TIME_SAMPLE_ADD_SETUP_HOOKS() 141 | 142 | #define TS_SETUP() 143 | #define TS(x) 144 | #define TS_ACC(x) 145 | 146 | #define TS_START(x, ...) 147 | #define TS_STOP(x) 148 | #define TS_START_ACC(x, ... ) 149 | #define TS_STOP_ACC(x) 150 | 151 | #define TS_START_NIF(x, ...) 152 | #define TS_STOP_NIF(x) 0 /*usually retuns a timing*/ 153 | #define TS_START_ACC_NIF(x, ...) 154 | #define TS_STOP_ACC_NIF(x) 155 | 156 | //scope timings 157 | #define TS_SCOPE(x) 158 | #define TS_SCOPE_ACC(x) 159 | #define TS_ASCOPE() 160 | 161 | //auto named 162 | #define TS_ASTART() 163 | #define TS_ASTOP() 164 | #define TS_ASTART_NIF() 165 | #define TS_ASTOP_NIF() 166 | #define TS_ASTART_ACC() 167 | #define TS_ASTOP_ACC() 168 | #define TS_ASTART_ACC_NIF() 169 | #define TS_ASTOP_ACC_NIF() 170 | 171 | //GL 172 | #ifndef TARGET_OPENGLES 173 | #define TSGL_START(x) 174 | #define TSGL_STOP(x) 175 | #define TSGL_START_NIF(x) 176 | #define TSGL_STOP_NIF(x) 177 | #endif 178 | 179 | #endif 180 | 181 | //locations shortcuts 182 | #define TIME_SAMPLE_DRAW_LOC_TOP_LEFT TIME_MEASUREMENTS_TOP_LEFT 183 | #define TIME_SAMPLE_DRAW_LOC_BOTTOM_LEFT TIME_MEASUREMENTS_BOTTOM_LEFT 184 | #define TIME_SAMPLE_DRAW_LOC_BOTTOM_RIGHT TIME_MEASUREMENTS_BOTTOM_RIGHT 185 | #define TIME_SAMPLE_DRAW_LOC_TOP_RIGHT TIME_MEASUREMENTS_TOP_RIGHT 186 | 187 | //auto measurement naming 188 | 189 | #if !defined(TARGET_WIN32) && !defined(TARGET_OS_IOS) 190 | inline std::string demangledTypeInfoName(const std::type_info&ti){ 191 | 192 | char demangleBuffer[128]; 193 | int status; 194 | size_t len = 128; 195 | abi::__cxa_demangle(ti.name(),(char*)&demangleBuffer, &len, &status); 196 | //note theres no need to free the returned char* as we are providing our own buffer 197 | std::string finalS = std::string(demangleBuffer); 198 | if(finalS.size() > 0){ 199 | finalS = finalS.substr(0, finalS.size() - 1); 200 | } 201 | return finalS; 202 | } 203 | #endif 204 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | # ofxTimeMeasurements 2 | 3 | [![Build Status](https://travis-ci.org/armadillu/ofxTimeMeasurements.svg?branch=master)](https://travis-ci.org/armadillu/ofxTimeMeasurements) 4 | [![Build status](https://ci.appveyor.com/api/projects/status/0cne779u0mdp8mvp/branch/master?svg=true)](https://ci.appveyor.com/project/armadillu/ofxremoteui/branch/master) 5 | 6 | Simple OpenFrameworks addon to easily measure execution times across any sections of your code. 7 | 8 | ![ofxTimeMeasurements anim](https://www.dropbox.com/s/y6nsin52sugpmms/ofxTimeMeasurementsAnim.gif?dl=1) 9 | 10 | ## 1. FEATURES 11 | 12 | * Measure execution time of any parts of your app 13 | * Enable / disable execution of code sections on the fly through the widget 14 | * Collapsable GUI, see only what matters at the time (saves on quit) 15 | * Execution times organized by Thread 16 | * Measure exact times, or averaged time across frames 17 | * Measure cumulative times (TS_START_ACC) for shared code sections 18 | * Highlight frequently used calls, slowly fade unused calls 19 | * update(), and draw() are automatically time sampled 20 | * Measure GL driver execution times; actually querying openGL with glBeginQuery(GL_TIME_ELAPSED_EXT) & co. 21 | * Optionally use [ofxMSATimer](https://github.com/obviousjim/ofxMSATimer) for higher precision (recommended on windows) for < OF_09 (not really needed nowadays). 22 | * Optionally use [ofxHistoryPlot](https://github.com/armadillu/ofxHistoryPlot) to show timings over time 23 | * Optionally use [ofxFontStash](https://github.com/armadillu/ofxFontStash) for rendering, which looks nicer and is much faster to draw than the default ofDrawBitmapFont. (see drawUiWithFontStash()) 24 | 25 | ## 2. QUICK START 26 | 27 | That's all you need to get basic measurements of update() and draw(); 28 | 29 | ```c++ 30 | #include "ofxTimeMeasurements.h" 31 | 32 | void setup(){ 33 | TIME_SAMPLE_SET_FRAMERATE(60.0f); //specify a target framerate 34 | } 35 | ``` 36 | 37 | ## 3. HOW TO MEASURE TIMES / PROFILE YOUR CODE 38 | 39 | ### 3.1 Standard Mode 40 | 41 | You can measure times across any code section with TS_START() - TS_STOP() 42 | 43 | ```c++ 44 | TS_START("measurement1"); 45 | //my code here 46 | TS_STOP("measurement1") 47 | ``` 48 | The ofxTimeMeasurements widget will list a line named "measurement1" showing how long it took for the section to execute. 49 | 50 | ### 3.2 Accumulation Mode 51 | 52 | You can accumulate the time spent across one frame over sections of your code. This can be handy when a methods is called several times over one frame. 53 | 54 | ```c++ 55 | for(int i = 0; i < 10; i++){ 56 | TS_START_ACC("acc measurement"); 57 | //do something here 58 | TS_STOP_ACC("acc measurement") 59 | } 60 | ``` 61 | If we were to use TS_START/STOP on the example above, ofxTimeMeasurements would only report the time spent on the last iteration of the for loop. By using TS_START_ACC / TS_STOP_ACC, it accumulates the time spent on each iteration of the loop, reporting the total time spent. 62 | 63 | 64 | ### 3.3 "No If" Mode (_NIF) 65 | 66 | ofxTimeMeasurements wraps your code around an if(){} clause. It does so to be able to disable code on the fly from its GUI. This can be problematic if you declare variables inside a measurement, as they will not be in scope outside the measurement. If that's the case, you use the _NIF extension in your macros, this way your code will not be wrapped around an if(){} clause. The only drawback is that you will not be able to enable/disable that code section from the GUI. 67 | 68 | ```c++ 69 | TS_START_NIF("nif"); 70 | int a = 0; 71 | TS_STOP_NIF("nif"); 72 | 73 | a = 1; //we can now access the variable declared in the TM scope. 74 | ``` 75 | 76 | You can also use both NIF and ACC, see TS_START_ACC_NIF() and family. 77 | 78 | ### 3.4 Scope Mode 79 | 80 | This mode measures execution times in a particular scope. It creates a temporary minimal object that will start measuring when its constructor is called, and it will stop measuring when its destructor is called. 81 | 82 | ```c++ 83 | { 84 | TS_SCOPE("myTime"); 85 | doSomething(); 86 | doSomethingElse(); 87 | } 88 | ``` 89 | The example above will report the time it takes for doSomething() and doSomethingElse() to execute. The advantage here is that only one ofxTimeMeasurements call is needed to measure time. 90 | 91 | ### 3.5 TS() Mode 92 | 93 | TS() is a very convenient ultra-short macro to measure the time a single method takes to execute. This will show up as "myMethod()" on your measurements widget. 94 | 95 | ```c++ 96 | TS(myMethod()); 97 | ``` 98 | 99 | ### 3.6 OpenGL Execution Times 100 | 101 | This will NOT measure the time it takes the CPU to execute the code between the start() and stop() methods, but the time it takes the GPU to execute all the OpenGL instructions issued between start() and stop(). 102 | 103 | It does so by sending ```glBeginQuery(GL_TIME_ELAPSED_EXT)``` and ```glEndQuery(GL_TIME_ELAPSED_EXT)``` OpenGL commands, and retrieving the time it actually took to render by querying OpenGL after the rendering has happened. This method was chosen vs the ```GL_TIMESTAMP``` based, bc it seems to work with OpenGL 2.0, vs the ```GL_TIMESTAMP``` one requiring OpenGL 3.3. 104 | 105 | ![OpenGL Timings](https://farm1.staticflickr.com/576/32429675235_3844e560bf_o_d.png) 106 | 107 | OpenGL timings will show in their own separate section named "OpenGL", similar to timings in different threads. They can be disabled the same way as CPU timings, and also averaged and plotted. 108 | 109 | ```c++ 110 | TSGL_START("FancyShader"); 111 | myFancyShader.run(); 112 | TSGL_STOP("FancyShader"); 113 | ``` 114 | 115 | 116 | ##### Some Caveats: 117 | * OpenGL timing measurements can't be nested, you can't start a measurement unless you stopped the previous one. It's still ok to measure several things each frame, but not nested. 118 | * It's ok to nest/mix these within standard "CPU" measurements. 119 | * There's also "no if" version of this measurement, see 3.3 to learn more. TSGL_START_NIF() and TSGL_STOP_NIF(). 120 | * Because the timings can only be known after the frame has been fully drawn, the timings appear 2-3 frames after measured. 121 | 122 | 123 | ## 4. KEYBOARD COMMANDS / INTERACTIVE MODE 124 | 125 | ofxTimeMeasurements responds to a few pre-defined keyboard commands: 126 | 127 | * OF_KEY_PAGE_DOWN to toggle all time measuring, and the drawing of the widget 128 | * 'A' to toggle time sample averaging. 129 | * 'T' when widget is visible (enabled) to get into interactive mode. 130 | * When in interactive mode, a "KEYBOARD COMMANDS" list is shown : basically on-screen instructions. 131 | ![](https://farm1.staticflickr.com/473/20007668446_d428e77cdf_o_d.png) 132 | 133 | Up / Down keys to select, Left / Right keys to expand/collapse the time measurements tree. 134 | 135 | 136 | ## 5. RANDOM NOTES 137 | 138 | #### 5.1 OVERHEAD 139 | 140 | ofxTimeMeasurements adds some overhead when measuring times; and it adds even more when drawing its widget. You can see how much it adds by pressing the "B" key, it will display two extra timings, the time it takes for the measurements to be taken, and the time it takes for the widget to be drawn. When ofxTimeMeasurements is disabled (by pressing PgDown or by calling TIME_SAMPLE_DISABLE() at setup), it barely adds any overhead (it doesn't measure anything when the widget is not drawn) but it does add a tiny bit as the calls to its methods are still going through. To entirely remove all ofxTimeMeasurements calls without actually removing them from your code, you can define "TIME_MEASUREMENTS_DISABLED" in your project pro-processor macros for it to be entirely bypassed at compile time. This will only work if you use the recommended MACRO based API; if you have code that gets the ofxTimeMeasurements instance ```ofxTimeMeasurements::instance()``` and operates with it, your project will not compile. 141 | 142 | #### 5.2 SAMPLE AVERAGING 143 | 144 | If times vary too much from frame to frame to be readable on the widget, you can enable sample averaging; if you do so, each new time sample will be blended with the previous one; obtaining a smoother reading. Keep in mind that several samples will be required to get accurate readings when using averaging. You can also completely disable averaging. 145 | 146 | ```c++ 147 | TIME_SAMPLE_SET_AVERAGE_RATE(0.01); //every new sample effects 1% of the value shown 148 | TIME_SAMPLE_DISABLE_AVERAGE(); //disable time sample averaging 149 | ``` 150 | 151 | #### 5.3 COLORS / THREADS 152 | 153 | The ofxTimeMeasurements widget can show measurements in different colored sections. The first section will always represent times measured in the main thread. Following sections show times measured other threads. ofxTimeMeasurements keeps a collapsable tree per each thread. 154 | 155 | ![](https://farm1.staticflickr.com/298/20034358155_b4a49742b7_o_d.png) 156 | 157 | Active Time Measurements will appear in a bright color if the corresponding code section has just been executed, and they will slowly fade to a darker color if that section of code is not accessed / measured. The final fading percentage and the fade speed are customizable. 158 | 159 | 160 | #### 5.4 CUSTOMIZATIONS 161 | Most key commands and ui colors are customizable, you only need to get a hold of the instance. Keep in mind that by doing that the use of the "TIME_MEASUREMENTS_DISABLED" macro might lead to compile errors. 162 | 163 | ```c++ 164 | TIME_SAMPLE_GET_INSTANCE()->setUIActivationKey('T'); 165 | TIME_SAMPLE_GET_INSTANCE()->setHighlightColor(ofColor::red); 166 | ``` 167 | 168 | #### 5.5 MEASURING setup() 169 | If you want the setup() call to be automatically measured, you need to at least call once ofxTimeMeasurements in your main.cpp, just before the ofRunApp() line. This is so that the ofxTimeMeasurements instance is allocated before setup() is called. Make sure you call it after ofSetupOpenGL() otherwise it might not be able to find its settings file, because ofToDataPath might not be set yet. 170 | 171 | 172 | ```c++ 173 | int main(){ 174 | ofSetupOpenGL(1680,1050, OF_WINDOW); 175 | TIME_SAMPLE_SET_FRAMERATE(60); 176 | ofRunApp(new testApp()); 177 | } 178 | ``` 179 | 180 | in OF v09, you need to call ```TIME_SAMPLE_ADD_SETUP_HOOKS()``` in your main() 181 | 182 | #### 5.6 PERCENTAGES 183 | The bars shown besides the ms counts represent how much of a full frame that code section takes. For example; on a 60fps app, if a code section takes 16.6ms, the bar will take the whole width. 184 | 185 | #### 5.7 TIMING RESOLUTION 186 | You can increase the resolution/accuracy of the time measurements by using [ofxMSATimer](https://github.com/obviousjim/ofxMSATimer). This is recommended on Windows, as the default OF timer resolution is not very high on that platform. To use ofxMSATimer you need to add the ofxMSATimer to the project, and define USE_MSA_TIMER in your project Pre-Processor macros. **This is not necessary anymore, OF uses an accurate timer without having to use ofxMSATimer.*** 187 | 188 | #### 5.8 USING ofxHistoryPlot 189 | 190 | If you add [ofxHistoryPlot](https://github.com/armadillu/ofxHistoryPlot) to your project, you will also be able to track your timings over time. To do so, add ofxHistoryPlot to your project, and ofxTimeMeasurements will automatically detect it and enable the use of time plots. * On Windows, you will still need to manually add USE_OFX_HISTORYPLOT to your project pre-processor macros for ofxHistoryPlot to be available. 191 | 192 | If you do so, when in interactive mode, you can press 'P' to toggle the plotting of the timings of the selected section over time. 193 | 194 | ![img](https://farm1.staticflickr.com/510/19847290279_4b9761ff4d_o_d.png) 195 | 196 | Measurements that are being plotted will show a colored label on the left side, matching the color of the plot. You can plot as many measurements as you like simultaneously; you can press "G" from the interactive menu to toggle stacking the plots or overlaying them all in the same area. 197 | 198 | #### 5.9 USING ofxFontStash 199 | 200 | If you include [ofxFontStash](https://github.com/armadillu/ofxFontStash) in your project, you can use it to draw the widget with any font of your liking. FontStash is faster at drawing text than ofDrawBitmapString(), which is what ofxTimeMeasurements uses by default. Define USE_OFX_FONTSTASH in your project's PreProcessor Macros, and call drawUiWithFontStash() supplying a monospaced ttf font. 201 | 202 | ```c++ 203 | TIME_SAMPLE_GET_INSTANCE()->setUiScale( 2.0 ); //x2 the size for 4k screens 204 | TIME_SAMPLE_GET_INSTANCE()->drawUiWithFontStash("VeraMono.ttf"); 205 | ``` 206 | 207 | Another perk of using ofxFontStash is that you can set a custom font/UI scale. Look for setUiScale(); but make sure you set the UI Scale before you supply the TTF font. 208 | 209 | This can be useful on retina/4k screens to make the widget more legible, as shown on the screenshot below. 210 | ![img](https://farm1.staticflickr.com/533/20034108485_fdaa20bd72_o_d.png) 211 | 212 | 213 | ## LICENSE 214 | ofxTimeMeasurements is made available under the [MIT](http://opensource.org/licenses/MIT) license. 215 | -------------------------------------------------------------------------------- /src/ofxTimeMeasurements.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ofxTimeMeasurements.h 3 | * emptyExample 4 | * 5 | * Created by Oriol Ferrer Mesià on 28/01/11. 6 | * Copyright 2011 uri.cat. All rights reserved. 7 | * 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "ofMain.h" 13 | #include "MinimalTree.h" 14 | #include 15 | #include "GL_Measurement.h" 16 | 17 | 18 | #if __cplusplus>=201103L || defined(_MSC_VER) 19 | #include 20 | #include 21 | #else 22 | #include 23 | using std::tr1::unordered_map; 24 | #endif 25 | 26 | 27 | #if defined(__has_include) /*llvm only - query about header files being available or not*/ 28 | #if __has_include("ofxHistoryPlot.h") 29 | #ifndef USE_OFX_HISTORYPLOT 30 | #define USE_OFX_HISTORYPLOT 31 | #endif 32 | #endif 33 | #endif 34 | 35 | #ifdef USE_OFX_HISTORYPLOT 36 | #include "ofxHistoryPlot.h" 37 | #endif 38 | 39 | // ofxFontStash /////////////////////////////////////////////////////////////////////////////////// 40 | 41 | #if defined(__has_include) /*llvm only - query about header files being available or not*/ 42 | #if __has_include("ofxFontStash.h") && !defined(DISABLE_AUTO_FIND_FONSTASH_HEADERS) 43 | #ifndef USE_OFX_FONTSTASH 44 | #define USE_OFX_FONTSTASH 45 | #endif 46 | #endif 47 | #endif 48 | 49 | #ifdef USE_OFX_FONTSTASH 50 | #include "ofxFontStash.h" 51 | #endif 52 | 53 | // ofxFontStash2 ////////////////////////////////////////////////////////////////////////////////// 54 | 55 | #if defined(__has_include) /*llvm only - query about header files being available or not*/ 56 | #if __has_include("ofxFontStash2.h") && !defined(DISABLE_AUTO_FIND_FONSTASH_HEADERS) 57 | #ifndef USE_OFX_FONTSTASH2 58 | #define USE_OFX_FONTSTASH2 59 | #endif 60 | #endif 61 | #endif 62 | 63 | #ifdef USE_OFX_FONTSTASH2 64 | #include "ofxFontStash2.h" 65 | #endif 66 | 67 | /////////////////////////////////////////////////////////////////////////////////////////////////// 68 | 69 | #include "ofxTimeMeasurementsMacros.h" 70 | 71 | #define TM_GET_MICROS() std::chrono::time_point_cast(std::chrono::high_resolution_clock::now()).time_since_epoch().count() 72 | 73 | 74 | #define TIME_MEASUREMENTS_LINE_HEIGHT (14) 75 | #define TIME_MEASUREMENTS_EDGE_GAP_H (5 * uiScale) 76 | #define TIME_MEASUREMENTS_EDGE_GAP_V (5 * uiScale) 77 | 78 | #define TIME_MEASUREMENTS_SETUP_KEY "setup()" 79 | #define TIME_MEASUREMENTS_UPDATE_KEY "update()" 80 | #define TIME_MEASUREMENTS_DRAW_KEY "draw()" 81 | #define TIME_MEASUREMENTS_KEYPRESSED_KEY "keyPressed()" 82 | #define TIME_MEASUREMENTS_KEYRELEASED_KEY "keyReleased()" 83 | 84 | #define TIME_MEASUREMENTS_GLOBAL_TOGGLE_KEY (OF_KEY_PAGE_DOWN) 85 | #define TIME_MEASUREMENTS_INTERACT_KEY 'T' 86 | #define TIME_MEASUREMENTS_TOGGLE_SAMPLE_KEY OF_KEY_RETURN 87 | 88 | #define TIME_MEASUREMENTS_SETTINGS_FILENAME (configsDir + "/" + "ofxTimeMeasurements.settings") 89 | 90 | #ifndef TIME_MEASUREMENTS_DISABLED 91 | 92 | enum ofxTMDrawLocation{ 93 | TIME_MEASUREMENTS_TOP_LEFT = 0, 94 | TIME_MEASUREMENTS_TOP_RIGHT, 95 | TIME_MEASUREMENTS_BOTTOM_LEFT, 96 | TIME_MEASUREMENTS_BOTTOM_RIGHT, 97 | TIME_MEASUREMENTS_CUSTOM_LOCATION, 98 | TIME_MEASUREMENTS_NUM_DRAW_LOCATIONS 99 | }; 100 | 101 | class ofxTimeMeasurements { 102 | 103 | public : 104 | 105 | static ofxTimeMeasurements* instance(); 106 | 107 | void setup(); 108 | void setConfigsDir(std::string d); 109 | void setDesiredFrameRate(float fr); //forced to do this as I can't access desiredFrameRate once set with ofSetFrameRate 110 | //affects the % busy indicator 111 | 112 | //ifEnabled > if true, it means that measurement is wrapped around with an if() caluse 113 | bool startMeasuring(const std::string & ID, bool accumulate, bool ifEnabled = true); 114 | float stopMeasuring(const std::string & ID, bool accumulate); 115 | 116 | // GL (GPU render time) measurements 117 | #ifndef TARGET_OPENGLES 118 | bool startMeasuringGL(const std::string & name); 119 | void stopMeasuringGL(const std::string & name); 120 | #endif 121 | // end GL 122 | 123 | void setEnabled( bool enable ); 124 | bool getEnabled(); 125 | void setSavesSettingsOnExit(bool save) { savesSettingsOnExit = save; }; 126 | void setDrawLocation(ofxTMDrawLocation loc, ofVec2f p = ofVec2f()); //p only relevant if using TIME_MEASUREMENTS_CUSTOM_LOCATION 127 | ofxTMDrawLocation getDrawLocation(){return drawLocation;} 128 | void setMsPrecision(int digits); //how many decimals for the ms units 129 | void setTimeAveragePercent(double p); //[0..1] >> if set to 1.0, 100% of every new sample contributes to the average. 130 | //if set to 0.1, a new sample contributes 10% to the average 131 | void setDrawPercentageAsGraph(bool d){drawPercentageAsGraph = d;} 132 | float durationForID(const std::string & ID); 133 | void setBgColor(ofColor c){bgColor = c;} 134 | void setHighlightColor(ofColor c); 135 | void setThreadColors(const std::vector & tc); //supply your own thread color list 136 | 137 | //[0..1], 0.5 means inactive times show 50% darker than active ones 138 | void setIdleTimeColorFadePercent(float p){ idleTimeColorFadePercent = p;} 139 | 140 | //[0..1], 0.97 means that timeSample decreases 3% its life per frame 141 | void setIdleTimeDecay(float decay){ idleTimeColorDecay = decay;} 142 | void setDeadThreadTimeDecay(float decay); //when a thread is dead and to be removed from screen, extend a but its stay by using a slower decay 143 | 144 | //set keyboard command activation keys 145 | void setUIActivationKey(unsigned int k){activateKey = k;} //to show/hide the widget 146 | void setGlobalEnableDisableKey(unsigned int k){enableKey = k;} //to enable/disable widget interaction 147 | void setEnableDisableSectionKey(unsigned int k){toggleSampleKey = k;} //to enable/disable the selected time measurement 148 | 149 | 150 | float getWidth() const; 151 | float getHeight() const; 152 | 153 | float getLastDurationFor(const std::string & ID); //ms 154 | float getAvgDurationFor(const std::string & ID); //ms 155 | 156 | void setUiScale(float scale); 157 | float getUiScale(){return uiScale;} 158 | 159 | void setRemoveExpiredThreads(bool b){removeExpiredThreads = b;} 160 | void setRemoveExpiredTimings(bool r){removeExpiredTimings = r;} 161 | 162 | #if defined(USE_OFX_HISTORYPLOT) 163 | void setPlotHeight(int h){plotHeight = h;} 164 | void setPlotBaseY(float y){plotBaseY = y;} 165 | 166 | ///plot resolution is a scaling factor for the ofxHistoryPlot number of samples. 167 | ///if r = 1.0, plot will have same # of samples as pixels wide the window is. 168 | /// r = 0.5; plot will have half of that. 169 | void setPlotResolution(float r){plotResolution = r;} 170 | void setMaxNumPlotSamples(int nSamples){maxPlotSamples = nSamples;} 171 | void setDrawAllPlotsOnTopOfEachOther(bool doit){ allPlotsTogether = doit;} 172 | #endif 173 | float getPlotsHeight(); 174 | 175 | // Font render configs /////// 176 | void drawUiWithBitmapFont(); 177 | 178 | #if defined(USE_OFX_FONTSTASH) 179 | void drawUiWithFontStash(std::string fontPath, float fontSize = 13.0f /*good with VeraMono*/); 180 | ofxFontStash & getFont(){return font;} 181 | #endif 182 | 183 | #if defined(USE_OFX_FONTSTASH2) 184 | void drawUiWithFontStash2(std::string fontPath, float fontSize = 13.0f /*good with VeraMono*/); 185 | ofxFontStash2::Fonts & getFont2(){return font2;} 186 | #endif 187 | 188 | void enableInternalBenchmark(bool bench){internalBenchmark = bench;} 189 | 190 | void setAutoDraw(bool b){drawAuto = b;} 191 | void draw(int x, int y) ; 192 | 193 | void addEventHooks(ofCoreEvents* eventHooks = nullptr); 194 | void addSetupHooks(ofCoreEvents* eventHooks = nullptr); 195 | void removeEventHooks(ofCoreEvents* eventHooks); 196 | void removeSetupHooks(ofCoreEvents* eventHooks); 197 | 198 | static void drawSmoothFpsClock(float x, float y, float radius); 199 | 200 | private: 201 | 202 | ofxTimeMeasurements(); // use ofxTimeMeasurements::instance() instead! 203 | 204 | struct TimeMeasurementSettings{ 205 | bool visible; 206 | bool enabled; 207 | #if defined(USE_OFX_HISTORYPLOT) 208 | bool plotting; 209 | TimeMeasurementSettings(){plotting = false;} 210 | #endif 211 | }; 212 | 213 | typedef std::thread::id ThreadId; 214 | 215 | struct TimeMeasurement{ 216 | uint64_t microsecondsStart; 217 | uint64_t microsecondsStop; 218 | uint64_t microsecondsAccum; 219 | uint64_t duration; 220 | double avgDuration; 221 | bool measuring; 222 | bool error; 223 | bool updatedLastFrame; 224 | bool ifClause; //is this measurement under an if() clause or not? (TS_START vs TS_START_NIF) 225 | bool accumulating; //start stop doesnt reset the timing, unless you call stop with accum=true 226 | int numAccumulations; 227 | int frame; //used to compare start-stop calls frame, and see if its an across-frames measurement 228 | bool acrossFrames; 229 | std::string key; 230 | ThreadId thread; 231 | float life; 232 | bool isGL; //this is a GL measurement, some behaviors don't apply 233 | TimeMeasurementSettings settings; 234 | 235 | TimeMeasurement(); 236 | }; 237 | 238 | struct PrintedLine{ 239 | std::string key; 240 | std::string formattedKey; 241 | std::string time; 242 | std::string fullLine; 243 | bool shouldDrawPctGraph; 244 | bool isAccum; 245 | ofColor color; 246 | ofColor lineBgColor; 247 | ofColor plotColor; //if a > 0 , this measurement is being plotted 248 | float percentGraph; 249 | TimeMeasurement * tm; 250 | PrintedLine(){ tm = NULL; plotColor.a = 0; isAccum = false; percentGraph = 0.0f; } 251 | }; 252 | 253 | struct ThreadInfo{ 254 | //core::tree::iterator tit; //tree iterator, to keep track of which node are we measuring now 255 | //core::tree tree; 256 | 257 | MinimalTree tree; 258 | MinimalTree::Node* tit; 259 | 260 | ofColor color; 261 | int order; 262 | }; 263 | 264 | void _beforeSetup(ofEventArgs &d){startMeasuring(TIME_MEASUREMENTS_SETUP_KEY, false);}; 265 | void _afterSetup(ofEventArgs &d){stopMeasuring(TIME_MEASUREMENTS_SETUP_KEY, false);}; 266 | void _beforeUpdate(ofEventArgs &d){startMeasuring(TIME_MEASUREMENTS_UPDATE_KEY, false);}; 267 | void _afterUpdate(ofEventArgs &d){stopMeasuring(TIME_MEASUREMENTS_UPDATE_KEY, false);}; 268 | void _beforeDraw(ofEventArgs &d){startMeasuring(TIME_MEASUREMENTS_DRAW_KEY, false);}; 269 | void _afterDraw(ofEventArgs &d); 270 | void _beforeKeyPressed(ofKeyEventArgs &d){startMeasuring(TIME_MEASUREMENTS_KEYPRESSED_KEY, false);}; 271 | void _afterKeyPressed(ofKeyEventArgs &d){stopMeasuring(TIME_MEASUREMENTS_KEYPRESSED_KEY, false);}; 272 | 273 | // void _beforeKeyReleased(ofKeyEventArgs &d){startMeasuring(TIME_MEASUREMENTS_KEYRELEASED_KEY, false);}; 274 | // void _afterKeyReleased(ofKeyEventArgs &d){stopMeasuring(TIME_MEASUREMENTS_KEYRELEASED_KEY, false);}; 275 | 276 | void _appExited(ofEventArgs &e); 277 | bool _keyPressed(ofKeyEventArgs &e); 278 | 279 | void _windowResized(ofResizeEventArgs &e); 280 | 281 | void autoDraw(); 282 | void collapseExpand(const string & sel, bool colapse); 283 | void updateLongestLabel(); 284 | void loadSettings(); 285 | void saveSettings(); 286 | 287 | std::string formatTime(uint64_t microSeconds, int precision); 288 | std::string getTimeStringForTM(TimeMeasurement* tm); 289 | float getPctForTM(TimeMeasurement* tm); 290 | void drawString(const std::string & text, const float & x, const float & y); 291 | 292 | 293 | struct ThreadContainer{ 294 | ThreadId id; 295 | ThreadInfo * info; 296 | }; 297 | 298 | static bool compareThreadPairs(const ThreadContainer& l, const ThreadContainer& r){ 299 | return l.info->order < r.info->order; 300 | } 301 | 302 | static ofxTimeMeasurements* singleton; 303 | 304 | float desiredFrameRate; 305 | bool enabled; 306 | 307 | std::unordered_map times; 308 | std::unordered_map settings; //visible/not at startup 309 | 310 | std::unordered_map threadInfo; 311 | ThreadId mainThreadID; //usually NULL 312 | 313 | bool isMainThread(ThreadId tid){return tid == mainThreadID;} 314 | 315 | ThreadId getThreadID(){ 316 | return std::this_thread::get_id(); 317 | } 318 | 319 | std::vector drawLines; //what's drawn line by line 320 | 321 | double timeAveragePercent; 322 | bool averaging; 323 | int msPrecision; //number of decimals to show 324 | 325 | ofxTMDrawLocation drawLocation; 326 | ofVec2f customDrawLocation; 327 | int maxW; //for a text line 328 | int longestLabel; // 329 | 330 | ofColor bgColor; 331 | ofColor hilightColor; 332 | ofColor glColor; 333 | ofColor textColor; 334 | ofColor disabledTextColor; 335 | ofColor measuringColor; 336 | ofColor frozenColor; 337 | 338 | float idleTimeColorFadePercent; 339 | float idleTimeColorDecay; 340 | float deadThreadExtendedLifeDecSpeed; 341 | 342 | std::vector threadColorTable; 343 | int numThreads; 344 | 345 | std::string selection; 346 | int numVisible; 347 | 348 | unsigned int enableKey; //the whole addon 349 | unsigned int activateKey; 350 | unsigned int toggleSampleKey; //selected time sample 351 | 352 | bool freeze; //if enabled, ignore current timings and show the last one we had b4 freezing 353 | bool menuActive; 354 | 355 | ofMutex mutex; 356 | 357 | std::string configsDir; 358 | 359 | bool removeExpiredThreads; 360 | bool removeExpiredTimings; 361 | bool drawPercentageAsGraph; 362 | 363 | bool settingsLoaded; 364 | float uiScale; 365 | #if defined(USE_OFX_HISTORYPLOT) 366 | std::map plots; 367 | int plotHeight; 368 | int plotBaseY; 369 | int numAllocatdPlots; 370 | int numActivePlots; 371 | bool allPlotsTogether; 372 | 373 | ofxHistoryPlot* makeNewPlot(std::string name); 374 | float plotResolution; 375 | int maxPlotSamples; 376 | #endif 377 | 378 | enum FontRenderer{ 379 | RENDER_WITH_OF_BITMAP_FONT, 380 | RENDER_WITH_OFXFONTSTASH, 381 | RENDER_WITH_OFXFONTSTASH2 382 | }; 383 | 384 | FontRenderer fontRenderer = RENDER_WITH_OF_BITMAP_FONT; 385 | #ifdef USE_OFX_FONTSTASH 386 | std::string fontStashFile; 387 | ofxFontStash font; 388 | float fontSize; 389 | #endif 390 | 391 | #ifdef USE_OFX_FONTSTASH2 392 | std::string fontStashFile2; 393 | ofxFontStash2::Fonts font2; 394 | float fontSize2; 395 | #endif 396 | 397 | float charW; //to draw text flexibly with bitmap / fontstash 398 | float charH; 399 | 400 | bool drawAuto; 401 | unsigned char dimColorA; 402 | 403 | uint64_t currentFrameNum; 404 | 405 | //internal performance testing 406 | bool internalBenchmark; 407 | uint64_t wastedTimeThisFrame; 408 | uint64_t wastedTimeDrawingThisFrame; 409 | uint64_t wastedTimeAvg; 410 | uint64_t wastedTimeDrawingAvg; 411 | 412 | /// GL measurements 413 | #ifndef TARGET_OPENGLES 414 | std::unordered_map glTimes; 415 | std::string measuringGlLabel; 416 | const std::string glPrefix = "GL_"; 417 | bool glMeasurementMode = false; 418 | int threadIDGL = -1; 419 | void updateGLMeasurements(); 420 | #endif 421 | 422 | bool savesSettingsOnExit = true; 423 | 424 | void beginTextBatch(); 425 | void endTextBatch(); 426 | }; 427 | 428 | #include "ofxTimeMeasurementsScoped.h" 429 | 430 | #endif 431 | -------------------------------------------------------------------------------- /src/ofxTimeMeasurements.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ofxTimeMeasurements.cpp 3 | * emptyExample 4 | * 5 | * Created by Oriol Ferrer Mesià on 28/01/11. 6 | * Copyright 2011 uri.cat. All rights reserved. 7 | * 8 | */ 9 | 10 | #include "ofxTimeMeasurements.h" 11 | #include 12 | 13 | #ifndef TIME_MEASUREMENTS_DISABLED 14 | 15 | using namespace std; 16 | 17 | ofxTimeMeasurements* ofxTimeMeasurements::singleton = NULL; 18 | 19 | ofxTimeMeasurements::TimeMeasurement::TimeMeasurement(){ 20 | microsecondsAccum = 0; 21 | numAccumulations = 0; 22 | settings.visible = true; 23 | settings.enabled = true; 24 | life = 1.0f; 25 | accumulating = false; 26 | duration = 0; 27 | avgDuration = 0.0; 28 | isGL = false; 29 | } 30 | 31 | 32 | ofxTimeMeasurements::ofxTimeMeasurements(){ 33 | 34 | currentFrameNum = 0; 35 | uiScale = 1.0; 36 | desiredFrameRate = 60.0f; //assume 60 37 | enabled = true; 38 | timeAveragePercent = 0.05; 39 | averaging = false; 40 | msPrecision = 1; 41 | maxW = 27; 42 | drawAuto = true; 43 | internalBenchmark = false; 44 | 45 | #if defined(USE_OFX_HISTORYPLOT) 46 | plotHeight = 60; 47 | numAllocatdPlots = 0; 48 | plotBaseY = 0; 49 | plotResolution = 1; 50 | maxPlotSamples = 4096; 51 | numActivePlots = 0; 52 | allPlotsTogether = true; 53 | #endif 54 | 55 | mainThreadID = getThreadID(); 56 | 57 | bgColor = ofColor(0); 58 | hilightColor = ofColor(44,77,255) * 1.5; 59 | glColor = ofColor(255,148,79); //Gl logo color 60 | disabledTextColor = ofColor(255,0,128); 61 | measuringColor = ofColor(0,130,0); 62 | frozenColor = hilightColor * 1.5; 63 | 64 | dimColorA = 40; 65 | 66 | freeze = false; 67 | 68 | idleTimeColorFadePercent = 0.5; 69 | idleTimeColorDecay = 0.97; 70 | deadThreadExtendedLifeDecSpeed = 0.975; 71 | 72 | longestLabel = 0; 73 | selection = TIME_MEASUREMENTS_UPDATE_KEY; 74 | drawLocation = TIME_MEASUREMENTS_BOTTOM_RIGHT; 75 | numVisible = 0; 76 | 77 | enableKey = TIME_MEASUREMENTS_GLOBAL_TOGGLE_KEY; 78 | activateKey = TIME_MEASUREMENTS_INTERACT_KEY; 79 | toggleSampleKey = TIME_MEASUREMENTS_TOGGLE_SAMPLE_KEY; 80 | 81 | menuActive = false; 82 | drawLines.reserve(50); 83 | 84 | int numHues = 7; 85 | float brightness = 190.0f; 86 | for (int i = 0; i < numHues; i++) { 87 | float hue = fmod( i * (255.0f / float(numHues)), 255.0f); 88 | ofColor c = ofColor::fromHsb(hue, 255.0f, brightness, 255.0f); 89 | threadColorTable.push_back(c); 90 | } 91 | 92 | numThreads = 0; 93 | configsDir = "configs"; 94 | removeExpiredThreads = true; 95 | removeExpiredTimings = false; 96 | settingsLoaded = false; 97 | drawPercentageAsGraph = true; 98 | 99 | charW = 8; //ofBitmap font char w 100 | charH = TIME_MEASUREMENTS_LINE_HEIGHT; 101 | 102 | //used for internal benchmark ('B') 103 | wastedTimeThisFrame = wastedTimeAvg = 0; 104 | wastedTimeDrawingThisFrame = wastedTimeDrawingAvg = 0; 105 | 106 | addEventHooks(); 107 | 108 | // 109 | startMeasuring(TIME_MEASUREMENTS_UPDATE_KEY, false, false); 110 | stopMeasuring(TIME_MEASUREMENTS_UPDATE_KEY, false); 111 | } 112 | 113 | void ofxTimeMeasurements::addEventHooks(ofCoreEvents* eventHooks /*= nullptr*/) { 114 | 115 | if ( eventHooks == nullptr ){ 116 | eventHooks = &ofEvents(); 117 | }else{ 118 | removeEventHooks(&ofEvents()); 119 | } 120 | 121 | #if (OF_VERSION_MINOR >= 8) 122 | 123 | //-100 and +100 are to make sure we are always the first AND last at update and draw events, so we can sum everyone's times 124 | #if (OF_VERSION_MINOR < 9) 125 | ofAddListener(eventHooks->setup, this, &ofxTimeMeasurements::_beforeSetup, OF_EVENT_ORDER_BEFORE_APP - 100); 126 | ofAddListener(eventHooks->setup, this, &ofxTimeMeasurements::_afterSetup, OF_EVENT_ORDER_AFTER_APP + 100); 127 | #endif 128 | ofAddListener(eventHooks->update, this, &ofxTimeMeasurements::_beforeUpdate, OF_EVENT_ORDER_BEFORE_APP - 100); 129 | ofAddListener(eventHooks->update, this, &ofxTimeMeasurements::_afterUpdate, OF_EVENT_ORDER_AFTER_APP + 100); 130 | ofAddListener(eventHooks->draw, this, &ofxTimeMeasurements::_beforeDraw, OF_EVENT_ORDER_BEFORE_APP - 100); 131 | ofAddListener(eventHooks->draw, this, &ofxTimeMeasurements::_afterDraw, OF_EVENT_ORDER_AFTER_APP + 100); 132 | ofAddListener(eventHooks->keyPressed, this, &ofxTimeMeasurements::_beforeKeyPressed, OF_EVENT_ORDER_BEFORE_APP - 100); 133 | ofAddListener(eventHooks->keyPressed, this, &ofxTimeMeasurements::_afterKeyPressed, OF_EVENT_ORDER_AFTER_APP + 100); 134 | // ofAddListener(eventHooks->keyReleased, this, &ofxTimeMeasurements::_beforeKeyReleased, OF_EVENT_ORDER_BEFORE_APP - 100); 135 | // ofAddListener(eventHooks->keyReleased, this, &ofxTimeMeasurements::_afterKeyReleased, OF_EVENT_ORDER_AFTER_APP + 100); 136 | ofAddListener(eventHooks->keyPressed, this, &ofxTimeMeasurements::_keyPressed, OF_EVENT_ORDER_BEFORE_APP); 137 | ofAddListener(eventHooks->exit, this, &ofxTimeMeasurements::_appExited); //to save to xml 138 | #if defined(USE_OFX_HISTORYPLOT) 139 | ofAddListener(eventHooks->windowResized, this, &ofxTimeMeasurements::_windowResized); //to save to xml 140 | #endif 141 | #else 142 | #if (OF_VERSION == 7 && OF_VERSION_MINOR >= 2 ) 143 | ofAddListener(eventHooks->update, this, &ofxTimeMeasurements::_beforeUpdate); 144 | ofAddListener(eventHooks->update, this, &ofxTimeMeasurements::_afterUpdate); 145 | ofAddListener(eventHooks->draw, this, &ofxTimeMeasurements::_afterDraw); 146 | ofAddListener(eventHooks->draw, this, &ofxTimeMeasurements::_beforeDraw); 147 | #else 148 | ofAddListener(eventHooks->update, this, &ofxTimeMeasurements::_afterUpdate); 149 | ofAddListener(eventHooks->update, this, &ofxTimeMeasurements::_beforeUpdate); 150 | ofAddListener(eventHooks->draw, this, &ofxTimeMeasurements::_afterDraw); 151 | ofAddListener(eventHooks->draw, this, &ofxTimeMeasurements::_beforeDraw); 152 | #endif 153 | #endif 154 | } 155 | 156 | void ofxTimeMeasurements::addSetupHooks(ofCoreEvents* eventHooks /*= nullptr*/){ 157 | 158 | if ( eventHooks == nullptr ){ 159 | eventHooks = &ofEvents(); 160 | }else{ 161 | removeSetupHooks(&ofEvents()); 162 | } 163 | 164 | #if (OF_VERSION_MINOR >= 9) 165 | ofAddListener(eventHooks->setup, this, &ofxTimeMeasurements::_beforeSetup, OF_EVENT_ORDER_BEFORE_APP - 100); 166 | ofAddListener(eventHooks->setup, this, &ofxTimeMeasurements::_afterSetup, OF_EVENT_ORDER_AFTER_APP + 100); 167 | #endif 168 | } 169 | 170 | void ofxTimeMeasurements::removeEventHooks(ofCoreEvents* eventHooks) { 171 | 172 | #if (OF_VERSION_MINOR >= 8) 173 | //-100 and +100 are to make sure we are always the first AND last at update and draw events, so we can sum everyone's times 174 | #if (OF_VERSION_MINOR < 9) 175 | ofRemoveListener(eventHooks->setup, this, &ofxTimeMeasurements::_beforeSetup, OF_EVENT_ORDER_BEFORE_APP - 100); 176 | ofRemoveListener(eventHooks->setup, this, &ofxTimeMeasurements::_afterSetup, OF_EVENT_ORDER_AFTER_APP + 100); 177 | #endif 178 | 179 | ofRemoveListener(eventHooks->update, this, &ofxTimeMeasurements::_beforeUpdate, OF_EVENT_ORDER_BEFORE_APP - 100); 180 | ofRemoveListener(eventHooks->update, this, &ofxTimeMeasurements::_afterUpdate, OF_EVENT_ORDER_AFTER_APP + 100); 181 | ofRemoveListener(eventHooks->draw, this, &ofxTimeMeasurements::_beforeDraw, OF_EVENT_ORDER_BEFORE_APP - 100); 182 | ofRemoveListener(eventHooks->draw, this, &ofxTimeMeasurements::_afterDraw, OF_EVENT_ORDER_AFTER_APP + 100); 183 | ofRemoveListener(eventHooks->keyPressed, this, &ofxTimeMeasurements::_beforeKeyPressed, OF_EVENT_ORDER_BEFORE_APP - 100); 184 | ofRemoveListener(eventHooks->keyPressed, this, &ofxTimeMeasurements::_afterKeyPressed, OF_EVENT_ORDER_AFTER_APP + 100); 185 | //ofRemoveListener(eventHooks->.keyReleased, this, &ofxTimeMeasurements::_beforeKeyReleased, OF_EVENT_ORDER_BEFORE_APP - 100); 186 | //ofRemoveListener(eventHooks->.keyReleased, this, &ofxTimeMeasurements::_afterKeyReleased, OF_EVENT_ORDER_AFTER_APP + 100); 187 | ofRemoveListener(eventHooks->keyPressed, this, &ofxTimeMeasurements::_keyPressed, OF_EVENT_ORDER_BEFORE_APP - 200); 188 | ofRemoveListener(eventHooks->exit, this, &ofxTimeMeasurements::_appExited); //to save to xml 189 | 190 | #if defined(USE_OFX_HISTORYPLOT) 191 | ofRemoveListener(eventHooks->windowResized, this, &ofxTimeMeasurements::_windowResized); //to save to xml 192 | #endif 193 | #else 194 | #if (OF_VERSION == 7 && OF_VERSION_MINOR >= 2 ) 195 | ofRemoveListener(eventHooks->update, this, &ofxTimeMeasurements::_beforeUpdate); 196 | ofRemoveListener(eventHooks->update, this, &ofxTimeMeasurements::_afterUpdate); 197 | ofRemoveListener(eventHooks->draw, this, &ofxTimeMeasurements::_afterDraw); 198 | ofRemoveListener(eventHooks->draw, this, &ofxTimeMeasurements::_beforeDraw); 199 | #else 200 | ofRemoveListener(eventHooks->update, this, &ofxTimeMeasurements::_afterUpdate); 201 | ofRemoveListener(eventHooks->update, this, &ofxTimeMeasurements::_beforeUpdate); 202 | ofRemoveListener(eventHooks->draw, this, &ofxTimeMeasurements::_afterDraw); 203 | ofRemoveListener(eventHooks->draw, this, &ofxTimeMeasurements::_beforeDraw); 204 | #endif 205 | #endif 206 | } 207 | 208 | 209 | void ofxTimeMeasurements::removeSetupHooks(ofCoreEvents* eventHooks) { 210 | ofRemoveListener(eventHooks->setup, this, &ofxTimeMeasurements::_beforeSetup, OF_EVENT_ORDER_BEFORE_APP - 100); 211 | ofRemoveListener(eventHooks->setup, this, &ofxTimeMeasurements::_afterSetup, OF_EVENT_ORDER_AFTER_APP + 100); 212 | } 213 | 214 | 215 | void ofxTimeMeasurements::_windowResized(ofResizeEventArgs &e) { 216 | #if defined(USE_OFX_HISTORYPLOT) 217 | int hist = plotResolution * e.width; 218 | map::iterator it = plots.begin(); 219 | while(it != plots.end()){ 220 | if(it->second != NULL){ 221 | it->second->setMaxHistory(MIN(maxPlotSamples, hist)); 222 | } 223 | ++it; 224 | } 225 | #endif 226 | } 227 | 228 | 229 | void ofxTimeMeasurements::setThreadColors(const vector & tc){ 230 | threadColorTable.clear(); 231 | threadColorTable = tc; 232 | } 233 | 234 | 235 | ofxTimeMeasurements* ofxTimeMeasurements::instance(){ 236 | if (!singleton){ // Only allow one instance of class to be generated. 237 | singleton = new ofxTimeMeasurements(); 238 | } 239 | return singleton; 240 | } 241 | 242 | void ofxTimeMeasurements::setup(){} 243 | 244 | void ofxTimeMeasurements::setConfigsDir(string d){ 245 | configsDir = d; 246 | loadSettings(); //as we load settings on construction time, lets try re-load settings 247 | //a second time when a new configs location is given 248 | } 249 | 250 | 251 | void ofxTimeMeasurements::setDeadThreadTimeDecay(float decay){ 252 | deadThreadExtendedLifeDecSpeed = ofClamp(decay, idleTimeColorDecay, 1.0); 253 | } 254 | 255 | float ofxTimeMeasurements::getHeight() const{ 256 | if (!enabled) return 0; 257 | return (drawLines.size() + 1 ) * charH; 258 | } 259 | 260 | float ofxTimeMeasurements::getPlotsHeight(){ 261 | #if defined(USE_OFX_HISTORYPLOT) 262 | if(allPlotsTogether){ 263 | if(numActivePlots > 0) return plotHeight + numActivePlots * charH; // for text labelsH 264 | else return 0; 265 | }else{ 266 | return numActivePlots * plotHeight; 267 | } 268 | #endif 269 | return 0.0f; 270 | } 271 | 272 | 273 | float ofxTimeMeasurements::getLastDurationFor(const string & ID){ 274 | 275 | float r = 0.0f; 276 | unordered_map::iterator it; 277 | it = times.find(ID); 278 | if ( it != times.end() ){ //not found! 279 | r = it->second->duration / 1000.0f; //to ms 280 | } 281 | return r; 282 | } 283 | 284 | 285 | float ofxTimeMeasurements::getAvgDurationFor(const string & ID){ 286 | 287 | float r = 0.0f; 288 | unordered_map::iterator it; 289 | it = times.find(ID); 290 | if ( it != times.end() ){ //not found! 291 | r = it->second->avgDuration / 1000.0f; //to ms 292 | } 293 | return r; 294 | } 295 | 296 | void ofxTimeMeasurements::setHighlightColor(ofColor c){ 297 | hilightColor = c; 298 | threadInfo[mainThreadID].color = c; 299 | } 300 | 301 | #ifndef TARGET_OPENGLES 302 | bool ofxTimeMeasurements::startMeasuringGL(const string & name){ 303 | 304 | if (!enabled) return true; 305 | 306 | if(measuringGlLabel.size() > 0){ 307 | ofLogError("ofxTimeMeasurements") << "Can't startMeasuringGL()! You can't have nested GL measurements : " << name; 308 | return true; 309 | }else{ 310 | 311 | string glName = glPrefix + name; 312 | measuringGlLabel = glName; 313 | GL_Measurement * m; 314 | auto it = glTimes.find(glName); 315 | if (it == glTimes.end()){ //new measurement, we need to create an object for it 316 | m = new GL_Measurement(); 317 | m->init(); 318 | glTimes[glName] = m; 319 | // we fake 1 single measurement! note the horrible hack glMeasurementMode == true to modify the start & stop behavior 320 | // the whole thing is about prentending GL calls are in an immaginary thread, so that they are drawn separatelly 321 | // automatically. search for "if(glMeasurementMode)" to see all the nasty stuff necessary to make this work. 322 | // Note we only have 1 measurement, and we hijack the data object with the values obtained from the GL measurement. 323 | glMeasurementMode = true; 324 | TS_START_NIF(glName); 325 | TS_STOP_NIF(glName); 326 | glMeasurementMode = false; 327 | }else{ 328 | m = it->second; 329 | } 330 | TimeMeasurement * tm = times[glName]; 331 | if (tm->settings.enabled){ 332 | if (m->canStartMeasuring()){ 333 | m->start(); 334 | } 335 | } 336 | return tm->settings.enabled; 337 | } 338 | } 339 | 340 | void ofxTimeMeasurements::stopMeasuringGL(const string & name){ 341 | 342 | if (!enabled) return; 343 | 344 | string glName = glPrefix + name; 345 | 346 | if (measuringGlLabel != glName){ 347 | ofLogError("ofxTimeMeasurements") << "Can't stopMeasuringGL()! you haven't started measuring yet! : " << name; 348 | }else{ 349 | GL_Measurement * m; 350 | auto it = glTimes.find(glName); 351 | if (it == glTimes.end()){ //unkonwn measurement! mismatched start-stop! 352 | ofLogError("ofxTimeMeasurements") << "Can't stopMeasuringGL()! you haven't started measuring yet! : " << name; 353 | }else{ 354 | m = it->second; 355 | } 356 | TimeMeasurement * tm = times[glName]; 357 | if (tm->settings.enabled){ 358 | if (m->canStopMeasuring()){ 359 | m->stop(); 360 | } 361 | } 362 | measuringGlLabel = ""; 363 | } 364 | } 365 | #endif 366 | 367 | 368 | bool ofxTimeMeasurements::startMeasuring(const string & ID, bool accumulate, bool ifClause){ 369 | 370 | string localID = ID; 371 | if (!enabled) return true; 372 | 373 | uint64_t wastedTime; 374 | if(internalBenchmark){ 375 | wastedTime = TM_GET_MICROS(); 376 | } 377 | 378 | if (!settingsLoaded){ 379 | loadSettings(); 380 | settingsLoaded = true; 381 | } 382 | 383 | string threadName = "Thread"; 384 | ThreadId thread = getThreadID(); 385 | #ifndef TARGET_OPENGLES 386 | if (glMeasurementMode){ 387 | thread = std::thread::id(); 388 | } 389 | #endif 390 | bool bIsMainThread = isMainThread(thread); 391 | 392 | if(!bIsMainThread){ 393 | #ifdef TARGET_WIN32 394 | //threadName = ""; //FIXME: find out thread name on all platforms 395 | #else 396 | char buf[64]; 397 | int r = pthread_getname_np(pthread_self(), buf, 64); 398 | if ( r == 0 ){ 399 | threadName = string(buf); 400 | } 401 | #endif 402 | } 403 | 404 | mutex.lock(); 405 | 406 | unordered_map::iterator threadIt = threadInfo.find(thread); 407 | ThreadInfo * tinfo = NULL; 408 | MinimalTree *tree = NULL; 409 | 410 | bool newThread = threadIt == threadInfo.end(); 411 | 412 | if (newThread){ //new thread! 413 | 414 | //cout << "NewThread! " << ID << " " << &thread << endl; 415 | threadInfo[thread] = ThreadInfo(); 416 | tinfo = &threadInfo[thread]; 417 | tree = &tinfo->tree; //easier to read, tr is our tree from now on 418 | 419 | if (!bIsMainThread){ 420 | tinfo->color = threadColorTable[numThreads%(threadColorTable.size())]; 421 | numThreads++; 422 | }else{ //main thread 423 | tinfo->color = hilightColor; 424 | } 425 | tinfo->order = numThreads; 426 | #ifndef TARGET_OPENGLES 427 | if (glMeasurementMode){ 428 | threadIDGL = tinfo->order; 429 | tinfo->color = glColor; 430 | } 431 | #endif 432 | 433 | string tName = bIsMainThread ? "Main Thread" : ("T" + ofToString(tinfo->order) + ": " + threadName); 434 | #ifndef TARGET_OPENGLES 435 | if (glMeasurementMode){ 436 | tName = "OpenGL"; 437 | } 438 | #endif 439 | 440 | //init the iterator 441 | tinfo->tit = tinfo->tree.setup(tName); 442 | 443 | }else{ 444 | tinfo = &threadIt->second; 445 | tree = &(tinfo->tree); 446 | } 447 | 448 | if(tinfo->order > 0){ 449 | #ifndef TARGET_OPENGLES 450 | if (!glMeasurementMode){ 451 | #endif 452 | localID = "T" + ofToString(tinfo->order) + ":" + localID; 453 | #ifndef TARGET_OPENGLES 454 | } 455 | #endif 456 | } 457 | 458 | //see if we had an actual measurement, or its a new one 459 | unordered_map::iterator tit = times.find(localID); 460 | TimeMeasurement* t; 461 | 462 | if(tit == times.end()){ //if it wasnt in the tree, append it 463 | times[localID] = t = new TimeMeasurement(); 464 | unordered_map::iterator it2 = settings.find(localID); 465 | if (it2 != settings.end()){ 466 | t->settings = settings[localID]; 467 | 468 | if(tinfo->tit->getParent() == nullptr){ //if we are the tree root - we cant be hidden! 469 | t->settings.visible = true; 470 | } 471 | } 472 | //tinfo->tit = tinfo->tit.push_back(localID); 473 | tinfo->tit = tinfo->tit->addChildren(localID); 474 | 475 | }else{ 476 | MinimalTree::Node* temptit = tree->find(localID); 477 | if(temptit){ 478 | tinfo->tit = temptit; 479 | }else{ 480 | //cout << "gotcha!" << endl; 481 | //this is the rare case where we already had a measurement for this ID, 482 | //but it must be assigned to another old thread bc we cant find it! 483 | //so we re-add that ID for this thread and update the tree iterator 484 | tinfo->tit = tinfo->tit->addChildren(localID); 485 | } 486 | t = tit->second; 487 | } 488 | 489 | t->key = localID; 490 | t->life = 1.0f; // 491 | t->measuring = true; 492 | t->ifClause = ifClause; 493 | t->microsecondsStop = 0; 494 | t->accumulating = accumulate; 495 | if(accumulate) t->numAccumulations++; 496 | t->error = false; 497 | t->frame = currentFrameNum; 498 | t->updatedLastFrame = true; 499 | t->microsecondsStart = TM_GET_MICROS(); 500 | t->thread = thread; 501 | 502 | mutex.unlock(); 503 | 504 | if(internalBenchmark){ 505 | wastedTimeThisFrame += t->microsecondsStart - wastedTime; 506 | } 507 | 508 | return t->settings.enabled; 509 | } 510 | 511 | 512 | float ofxTimeMeasurements::stopMeasuring(const string & ID, bool accumulate){ 513 | 514 | if (!enabled) return 0.0f; 515 | float ret = 0.0f; 516 | string localID = ID; 517 | 518 | uint64_t timeNow = TM_GET_MICROS(); //get the time before the lock() to avoid affecting 519 | 520 | ThreadId thread = getThreadID(); 521 | #ifndef TARGET_OPENGLES 522 | if (glMeasurementMode){ 523 | thread = std::thread::id(); 524 | } 525 | #endif 526 | 527 | bool bIsMainThread = isMainThread(thread); 528 | 529 | mutex.lock(); 530 | 531 | unordered_map::iterator threadIt = threadInfo.find(thread); 532 | 533 | if(threadIt == threadInfo.end()){ //thread not found! 534 | mutex.unlock(); 535 | return 0.0f; 536 | } 537 | 538 | ThreadInfo & tinfo = threadIt->second; 539 | 540 | if(tinfo.order > 0){ 541 | #ifndef TARGET_OPENGLES 542 | if(!glMeasurementMode){ 543 | #endif 544 | localID = "T" + ofToString(tinfo.order) + ":" + localID; 545 | #ifndef TARGET_OPENGLES 546 | } 547 | #endif 548 | } 549 | 550 | if (tinfo.tit->getParent()){ 551 | tinfo.tit =tinfo.tit->getParent(); 552 | }else{ 553 | ofLogError("ofxTimeMeasurements") << "tree climbing too high up! (" << localID << ")"; 554 | } 555 | 556 | unordered_map::iterator it; 557 | it = times.find(localID); 558 | 559 | if ( it == times.end() ){ //not found! 560 | ofLogWarning("ofxTimeMeasurements") << "ID \""<< localID << "\" not found at stopMeasuring(). Make sure you called startMeasuring with that ID first."; 561 | }else{ 562 | 563 | TimeMeasurement* t = it->second; 564 | if ( t->measuring ){ 565 | t->measuring = false; 566 | t->thread = thread; 567 | t->error = false; 568 | t->acrossFrames = (bIsMainThread && t->frame != currentFrameNum); //we only care about across-frames in main thread 569 | t->microsecondsStop = timeNow; 570 | ret = t->duration = timeNow - t->microsecondsStart; 571 | if (!freeze) { 572 | if (!averaging) { 573 | t->avgDuration = t->duration; 574 | } 575 | else { 576 | t->avgDuration = (1.0f - timeAveragePercent) * t->avgDuration + t->duration * timeAveragePercent; 577 | } 578 | } 579 | if (accumulate && !freeze){ 580 | t->microsecondsAccum += t->avgDuration; 581 | } 582 | }else{ //wrong use, start first, then stop 583 | t->error = true; 584 | ofLogWarning("ofxTimeMeasurements") << "Can't stopMeasuring(" << localID << "). Make sure you called startMeasuring() with that ID first."; 585 | } 586 | } 587 | 588 | mutex.unlock(); 589 | 590 | if(internalBenchmark){ 591 | wastedTimeThisFrame += TM_GET_MICROS() - timeNow; 592 | } 593 | 594 | return ret / 1000.0f; //convert to ms 595 | } 596 | 597 | 598 | void ofxTimeMeasurements::setDrawLocation(ofxTMDrawLocation l, ofVec2f p){ 599 | drawLocation = l; 600 | customDrawLocation = p; 601 | } 602 | 603 | 604 | void ofxTimeMeasurements::_afterDraw(ofEventArgs &d){ 605 | stopMeasuring(TIME_MEASUREMENTS_DRAW_KEY, false); 606 | if(drawAuto){ 607 | autoDraw(); 608 | } 609 | }; 610 | 611 | 612 | void ofxTimeMeasurements::autoDraw(){ 613 | 614 | float yy = 0; 615 | #ifdef USE_OFX_HISTORYPLOT 616 | yy = getPlotsHeight(); 617 | #endif 618 | 619 | switch(drawLocation){ 620 | 621 | case TIME_MEASUREMENTS_TOP_LEFT: 622 | draw(TIME_MEASUREMENTS_EDGE_GAP_H,TIME_MEASUREMENTS_EDGE_GAP_V); 623 | break; 624 | case TIME_MEASUREMENTS_TOP_RIGHT: 625 | draw( ofGetWidth() / uiScale - getWidth() - TIME_MEASUREMENTS_EDGE_GAP_H, 626 | TIME_MEASUREMENTS_EDGE_GAP_V); 627 | break; 628 | case TIME_MEASUREMENTS_BOTTOM_LEFT: 629 | draw(TIME_MEASUREMENTS_EDGE_GAP_H, 630 | ofGetHeight() / uiScale - getHeight() - TIME_MEASUREMENTS_EDGE_GAP_V - yy); 631 | break; 632 | case TIME_MEASUREMENTS_BOTTOM_RIGHT: 633 | draw( ofGetWidth() / uiScale - getWidth() - TIME_MEASUREMENTS_EDGE_GAP_H, 634 | ofGetHeight() / uiScale - getHeight() - TIME_MEASUREMENTS_EDGE_GAP_V - yy); 635 | break; 636 | case TIME_MEASUREMENTS_CUSTOM_LOCATION: 637 | draw(customDrawLocation.x, customDrawLocation.y); 638 | break; 639 | default: 640 | draw(TIME_MEASUREMENTS_EDGE_GAP_H,TIME_MEASUREMENTS_EDGE_GAP_V); 641 | ofLogError("ofxTimeMeasurements") << "Unknown Draw Location!"; 642 | break; 643 | } 644 | } 645 | 646 | 647 | void ofxTimeMeasurements::updateLongestLabel(){ 648 | 649 | longestLabel = 0; 650 | for(size_t i = 0; i < drawLines.size(); i++ ){ 651 | 652 | TimeMeasurement *t = drawLines[i].tm; 653 | if (t){ 654 | if (t->settings.visible){ //kinda redundant ... 655 | int len = drawLines[i].formattedKey.length(); 656 | if (len > longestLabel){ 657 | longestLabel = len; 658 | } 659 | } 660 | } 661 | } 662 | } 663 | 664 | #ifndef TARGET_OPENGLES 665 | void ofxTimeMeasurements::updateGLMeasurements(){ 666 | for(auto & it : glTimes){ 667 | it.second->update(); 668 | TimeMeasurement * tm = times[it.first]; 669 | if (it.second->isMeasurementReady()){ 670 | it.second->acknowledgeMeasurement(); 671 | double meas = it.second->getMeasurement(); 672 | 673 | //ugly hack - inject the GL measurements in the internal TM data 674 | tm->duration = meas * 1000 ; //from ms to microseconds 675 | if (!freeze) { 676 | if(!averaging){ 677 | tm->avgDuration = tm->duration; 678 | }else{ 679 | tm->avgDuration = (1.0f - timeAveragePercent) * tm->avgDuration + tm->duration * timeAveragePercent; 680 | } 681 | } 682 | tm->isGL = true; 683 | tm->life = 1.0; 684 | } 685 | tm->ifClause = true; 686 | tm->updatedLastFrame = true; 687 | } 688 | } 689 | #endif 690 | 691 | void ofxTimeMeasurements::draw(int x, int y) { 692 | 693 | if (!enabled){ 694 | //drawString(ofToString(fr, msPrecision), 10, fontSize); 695 | return; 696 | } 697 | 698 | float fr = ofGetFrameRate(); 699 | uint64_t timeNow; 700 | if(internalBenchmark){ 701 | timeNow = TM_GET_MICROS(); 702 | } 703 | currentFrameNum = ofGetFrameNum(); 704 | if(currentFrameNum%120 == 60){ 705 | int newFrameRate = ofGetTargetFrameRate(); 706 | if(newFrameRate > 0.0f){ 707 | #if defined(USE_OFX_HISTORYPLOT) 708 | if(newFrameRate != desiredFrameRate){ 709 | for(auto p : plots){ 710 | if(p.second){ 711 | p.second->clearHorizontalGuides(); 712 | p.second->addHorizontalGuide(1000.0f/newFrameRate, ofColor(0,255,0, 128)); 713 | } 714 | } 715 | } 716 | #endif 717 | desiredFrameRate = newFrameRate; 718 | } 719 | } 720 | 721 | #ifndef TARGET_OPENGLES 722 | updateGLMeasurements(); 723 | #endif 724 | 725 | drawLines.clear(); 726 | float percentTotal = 0.0f; 727 | float timePerFrame = 1000.0f / desiredFrameRate; 728 | 729 | mutex.lock(); 730 | 731 | vector toResetUpdatedLastFrameFlag; 732 | 733 | //update time stuff, build draw lists 734 | for( unordered_map::iterator ii = times.begin(); ii != times.end(); ++ii ){ 735 | TimeMeasurement* t = ii->second; 736 | string key = ii->first; 737 | if(!t->measuring){ 738 | if (t->life > 0.01){ 739 | t->life *= idleTimeColorDecay; //decrease life 740 | }else{ //life decays very slow when very low 741 | t->life *= deadThreadExtendedLifeDecSpeed; //decrease life very slowly 742 | } 743 | } 744 | // if (!t->updatedLastFrame && averaging){ // if we didnt update that time, make it tend to zero slowly 745 | // t->avgDuration = (1.0f - timeAveragePercent) * t->avgDuration; 746 | // } 747 | toResetUpdatedLastFrameFlag.push_back(t); 748 | } 749 | 750 | unordered_map::iterator ii; 751 | vector expiredThreads; 752 | 753 | //lets make sure the Main Thread is always on top 754 | vector< ThreadContainer > sortedThreadList; 755 | 756 | for( ii = threadInfo.begin(); ii != threadInfo.end(); ++ii ){ //walk all thread trees 757 | ThreadContainer cont; 758 | cont.id = ii->first; 759 | cont.info = &ii->second; 760 | if (isMainThread(ii->first)){ //is main thread 761 | sortedThreadList.insert(sortedThreadList.begin(), cont); 762 | }else{ 763 | sortedThreadList.push_back(cont); 764 | } 765 | } 766 | std::sort(sortedThreadList.begin(), sortedThreadList.end(), compareThreadPairs); 767 | 768 | #if defined(USE_OFX_HISTORYPLOT) 769 | vector plotsToDraw; 770 | #endif 771 | 772 | for(size_t k = 0; k < sortedThreadList.size(); k++ ){ //walk all thread trees 773 | 774 | ThreadId thread = sortedThreadList[k].id; 775 | MinimalTree &tree = sortedThreadList[k].info->tree; 776 | 777 | ThreadInfo & tinfo = threadInfo[thread]; 778 | PrintedLine header; 779 | header.formattedKey = "+ " + tree.getRoot()->getData(); 780 | header.color = tinfo.color; 781 | header.lineBgColor = ofColor(header.color, dimColorA * 2); //header twice as alpha 782 | header.key = tree.getRoot()->getData(); //key for selection, is thread name 783 | drawLines.push_back(header); //add header to drawLines 784 | 785 | int numAlive = 0; 786 | int numAdded = 0; 787 | 788 | #if defined(USE_OFX_HISTORYPLOT) 789 | float winW = ofGetWidth(); 790 | #endif 791 | 792 | std::vector> allKeys; 793 | tree.getRoot()->getAllData(allKeys); 794 | 795 | for(auto & pair : allKeys){ 796 | 797 | MinimalTree::Node * node = pair.first; 798 | 799 | if(node->getParent()){ //skip root 800 | std::string key = node->getData(); 801 | int level = pair.second; 802 | 803 | TimeMeasurement * t = times[key]; 804 | if(t->thread == thread){ 805 | 806 | #if defined(USE_OFX_HISTORYPLOT) 807 | bool plotActive = false; 808 | ofxHistoryPlot* plot = plots[key]; 809 | if(plot){ 810 | if(t->settings.plotting){ 811 | if(t->updatedLastFrame){ 812 | //update plot res every now and then 813 | if(currentFrameNum%120 == 1) plot->setMaxHistory(MIN(maxPlotSamples, winW * plotResolution)); 814 | if (!freeze && t->settings.enabled) { 815 | if (t->accumulating) { 816 | plot->update(t->microsecondsAccum / 1000.0f); 817 | } 818 | else { 819 | plot->update(t->avgDuration / 1000.0f); 820 | } 821 | } 822 | } 823 | plotsToDraw.push_back(plot); 824 | plotActive = true; 825 | } 826 | } 827 | #endif 828 | 829 | bool visible = t->settings.visible; 830 | bool alive = t->life > 0.0001; 831 | if(alive){ 832 | numAlive++; 833 | } 834 | 835 | if (visible && (removeExpiredTimings ? alive : true)){ 836 | PrintedLine l; 837 | l.key = key; 838 | l.tm = t; 839 | l.lineBgColor = ofColor(tinfo.color, dimColorA); 840 | 841 | int depth = level; 842 | for(int i = 0; i < depth; ++i) l.formattedKey += " "; 843 | 844 | if (node->getNumChildren() == 0){ 845 | l.formattedKey += "-"; 846 | }else{ 847 | l.formattedKey += "+"; 848 | } 849 | string keyStr; 850 | if (!t->isGL){ 851 | keyStr = key; 852 | }else{ //lets remove the GL_ prefiix on display 853 | #ifndef TARGET_OPENGLES 854 | keyStr = key.substr(glPrefix.size(), key.size() - glPrefix.size()); 855 | #endif 856 | } 857 | l.formattedKey += keyStr + string(t->accumulating ? "[" + ofToString(t->numAccumulations)+ "]" : "" ); 858 | l.isAccum = t->accumulating; 859 | l.time = getTimeStringForTM(t); 860 | if(drawPercentageAsGraph){ 861 | l.percentGraph = getPctForTM(t); 862 | } 863 | 864 | l.color = tinfo.color * ((1.0 - idleTimeColorFadePercent) + idleTimeColorFadePercent * t->life); 865 | if (!t->settings.enabled){ 866 | if(t->ifClause){ 867 | l.color = disabledTextColor; 868 | }else{ 869 | l.color = disabledTextColor.getInverted(); 870 | } 871 | } 872 | 873 | #if defined(USE_OFX_HISTORYPLOT) 874 | if(plotActive){ 875 | l.plotColor = ofColor(plots[key]->getColor(), 200); 876 | } 877 | #endif 878 | 879 | if (menuActive && t->key == selection){ 880 | if(currentFrameNum%5 < 4){ 881 | l.color.invert(); 882 | l.lineBgColor = ofColor(tinfo.color, dimColorA * 1.5); 883 | } 884 | } 885 | 886 | drawLines.push_back(l); 887 | numAdded++; 888 | } 889 | 890 | //only update() and draw() count to the final % 891 | if(key == TIME_MEASUREMENTS_DRAW_KEY || key == TIME_MEASUREMENTS_UPDATE_KEY){ 892 | percentTotal += (t->avgDuration * 0.1f) / timePerFrame; 893 | } 894 | //reset accumulator 895 | t->accumulating = false; 896 | t->numAccumulations = 0; 897 | t->microsecondsAccum = 0; 898 | } 899 | } 900 | } 901 | 902 | #if defined(USE_OFX_HISTORYPLOT) 903 | numActivePlots = (int)plotsToDraw.size(); 904 | #endif 905 | 906 | if (numAlive == 0 && removeExpiredThreads){ 907 | //drop that whole section if all entries in it are not alive 908 | for(int i = 0; i < numAdded + 1; i++){ 909 | if(drawLines.size() > 0){ 910 | int delID = (int)drawLines.size() - 1; 911 | //clear selection if needed 912 | if (selection == drawLines[delID].key){ 913 | selection = TIME_MEASUREMENTS_UPDATE_KEY; 914 | } 915 | drawLines.erase(drawLines.begin() + delID); 916 | } 917 | } 918 | expiredThreads.push_back(thread); 919 | } 920 | } 921 | 922 | //delete expired threads 923 | for(size_t i = 0; i < expiredThreads.size(); i++){ 924 | unordered_map::iterator treeIt = threadInfo.find(expiredThreads[i]); 925 | if (treeIt != threadInfo.end()) threadInfo.erase(treeIt); 926 | } 927 | 928 | mutex.unlock(); 929 | 930 | updateLongestLabel(); 931 | 932 | //find headers 933 | int tempMaxW = -1; 934 | vector headerLocations; 935 | for(size_t i = 0; i < drawLines.size(); i++ ){ 936 | if (drawLines[i].tm){ //its a measurement 937 | //add padding to draw in columns 938 | for(int j = (int)drawLines[i].formattedKey.length(); j < longestLabel; j++){ 939 | drawLines[i].formattedKey += " "; 940 | } 941 | if (!drawLines[i].tm->error){ 942 | drawLines[i].shouldDrawPctGraph = true; 943 | drawLines[i].fullLine = drawLines[i].formattedKey + " " + drawLines[i].time; 944 | }else{ 945 | drawLines[i].shouldDrawPctGraph = false; 946 | drawLines[i].fullLine = drawLines[i].formattedKey + " Error!" ; 947 | } 948 | int len = (int)drawLines[i].fullLine.length(); 949 | if(len > tempMaxW) tempMaxW = len; 950 | if(drawLines[i].tm->measuring) drawLines[i].shouldDrawPctGraph = false; 951 | }else{ //its a header 952 | drawLines[i].fullLine = drawLines[i].formattedKey; 953 | drawLines[i].shouldDrawPctGraph = false; 954 | headerLocations.push_back(i); 955 | } 956 | } 957 | 958 | int numInstructionLines = 0; 959 | if(menuActive){ //add instructions line if menu active 960 | PrintedLine l; 961 | //title line 962 | l.color = hilightColor; 963 | l.lineBgColor = ofColor(hilightColor, dimColorA * 2); 964 | l.fullLine = " KEYBOARD COMMANDS "; //len = 23 965 | int numPad = 2 + ceil((getWidth() - charW * (23)) / charW); 966 | for(size_t i = 0; i < floor(numPad/2.0); i++ ) l.fullLine = "#" + l.fullLine; 967 | for(size_t i = 0; i < ceil(numPad/2.0); i++ ) l.fullLine += "#"; 968 | l.fullLine = " " + l.fullLine; 969 | drawLines.push_back(l); numInstructionLines++; 970 | //key command lines 971 | l.lineBgColor = ofColor(hilightColor, dimColorA); 972 | l.fullLine = " 'UP/DOWN' select measur."; drawLines.push_back(l); numInstructionLines++; 973 | l.fullLine = " 'LFT/RGHT' expand/collaps"; drawLines.push_back(l); numInstructionLines++; 974 | l.fullLine = " 'RET' toggle code section"; drawLines.push_back(l); numInstructionLines++; 975 | l.fullLine = " 'A' average measurements"; drawLines.push_back(l); numInstructionLines++; 976 | l.fullLine = " 'F' freeze measurements"; drawLines.push_back(l); numInstructionLines++; 977 | l.fullLine = " 'L' cycle widget location"; drawLines.push_back(l); numInstructionLines++; 978 | l.fullLine = " 'PG_DWN' en/disable addon"; drawLines.push_back(l); numInstructionLines++; 979 | l.fullLine = " 'V' expand all measur."; drawLines.push_back(l); numInstructionLines++; 980 | l.fullLine = " '+'/'-' GUI size"; drawLines.push_back(l); numInstructionLines++; 981 | #if defined USE_OFX_HISTORYPLOT 982 | l.fullLine = " 'P' plot selectd measur."; drawLines.push_back(l); numInstructionLines++; 983 | l.fullLine = " 'G' toggle plot grouping"; drawLines.push_back(l); numInstructionLines++; 984 | l.fullLine = " 'C' clear all plots"; drawLines.push_back(l); numInstructionLines++; 985 | #endif 986 | } 987 | 988 | maxW = tempMaxW; 989 | 990 | ofSetupScreen(); //mmmm---- 991 | ofPushStyle(); 992 | ofSetRectMode(OF_RECTMODE_CORNER); 993 | ofSetDrawBitmapMode(OF_BITMAPMODE_SIMPLE); 994 | ofEnableAlphaBlending(); 995 | 996 | ofPushMatrix(); 997 | ofScale(uiScale,uiScale); 998 | 999 | ofFill(); 1000 | 1001 | //draw all plots 1002 | #if defined(USE_OFX_HISTORYPLOT) 1003 | //int numCols = plotsToDraw.size() 1004 | 1005 | float highest = FLT_MIN; 1006 | int plotC = 0; 1007 | for(auto plot : plotsToDraw){ 1008 | if(allPlotsTogether){ //lets find the range that covers all the plots 1009 | float high = plot->getHighestValue(); 1010 | if (high > highest) highest = high; 1011 | plot->setDrawTitle(false); 1012 | if(plotC == 0){ 1013 | plot->setDrawBackground(true); 1014 | plot->setDrawGrid(true); 1015 | }else{ 1016 | plot->setDrawBackground(false); 1017 | plot->setDrawGrid(false); 1018 | } 1019 | plot->setShowSmoothedCurve(false); 1020 | }else{ 1021 | plot->setDrawTitle(true); 1022 | plot->setDrawBackground(true); 1023 | plot->setDrawGrid(true); 1024 | plot->setLowerRange(0); 1025 | plot->setShowSmoothedCurve(false); 1026 | plot->setDrawGrid(true); 1027 | } 1028 | plotC++; 1029 | } 1030 | 1031 | float canvasW = ofGetWidth() / uiScale; 1032 | float canvasH = ofGetHeight() / uiScale; 1033 | 1034 | if(allPlotsTogether && plotsToDraw.size()){ 1035 | ofSetColor(0, 230); 1036 | ofDrawRectangle(0, canvasH - plotHeight, canvasW , plotHeight ); 1037 | } 1038 | 1039 | ofSetColor(255); 1040 | for(size_t i = 0; i < plotsToDraw.size(); i++){ 1041 | int y = (plotBaseY == 0 ? canvasH : plotBaseY) - plotHeight * (i + 1) ; 1042 | if(allPlotsTogether){ 1043 | plotsToDraw[i]->setRange(0, highest); 1044 | y = ((plotBaseY == 0 ? canvasH : plotBaseY) - plotHeight) ; 1045 | } 1046 | plotsToDraw[i]->draw(0, y, canvasW , plotHeight); 1047 | if(!allPlotsTogether){ 1048 | ofSetColor(99); 1049 | if(i != plotsToDraw.size() -1){ 1050 | ofDrawLine(0, y, canvasW , y ); 1051 | } 1052 | } 1053 | } 1054 | 1055 | beginTextBatch(); ///////////////////////////////////// 1056 | for(size_t i = 0; i < plotsToDraw.size(); i++){ 1057 | if(allPlotsTogether){ 1058 | ofSetColor(plotsToDraw[i]->getColor()); 1059 | deque& vals = plotsToDraw[i]->getValues(); 1060 | float val = 0.0f; 1061 | if(!vals.empty()) val = vals.back(); 1062 | string msg = plotsToDraw[i]->getVariableName() + " " + ofToString(val, 2); 1063 | drawString(msg, 1064 | canvasW - charW * (msg.size() + 0.75 ), 1065 | canvasH - plotHeight - 4 - charH * (plotsToDraw.size() -1 - i) 1066 | ); 1067 | } 1068 | } 1069 | endTextBatch(); ///////////////////////////////////// 1070 | #endif 1071 | 1072 | float totalW = getWidth(); 1073 | float totalH = getHeight(); 1074 | 1075 | //draw bg rect 1076 | ofSetColor(bgColor); 1077 | ofDrawRectangle(x, y + 1, totalW, totalH); 1078 | 1079 | 1080 | //draw all lines 1081 | for(size_t i = 0; i < drawLines.size(); i++){ 1082 | ofSetColor(drawLines[i].lineBgColor); 1083 | ofRectangle lineRect = ofRectangle(x, y + i * charH, totalW, charH + (drawLines[i].tm ? 0 : 1)); 1084 | ofDrawRectangle(lineRect); 1085 | if(drawLines[i].isAccum && drawLines[i].tm != NULL){ 1086 | ofSetColor(drawLines[i].color, 128); 1087 | ofDrawRectangle(x + totalW, 1088 | y + 3 + i * charH, 1089 | -5, 1090 | charH - 1 ); 1091 | } 1092 | if(drawLines[i].plotColor.a > 0){ //plot highlight on the sides 1093 | ofSetColor(drawLines[i].plotColor); 1094 | float y1 = y + 2.4f + i * charH; 1095 | int voffset = -2; 1096 | if(fontRenderer == RENDER_WITH_OFXFONTSTASH || fontRenderer == RENDER_WITH_OFXFONTSTASH2) voffset = 0; 1097 | ofDrawTriangle( x, y1 + voffset, 1098 | x, y1 + charH + voffset, 1099 | x + charW * 0.7f, y1 + charH * 0.5f + voffset); 1100 | } 1101 | 1102 | if(drawPercentageAsGraph && drawLines[i].shouldDrawPctGraph && drawLines[i].percentGraph > 0.02f){ 1103 | float ww = charW * 5.5; 1104 | float xx = lineRect.x + lineRect.width - charW * 7; 1105 | float pct = MIN(drawLines[i].percentGraph, 1.0); 1106 | unsigned char a = 64; 1107 | ofColor gC; 1108 | if(drawLines[i].percentGraph > 1.0){ 1109 | gC = ofColor(255,0,0, (currentFrameNum%4 > 2) ? 1.5 * a : a); 1110 | }else{ 1111 | gC = ofColor(drawLines[i].lineBgColor, a) * (1.0f - pct) + ofColor(255,0,0,a) * pct; 1112 | } 1113 | 1114 | ofSetColor(gC); 1115 | ofDrawRectangle( xx, 1116 | lineRect.y + 0.2 * lineRect.height , 1117 | ww * pct, 1118 | lineRect.height * 0.65 1119 | ); 1120 | } 1121 | } 1122 | 1123 | {//lines 1124 | ofSetColor(hilightColor); 1125 | ofMesh lines; 1126 | ofSetLineWidth(0.1); 1127 | lines.setMode(OF_PRIMITIVE_LINES); 1128 | float fuzzyFix = 0.5; 1129 | float yy = y + 1 + fuzzyFix; 1130 | lines.addVertex(ofVec3f(x, yy)); 1131 | lines.addVertex(ofVec3f(x + totalW, yy)); 1132 | yy = y + totalH - charH + fuzzyFix; 1133 | lines.addVertex(ofVec3f(x, yy)); 1134 | lines.addVertex(ofVec3f(x + totalW, yy)); 1135 | yy = y + totalH + fuzzyFix; 1136 | lines.addVertex(ofVec3f(x, yy)); 1137 | lines.addVertex(ofVec3f(x + totalW, yy)); 1138 | if(menuActive){ 1139 | yy = y + totalH + fuzzyFix - (numInstructionLines + 1) * charH; 1140 | lines.addVertex(ofVec3f(x, yy)); 1141 | lines.addVertex(ofVec3f(x + totalW, yy)); 1142 | yy = y + totalH + fuzzyFix - (numInstructionLines) * charH; 1143 | lines.addVertex(ofVec3f(x, yy)); 1144 | lines.addVertex(ofVec3f(x + totalW, yy)); 1145 | } 1146 | lines.draw(); 1147 | }//lines 1148 | 1149 | //print bottom line, fps and stuff 1150 | float fDiff = fabs( fr - desiredFrameRate); 1151 | float minDiff = desiredFrameRate * 0.025; 1152 | bool missingFrames = fDiff > minDiff; 1153 | 1154 | // all text drawn between these begin/end() calls 1155 | beginTextBatch(); //////////////////////////////////////////////////////////////////// 1156 | 1157 | for(size_t i = 0; i < drawLines.size(); i++){ 1158 | ofSetColor(drawLines[i].color); 1159 | drawString(drawLines[i].fullLine, x , y + (i + 1) * charH); 1160 | } 1161 | 1162 | if (freeze) { 1163 | if(currentFrameNum%5 < 4) ofSetColor(frozenColor); 1164 | else ofSetColor(ofColor::white); 1165 | drawString("Frozen! 'F'", x + totalW - 13 * charW, y + charH ); 1166 | } 1167 | 1168 | static char msg[128]; 1169 | 1170 | if(missingFrames){ 1171 | sprintf(msg, "%2.1f fps (%d) %5.1f%%", fr, (int)desiredFrameRate, percentTotal ); 1172 | }else{ 1173 | sprintf(msg, "%2.1f fps %5.1f%%", fr, percentTotal ); 1174 | } 1175 | if(missingFrames){ 1176 | ofSetColor(170,33,33); //reddish fps below desired fps 1177 | }else{ 1178 | ofSetColor(hilightColor); 1179 | } 1180 | string pad; 1181 | int diff = (maxW - strlen(msg)); 1182 | for(size_t i = 0; i < diff; i++) pad += " "; 1183 | int lastLine = ( drawLines.size() + 1 ) * charH; 1184 | drawString( pad + msg, x, y + lastLine ); 1185 | 1186 | //show activate menu key 1187 | if(menuActive ) ofSetColor(hilightColor.getInverted()); 1188 | else ofSetColor(hilightColor); 1189 | drawString(" '" + ofToString(char(activateKey)) + "'", x, y + lastLine); 1190 | 1191 | //show averaging warning 1192 | if (averaging) { 1193 | if (currentFrameNum % 5 < 2) ofSetColor(hilightColor); 1194 | else ofSetColor(ofColor::limeGreen); 1195 | drawString(" avg!", x + charW * 3.5, y + lastLine); 1196 | } 1197 | 1198 | endTextBatch(); //////////////////////////////////////////////////////////////////////// 1199 | 1200 | if(internalBenchmark){ 1201 | float offset = 0; 1202 | if(drawLocation == TIME_MEASUREMENTS_TOP_LEFT || 1203 | drawLocation == TIME_MEASUREMENTS_TOP_RIGHT || 1204 | drawLocation == TIME_MEASUREMENTS_CUSTOM_LOCATION ){ 1205 | offset = (drawLines.size() + 2.5) * charH; 1206 | } 1207 | ofSetColor(0); 1208 | ofDrawRectangle(x, offset + y - charH, totalW, charH); 1209 | ofSetColor(currentFrameNum%3 ? 255 : 64); 1210 | FontRenderer oldFontRenderer = fontRenderer; 1211 | fontRenderer = RENDER_WITH_OF_BITMAP_FONT; 1212 | drawString(" Meas: " + ofToString(wastedTimeAvg / 1000.f, 2) + "ms " + 1213 | " Draw: " + ofToString(wastedTimeDrawingAvg / 1000.f, 2) + "ms ", 1214 | x, offset + y - charH * 0.12); 1215 | fontRenderer = oldFontRenderer; 1216 | } 1217 | 1218 | for(size_t i = 0; i < toResetUpdatedLastFrameFlag.size(); i++){ 1219 | toResetUpdatedLastFrameFlag[i]->updatedLastFrame = false; 1220 | } 1221 | ofPopMatrix(); 1222 | ofPopStyle(); 1223 | 1224 | if(internalBenchmark){ 1225 | wastedTimeDrawingThisFrame += TM_GET_MICROS() - timeNow; 1226 | wastedTimeAvg = wastedTimeThisFrame * 0.025f + 0.975f * wastedTimeAvg; 1227 | wastedTimeDrawingAvg = wastedTimeDrawingThisFrame * 0.025f + 0.975f * wastedTimeDrawingAvg; 1228 | wastedTimeThisFrame = wastedTimeDrawingThisFrame = 0; 1229 | } 1230 | } 1231 | 1232 | 1233 | void ofxTimeMeasurements::beginTextBatch(){ 1234 | #ifdef USE_OFX_FONTSTASH2 1235 | if(fontRenderer == RENDER_WITH_OFXFONTSTASH2){ 1236 | font2.beginBatch(); 1237 | } 1238 | #endif 1239 | #ifdef USE_OFX_FONTSTASH 1240 | if(fontRenderer == RENDER_WITH_OFXFONTSTASH){ 1241 | font.beginBatch(); 1242 | } 1243 | #endif 1244 | } 1245 | 1246 | void ofxTimeMeasurements::endTextBatch(){ 1247 | #ifdef USE_OFX_FONTSTASH 1248 | if(fontRenderer == RENDER_WITH_OFXFONTSTASH){ 1249 | font.endBatch(); 1250 | } 1251 | #endif 1252 | #ifdef USE_OFX_FONTSTASH2 1253 | if(fontRenderer == RENDER_WITH_OFXFONTSTASH2){ 1254 | font2.endBatch(); 1255 | } 1256 | #endif 1257 | } 1258 | 1259 | 1260 | #if defined(USE_OFX_HISTORYPLOT) 1261 | ofxHistoryPlot* ofxTimeMeasurements::makeNewPlot(string name){ 1262 | 1263 | ofxHistoryPlot * plot = new ofxHistoryPlot( NULL, name, ofGetWidth() * plotResolution, false); 1264 | int colorID = numAllocatdPlots%(threadColorTable.size()); 1265 | plot->setColor( threadColorTable[colorID] * 1.7 ); 1266 | plot->setBackgroundColor(ofColor(0,220)); 1267 | plot->setShowNumericalInfo(true); 1268 | plot->setRangeAuto(); 1269 | plot->setRespectBorders(true); 1270 | plot->setLineWidth(1); 1271 | plot->setLowerRange(0); 1272 | plot->setCropToRect(false); 1273 | plot->addHorizontalGuide(1000.0f/desiredFrameRate, ofColor(0,255,0, 128)); 1274 | plot->setDrawGrid(true); 1275 | plot->setGridUnit(16); 1276 | plot->setGridColor(ofColor(22,255)); 1277 | plot->setAutoRangeShrinksBack(true); 1278 | plot->setShowSmoothedCurve(true); 1279 | plot->setSmoothFilter(0.03); 1280 | plot->setDrawFromRight(false); 1281 | numAllocatdPlots++; 1282 | return plot; 1283 | } 1284 | #endif 1285 | 1286 | bool ofxTimeMeasurements::_keyPressed(ofKeyEventArgs &e){ 1287 | 1288 | if (e.key == enableKey){ 1289 | TIME_SAMPLE_SET_ENABLED(!TIME_SAMPLE_GET_ENABLED()); 1290 | } 1291 | 1292 | if (TIME_SAMPLE_GET_ENABLED()){ 1293 | if (e.key == activateKey){ 1294 | menuActive = !menuActive; 1295 | } 1296 | if(menuActive){ 1297 | if(e.key == '+'){ setUiScale(uiScale + 0.1);} 1298 | if(e.key == '-'){ setUiScale(MAX(uiScale - 0.1, 0.50));} 1299 | if(e.key == '.'){ msPrecision = ofClamp(msPrecision+1, 0, 8);} 1300 | if(e.key == ','){ msPrecision = ofClamp(msPrecision-1, 1, 8);} 1301 | 1302 | } 1303 | 1304 | if(e.key == 'A') averaging ^= true; //Average Toggle 1305 | if(e.key == 'B') internalBenchmark ^= true; //internalBenchmark Toggle 1306 | if(e.key == 'F') freeze ^= true; //free measurements 1307 | 1308 | if(e.key == 'V'){ //make all timings visible! 1309 | unordered_map::iterator it = times.begin(); 1310 | while(it != times.end()){ 1311 | it->second->settings.visible = true; 1312 | ++it; 1313 | } 1314 | } 1315 | 1316 | #if defined(USE_OFX_HISTORYPLOT) 1317 | if(e.key == 'C'){ //clear all plots! 1318 | for(auto & p : plots){ 1319 | if(p.second) p.second->reset(); 1320 | } 1321 | } 1322 | #endif 1323 | 1324 | if(menuActive && e.key == 'L'){ 1325 | drawLocation = ofxTMDrawLocation(drawLocation+1); 1326 | if(drawLocation == TIME_MEASUREMENTS_NUM_DRAW_LOCATIONS) drawLocation = ofxTMDrawLocation(0); 1327 | } 1328 | 1329 | bool ret = false; 1330 | if(menuActive){ 1331 | 1332 | if (drawLines.size()){ 1333 | int selIndex = -1; 1334 | for(size_t i = 0; i < drawLines.size(); i++){ 1335 | if (drawLines[i].key == selection) selIndex = i; 1336 | } 1337 | if(selIndex != -1){ 1338 | switch (e.key) { 1339 | 1340 | case OF_KEY_DOWN:{ 1341 | if(ofGetKeyPressed(OF_KEY_SHIFT)){ //shift + UP to grow plot heights 1342 | #if defined(USE_OFX_HISTORYPLOT) 1343 | plotHeight -= 10; 1344 | plotHeight = MAX(plotHeight, 10); 1345 | #endif 1346 | }else{ 1347 | selIndex ++; 1348 | if(selIndex >= drawLines.size()) selIndex = 0; 1349 | while(drawLines[selIndex].tm == NULL){ 1350 | selIndex ++; 1351 | if(selIndex >= drawLines.size()) selIndex = 0; 1352 | } 1353 | selection = drawLines[selIndex].key; 1354 | } 1355 | }break; 1356 | 1357 | case OF_KEY_UP:{ 1358 | if(ofGetKeyPressed(OF_KEY_SHIFT)){ //shift + DOWN to shrink plot heights 1359 | #if defined(USE_OFX_HISTORYPLOT) 1360 | plotHeight += 10; 1361 | #endif 1362 | }else{ 1363 | selIndex --; 1364 | if(selIndex < 0 ) selIndex = (int)drawLines.size() - 1; 1365 | while(drawLines[selIndex].tm == NULL){ 1366 | selIndex --; 1367 | if(selIndex < 0 ) selIndex = (int)drawLines.size() - 1; 1368 | } 1369 | selection = drawLines[selIndex].key; 1370 | } 1371 | }break; 1372 | 1373 | #if defined(USE_OFX_HISTORYPLOT) 1374 | case 'P':{ 1375 | if (!plots[selection]){ 1376 | plots[selection] = makeNewPlot(selection); 1377 | times[selection]->settings.plotting = true; 1378 | }else{ 1379 | times[selection]->settings.plotting ^= true; 1380 | } 1381 | }break; 1382 | 1383 | case 'G': 1384 | for(auto & p : plots){ 1385 | if(p.second) p.second->reset(); 1386 | } 1387 | allPlotsTogether ^= true; 1388 | break; 1389 | #endif 1390 | 1391 | case OF_KEY_RETURN:{ 1392 | //cant disable update() & draw() 1393 | if (selection != TIME_MEASUREMENTS_SETUP_KEY && 1394 | selection != TIME_MEASUREMENTS_UPDATE_KEY && 1395 | selection != TIME_MEASUREMENTS_DRAW_KEY && 1396 | drawLines[selIndex].tm 1397 | ){ 1398 | times[selection]->settings.enabled ^= true; 1399 | } 1400 | }break; 1401 | 1402 | case OF_KEY_RIGHT: 1403 | collapseExpand(selection, false); //expand 1404 | break; 1405 | 1406 | case OF_KEY_LEFT: 1407 | collapseExpand(selection, true ); //collapse 1408 | break; 1409 | } 1410 | } 1411 | } 1412 | ret = e.key != OF_KEY_ESC; //return true or false; if returning true, it stops the event chain 1413 | //so, next listerners will not get notified 1414 | if(ret == true && times.find(TIME_MEASUREMENTS_KEYPRESSED_KEY) != times.end() && times[TIME_MEASUREMENTS_KEYPRESSED_KEY]->measuring){ 1415 | stopMeasuring(TIME_MEASUREMENTS_KEYPRESSED_KEY, false); //if enabling the menu, we interrupt the following events, 1416 | //so we manually stop the timing as otherwise its never stopped 1417 | //bc the "after" kepressed event is never reached. 1418 | } 1419 | } 1420 | return ret; 1421 | } 1422 | return false; //if TM is disabled, dont interrtup event chain 1423 | } 1424 | 1425 | 1426 | void ofxTimeMeasurements::collapseExpand(const string & sel, bool collapse){ 1427 | 1428 | unordered_map::iterator ii; 1429 | 1430 | for( ii = threadInfo.begin(); ii != threadInfo.end(); ++ii ){ 1431 | 1432 | MinimalTree &tr = ii->second.tree; 1433 | MinimalTree::Node * loc = tr.find(sel); 1434 | 1435 | if( loc ) { 1436 | std::vector> subTree; 1437 | loc->getAllData(subTree); 1438 | for(auto & pair : subTree){ 1439 | const std::string & key = pair.first->getData(); 1440 | times[key]->settings.visible = !collapse; 1441 | } 1442 | } 1443 | } 1444 | } 1445 | 1446 | 1447 | string ofxTimeMeasurements::formatTime(uint64_t microSeconds, int precision){ 1448 | 1449 | float time = microSeconds / 1000.0f; //to ms 1450 | string timeUnit = "ms"; 1451 | if (time > 1000.0f){ //if more than 1 sec 1452 | time /= 1000.0f; 1453 | timeUnit = "sec"; 1454 | if(time > 60.0f){ //if more than a minute 1455 | if(time > 3600.0f){ //if more than a minute 1456 | time /= 3600.0f; 1457 | timeUnit = "hrs"; 1458 | }else{ 1459 | time /= 60.0f; 1460 | timeUnit = "min"; 1461 | } 1462 | } 1463 | } 1464 | return ofToString(time, precision) + timeUnit; 1465 | } 1466 | 1467 | float ofxTimeMeasurements::getPctForTM(TimeMeasurement * tm){ 1468 | 1469 | if (!tm->settings.enabled){ 1470 | return 0.0f; 1471 | }else{ 1472 | float time; 1473 | if(!tm->updatedLastFrame) return 0.0f; 1474 | if(tm->accumulating){ 1475 | time = tm->microsecondsAccum / 1000.0f; 1476 | }else{ 1477 | time = tm->avgDuration / 1000.0f; 1478 | } 1479 | return time / (1000.0f / desiredFrameRate); 1480 | } 1481 | } 1482 | 1483 | string ofxTimeMeasurements::getTimeStringForTM(TimeMeasurement* tm) { 1484 | 1485 | float time; 1486 | if (tm->measuring){ 1487 | string anim; 1488 | switch ((int(currentFrameNum * 0.2f))%6) { 1489 | case 0: anim = " "; break; 1490 | case 1: anim = ". "; break; 1491 | case 2: anim = ".. "; break; 1492 | case 3: anim = "..."; break; 1493 | case 4: anim = " .."; break; 1494 | case 5: anim = " ."; break; 1495 | } 1496 | return string((currentFrameNum % 6 < 3 ) ? " > " : " ") + 1497 | formatTime( TM_GET_MICROS() - tm->microsecondsStart, 1) + 1498 | anim; 1499 | }else{ 1500 | 1501 | string timeString; 1502 | string percentStr; 1503 | static char percentChar[64]; 1504 | 1505 | if (!tm->settings.enabled){ 1506 | if(tm->ifClause){ 1507 | return " DISABLED!"; 1508 | }else{ 1509 | return " CAN'T DISABLE!"; 1510 | } 1511 | }else{ 1512 | 1513 | if(tm->accumulating){ 1514 | timeString = formatTime(tm->microsecondsAccum, msPrecision); 1515 | time = tm->microsecondsAccum / 1000.0f; 1516 | }else{ 1517 | timeString = formatTime(tm->avgDuration, msPrecision); 1518 | time = tm->avgDuration / 1000.0f; 1519 | } 1520 | 1521 | int originalLen = (int)timeString.length(); 1522 | 1523 | int expectedLen = 8; 1524 | for(int i = 0; i < expectedLen - originalLen; i++){ 1525 | timeString = " " + timeString; 1526 | } 1527 | 1528 | if(!drawPercentageAsGraph){ 1529 | float percent = 100.0f * time / (1000.0f / desiredFrameRate); 1530 | bool over = false; 1531 | if (percent > 100.0f){ 1532 | percent = 100.0f; 1533 | over = true; 1534 | } 1535 | 1536 | if (over){ 1537 | sprintf(percentChar, int(currentFrameNum * 0.8)%5 < 3 ? " >100": " 100"); 1538 | }else{ 1539 | sprintf(percentChar, "% 5.1f", percent); 1540 | } 1541 | percentStr = string(percentChar) + "%"; 1542 | }else{ 1543 | percentStr = " "; 1544 | } 1545 | } 1546 | return timeString + percentStr; 1547 | } 1548 | } 1549 | 1550 | 1551 | void ofxTimeMeasurements::loadSettings(){ 1552 | 1553 | //FIXME: this might get called before OF is setup, os ofToDataPath gives us weird results sometimes? 1554 | string f = ofToDataPath(TIME_MEASUREMENTS_SETTINGS_FILENAME, true); 1555 | ifstream myfile(f.c_str()); 1556 | string name, visible, enabled_, plotting; 1557 | bool fileHasPlotData = false; 1558 | if (myfile.is_open()){ 1559 | 1560 | int c = 0; 1561 | while( !myfile.eof() ){ 1562 | 1563 | if (c == 0){ //see if file has PlotData, 2 '|' per line if it does, only 1 if it doesnt 1564 | string wholeLine; 1565 | getline( myfile, wholeLine, '\n' );//see what version we are on 1566 | int numBars = 0; 1567 | for(size_t i = 0; i < wholeLine.size(); i++){ 1568 | if (wholeLine[i] == '|') numBars++; 1569 | } 1570 | if(numBars == 2) fileHasPlotData = true; 1571 | myfile.clear(); 1572 | myfile.seekg(0, ios::beg); 1573 | c++; 1574 | } 1575 | 1576 | getline( myfile, name, '=' );//name 1577 | 1578 | if(name.length()){ 1579 | getline( myfile, visible, '|' ); //visible 1580 | if(fileHasPlotData){ 1581 | getline( myfile, enabled_, '|' ); //enabled 1582 | getline( myfile, plotting, '\n' ); //plotting 1583 | }else{ 1584 | getline( myfile, enabled_, '\n' ); //enabled 1585 | } 1586 | 1587 | if (name == TIME_MEASUREMENTS_SETUP_KEY || 1588 | name == TIME_MEASUREMENTS_UPDATE_KEY || 1589 | name == TIME_MEASUREMENTS_DRAW_KEY ){ 1590 | visible = enabled_ = "1"; 1591 | } 1592 | 1593 | settings[name].visible = bool(visible == "1" ? true : false); 1594 | settings[name].enabled = bool(enabled_ == "1" ? true : false); 1595 | #if defined(USE_OFX_HISTORYPLOT) 1596 | settings[name].plotting = bool(plotting == "1" ? true : false); 1597 | if(settings[name].plotting){ 1598 | ofxHistoryPlot * plot = makeNewPlot(name); 1599 | plots[name] = plot; 1600 | } 1601 | #endif 1602 | } 1603 | //ofLogVerbose("ofxTimeMeasurements") << "loaded settings for " << name << " enabled: " << settings[name].enabled << " visible: " << settings[name].visible ; 1604 | } 1605 | myfile.close(); 1606 | }else{ 1607 | ofLogWarning("ofxTimeMeasurements") << "Unable to load Settings file " << TIME_MEASUREMENTS_SETTINGS_FILENAME; 1608 | } 1609 | } 1610 | 1611 | 1612 | void ofxTimeMeasurements::saveSettings(){ 1613 | 1614 | if(!ofDirectory::doesDirectoryExist(configsDir)){ 1615 | ofDirectory::createDirectory(configsDir, true, true); 1616 | } 1617 | ofstream myfile; 1618 | myfile.open(ofToDataPath(TIME_MEASUREMENTS_SETTINGS_FILENAME,true).c_str()); 1619 | for( unordered_map::iterator ii = times.begin(); ii != times.end(); ++ii ){ 1620 | string keyName = ii->first; 1621 | bool visible = times[keyName]->settings.visible; 1622 | bool enabled = times[keyName]->settings.enabled; 1623 | #if defined(USE_OFX_HISTORYPLOT) 1624 | bool plotting = times[keyName]->settings.plotting; 1625 | #endif 1626 | 1627 | if (keyName == TIME_MEASUREMENTS_SETUP_KEY || 1628 | keyName == TIME_MEASUREMENTS_UPDATE_KEY || 1629 | keyName == TIME_MEASUREMENTS_DRAW_KEY){ 1630 | visible = enabled = true; 1631 | } 1632 | 1633 | myfile << keyName << "=" << string(visible ? "1" : "0") << "|" << string(enabled ? "1" : "0") 1634 | #if defined(USE_OFX_HISTORYPLOT) 1635 | << "|" << string(plotting ? "1" : "0") 1636 | #endif 1637 | << endl; 1638 | } 1639 | myfile.close(); 1640 | } 1641 | 1642 | 1643 | void ofxTimeMeasurements::_appExited(ofEventArgs &e){ 1644 | if(savesSettingsOnExit) saveSettings(); 1645 | } 1646 | 1647 | #ifdef USE_OFX_FONTSTASH 1648 | void ofxTimeMeasurements::drawUiWithFontStash(string fontPath, float fontSize_){ 1649 | if(!ofIsGLProgrammableRenderer()){ 1650 | fontRenderer = RENDER_WITH_OFXFONTSTASH; fontSize = fontSize_; fontStashFile = fontPath; 1651 | font = ofxFontStash(); 1652 | font.setup(ofToDataPath(fontPath, true), 1.0, 512, false, 0, uiScale); 1653 | ofRectangle r = font.getBBox("M", fontSize, 0, 0); 1654 | charW = r.width; 1655 | charH = ceil(r.height * 1.55); 1656 | }else{ 1657 | ofLogError("ofxTimeMeasurements") << "Can't use ofxFontStash with the Programmable Renderer!"; 1658 | } 1659 | } 1660 | #endif 1661 | 1662 | #ifdef USE_OFX_FONTSTASH2 1663 | void ofxTimeMeasurements::drawUiWithFontStash2(string fontPath, float fontSize_){ 1664 | fontRenderer = RENDER_WITH_OFXFONTSTASH2; fontSize2 = fontSize_; fontStashFile2 = fontPath; 1665 | font2 = ofxFontStash2::Fonts(); 1666 | font2.setup(); 1667 | font2.addFont("mono", ofToDataPath(fontStashFile2, true)); 1668 | ofxFontStash2::Style style = ofxFontStash2::Style("mono", fontSize2); 1669 | ofRectangle r = font2.getTextBounds("M", style, 0, 0); 1670 | charW = r.width; 1671 | charH = ceil(r.height); 1672 | } 1673 | #endif 1674 | 1675 | void ofxTimeMeasurements::drawUiWithBitmapFont(){ 1676 | fontRenderer = RENDER_WITH_OF_BITMAP_FONT; 1677 | charW = 8; 1678 | charH = TIME_MEASUREMENTS_LINE_HEIGHT; 1679 | } 1680 | 1681 | void ofxTimeMeasurements::drawString(const string & text, const float & x, const float & y){ 1682 | 1683 | switch (fontRenderer) { 1684 | case RENDER_WITH_OF_BITMAP_FONT: ofDrawBitmapString(text, x, y - 2); 1685 | break; 1686 | 1687 | #ifdef USE_OFX_FONTSTASH 1688 | case RENDER_WITH_OFXFONTSTASH: font.drawBatch(text, fontSize, x + 2, y - charH * 0.125); 1689 | break; 1690 | #endif 1691 | 1692 | #ifdef USE_OFX_FONTSTASH2 1693 | case RENDER_WITH_OFXFONTSTASH2:{ 1694 | ofxFontStash2::Style style = ofxFontStash2::Style("mono", fontSize2, ofGetStyle().color); 1695 | font2.draw(text, style, x + 2, y - charH * 0.125); 1696 | } 1697 | break; 1698 | #endif 1699 | default:break; 1700 | } 1701 | } 1702 | 1703 | 1704 | void ofxTimeMeasurements::setUiScale(float scale){ 1705 | if(fabs(scale - uiScale) > FLT_EPSILON){ 1706 | uiScale = scale; 1707 | #ifdef USE_OFX_FONTSTASH 1708 | if(fontStashFile.size()){ 1709 | drawUiWithFontStash(fontStashFile, fontSize); 1710 | } 1711 | #endif 1712 | } 1713 | } 1714 | 1715 | 1716 | 1717 | float ofxTimeMeasurements::durationForID( const string & ID){ 1718 | 1719 | unordered_map::iterator it; 1720 | it = times.find(ID); 1721 | 1722 | if ( it == times.end() ){ //not found! 1723 | if ( times[ID]->error ){ 1724 | return times[ID]->duration / 1000.0; //to ms 1725 | } 1726 | } 1727 | return 0; 1728 | } 1729 | 1730 | 1731 | void ofxTimeMeasurements::setTimeAveragePercent(double p){ 1732 | if(p > 1.0) p = 1.0; 1733 | if(p < 0.0) p = 0.0; 1734 | if(p >= 0.99999f){ 1735 | averaging = false; 1736 | }else{ 1737 | averaging = true; 1738 | timeAveragePercent = p; 1739 | } 1740 | } 1741 | 1742 | 1743 | void ofxTimeMeasurements::setDesiredFrameRate(float fr){ 1744 | desiredFrameRate = fr; 1745 | } 1746 | 1747 | 1748 | void ofxTimeMeasurements::setEnabled(bool ena){ 1749 | enabled = ena; 1750 | } 1751 | 1752 | 1753 | bool ofxTimeMeasurements::getEnabled(){ 1754 | return enabled; 1755 | } 1756 | 1757 | 1758 | void ofxTimeMeasurements::setMsPrecision(int digits){ 1759 | msPrecision = digits; 1760 | } 1761 | 1762 | 1763 | float ofxTimeMeasurements::getWidth() const{ 1764 | switch(fontRenderer){ 1765 | case RENDER_WITH_OF_BITMAP_FONT: return (maxW + 1) * charW; 1766 | case RENDER_WITH_OFXFONTSTASH: return (maxW + 0.25) * charW; 1767 | case RENDER_WITH_OFXFONTSTASH2: return (maxW + 0.25) * charW; 1768 | } 1769 | return 0.0f; 1770 | } 1771 | 1772 | void ofxTimeMeasurements::drawSmoothFpsClock(float x, float y, float radius){ 1773 | 1774 | static float r = 0; 1775 | ofPushMatrix(); 1776 | ofTranslate(x, y); 1777 | #if (OF_VERSION_MINOR <= 9) 1778 | ofRotate(r, 0, 0, 1); 1779 | #else 1780 | ofRotateDeg(r, 0, 0, 1); 1781 | #endif 1782 | float s = radius * 0.05; 1783 | ofDrawRectangle(-s * 0.5f, - 0.5f * s, radius, s); 1784 | ofPopMatrix(); 1785 | r+= 10; 1786 | } 1787 | 1788 | #endif 1789 | 1790 | 1791 | --------------------------------------------------------------------------------