我想知道是否有一种很好的方法来监控我的应用程序内部,最好是以现有库的形式。
我的应用程序是多线程的,并使用消息传递系统在线程之间和外部世界之间进行通信。我的目标是监控发送的消息类型,频率等等。
还可以以更一般的方式存在其他统计信息,例如每分钟生成多少线程,调用多少新/删除,或者应用程序的更具体方面;你说出来的。
最棒的是像谷歌浏览器的“内部页面”,如net或chrome://跟踪,但是以命令行方式。
如果有一个足够通用的库来容纳我的应用程序的特性,那就太棒了。
否则我准备实施一个可以完成这项工作的小班,但我不知道从哪里开始。我认为最重要的是代码不应该过多干扰,因此性能不会受到影响。
你们对这件事有什么指示吗?
编辑:我的应用程序在Linux上运行,在嵌入式环境中,很遗憾Valgrind不支持:(
答案 0 :(得分:3)
我建议您在代码中维护增加的计数器。计数器可以是static
类成员或全局变量。如果使用类来定义计数器,则可以让构造函数将计数器注册到单个存储库以及名称。然后,您可以通过咨询存储库来查询和重置计数器。
struct Counter {
unsigned long c_;
unsigned long operator++ () { return ++c_; }
operator unsigned long () const { return c_; }
void reset () { unsigned long c = c_; ATOMIC_DECREMENT(c_, c); }
Counter (std::string name);
};
struct CounterAtomic : public Counter {
unsigned long operator++ () { return ATOMIC_INCREMENT(c_, 1); }
CounterAtomic (std::string name) : Counter(name) {}
};
ATOMIC_INCREMENT
将是一种特定于平台的机制,可以原子地递增计数器。 GCC为此提供了内置__sync_add_and_fetch
。 ATOMIC_DECREMENT
类似,GCC内置__sync_sub_and_fetch
。
struct CounterRepository {
typedef std::map<std::string, Counter *> MapType;
mutable Mutex lock_;
MapType map_;
void add (std::string n, Counter &c) {
ScopedLock<Mutex> sl(lock_);
if (map_.find(n) != map_.end()) throw n;
map_[n] = &c;
}
Counter & get (std::string n) const {
ScopedLock<Mutex> sl(lock_);
MapType::const_iterator i = map_.find(n);
if (i == map_.end()) throw n;
return *(i->second);
}
};
CounterRepository counterRepository;
Counter::Counter (std::string name) {
counterRepository.add(name, *this);
}
如果您知道同一个计数器将增加多个线程,则使用CounterAtomic
。对于特定于线程的计数器,只需使用Counter
。
答案 1 :(得分:3)
我认为您正在尝试实现运行时统计信息的收集 - 例如您发送了多少字节,运行了多长时间以及用户激活特定功能的次数。
通常,为了从各种来源(如工作线程)编译运行时统计信息,我会让每个源(线程)增加自己的,最基本数据的本地计数器,但不执行任何对数据进行冗长的数学或分析。
然后回到主线程(或者您想要分析和显示这些统计数据的任何地方),我向每个工作线程发送RequestProgress
类型的消息。作为响应,工作线程将收集所有基础数据,并可能执行一些简单的分析。此数据以及基本分析的结果将在ProgressReport
消息中发送回请求(主)线程。主线程然后聚合所有这些数据,进行额外的(可能是昂贵的)分析,格式化和显示给用户或记录。
主线程根据用户请求(如按RequestProgress
键时)或定时间隔发送此S
消息。如果定时间隔是我想要的,我通常会实现另一个新的“心跳”线程。所有这个线程在指定时间内都是Sleep()
,然后向主线程发送Heartbeat
消息。主线程依次通过向要收集统计信息的每个工作线程发送Heartbeat
消息来对此RequestProgress
消息起作用。
收集统计数据的行为似乎应该相当简单。那么为什么这么复杂的机制呢?答案是双重的。
首先,工作线程有工作要做,而计算使用情况统计数据则不然。试图重构这些线程以承担与其主要目的正交的第二个责任,有点像试图将方形钉子塞入圆孔中。它们不是为了这样做而构建的,因此代码将不会被写入。
其次,如果你经常尝试做太多,运行时统计的计算可能会很昂贵。例如,假设您有一个工作线程在网络上发送多播数据,并且您想要收集吞吐量数据。多少字节,一段时间的长度,以及每秒多少字节的平均值。你可以让工作线程在运行中自行计算所有这些,但这是很多工作,并且工作线程更好地花费CPU时间做他们应该做的事情 - 发送多播数据。相反,如果只是递增一个计数器,表示每次发送消息时发送的字节数,计数对线程性能的影响最小。然后,为了回应偶尔的RequestProgress
消息,您可以找出开始和放大停止时间,并发送它,让主线程完成所有的divison等。
答案 2 :(得分:1)
使用共享内存(POSIX,System V,mmap或任何可用的内容)。通过将原始内存块转换为数组定义,在其中放置一个固定长度的易失性无符号32位或64位整数数组(即,您可以在平台上原子递增的最大值)。请注意,volatile不会让你获得原子性;它会阻止可能会丢弃您的统计信息值的编译器优化。使用像gcc的__sync_add_and_fetch()或更新的C ++ 11 atomic&lt;&gt;这样的内在函数类型。
然后,您可以编写一个附加到同一共享内存块的小程序,并可以打印出一个或所有统计信息。这个小型统计读取器程序和你的主程序必须共享一个公共头文件,强制执行数组中每个stat的位置。
这里明显的缺点是你坚持使用固定数量的计数器。但是,在性能方面,它很难被击败。影响是程序中各个点的整数的原子增量。
答案 3 :(得分:1)
在嵌入式系统中,常见的技术是为“日志”保留一块内存,并将其视为循环队列。写一些可以读取这块内存的代码;这将有助于在运行时拍摄“快照”。
在网络上搜索“调试日志记录”。应该找出你可以用来玩的一些来源。我去过的大多数商店通常都是自己的。
如果你有额外的非易失性内存,你可以保留一个区域并写入。如果您的系统足够大以支持文件系统,这还包括文件。
最坏的情况是,将数据写入调试(串行)端口。
对于实际的实时测量,我们通常使用连接到GPIO或测试点的示波器,并将脉冲输出到GPIO /测试点。
答案 4 :(得分:0)
看看valgrind / callgrind。
它可以用于分析,这是我理解你正在寻找的。我不认为它在运行时有效,但它可以在您的过程完成后生成。
答案 5 :(得分:0)
这是一个很好的答案,@ John Dibling!我有一个与此非常相似的系统。但是,我的“stat”线程每秒查询工作10次并且它影响了工作线程的性能,因为每次“stat”线程请求数据时,有一个关键部分访问这些数据(计数器等)并且它表示在检索此数据时阻塞工作线程。事实证明,在工作线程的重负荷下,这10Hz的统计查询影响了工人的整体表现。
所以我切换到一个稍微不同的统计报告模型 - 而不是主动从主线程查询工作线程,我现在有工作线程将其基本统计计数器报告给他们的独占统计信息库,主线程可以查询在任何时候都不会对工人产生直接影响。
答案 6 :(得分:0)
如果您使用的是C ++ 11,则可以使用std :: atomic&lt;&gt;
#include <atomic>
class GlobalStatistics {
public:
static GlobalStatistics &get() {
static GlobalStatistics instance;
return instance;
}
void incrTotalBytesProcessed(unsigned int incrBy) {
totalBytesProcessed += incrBy;
}
long long getTotalBytesProcessed() const { return totalBytesProcessed; }
private:
std::atomic_llong totalBytesProcessed;
GlobalStatistics() { }
GlobalStatistics(const GlobalStatistics &) = delete;
void operator=(const GlobalStatistics &) = delete;
};