How to read a counter from a linux C program to a bash test script?

时间:2021-09-02 00:08:26

I have a large C/C++ program on a Suse linux system. We do automated testing of it with a bash script, which sends input to the program, and reads the output. It's mainly "black-box" testing, but some tests need to know a few internal details to determine if a test has passed.

我在Suse linux系统上有一个大型的C / C ++程序。我们使用bash脚本对其进行自动测试,该脚本将输入发送到程序,并读取输出。它主要是“黑盒”测试,但有些测试需要知道一些内部细节以确定测试是否已通过。

One test in particular needs to know how times the program runs a certain function (which parses a particular response message). When that function runs it issues a log and increments a counter variable. The automated test currently determines the number of invocations by grepping in the log file for the log message, and counting the number of occurrences before and after the test. This isn't ideal, because the logs (syslog-ng) aren't guaranteed, and they're frequently turned off by configuration, because they're basically debug logs.

特别是一个测试需要知道程序运行某个函数(解析特定响应消息)的次数。当该函数运行时,它会发出一个日志并递增计数器变量。自动测试当前通过在日志文件中为日志消息进行grepping并计算测试之前和之后的出现次数来确定调用次数。这并不理想,因为日志(syslog-ng)无法保证,并且它们经常被配置关闭,因为它们基本上是调试日志。

I'm looking for a better alternative. I can change the program to enhance the testability, but it shouldn't be heavy impact to normal operation. My first thought was, I could just read the counter after each test. Something like this:

我正在寻找更好的选择。我可以更改程序以增强可测试性,但它不应该对正常操作产生重大影响。我的第一个想法是,我可以在每次测试后阅读计数器。像这样的东西:

gdb --pid=$PID --batch -ex "p numServerResponseX"

That's slow when it runs, but it's good because the program doesn't need to be changed at all. With a little work, I could probably write a ptrace command to do this a little more efficiently.

它运行时很慢,但它很好,因为程序根本不需要改变。通过一些工作,我可能会编写一个ptrace命令来更有效地执行此操作。

But I'm wondering if there isn't a simpler way to do this. Could I write the counter to shared memory (with shm_open / mmap), and then read /dev/shm in the bash script? Is there some simpler way I could setup the counter to make it easy to read, without making it slow to increment?

但我想知道是否有更简单的方法来做到这一点。我可以将计数器写入共享内存(使用shm_open / mmap),然后在bash脚本中读取/ dev / shm吗?是否有一些更简单的方法我可以设置计数器,使其易于阅读,而不会增加缓慢?

Edit:

Details: The test setup is like this:

详细信息:测试设置如下:

testScript <-> sipp <-> programUnderTest <-> externalServer

The bash testScript injects sip messages with sipp, and it generally determines success or failure based on the completion code from sipp. But in certain tests it needs to know the number of responses the program received from the external server. The function "processServerResponseX" processes certain responses from the external server. During the testing there isn't much traffic running, so the function is only invoked perhaps 20 times over 10 seconds. When each test ends and we want to check the counter, there should be essentially no traffic. However during normal operation, it might be invoked hundreds of times a second. The function is roughly:

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);
}

The script currently does:

该脚本目前有:

grep processServerResponseX /var/log/logfile | wc -l

and compares the value before and after. My goal is to have this work even if DEBUG_ENABLED is false, and not have it be too slow. The program is multi-threaded, and it runs on an i86_64 smp machine, so adding any long blocking function would not be a good solution.

并比较之前和之后的值。我的目标是即使DEBUG_ENABLED为假,也不要让它太慢。该程序是多线程的,它运行在i86_64 smp机器上,因此添加任何长阻塞功能都不是一个好的解决方案。

4 个解决方案

#1


3  

I would have that certain function "(which parses a particular response message)" write (probably using fopen then fprintf then fclose) some textual data somewhere.

我会将某些功能“(解析特定的响应消息)”写入(可能使用fopen然后fprintf然后fclose)某些文本数据。

That destination could be a FIFO (see fifo(7) ...) or a temporary file in a tmpfs file system (which is a RAM file system), maybe /run/

该目的地可以是FIFO(参见fifo(7)...)或tmpfs文件系统中的临时文件(这是一个RAM文件系统),可能是/ run /

