Qt/C++ 加入轻便性能收集器

在做比较耗费计算资源或者存储资源的多线程程序时,往往需要分析每个环节耗费了多少时间。使用valgrind系列工具,在Linux下可以来做类似的工作,但是我们还是希望在所有平台下,以及最终发行

Release版本中(优化开关全开)完成评估。

实际上,只要能够有一个工具方便的记录每个关键位置的时刻,即可使用后期分析来计算每一步的成本。

1. 预期需求

1.1 调用方法

按照轻量级、简单的需要,我们要求:

包含至多一个头文件。

只需要简单的初始化。

在1行内完成记录。

线程安全。

可以按需关闭或开启记录。

输出为CSV格式,以便直接用WPS打开。

1.2 理想记录内容

记录要包括:

一个用户自定义的专题名,用于后续处理时的排序、分类。

文件名、行号、函数名、线程ID

精确到毫秒的时间。

我们的报告应该类似:

2. 设计样例

我们使用一个Qt控制台程序,进行理想的日志操作测试。

 1 //main.cpp
 2 #include <QCoreApplication>
 3 #include <QThread>
 4 #include "profile_log.h"
 5 void foo()
 6 {
 7     //一行完成标记
 8     LOG_PROFILE("FOO deal","Start (100 times)");
 9 #pragma omp parallel for
10     for (int i=0;i<100;++i)
11     {
12         //模拟多线程耗时操作
13         QThread::msleep(rand()%20+40);
14         LOG_PROFILE("FOO deal",QString("T%1").arg(i));
15     }
16     LOG_PROFILE("FOO deal","Finished ");
17 }
18 
19 int main(int argc, char *argv[])
20 {
21     QCoreApplication a(argc, argv);
22     //直接初始化,使用UUID的唯一文件名,在当前路径下的log创建。
23     profile_log::init();
24     //开启日志。在发布时,可以设为false。
25     profile_log::set_log_state(true);
26     //测试
27     foo();
28     return 0;
29 }

编译上述程序,需要开启openMP多线程并行,以便更好的测试线程。在VC下要在proj属性的语言特性页面设置,在Linux/GCC下,直接在Qt的工程文件中加入开关:

1 QMAKE_CXXFLAGS += -fopenmp
2 LIBS += -lgomp

就可以了。

3.具体实现

在Qt/C++下,可以仅用一个内联类完成上述功能。

