Skip to content

Commit

Permalink
sysstat#174: Initial implementation of CSV output from pidstat.
Browse files Browse the repository at this point in the history
  • Loading branch information
tst-ppenev committed Feb 28, 2018
1 parent 9e4b9b8 commit ae455e4
Show file tree
Hide file tree
Showing 4 changed files with 315 additions and 1 deletion.
96 changes: 96 additions & 0 deletions common.c
Original file line number Diff line number Diff line change
Expand Up @@ -1296,6 +1296,102 @@ void cprintf_s(int type, char *format, char *string)
printf("%s", sc_normal);
}

/*
***************************************************************************
* Print to a file, exiting on error.
*
* IN:
* @stream The stream to write to.
* @file_path
* File path of the output stream for error reporting.
* @format Output format.
***************************************************************************
*/
void efprintf(FILE *stream, const char *file_path, const char *format, ...)
{
va_list(args);

va_start(args, format);
if (vfprintf(stream, format, args) < 0) {
perror("fprintf");
if (file_path != NULL) {
fprintf(stderr, " while writing to file: %s", file_path);
}
exit(4);
}
va_end(args);
}

/*
***************************************************************************
* Print a quoted string field to a CSV file, exiting on error.
*
* We quote according to RFC 4180 (https://tools.ietf.org/html/rfc4180).
*
* IN:
* @stream The stream to write to.
* @file_path
* File path of the output stream for error reporting.
* @format Output format.
***************************************************************************
*/
void csv_efprintf_s(FILE *stream, const char *file_path,
const char *format, ...)
{
char buf[2];
va_list(args);
va_list(args_copy);
char *formatted_str;
int formatted_len;

va_start(args, format);

// Calculate the length of the formatted string:
va_copy(args_copy, args);
formatted_len = vsnprintf(buf, sizeof(buf), format, args_copy);
va_end(args_copy);
if (formatted_len < 0) {
perror("vsnprintf");
if (file_path != NULL) {
fprintf(stderr, " while writing to file: %s", file_path);
}
va_end(args);
exit(4);
}

// Format the string:
formatted_str = malloc(formatted_len + 1);
if (formatted_str == NULL) {
perror("malloc");
if (file_path != NULL) {
fprintf(stderr, " while writing to file: %s", file_path);
}
va_end(args);
exit(4);
}
va_end(args);
if (vsnprintf(formatted_str, formatted_len + 1, format, args) < 0) {
perror("vsnprintf");
if (file_path != NULL) {
fprintf(stderr, " while writing to file: %s", file_path);
}
free(formatted_str);
exit(4);
}

// Write the string to the output file:
efprintf(stream, file_path, "\"");
for (int i = 0; i < formatted_len; ++ i) {
// Escape double quotes:
if (formatted_str[i] == '"') {
efprintf(stream, file_path, "\"");
}
efprintf(stream, file_path, "%c", (int) formatted_str[i]);
}
free(formatted_str);
efprintf(stream, file_path, "\"");
}

/*
***************************************************************************
* Parse a string containing a numerical value (e.g. CPU or IRQ number).
Expand Down
2 changes: 2 additions & 0 deletions common.h
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ void xprintf
(int, const char *, ...);
void xprintf0
(int, const char *, ...);
void efprintf(FILE *, const char *, const char *, ...);
void csv_efprintf_s(FILE *, const char *, const char *, ...);

#endif /* SOURCE_SADC undefined */
#endif /* _COMMON_H */
216 changes: 215 additions & 1 deletion pidstat.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <sys/utsname.h>
#include <regex.h>
#include <linux/sched.h>
#include <stdio.h>

#include "version.h"
#include "pidstat.h"
Expand Down Expand Up @@ -62,6 +63,8 @@ char commstr[MAX_COMM_LEN];
char userstr[MAX_USER_LEN];
char procstr[MAX_COMM_LEN];
int show_threads = FALSE;
char *csv_file_path = NULL;
FILE *csv_file = NULL;

unsigned int pid_nr = 0; /* Nb of PID to display */
unsigned int pid_array_nr = 0;
Expand Down Expand Up @@ -92,6 +95,7 @@ void usage(char *progname)
fprintf(stderr, _("Options are:\n"
"[ -d ] [ -H ] [ -h ] [ -I ] [ -l ] [ -R ] [ -r ] [ -s ] [ -t ] [ -U [ <username> ] ]\n"
"[ -u ] [ -V ] [ -v ] [ -w ] [ -C <command> ] [ -G <process_name> ] [ --human ]\n"
"[ --csv <file_path> ]\n"
"[ -p { <pid> [,...] | SELF | ALL } ] [ -T { TASK | CHILD | ALL } ]\n"));
exit(1);
}
Expand Down Expand Up @@ -909,6 +913,42 @@ unsigned int count_tid_in_list(void)
return pid;
}