If your C++ program is big and complex enough, you could consider adding some probing facilities (some means for an external program to query about the internal state of your C++ program) e.g. a dedicated web service (using libonion in a separate thread), or some interface to systemd, or to D-bus, or some remote procedure call service like ONC/RPC, JSON-RPC, etc etc...

如果您的C ++程序足够大且复杂,您可以考虑添加一些探测工具(一些外部程序用于查询C ++程序内部状态的方法),例如一个专用的Web服务(在一个单独的线程中使用libonion),或者一些到systemd的接口,或一个D-bus,或者一些远程过程调用服务,如ONC / RPC,JSON-RPC等等......

You might be interested by POCOlib. Perhaps its logging framework should interest you.

您可能对POCOlib感兴趣。也许它的日志框架应该让你感兴趣。

As you mentioned, you might use Posix shared memory & semaphores (see shm_overview(7) and sem_overview(7) ...).

如您所述,您可以使用Posix共享内存和信号量(请参阅shm_overview(7)和sem_overview(7)...)。

Perhaps the Linux specific eventfd(2) is what you need.... (you could code a tiny C program to be invoked by your testing bash scripts....)

也许特定于Linux的eventfd(2)就是你需要的......(你可以编写一个微小的C程序,由你的测试bash脚本调用....)

You could also try to change the command line (I forgot how to do that, maybe libproc or write to /proc/self/cmdline see proc(5)...). Then ps would show it.

你也可以尝试更改命令行(我忘了怎么做,也许libproc或写入/ proc / self / cmdline参见proc(5)...)。然后ps会显示它。

#2


2  

I personally do usually use the methods Basile Starynkevitch outlined for this, but I wanted to bring up an alternative method using realtime signals.

我个人通常使用Basile Starynkevitch为此概述的方法,但我想提出一种使用实时信号的替代方法。

I am not claiming this is the best solution, but it is simple to implement and has very little overhead. The main downside is that the size of the request and response are both limited to one int (or technically, anything representable by an int or by a void *).

我并不是说这是最好的解决方案,但它实现起来很简单并且开销很小。主要缺点是请求和响应的大小都限制为一个int(或技术上,任何可由int或void *表示的)。

Basically, you use a simple helper program to send a signal to the application. The signal has a payload of one int your application can examine, and based on it, the application responds by sending the same signal back to the originator, with an int of its own as payload.

基本上,您使用简单的帮助程序向应用程序发送信号。该信号具有应用程序可以检查的一个int的有效负载,并且基于它,应用程序通过将相同的信号发送回发起者来响应,其自身的int作为有效负载。

If you don't need any locking, you can use a simple realtime signal handler. When it catches a signal, it examines the siginfo_t structure. If sent via sigqueue(), the request is in the si_value member of the siginfo_t structure. The handler answers to the originating process (si_pid member of the structure) using sigqueue(), with the response. This only requires about sixty lines of code to be added to your application. Here is an example application, app1.c:

如果您不需要任何锁定,则可以使用简单的实时信号处理程序。当它捕获信号时,它会检查siginfo_t结构。如果通过sigqueue()发送,则请求位于siginfo_t结构的si_value成员中。处理程序使用sigqueue()和响应来回答原始进程(结构的si_pid成员)。这只需要将大约60行代码添加到您的应用程序中。这是一个示例应用程序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;
}

The above responder responds to query 0 with the counter value, and with -1 to everything else. You can add other queries simply by adding a suitable case statement in responder().

上面的响应者用计数器值响应查询0,用其他一切响应-1。您可以通过在responder()中添加合适的case语句来添加其他查询。

Note that locking primitives (except for sem_post()) are not async-signal safe, and thus should not be used in a signal handler. So, the above code cannot implement any locking.

请注意,锁定原语(sem_post()除外)不是异步信号安全的,因此不应在信号处理程序中使用。所以,上面的代码无法实现任何锁定。

Signal delivery can interrupt a thread in a blocking call. In the above application, the nanosleep() call is usually interrupted by the signal delivery, causing the sleep to be cut short. (Similarly, read() and write() calls may return -1 with errno == EINTR, if they were interrupted by signal delivery.)