3.1 实现代码

  1 /**
  2   Class profile_log is a lite tool-kit for millsec multi-thread profile log.
  3   @author goldenhawking@163.com
  4   @date 2019-05-14
  5   */
  6 #ifndef PROFILE_LOG_H
  7 #define PROFILE_LOG_H
  8 #include <QDir>
  9 #include <QFile>
 10 #include <QIODevice>
 11 #include <QTextStream>
 12 #include <QThread>
 13 #include <QString>
 14 #include <QDateTime>
 15 #include <QMutex>
 16 #include <QUuid>
 17 #include <QCoreApplication>
 18 #include <memory>
 19 
 20 #define LOG_PROFILE(SUBJECT,DETAILED) profile_log::log(
 21     SUBJECT,DETAILED,__FILE__,__LINE__,__FUNCTION__)
 22 
 23 /*!
 24      * rief The profile_log class is a tool-class for lite profile log.
 25      * We can use this tool-kit simply by 3 steps:
 26      * 1. include profile_log.h
 27      * 2. Call profile_log::init at the very beginning of your program.
 28      * 3. Call LOG_PROFILE(Subject, Detailed) anywhere you want.
 29      */
 30 class profile_log{
 31 public:
 32     static inline bool init()
 33     {
 34         if (instance().get()!=nullptr)
 35             return false;
 36         instance() = std::shared_ptr<profile_log>(new profile_log());
 37         return instance()->write_title();
 38     }
 39     static inline bool init(const QString & filename)
 40     {
 41         if (instance().get()!=nullptr)
 42             return false;
 43         instance() = std::shared_ptr<profile_log>(new profile_log(filename));
 44         return instance()->write_title();
 45     }
 46     static inline bool init(QIODevice * dev)
 47     {
 48         if (instance().get()!=nullptr)
 49             return false;
 50         instance() = std::shared_ptr<profile_log>(new profile_log(dev));
 51         return instance()->write_title();
 52     }
 53     static inline std::shared_ptr<profile_log> & instance()
 54     {
 55         static std::shared_ptr<profile_log> plog;
 56         return plog;
 57     }
 58     static inline bool log_state()
 59     {
 60         if (!instance().get()) return false;
 61         return instance()->m_bLogOn;
 62     }
 63     static inline bool set_log_state(bool s)
 64     {
 65         if (!instance().get()) return false;
 66         return instance()->m_bLogOn = s;
 67     }
 68     static QString url(){
 69         if (!instance().get()) return "";
 70         return instance()->m_url;
 71     }
 72 protected:
 73     profile_log(){
 74         //m_url = QDir::tempPath()+"/"+QUuid::createUuid().toString()+".csv";
 75         m_url = QCoreApplication::applicationDirPath()+"/log/";
 76         QDir dir;
 77         dir.mkpath(m_url);
 78         m_url += "/" + QUuid::createUuid().toString()+".csv";
 79         QFile * fp  = new QFile(m_url);
 80         if(fp->open(QIODevice::WriteOnly))
 81         {
 82             m_pDev = fp;
 83             m_bOwnDev = true;
 84         }
 85     }
 86     profile_log(const QString & filename){
 87         QFile * fp  = new QFile(filename);
 88         if(fp->open(QIODevice::WriteOnly))
 89         {
 90             m_pDev = fp;
 91             m_bOwnDev = true;
 92             m_url = filename;
 93         }
 94     }
 95     profile_log(QIODevice * dev){
 96         m_pDev = dev;
 97         QFileDevice * fp = qobject_cast<QFileDevice *>(dev);
 98         if (fp)
 99             m_url = fp->fileName();
100         m_bOwnDev = false;
101     }
102 public:
103     ~profile_log()
104     {
105         if (m_bOwnDev && m_pDev)
106         {
107             if (m_pDev->isOpen())
108                 m_pDev->close();
109             m_pDev->deleteLater();
110         }
111         if (!m_bLogOn)
112             if (m_url.length())
113                 QFile::remove(m_url);
114 
115 
116     }
117     static inline bool write_title()
118     {
119         if (!instance().get()) return false;
120         if (instance()->log_state()==false)
121             return true;
122         instance()->m_mutex.lock();
123         QTextStream st_out(instance()->m_pDev);
124         st_out<<"Subject,Detailed,FileName,LineNum,FunctionName,Thread,UTC,Clock
";
125         st_out.flush();
126         instance()->m_mutex.unlock();
127         return true;
128     }
129     static inline bool log(const QString & subject, const QString & detailed,
130                            const QString & filename,
131                            const int linenum,
132                            const QString & funcname)
133     {
134         if (!instance()->m_pDev) return false;
135         if (instance()->log_state()==false)
136             return true;
137         instance()->m_mutex.lock();
138         QTextStream st_out(instance()->m_pDev);
139         st_out    <<    """ << subject <<""";
140         st_out    <<    ","" << detailed <<""";
141         st_out    <<    ","" << filename <<""";
142         st_out    <<    ","" << linenum <<""";
143         st_out    <<    ","" << funcname <<""";
144         st_out    <<    ","" << QThread::currentThreadId() <<""";
145         st_out    <<    ","" << QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddTHH:mm:ss.zzz") <<""";
146         st_out    <<    ","" << clock() <<""
";
147         instance()->m_mutex.unlock();
148         return true;
149     }
150 private:
151     QIODevice * m_pDev = nullptr;
152     QString m_url;
153     bool    m_bOwnDev = false;
154     bool    m_bLogOn = true;
155     QMutex  m_mutex;
156 };
157 #endif // PROFILE_LOG_H

3.2 设计要点

上述代码有几个设计要点:

1.使用全局唯一实例。构造函数为保护,不允许直接创建实例。 只能靠静态函数创建唯一实例。

2.用宏简化操作。设计一个宏,以便用最短的代码进行日志标记。

3.支持创建临时文件、从已经打开的QIODevice创建,以及给定文件名创建。特别是QIODevice创建,将可以把内容直接输出到网络等部位,而非落盘。

4.局限:可以去除 QDateTime,以便减少时间消耗。

原文地址:https://www.cnblogs.com/ybqjymy/p/12654467.html