/*
***************************************************************************
* Write column names for all fields that would be output as CSVs.
***************************************************************************
*/
void csv_write_header(void)
{
efprintf(csv_file, csv_file_path,
"timestamp,interval_ticks,interval_all_cpu_ticks,User,UID,TGID,TID,PID");
if (DISPLAY_CPU(actflag)) {
efprintf(csv_file, csv_file_path,
",user_ticks,system_ticks,guest_ticks,wait_ticks,%%CPU,CPU,ticks/s");
}
if (DISPLAY_MEM(actflag)) {
efprintf(csv_file, csv_file_path,
",minflt,majflt,VSZ_kb,RSS_kb,total_mem_kb,%%mem");
}
if (DISPLAY_STACK(actflag)) {
efprintf(csv_file, csv_file_path, ",StkSize_kb,StkRef_kb");
}
if (DISPLAY_IO(actflag)) {
efprintf(csv_file, csv_file_path,
",B_rd,B_wr,B_ccwr,IOdelay_ticks");
}
if (DISPLAY_CTXSW(actflag)) {
efprintf(csv_file, csv_file_path, ",cswch,nvcswch");
}
if (DISPLAY_KTAB(actflag)) {
efprintf(csv_file, csv_file_path, ",thread_count,fd_count");
}
if (DISPLAY_RT(actflag)) {
efprintf(csv_file, csv_file_path, ",prio,policy");
}
efprintf(csv_file, csv_file_path, ",Command\n");
}

/*
***************************************************************************
* Allocate and init structures according to system state.
Expand Down Expand Up @@ -936,6 +976,16 @@ void pid_sys_init(void)
pid_nr = pid_array_nr;
salloc_pid(pid_nr);
}

/* Open output files: */
if (OUTPUT_CSV(pidflag)) {
csv_file = fopen(csv_file_path, "w");
if (csv_file == NULL) {
perror("Opening CSV output file");
exit(4);
}
csv_write_header();
}
}