信号传递可以在阻塞呼叫中中断线程。在上述申请中,nanosleep()调用通常被信号传递中断,导致睡眠被缩短。 (类似地,如果信号传递中断了read()和write()调用,则可以使用errno == EINTR返回-1。)

If that is a problem, or you are not sure if all your code handles errno == EINTR correctly, or your counters need locking, you can use separate thread dedicated for the signal handling instead.

如果这是一个问题,或者您不确定所有代码是否正确处理errno == EINTR,或者您的计数器需要锁定,则可以使用专用于信号处理的单独线程。

The dedicated thread will sleep unless a signal is delivered, and only requires a very small stack, so it really does not consume any significant resources at run time.

除非传递信号,否则专用线程将处于休眠状态,并且只需要非常小的堆栈,因此在运行时它确实不消耗任何重要资源。

The target signal is blocked in all threads, with the dedicated thread waiting in sigwaitinfo(). If it catches any signals, it processes them just like above -- except that since this is a thread and not a signal handler per se, you can freely use any locking etc., and do not need to limit yourself to async-signal safe functions.

目标信号在所有线程中被阻塞,专用线程在sigwaitinfo()中等待。如果它捕获任何信号,它就像上面那样处理它们 - 除了因为这是一个线程而不是信号处理程序本身,你可以*地使用任何锁定等,而不需要限制你自己的异步信号安全功能。

This threaded approach is slightly longer, adding almost a hundred lines of code to your application. (The differences are contained in the responder() and install_responder() functions; even the code added to main() is exactly the same as in app1.c.)

这种线程化方法稍长,为您的应用程序添加了近百行代码。 (差异包含在responder()和install_responder()函数中;即使添加到main()的代码也与app1.c中的代码完全相同。)

Here is app2.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;
}

For both app1.c and app2.c the application itself is the same. The only modifications needed to the application are making sure all the necessary header files get #included, adding responder() and install_responder(), and a call to install_responder() as early as possible in main().

对于app1.c和app2.c,应用程序本身是相同的。应用程序所需的唯一修改是确保所有必需的头文件获得#included,添加responder()和install_responder(),并在main()中尽早调用install_responder()。

(app1.c and app2.c only differ in responder() and install_responder(); and in that app2.c needs pthreads.)

(app1.c和app2.c仅在responder()和install_responder()中有所不同;并且在那个app2.c中需要pthreads。)

Both app1.c and app2.c use the signal SIGRTMAX-1, which should be unused in most applications.

app1.c和app2.c都使用信号SIGRTMAX-1,在大多数应用程序中都应该使用该信号。

app2.c approach, also has a useful side-effect you might wish to use in general: if you use other signals in your application, but don't want them to interrupt blocking I/O calls et cetera -- perhaps you have a library that was written by a third party, and does not handle EINTR correctly, but you do need to use signals in your application --, you can simply block the signals after the install_responder() call in your application. The only thread, then, where the signals are not blocked is the responder thread, and the kernel will use tat to deliver the signals. Therefore, the only thread that will ever get interrupted by the signal delivery is the responder thread, more specifically sigwaitinfo() in responder(), and it ignores any interruptions. If you use for example async I/O or timers, or this is a heavy math or data processing application, this might be useful.

app2.c方法,也有一个有用的副作用,你可能希望一般使用:如果你在你的应用程序中使用其他信号,但不希望它们中断阻塞I / O调用等等 - 也许你有一个由第三方编写的库,并不能正确处理EINTR,但您确实需要在应用程序中使用信号 - 您可以在应用程序中的install_responder()调用之后简单地阻止信号。然后,信号未被阻塞的唯一线程是响应者线程,内核将使用tat来传递信号。因此,唯一会被信号传递中断的线程是响应者线程,更具体地说是响应者()中的sigwaitinfo(),它忽略了任何中断。如果您使用例如异步I / O或定时器,或者这是一个繁重的数学或数据处理应用程序,这可能很有用。

Both application implementations can be queried using a very simple query program, query.c:

可以使用非常简单的查询程序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;
}

