我有一个应用程序,它会不时输出到标准输出 - 随机间隔,随机输出持续时间,然后再次保持沉默。其输出(二进制数据)被捕获到文件中。
我希望能够记录输出开始时刻的时间戳,但前提是应用程序保持输出至少 3 秒,并且如果至少 5 秒没有收到输出,还记录输出停止的时间停止后的秒数(如果收到,则将其视为前一周期的一部分)。
Start: (date +'%d.%m - %H:%M:%S')
End: (date +'%d.%m - %H:%M:%S')
此后输出必须透明地重定向到文件。
类似于
tee
,但带有时间戳记录器,例如:
app | monitor > out
monitor
可以输出到日志文件或stderr,没关系。
有没有办法使用 shell 上可用的常用工具来做到这一点?
我可以编写一个 Python 脚本,但我不想无缘无故地使任务过于复杂。
不久前,当我想记录通过管道的数据以获取直方图时,我写了一些东西。这是一个相当快的肮脏的 C 程序,但它对我有用(tm)。您可以将其放在管道中的某个位置,然后通过它的数据就不会改变地传递。 JSON 输出到 stderror,或使用 -o 开关输出到文件。
这是一个例子:
{ echo -n A; sleep 3; echo -n B; sleep 6; echo -n C; } | ./pipe_histo2 -z 2>&1 >/dev/null | jq .
{
"measurements": [
[ 0, 1 ],
[ 2, 1 ],
[ 8, 1 ]
],
"min": 0,
"max": 1,
"avg": 0.333336,
"total": 3,
"start_time": "2024-06-28 23:31:34.338997847",
"start_epoch": "1719610294",
"end_time": "2024-06-28 23:31:43.338917382",
"end_epoch": "1719610303",
"elapsed": 8,
"total_readable": "3B",
"elapsed_readable": "08s"
}
这是 C 代码。将其复制到名为 pipeline_histo2.c 的文件中
/*
To compile: gcc -Wall -o pipe_histo2 pipe_histo2.c
Examples:
{ echo -n A; sleep 3; echo -n B; sleep 3; echo -n C; } | ./pipe_histo 2>&1 >/dev/null | jq .
ls -als /dev | ./pipe_histo2
*/
/***************************************************************************/
/** Includes **/
#include <stdio.h>
#include <stdlib.h> // atexit
#include <sys/time.h> // select
#include <sys/types.h> // select
#include <unistd.h> // select
#include <time.h> // clock_gettime
#include <string.h> // strlen
char *output_file = NULL;
FILE *output_fd;
unsigned int omit_zero_measurements = 0;
unsigned int omit_seconds = 0;
unsigned int only_output_json = 0;
unsigned int print_graph = 0;
void die(char *S)
{
printf("%s\n", S);
exit(1);
}
void Usage(char *Argv0)
{
printf("\n"
"Usage: <some process> | %s [options] [| <some process>]\n"
"\n"
" -o <output file> Output JSON to a file\n"
" -z Omit zero measurements. If value == 0, do not print\n"
" -s Omit seconds. Only print values in the JSON\n"
" -j Only print JSON. Does not output what has been read, only prints JSON\n"
" -g Output an ASCII art graph\n"
" -h This help text\n"
"\n"
"Example: { echo -n A; sleep 3; echo -n B; sleep 6; echo -n C; } | %s -z 2>&1 >/dev/null | jq .\n"
"\n"
" stdbuf -i0 -o0 -e0 find / -xdev | %s -o result.json | wc -l\n"
"\n"
,Argv0
,Argv0
,Argv0
);
exit(0);
}
void parse_arg(int argc, char *argv[])
{
int c;
while((c = getopt(argc, argv, "ho:zsjg")) != -1)
switch(c)
{
case 'o': output_file = optarg;
break;
case 'z': omit_zero_measurements = 1;
break;
case 's': omit_seconds = 1;
break;
case 'j': only_output_json = 1;
break;
case 'g': print_graph = 1;
break;
case 'h': Usage(argv[0]);
break;
case '?': die("getopt: unknown option");
default: die("getopt: default reached. Internal error");
}
output_fd = stderr;
if(only_output_json)
output_fd = stdout;
if(output_file)
if((output_fd = fopen(output_file, "w")) == NULL)
die("fopen");
}
static void timespec_diff(struct timespec *end, struct timespec *start, struct timespec *result)
{
if(end->tv_nsec < start->tv_nsec)
{
result->tv_sec = end->tv_sec - start->tv_sec - 1;
result->tv_nsec = 1000000000 + end->tv_nsec - start->tv_nsec;
}
else
{
result->tv_sec = end->tv_sec - start->tv_sec;
result->tv_nsec = end->tv_nsec - start->tv_nsec;
}
}
// https://stackoverflow.com/questions/8304259/formatting-struct-timespec/14746954
static char *timespec2str(struct timespec *TS)
{
static char ret[] = "2012-12-31 12:59:59.123456789"; // Maximum size
struct tm t;
tzset();
if(localtime_r(&(TS->tv_sec), &t) == NULL)
die("localtime_r");
if(strftime(ret, strlen(ret), "%F %T", &t) == 0)
die("strftime");
sprintf(&ret[strlen(ret)], ".%09ld", TS->tv_nsec);
return ret;
}
double timespec2double(struct timespec *TS)
{
double ret = TS->tv_nsec;
while(ret > 1)
ret = ret / 10;
return ret + TS->tv_sec;
}
char *printReadableSize(size_t Size)
{
unsigned long long int scale = 1;
char *postfix = "BKMGTPEZY";
static char result[16];
for(; Size / (scale * 1024); scale = scale * 1024, postfix++);
if(Size % scale)
sprintf(result, "%.02f%.1s", (float) Size / scale, postfix);
else
sprintf(result, "%u%.1s", (unsigned int) (Size / scale), postfix);
return result;
}
char *printReadableTime(unsigned long int Seconds)
{
static char ret[22];
unsigned int ret_len = 0;
if(Seconds > 31536000) // years
{
ret_len = ret_len + sprintf(&ret[ret_len], "%luy", Seconds / 31536000);
Seconds = Seconds % 31536000;
}
if(Seconds > 604800) // weeks
{
ret_len = ret_len + sprintf(&ret[ret_len], "%luw", Seconds / 604800);
Seconds = Seconds % 604800;
}
if(Seconds > 86400) // days
{
ret_len = ret_len + sprintf(&ret[ret_len], "%lud", Seconds / 86400);
Seconds = Seconds % 86400;
}
if(Seconds > 3600) // hours
{
ret_len = ret_len + sprintf(&ret[ret_len], "%luh", Seconds / 3600);
Seconds = Seconds % 3600;
}
if(Seconds > 60) // minutes
{
ret_len = ret_len + sprintf(&ret[ret_len], "%lum", Seconds / 60);
Seconds = Seconds % 60;
}
ret_len = ret_len + sprintf(&ret[ret_len], "%02lus", Seconds);
ret[ret_len] = '\0';
return ret;
}
int main(int argc, char *argv[])
{
char buf[4096];
unsigned int buf_len=0;
fd_set read_set;
fd_set error_set;
unsigned long int t;
struct timespec start;
struct timespec end;
struct timespec tmp;
size_t histo[604800] = { 0 }; // A weeks worth of seconds
unsigned long int histo_len = 0;
size_t value_min;
size_t value_max;
size_t value_total;
unsigned int first = 1;
parse_arg(argc, argv);
FD_ZERO(&read_set);
FD_SET(STDIN_FILENO, &read_set);
FD_ZERO(&error_set);
FD_SET(STDIN_FILENO, &error_set);
if(clock_gettime(CLOCK_REALTIME, &start) == -1)
die("CLOCK_REALTIME");
while(1)
{
t = select(STDIN_FILENO+1, &read_set, NULL, &error_set, NULL);
if(FD_ISSET(STDIN_FILENO, &error_set))
die("error_set\n");
switch(t)
{
case -1: die("Getch_timeout: select returned -1\n");
case 0: die("0 - on blocking call\n");
default: buf_len = read(STDIN_FILENO, buf, 4096);
switch(buf_len)
{
case -1: die("read");
case 0: goto exit_copy; // EOF
}
if(clock_gettime(CLOCK_REALTIME, &end) == -1)
die("CLOCK_REALTIME");
timespec_diff(&end, &start, &tmp);
histo[tmp.tv_sec] += buf_len;
histo_len = tmp.tv_sec;
if(!only_output_json)
{
t = write(STDOUT_FILENO, buf, buf_len);
if(t != buf_len)
die("write != read\n");
}
break;
} // switch
} // while
exit_copy:
if(clock_gettime(CLOCK_REALTIME, &end) == -1)
die("CLOCK_REALTIME");
// Display results
fprintf(output_fd, "{\"measurements\":[");
value_min = histo[0];
value_max = 0;
value_total = 0;
for(t=0; t <= histo_len; t++)
{
if(!omit_zero_measurements || histo[t])
{
if(first)
first = 0;
else
fprintf(output_fd, ",");
if(omit_seconds)
fprintf(output_fd, "%lu", histo[t]);
else
fprintf(output_fd, "[%lu,%lu]", t, histo[t]);
}
if(histo[t] < value_min)
value_min = histo[t];
if(histo[t] > value_max)
value_max = histo[t];
value_total += histo[t];
}
timespec_diff(&end, &start, &tmp);
fprintf(output_fd, "],\"min\":%lu,\"max\":%lu,\"avg\":%f,\"total\":%lu", value_min, value_max, (double) value_total / timespec2double(&tmp), value_total);
fprintf(output_fd, ",\"start_time\":\"%s\",\"start_epoch\":\"%lu\"", timespec2str(&start), start.tv_sec);
fprintf(output_fd, ",\"end_time\":\"%s\",\"end_epoch\":\"%lu\"", timespec2str(&end), end.tv_sec);
fprintf(output_fd, ",\"elapsed\":%lu", tmp.tv_sec);
fprintf(output_fd, ",\"total_readable\":\"%s\",\"elapsed_readable\":\"%s\"", printReadableSize(value_total), printReadableTime(tmp.tv_sec));
if(print_graph)
{
fprintf(output_fd, ",\"graph\":\"xx TODO xx\"");
}
fprintf(output_fd, "}\n");
fclose(output_fd);
return 0;
}
// EOF