/*
Expand Down Expand Up @@ -2271,6 +2321,146 @@ int write_pid_ktab_stats(int prev, int curr, int dis, int disp_avg,
return again;
}

/*
***************************************************************************
* Display statistics.
*
* IN:
* @prev Index in array where stats used as reference are.
* @curr Index in array for current sample statistics.
* @prev_tm Pointer to a tm struct with the the timestamp of the
* previous sample.
* @curr_tm Pointer to a tm struct with the timestamp of the current sample.
* @itv Interval of time in jiffies.
* @deltot_jiffies
* Number of jiffies spent on the interval by all processors.
***************************************************************************
*/
void csv_write_stats(int prev, int curr,
struct tm *prev_tm, struct tm *curr_tm,
unsigned long long itv, unsigned long long deltot_jiffies)
{
struct pid_stats *pstc, *pstp;
unsigned int p;
char time_buf[256];

for (p = 0; p < pid_nr; p++) {

if (get_pid_to_display(prev, curr, p, actflag, P_TASK,
&pstc, &pstp) <= 0)
continue;

// Write timestamp,interval_ticks,interval_all_cpu_ticks:
if (strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", curr_tm) < 1) {
perror("strftime");
exit(4);
}
efprintf(csv_file, csv_file_path, "%s,%llu,%llu,",
time_buf, itv, deltot_jiffies);

// Write user name:
struct passwd *pwdent;

if ((pwdent = getpwuid(pstc->uid)) != NULL) {
csv_efprintf_s(csv_file, csv_file_path, "%s", pwdent->pw_name);
}

// Write UID, TGID, TID, PID:
efprintf(csv_file, csv_file_path, ",%d,%d,", pstc->uid, pstc->tgid);
if (pstc->tgid) {
// pstc->pid is, actually, a TID:
efprintf(csv_file, csv_file_path, "%d,0,", pstc->pid);
}
else
{
efprintf(csv_file, csv_file_path, "0,%d,", pstc->pid);
}

if (DISPLAY_CPU(actflag)) {
// Write user_ticks,system_ticks,guest_ticks,wait_ticks,
// %CPU,CPU,ticks/s:
efprintf(csv_file, csv_file_path,
"%llu,%llu,%llu,%llu,%f,%u,%lu,",
pstc->utime, pstc->stime, pstc->gtime, pstc->wtime,
/* User time already includes guest time */
IRIX_MODE_OFF(pidflag) ?
SP_VALUE_100(pstp->utime + pstp->stime,
pstc->utime + pstc->stime, deltot_jiffies) :
SP_VALUE_100(pstp->utime + pstp->stime,
pstc->utime + pstc->stime, itv * HZ / 100),
pstc->processor, HZ);
}

if (DISPLAY_MEM(actflag)) {
// Write minflt,majflt,VSZ_kb,RSS_kb,total_mem_kb,%mem:
efprintf(csv_file, csv_file_path,
"%llu,%llu,%llu,%llu,%lu,%f,",
pstc->minflt - pstp->minflt,
pstc->majflt - pstp->majflt,
pstc->vsz,
pstc->rss,
tlmkb,
tlmkb ? SP_VALUE(0, pstc->rss, tlmkb) : 0.0);
}

if (DISPLAY_STACK(actflag)) {
// Write StkSize_kb,StkRef_kb:
efprintf(csv_file, csv_file_path, "%lu,%lu,",
pstc->stack_size, pstc->stack_ref);
}

if (DISPLAY_IO(actflag)) {
// Write B_rd,B_wr,B_ccwr,IOdelay_ticks:
if (!NO_PID_IO(pstc->flags))
{
efprintf(csv_file, csv_file_path, "%llu,%llu,%llu,",
pstc->read_bytes, pstc->write_bytes,
pstp->cancelled_write_bytes);
}
else {
/*
* Keep the layout even though this task has no I/O
* typically threads with no I/O measurements.
*/
efprintf(csv_file, csv_file_path, "-1,-1,-1,");
}
/* I/O delays come from another file (/proc/#/stat) */
efprintf(csv_file, csv_file_path, "%llu,",
(unsigned long long) (pstc->blkio_swapin_delays - pstp->blkio_swapin_delays));
}

if (DISPLAY_CTXSW(actflag)) {
// Write cswch,nvcswch:
efprintf(csv_file, csv_file_path, "%lu,%lu,",
pstc->nvcsw - pstp->nvcsw,
pstc->nivcsw - pstp->nivcsw);
}

if (DISPLAY_KTAB(actflag)) {
// Write thread_count,fd_count:
efprintf(csv_file, csv_file_path, "%lu,", pstc->threads);
if (NO_PID_FD(pstc->flags)) {
/* /proc/#/fd directory not readable */
efprintf(csv_file, csv_file_path, "-1,");
}
else {
efprintf(csv_file, csv_file_path, "%lu,", pstc->fd_nr);
}
}

if (DISPLAY_RT(actflag)) {
// Write prio,policy:
efprintf(csv_file, csv_file_path, "%lu,%s,",
pstc->priority, GET_POLICY(pstc->policy));
}

// Write Command:
csv_efprintf_s(csv_file, csv_file_path, "%s", get_tcmd(pstc));

efprintf(csv_file, csv_file_path, "\n");
}
}

/*
***************************************************************************
* Display statistics.
Expand Down Expand Up @@ -2382,6 +2572,11 @@ int write_stats_core(int prev, int curr, int dis, int disp_avg,
again = 1;
}

if (!disp_avg && OUTPUT_CSV(pidflag)) {
csv_write_stats(!curr, curr, &ps_tstamp[!curr], &ps_tstamp[curr],
itv, deltot_jiffies);
}

return again;
}

Expand Down Expand Up @@ -2744,6 +2939,15 @@ int main(int argc, char **argv)
opt++;
}

else if (!strcmp(argv[opt], "--csv")) {
if (!argv[++opt]) {
usage(argv[0]);
}
pidflag |= P_O_CSV;
csv_file_path = argv[opt];
opt++;
}

/* Option used individually. See below for grouped option */
else if (!strcmp(argv[opt], "-U")) {
/* Display username instead of UID */
Expand Down Expand Up @@ -2913,5 +3117,15 @@ int main(int argc, char **argv)
free(pid_array);
sfree_pid();

return 0;
int exit_status = 0;

/* Close streams: */
if (csv_file != NULL) {
if (fclose(csv_file)) {
perror("Closing output CSV file");
exit_status = 4;
}
}

return exit_status;
}
Loading

0 comments on commit ae455e4

Please sign in to comment.