Note that I did not hardcode the signal number here; use SIGRTMAX-1 on the command line for app1.c and app2.c. (You can change it. query.c does understand SIGRTMIN+n too. You must use a realtime signal, SIGRTMIN+0 to SIGRTMAX-0, inclusive.)

请注意,我没有在这里硬编码信号编号;在app1.c和app2.c的命令行中使用SIGRTMAX-1。 (你可以改变它.query.c也理解SIGRTMIN + n。你必须使用实时信号,SIGRTMIN + 0到SIGRTMAX-0,包括在内。)

You can compile all three programs using

您可以使用编译所有三个程序

gcc -Wall -O3 app1.c -o app1
gcc -Wall -O3 app2.c -lpthread -o app2
gcc -Wall -O3 query.c -o query

Both ./app1 and ./app2 print their PIDs, so you don't need to look for it. (You can find the PID using e.g. ps -o pid= -C app1 or ps -o pid= -C app2, though.)

./app1和./app2都会打印它们的PID,因此您无需查找它。 (你可以使用例如ps -o pid = -C app1或ps -o pid = -C app2找到PID。)

If you run ./app1 or ./app2 in one shell (or both in separate shells), you can see them outputting the dots at about once per second. The counter increases every 1/100th of a second. (Press Ctrl+C to stop.)

如果在一个shell中运行./app1或./app2(或者在单独的shell中运行两者),您可以看到它们以每秒大约一次的速度输出点。计数器每1/100秒增加一次。 (按Ctrl + C停止。)

If you run ./query PID SIGRTMAX-1 in another shell in the same directory on the same machine, you can see the counter value.

如果在同一台机器上同一目录中的另一个shell中运行./query PID SIGRTMAX-1,则可以看到计数器值。

An example run on my machine:

在我的机器上运行示例:

A$ ./app1
PID = 28519
...........

B$ ./query 28519 SIGRTMAX-1
11387

C$ ./app2
PID = 28522
...

B$ ./query 28522 SIGRTMAX -1
371

As mentioned, the downside of this mechanism is that the response is limited to one int (or technically an int or a void *). There are ways around that, however, by also using some of the methods Basile Starynkevich outlined. Typically, the signal is then just a notification for the application that it should update the state stored in a file, shared memory segment, or wherever. I recommend using the dedicated thread approach for that, as it has very little overheads, and minimal impact on the application itself.

如上所述,这种机制的缺点是响应仅限于一个int(或技术上是int或void *)。然而,通过使用Basile Starynkevich概述的一些方法,可以解决这个问题。通常,信号只是应用程序的通知,它应该更新存储在文件,共享内存段或任何地方的状态。我建议使用专用线程方法,因为它具有非常小的开销,并且对应用程序本身的影响最小。

Any questions?

#3


0  

A hard-coded systemtap solution could look like:

硬编码的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

#4


0  

Thanks for the responses. There is lots of good information in the other answers. However, here's what I did. First I tweaked the program to add a counter in a shm file:

谢谢你的回复。其他答案中有很多好的信息。但是,这就是我所做的。首先,我调整了程序,在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;
}

And changed processServerResponseX to increment stats[0].counter in the locked section. Then I changed the script to parse the shm file with "hexdump":

并更改了processServerResponseX以增加锁定部分中的stats [0] .counter。然后我更改了脚本以使用“hexdump”解析shm文件:

hexdump /dev/shm/TestStats -e ' 1/8 "%s " 1/8 "%d\n"'

This will then show something like this:

这将显示如下:

nRespX  23

This way I can extend this later if I want to also look at response Y, ...

这样我以后可以扩展它,如果我也想看看响应Y,...

Not sure if there are mutual exclusion problems with hexdump if it accessed the file while it was being changed. But in my case, I don't think it matters, because the script only calls it before and after the test, it should not be in the middle of an update.

如果在更改文件时访问文件,则不确定hexdump是否存在互斥问题。但在我的情况下,我认为这不重要,因为脚本只在测试之前和之后调用它,它不应该在更新的中间。

#1


3  

I would have that certain function "(which parses a particular response message)" write (probably using fopen then fprintf then fclose) some textual data somewhere.

我会将某些功能“(解析特定的响应消息)”写入(可能使用fopen然后fprintf然后fclose)某些文本数据。

