[英]How to read a counter from a linux C program to a bash test script?
我在Suse linux系統上有一個大型C / C ++程序。 我們使用bash腳本對其進行自動化測試,該腳本將輸入發送到程序,並讀取輸出。 它主要是“黑匣子”測試,但是某些測試需要了解一些內部細節才能確定測試是否通過。
特別是一項測試,需要知道程序運行特定功能(解析特定響應消息)的時間。 該函數運行時,它將發出日志並增加計數器變量。 當前,自動化測試通過在日志文件的日志文件中grep並確定測試前后的發生次數來確定調用次數。 這是不理想的,因為不能保證日志(syslog-ng),並且經常被配置關閉,因為它們基本上是調試日志。
我正在尋找更好的選擇。 我可以更改程序以增強可測試性,但是對正常運行不會造成重大影響。 我的第一個想法是,每次測試后我都可以閱讀計數器。 像這樣:
gdb --pid=$PID --batch -ex "p numServerResponseX"
它在運行時速度很慢,但是很好,因為根本不需要更改程序。 稍作工作,我可能可以編寫一個ptrace命令來更有效地執行此操作。
但是我想知道是否沒有更簡單的方法可以做到這一點。 我可以將計數器寫入共享內存(使用shm_open / mmap),然后在bash腳本中讀取/ dev / shm嗎? 有沒有一些更簡單的方法可以設置計數器以使其易於讀取,而又不會使其緩慢增加?
編輯:
詳細信息:測試設置如下:
testScript <-> sipp <-> programUnderTest <-> externalServer
bash testScript使用sipp注入sip消息,並且通常根據sipp中的完成代碼確定成功或失敗。 但是在某些測試中,它需要知道程序從外部服務器收到的響應數。 函數“ processServerResponseX”處理來自外部服務器的某些響應。 在測試過程中,沒有太多流量在運行,因此在10秒內,該函數可能僅被調用20次。 當每個測試結束並且我們要檢查計數器時,基本上應該沒有流量。 但是,在正常操作期間,它可能每秒被調用數百次。 該函數大致為:
unsigned long int numServerResponseX;
int processServerResponseX(DMsg_t * dMsg, AppId id)
{
if (DEBUG_ENABLED)
{
syslog(priority, "%s received %d", __func__, (int) id);
}
myMutex->getLock();
numServerResponseX++;
doLockedStuff(dMsg, id);
myMutex->releaseLock();
return doOtherStuff(dMsg, id);
}
該腳本當前執行以下操作:
grep processServerResponseX /var/log/logfile | wc -l
並比較之前和之后的值。 我的目標是即使DEBUG_ENABLED為false也要進行這項工作,並且不要太慢。 該程序是多線程的,並且在i86_64 smp計算機上運行,因此添加任何長時阻止功能將不是一個好的解決方案。
我將使用某個函數“(解析特定的響應消息)”在某處寫入(可能使用fopen
然后fprintf
然后fclose
)一些文本數據。
該目標可以是FIFO(請參閱fifo(7) ...)或tmpfs
文件系統(它是RAM文件系統)中的臨時文件,可能是/run/
如果您的C ++程序足夠大且復雜,則可以考慮添加一些探測工具(外部程序可以查詢C ++程序的內部狀態的某種方式),例如專用的Web服務(在單獨的線程中使用libonion ),或者一些與systemd或D-bus的接口 ,或一些遠程過程調用服務,例如ONC / RPC , JSON-RPC等。
您可能對POCOlib感興趣。 也許其日志記錄框架應該使您感興趣。
如前所述,您可以使用Posix共享內存和信號量(請參見shm_overview(7)和sem_overview(7) ...)。
也許您需要Linux特定的eventfd(2) 。...(您可以編寫一個小型C程序,以供測試bash腳本調用...。)
您也可以嘗試更改命令行(我忘了怎么做,也許是libproc
或寫到/proc/self/cmdline
見proc(5) ...)。 然后ps
將顯示它。
我個人通常會使用Basile Starynkevitch概述的方法,但是我想提出一種使用實時信號的替代方法。
我並不是說這是最好的解決方案,但它易於實現且開銷很小。 主要缺點是請求和響應的大小都限制為一個 int
(或者從技術上講,任何可以由int
或void *
表示的東西)。
基本上,您使用一個簡單的幫助程序將信號發送到應用程序。 該信號的有效負載為您的應用程序可以檢查的一個int
,並基於此有效負載,應用程序通過將相同的信號發送回始發方作為響應,並以其自身的int
作為有效負載。
如果不需要任何鎖定,則可以使用簡單的實時信號處理程序。 當捕獲到信號時,它將檢查siginfo_t
結構。 如果通過sigqueue()
發送,則該請求位於siginfo_t
結構的si_value
成員中。 處理程序使用sigqueue()
和響應來響應原始過程(結構的si_pid
成員)。 這僅需要將大約六十行代碼添加到您的應用程序。 這是一個示例應用程序app1.c
:
#define _POSIX_C_SOURCE 200112L
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <stdio.h>
#define INFO_SIGNAL (SIGRTMAX-1)
/* This is the counter we're interested in */
static int counter = 0;
static void responder(int signum, siginfo_t *info,
void *context __attribute__((unused)))
{
if (info && info->si_code == SI_QUEUE) {
union sigval value;
int response, saved_errno;
/* We need to save errno, to avoid interfering with
* the interrupted thread. */
saved_errno = errno;
/* Incoming signal value (int) determines
* what we respond back with. */
switch (info->si_value.sival_int) {
case 0: /* Request loop counter */
response = *(volatile int *)&counter;
break;
/* Other codes? */
default: /* Respond with -1. */
response = -1;
}
/* Respond back to signaler. */
value.sival_ptr = (void *)0L;
value.sival_int = response;
sigqueue(info->si_pid, signum, value);
/* Restore errno. This way the interrupted thread
* will not notice any change in errno. */
errno = saved_errno;
}
}
static int install_responder(const int signum)
{
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_sigaction = responder;
act.sa_flags = SA_SIGINFO;
if (sigaction(signum, &act, NULL))
return errno;
else
return 0;
}
int main(void)
{
if (install_responder(INFO_SIGNAL)) {
fprintf(stderr, "Cannot install responder signal handler: %s.\n",
strerror(errno));
return 1;
}
fprintf(stderr, "PID = %d\n", (int)getpid());
fflush(stderr);
/* The application follows.
* This one just loops at 100 Hz, printing a dot
* about once per second or so. */
while (1) {
struct timespec t;
counter++;
if (!(counter % 100)) {
putchar('.');
fflush(stdout);
}
t.tv_sec = 0;
t.tv_nsec = 10000000; /* 10ms */
nanosleep(&t, NULL);
/* Note: Since we ignore the remainder
* from the nanosleep call, we
* may sleep much shorter periods
* when a signal is delivered. */
}
return 0;
}
上面的響應者使用counter
值響應查詢0
,而其他響應則返回-1
。 您只需在responder()
添加適當的case
語句即可添加其他查詢。
請注意,鎖定原語( sem_post()
除外)不是異步信號安全的 ,因此不應在信號處理程序中使用。 因此,以上代碼無法實現任何鎖定。
信號傳遞可以中斷阻塞調用中的線程。 在上面的應用程序中, nanosleep()
調用通常被信號傳遞中斷,從而導致睡眠被縮短。 (類似地,如果read()
和write()
調用被信號傳遞中斷,則它們的返回值可能為errno == EINTR
,返回-1
。)
如果這是一個問題,或者不確定所有代碼是否正確處理errno == EINTR
,或者您的計數器需要鎖定,則可以改用專用於信號處理的單獨線程。
除非傳遞信號,否則專用線程將進入睡眠狀態,並且只需要很小的堆棧,因此在運行時它實際上不會消耗任何大量資源。
目標信號在所有線程中被阻塞,專用線程在sigwaitinfo()
等待。 如果它捕獲到任何信號,則按上面的方式處理它們-區別在於,由於這本身是線程而不是信號處理程序,因此您可以自由地使用任何鎖定等,而不必將自己限制在異步信號安全范圍內職能。
這種線程化方法稍長一些,將近一百行代碼添加到您的應用程序中。 (差異包含在app1.c
responder()
和install_responder()
函數中;甚至添加到main()
的代碼也與app1.c
中的app1.c
。)
這是app2.c
:
#define _POSIX_C_SOURCE 200112L
#include <signal.h>
#include <errno.h>
#include <pthread.h>
#include <string.h>
#include <time.h>
#include <stdio.h>
#define INFO_SIGNAL (SIGRTMAX-1)
/* This is the counter we're interested in */
static int counter = 0;
static void *responder(void *payload)
{
const int signum = (long)payload;
union sigval response;
sigset_t sigset;
siginfo_t info;
int result;
/* We wait on only one signal. */
sigemptyset(&sigset);
if (sigaddset(&sigset, signum))
return NULL;
/* Wait forever. This thread is automatically killed, when the
* main thread exits. */
while (1) {
result = sigwaitinfo(&sigset, &info);
if (result != signum) {
if (result != -1 || errno != EINTR)
return NULL;
/* A signal was delivered using *this* thread. */
continue;
}
/* We only respond to sigqueue()'d signals. */
if (info.si_code != SI_QUEUE)
continue;
/* Clear response. We don't leak stack data! */
memset(&response, 0, sizeof response);
/* Question? */
switch (info.si_value.sival_int) {
case 0: /* Counter */
response.sival_int = *(volatile int *)(&counter);
break;
default: /* Unknown; respond with -1. */
response.sival_int = -1;
}
/* Respond. */
sigqueue(info.si_pid, signum, response);
}
}
static int install_responder(const int signum)
{
pthread_t worker_id;
pthread_attr_t attrs;
sigset_t mask;
int retval;
/* Mask contains only signum. */
sigemptyset(&mask);
if (sigaddset(&mask, signum))
return errno;
/* Block signum, in all threads. */
if (sigprocmask(SIG_BLOCK, &mask, NULL))
return errno;
/* Start responder() thread with a small stack. */
pthread_attr_init(&attrs);
pthread_attr_setstacksize(&attrs, 32768);
retval = pthread_create(&worker_id, &attrs, responder,
(void *)(long)signum);
pthread_attr_destroy(&attrs);
return errno = retval;
}
int main(void)
{
if (install_responder(INFO_SIGNAL)) {
fprintf(stderr, "Cannot install responder signal handler: %s.\n",
strerror(errno));
return 1;
}
fprintf(stderr, "PID = %d\n", (int)getpid());
fflush(stderr);
while (1) {
struct timespec t;
counter++;
if (!(counter % 100)) {
putchar('.');
fflush(stdout);
}
t.tv_sec = 0;
t.tv_nsec = 10000000; /* 10ms */
nanosleep(&t, NULL);
}
return 0;
}
對於app1.c
和app2.c
,應用程序本身是相同的。 對應用程序的唯一修改是確保所有必需的頭文件都獲得#include
d,添加responder()
和install_responder()
,並盡早在main()
調用install_responder()
main()
。
( app1.c
和app2.c
僅在app2.c
responder()
和install_responder()
有所不同;並且app2.c
需要pthreads。)
app1.c
和app2.c
使用信號SIGRTMAX-1
,在大多數應用程序中均app1.c
使用該信號。
app2.c
方法也app2.c
具有有用的副作用:如果您在應用程序中使用其他信號,但又不希望它們中斷阻止I / O調用等,也許您有由第三方編寫的庫,不能正確處理EINTR
,但是您確實需要在應用程序中使用信號-您可以在應用程序中的install_responder()
調用之后簡單地阻止信號。 這樣,唯一沒有阻塞信號的線程就是響應者線程,內核將使用tat傳遞信號。 因此,唯一會被信號傳遞中斷的線程是響應者線程,更具體地說是sigwaitinfo()
中的sigwaitinfo()
responder()
,它忽略任何中斷。 如果您使用例如異步I / O或計時器,或者這是一個繁重的數學或數據處理應用程序,那么這可能會很有用。
可以使用非常簡單的查詢程序query.c
來查詢這兩個應用程序的實現:
#define _POSIX_C_SOURCE 200112L
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <stdio.h>
int query(const pid_t process, const int signum,
const int question, int *const response)
{
sigset_t prevmask, waitset;
struct timespec timeout;
union sigval value;
siginfo_t info;
int result;
/* Value sent to the target process. */
value.sival_int = question;
/* Waitset contains only signum. */
sigemptyset(&waitset);
if (sigaddset(&waitset, signum))
return errno = EINVAL;
/* Block signum; save old mask into prevmask. */
if (sigprocmask(SIG_BLOCK, &waitset, &prevmask))
return errno;
/* Send the signal. */
if (sigqueue(process, signum, value)) {
const int saved_errno = errno;
sigprocmask(signum, &prevmask, NULL);
return errno = saved_errno;
}
while (1) {
/* Wait for a response within five seconds. */
timeout.tv_sec = 5;
timeout.tv_nsec = 0L;
/* Set si_code to an uninteresting value,
* just to be safe. */
info.si_code = SI_KERNEL;
result = sigtimedwait(&waitset, &info, &timeout);
if (result == -1) {
/* Some other signal delivered? */
if (errno == EINTR)
continue;
/* No response; fail. */
sigprocmask(SIG_SETMASK, &prevmask, NULL);
return errno = ETIMEDOUT;
}
/* Was this an interesting signal? */
if (result == signum && info.si_code == SI_QUEUE) {
if (response)
*response = info.si_value.sival_int;
/* Return success. */
sigprocmask(SIG_SETMASK, &prevmask, NULL);
return errno = 0;
}
}
}
int main(int argc, char *argv[])
{
pid_t pid;
int signum, question, response;
long value;
char dummy;
if (argc < 3 || argc > 4 ||
!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s PID SIGNAL [ QUERY ]\n", argv[0]);
fprintf(stderr, "\n");
return 1;
}
if (sscanf(argv[1], " %ld %c", &value, &dummy) != 1) {
fprintf(stderr, "%s: Invalid process ID.\n", argv[1]);
return 1;
}
pid = (pid_t)value;
if (pid < (pid_t)1 || value != (long)pid) {
fprintf(stderr, "%s: Invalid process ID.\n", argv[1]);
return 1;
}
if (sscanf(argv[2], "SIGRTMIN %ld %c", &value, &dummy) == 1)
signum = SIGRTMIN + (int)value;
else
if (sscanf(argv[2], "SIGRTMAX %ld %c", &value, &dummy) == 1)
signum = SIGRTMAX + (int)value;
else
if (sscanf(argv[2], " %ld %c", &value, &dummy) == 1)
signum = value;
else {
fprintf(stderr, "%s: Unknown signal.\n", argv[2]);
return 1;
}
if (signum < SIGRTMIN || signum > SIGRTMAX) {
fprintf(stderr, "%s: Not a realtime signal.\n", argv[2]);
return 1;
}
/* Clear the query union. */
if (argc > 3) {
if (sscanf(argv[3], " %d %c", &question, &dummy) != 1) {
fprintf(stderr, "%s: Invalid query.\n", argv[3]);
return 1;
}
} else
question = 0;
if (query(pid, signum, question, &response)) {
switch (errno) {
case EINVAL:
fprintf(stderr, "%s: Invalid signal.\n", argv[2]);
return 1;
case EPERM:
fprintf(stderr, "Signaling that process was not permitted.\n");
return 1;
case ESRCH:
fprintf(stderr, "No such process.\n");
return 1;
case ETIMEDOUT:
fprintf(stderr, "No response.\n");
return 1;
default:
fprintf(stderr, "Failed: %s.\n", strerror(errno));
return 1;
}
}
printf("%d\n", response);
return 0;
}
注意,這里我沒有對信號編號進行硬編碼。 在app1.c
和app2.c
的命令行上使用SIGRTMAX-1
。 (您可以更改它query.c
的確也理解SIGRTMIN+n
。您必須使用實時信號SIGRTMIN+0
到SIGRTMAX-0
(含)。)
您可以使用以下命令編譯所有三個程序
gcc -Wall -O3 app1.c -o app1
gcc -Wall -O3 app2.c -lpthread -o app2
gcc -Wall -O3 query.c -o query
./app1
和./app2
打印其PID,因此您無需查找它。 (不過,您可以使用ps -o pid= -C app1
或ps -o pid= -C app2
找到PID。)
如果在一個外殼程序(或兩個外殼程序中)中運行./app1
或./app2
,則可以看到它們每秒大約輸出一次點。 計數器每1/100秒增加一次。 (按Ctrl + C停止。)
如果在同一台計算機上同一目錄中的另一個Shell中運行./query PID SIGRTMAX-1
,則可以看到計數器值。
在我的計算機上運行的示例:
A$ ./app1
PID = 28519
...........
B$ ./query 28519 SIGRTMAX-1
11387
C$ ./app2
PID = 28522
...
B$ ./query 28522 SIGRTMAX -1
371
如前所述,此機制的缺點是響應僅限於一個int
(或從技術上講是int
或void *
)。 但是,也可以通過使用Basile Starynkevich概述的某些方法來解決此問題。 通常,該信號只是針對應用程序的通知,它應更新存儲在文件,共享內存段或任何地方的狀態。 我建議為此使用專用線程方法,因為它的開銷很小,並且對應用程序本身的影響也很小。
任何問題?
硬編碼的systemtap解決方案可能如下所示:
% cat FOO.stp
global counts
probe process("/path/to/your/binary").function("CertainFunction") { counts[pid()] <<< 1 }
probe process("/path/to/your/binary").end { println ("pid %d count %sd", pid(), @count(counts[pid()]))
delete counts[pid()] }
# stap FOO.stp
pid 42323 count 112
pid 2123 count 0
... etc, until interrupted
感謝您的答復。 其他答案中有很多很好的信息。 但是,這就是我所做的。 首先,我調整了程序以在shm文件中添加計數器:
struct StatsCounter {
char counterName[8];
unsigned long int counter;
};
StatsCounter * stats;
void initStatsCounter()
{
int fd = shm_open("TestStats", O_RDWR|O_CREAT, 0);
if (fd == -1)
{
syslog(priority, "%s:: Initialization Failed", __func__);
stats = (StatsCounter *) malloc(sizeof(StatsCounter));
}
else
{
// For now, just one StatsCounter is used, but it could become an array.
ftruncate(fd, sizeof(StatsCounter));
stats = (StatsCounter *) mmap(NULL, sizeof(StatsCounter),
PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
}
// Initialize names. Pad them to 7 chars (save room for \0).
snprintf(stats[0].counterName, sizeof(stats[0].counterName), "nRespX ");
stats[0].counter = 0;
}
然后將processServerResponseX更改為在鎖定部分增加stats [0] .counter。 然后,我更改了腳本以使用“ hexdump”解析shm文件:
hexdump /dev/shm/TestStats -e ' 1/8 "%s " 1/8 "%d\n"'
然后將顯示如下內容:
nRespX 23
這樣,如果我還想查看響應Y,則可以在以后進行擴展...
如果在更改文件時訪問文件,則不確定hexdump是否存在互斥問題。 但就我而言,我認為這並不重要,因為腳本僅在測試之前和之后調用它,所以它不應在更新過程中。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.