在做比较耗费计算资源或者存储资源的多线程程序时,往往需要分析每个环节耗费了多少时间。使用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 * \brief 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\n";
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() <<"\"\n";
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,以便减少时间消耗。