That destination could be a FIFO (see fifo(7) ...) or a temporary file in a tmpfs file system (which is a RAM file system), maybe /run/

该目的地可以是FIFO(参见fifo(7)...)或tmpfs文件系统中的临时文件(这是一个RAM文件系统),可能是/ run /

If your C++ program is big and complex enough, you could consider adding some probing facilities (some means for an external program to query about the internal state of your C++ program) e.g. a dedicated web service (using libonion in a separate thread), or some interface to systemd, or to D-bus, or some remote procedure call service like ONC/RPC, JSON-RPC, etc etc...

如果您的C ++程序足够大且复杂,您可以考虑添加一些探测工具(一些外部程序用于查询C ++程序内部状态的方法),例如一个专用的Web服务(在一个单独的线程中使用libonion),或者一些到systemd的接口,或一个D-bus,或者一些远程过程调用服务,如ONC / RPC,JSON-RPC等等......

You might be interested by POCOlib. Perhaps its logging framework should interest you.

您可能对POCOlib感兴趣。也许它的日志框架应该让你感兴趣。

As you mentioned, you might use Posix shared memory & semaphores (see shm_overview(7) and sem_overview(7) ...).

如您所述,您可以使用Posix共享内存和信号量(请参阅shm_overview(7)和sem_overview(7)...)。

Perhaps the Linux specific eventfd(2) is what you need.... (you could code a tiny C program to be invoked by your testing bash scripts....)

也许特定于Linux的eventfd(2)就是你需要的......(你可以编写一个微小的C程序,由你的测试bash脚本调用....)

You could also try to change the command line (I forgot how to do that, maybe libproc or write to /proc/self/cmdline see proc(5)...). Then ps would show it.

你也可以尝试更改命令行(我忘了怎么做,也许libproc或写入/ proc / self / cmdline参见proc(5)...)。然后ps会显示它。

#2


2  

I personally do usually use the methods Basile Starynkevitch outlined for this, but I wanted to bring up an alternative method using realtime signals.

我个人通常使用Basile Starynkevitch为此概述的方法,但我想提出一种使用实时信号的替代方法。

I am not claiming this is the best solution, but it is simple to implement and has very little overhead. The main downside is that the size of the request and response are both limited to one int (or technically, anything representable by an int or by a void *).

我并不是说这是最好的解决方案,但它实现起来很简单并且开销很小。主要缺点是请求和响应的大小都限制为一个int(或技术上,任何可由int或void *表示的)。

Basically, you use a simple helper program to send a signal to the application. The signal has a payload of one int your application can examine, and based on it, the application responds by sending the same signal back to the originator, with an int of its own as payload.

基本上,您使用简单的帮助程序向应用程序发送信号。该信号具有应用程序可以检查的一个int的有效负载,并且基于它,应用程序通过将相同的信号发送回发起者来响应,其自身的int作为有效负载。

If you don't need any locking, you can use a simple realtime signal handler. When it catches a signal, it examines the siginfo_t structure. If sent via sigqueue(), the request is in the si_value member of the siginfo_t structure. The handler answers to the originating process (si_pid member of the structure) using sigqueue(), with the response. This only requires about sixty lines of code to be added to your application. Here is an example application, app1.c:

如果您不需要任何锁定,则可以使用简单的实时信号处理程序。当它捕获信号时,它会检查siginfo_t结构。如果通过sigqueue()发送,则请求位于siginfo_t结构的si_value成员中。处理程序使用sigqueue()和响应来回答原始进程(结构的si_pid成员)。这只需要将大约60行代码添加到您的应用程序中。这是一个示例应用程序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;
}

The above responder responds to query 0 with the counter value, and with -1 to everything else. You can add other queries simply by adding a suitable case statement in responder().

上面的响应者用计数器值响应查询0,用其他一切响应-1。您可以通过在responder()中添加合适的case语句来添加其他查询。

Note that locking primitives (except for sem_post()) are not async-signal safe, and thus should not be used in a signal handler. So, the above code cannot implement any locking.

请注意,锁定原语(sem_post()除外)不是异步信号安全的,因此不应在信号处理程序中使用。所以,上面的代码无法实现任何锁定。

Signal delivery can interrupt a thread in a blocking call. In the above application, the nanosleep() call is usually interrupted by the signal delivery, causing the sleep to be cut short. (Similarly, read() and write() calls may return -1 with errno == EINTR, if they were interrupted by signal delivery.)

信号传递可以在阻塞呼叫中中断线程。在上述申请中,nanosleep()调用通常被信号传递中断,导致睡眠被缩短。 (类似地,如果信号传递中断了read()和write()调用,则可以使用errno == EINTR返回-1。)

If that is a problem, or you are not sure if all your code handles errno == EINTR correctly, or your counters need locking, you can use separate thread dedicated for the signal handling instead.

如果这是一个问题,或者您不确定所有代码是否正确处理errno == EINTR,或者您的计数器需要锁定,则可以使用专用于信号处理的单独线程。

The dedicated thread will sleep unless a signal is delivered, and only requires a very small stack, so it really does not consume any significant resources at run time.

除非传递信号,否则专用线程将处于休眠状态,并且只需要非常小的堆栈,因此在运行时它确实不消耗任何重要资源。

The target signal is blocked in all threads, with the dedicated thread waiting in sigwaitinfo(). If it catches any signals, it processes them just like above -- except that since this is a thread and not a signal handler per se, you can freely use any locking etc., and do not need to limit yourself to async-signal safe functions.

目标信号在所有线程中被阻塞,专用线程在sigwaitinfo()中等待。如果它捕获任何信号,它就像上面那样处理它们 - 除了因为这是一个线程而不是信号处理程序本身,你可以*地使用任何锁定等,而不需要限制你自己的异步信号安全功能。

This threaded approach is slightly longer, adding almost a hundred lines of code to your application. (The differences are contained in the responder() and install_responder() functions; even the code added to main() is exactly the same as in app1.c.)

这种线程化方法稍长,为您的应用程序添加了近百行代码。 (差异包含在responder()和install_responder()函数中;即使添加到main()的代码也与app1.c中的代码完全相同。)

Here is app2.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;
}

For both app1.c and app2.c the application itself is the same. The only modifications needed to the application are making sure all the necessary header files get #included, adding responder() and install_responder(), and a call to install_responder() as early as possible in main().

对于app1.c和app2.c,应用程序本身是相同的。应用程序所需的唯一修改是确保所有必需的头文件获得#included,添加responder()和install_responder(),并在main()中尽早调用install_responder()。

(app1.c and app2.c only differ in responder() and install_responder(); and in that app2.c needs pthreads.)

(app1.c和app2.c仅在responder()和install_responder()中有所不同;并且在那个app2.c中需要pthreads。)

Both app1.c and app2.c use the signal SIGRTMAX-1, which should be unused in most applications.

app1.c和app2.c都使用信号SIGRTMAX-1,在大多数应用程序中都应该使用该信号。

app2.c approach, also has a useful side-effect you might wish to use in general: if you use other signals in your application, but don't want them to interrupt blocking I/O calls et cetera -- perhaps you have a library that was written by a third party, and does not handle EINTR correctly, but you do need to use signals in your application --, you can simply block the signals after the install_responder() call in your application. The only thread, then, where the signals are not blocked is the responder thread, and the kernel will use tat to deliver the signals. Therefore, the only thread that will ever get interrupted by the signal delivery is the responder thread, more specifically sigwaitinfo() in responder(), and it ignores any interruptions. If you use for example async I/O or timers, or this is a heavy math or data processing application, this might be useful.

app2.c方法,也有一个有用的副作用,你可能希望一般使用:如果你在你的应用程序中使用其他信号,但不希望它们中断阻塞I / O调用等等 - 也许你有一个由第三方编写的库,并不能正确处理EINTR,但您确实需要在应用程序中使用信号 - 您可以在应用程序中的install_responder()调用之后简单地阻止信号。然后,信号未被阻塞的唯一线程是响应者线程,内核将使用tat来传递信号。因此,唯一会被信号传递中断的线程是响应者线程,更具体地说是响应者()中的sigwaitinfo(),它忽略了任何中断。如果您使用例如异步I / O或定时器,或者这是一个繁重的数学或数据处理应用程序,这可能很有用。

Both application implementations can be queried using a very simple query program, query.c:

可以使用非常简单的查询程序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;
}

Note that I did not hardcode the signal number here; use SIGRTMAX-1 on the command line for app1.c and app2.c. (You can change it. query.c does understand SIGRTMIN+n too. You must use a realtime signal, SIGRTMIN+0 to SIGRTMAX-0, inclusive.)

请注意,我没有在这里硬编码信号编号;在app1.c和app2.c的命令行中使用SIGRTMAX-1。 (你可以改变它.query.c也理解SIGRTMIN + n。你必须使用实时信号,SIGRTMIN + 0到SIGRTMAX-0,包括在内。)

You can compile all three programs using

您可以使用编译所有三个程序

gcc -Wall -O3 app1.c -o app1
gcc -Wall -O3 app2.c -lpthread -o app2
gcc -Wall -O3 query.c -o query

Both ./app1 and ./app2 print their PIDs, so you don't need to look for it. (You can find the PID using e.g. ps -o pid= -C app1 or ps -o pid= -C app2, though.)

./app1和./app2都会打印它们的PID,因此您无需查找它。 (你可以使用例如ps -o pid = -C app1或ps -o pid = -C app2找到PID。)

If you run ./app1 or ./app2 in one shell (or both in separate shells), you can see them outputting the dots at about once per second. The counter increases every 1/100th of a second. (Press Ctrl+C to stop.)

如果在一个shell中运行./app1或./app2(或者在单独的shell中运行两者),您可以看到它们以每秒大约一次的速度输出点。计数器每1/100秒增加一次。 (按Ctrl + C停止。)

If you run ./query PID SIGRTMAX-1 in another shell in the same directory on the same machine, you can see the counter value.

如果在同一台机器上同一目录中的另一个shell中运行./query PID SIGRTMAX-1,则可以看到计数器值。

An example run on my machine:

在我的机器上运行示例:

A$ ./app1
PID = 28519
...........

B$ ./query 28519 SIGRTMAX-1
11387

C$ ./app2
PID = 28522
...

B$ ./query 28522 SIGRTMAX -1
371

As mentioned, the downside of this mechanism is that the response is limited to one int (or technically an int or a void *). There are ways around that, however, by also using some of the methods Basile Starynkevich outlined. Typically, the signal is then just a notification for the application that it should update the state stored in a file, shared memory segment, or wherever. I recommend using the dedicated thread approach for that, as it has very little overheads, and minimal impact on the application itself.

如上所述,这种机制的缺点是响应仅限于一个int(或技术上是int或void *)。然而,通过使用Basile Starynkevich概述的一些方法,可以解决这个问题。通常,信号只是应用程序的通知,它应该更新存储在文件,共享内存段或任何地方的状态。我建议使用专用线程方法,因为它具有非常小的开销,并且对应用程序本身的影响最小。

Any questions?

#3


0  

A hard-coded systemtap solution could look like:

硬编码的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

#4


0  

Thanks for the responses. There is lots of good information in the other answers. However, here's what I did. First I tweaked the program to add a counter in a shm file:

谢谢你的回复。其他答案中有很多好的信息。但是,这就是我所做的。首先,我调整了程序,在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;
}

And changed processServerResponseX to increment stats[0].counter in the locked section. Then I changed the script to parse the shm file with "hexdump":

并更改了processServerResponseX以增加锁定部分中的stats [0] .counter。然后我更改了脚本以使用“hexdump”解析shm文件:

hexdump /dev/shm/TestStats -e ' 1/8 "%s " 1/8 "%d\n"'

This will then show something like this:

这将显示如下:

nRespX  23

This way I can extend this later if I want to also look at response Y, ...

这样我以后可以扩展它,如果我也想看看响应Y,...

Not sure if there are mutual exclusion problems with hexdump if it accessed the file while it was being changed. But in my case, I don't think it matters, because the script only calls it before and after the test, it should not be in the middle of an update.

如果在更改文件时访问文件,则不确定hexdump是否存在互斥问题。但在我的情况下,我认为这不重要,因为脚本只在测试之前和之后调用它,它不应该在更新的中间。