File collect_logs.c of Package mmdebug-tools

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <time.h>

/*
 * A simple log collector into a file. It tries hard to guarantee
 * that the content will get into the output file even under a strong memory
 * pressure.
 *
 * Usage
 * ./read_vmstat -o output_file
 *
 * -i log_file - file to read. Mandatory argument
 * -o output_file - file to collect data into. File must not exist. Stdout if not
 *                  present
 * -t timeout - time period between two snapshots. s - seconds, ms - miliseconds
 * 	        and m - minutes suffix is allowed.
 * 	        1s by default
 * -T time_period[ms/s/h/d] - how long to collect data. The output file size will
 *                  be estimated to fit all the snapshots.
 *                  -s is ignored if this one is present
 * -s output_size[k/M/G] - size of the outoutput file. 10MB by default
 * -w - wraparound mode (aka ring buffer). Overwrites oldest entries.
 *      WARNING - this can clobber output format and will make it harder to parse.
 *      Use on own risk
 *      Doesn't have any effect when -T is used
 * -p - precision timestamps. Use us granularity for =S and E= timestamps. By default
 *      second (unix time - number of seconds and microseconds since the Epoch) is only used
 *      =S marker.
 *
 * Each snapshot is enclosed by header and footer.
 * =S timestamp[.usec]
 * [...]
 * E= [timestamp.usec]
 *
 * Please note that your ulimit has to be sufficient to allow to mlock the code+
 * all the buffers.
 *
 * This comes under GPL v2
 * Copyright: Michal Hocko <mhocko@suse.cz> 2015 
 */

/* TODOs
 * - snaphosts with better time granularity
 * - adaptive read buffer size 
 * - handle mlock limit transparently
 * - align the buffer size to records so no overlaps are possible on the overflow
 * - mark overflows
 */
#define NS_PER_MS (1000*1000)
#define NS_PER_SEC (1000*NS_PER_MS)
#define ALIGN(x, a) (((x) + (a) - 1) & ~((a) - 1))
#define PAGE_SIZE 4096
#define PAGE_ALIGN(addr) ALIGN(addr, PAGE_SIZE)
#define BUFER_SIZE (1UL<<20)

void usage(void)
{
	fprintf(stderr, "collect_logs -i collect_file [-o output_file] [-t timeout] [-T time_period] [-s buffer_size] [-w]\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "\t-i collect_file - file to snapshot. Mandatory argument\n");
	fprintf(stderr, "\t-o output_file - file to store snapshots. stdout if missing\n");
	fprintf(stderr, "\t-t timeout ms/s/m/h/d - timeout between snapshots. 1s by default\n");
	fprintf(stderr, "\t-T time_period ms/s/m/h/d - time period to collect snapshots\n");
	fprintf(stderr, "\t-s buffer_size k/M/G - size of the output file. 10M by default. Ignored if -T is specified\n");
	fprintf(stderr, "\t-w - wrap around when the buffer gets full (ring buffer mode). WARNING - use with care, output data processing is PITA\n");
	fprintf(stderr, "\t-p - use precision timestamps (us granularity) for both start and end markers\n");
}

int open_file(const char *str)
{
	int fd;

	fd = open(str, O_CREAT|O_EXCL|O_RDWR, 0755);
	if (fd == -1) {
		perror("open input");
		return 1;
	}

	return fd;
}

int read_timeout(const char *str, struct timespec *timeout)
{
	char *end;
	unsigned long val;

	val = strtoul(str, &end, 10);
	if (val == ULONG_MAX) {
		perror("Invalid number");
		return 1;
	}
	switch(*end) {
		case 's':
			timeout->tv_sec = val;
			break;
		case 'm':
			/* ms vs minute*/
			if (*(end+1) == 's') {
				timeout->tv_sec = (val * NS_PER_MS) / NS_PER_SEC;
				timeout->tv_nsec = (val * NS_PER_MS) % NS_PER_SEC;
			} else {
				timeout->tv_sec = val*60;
			}
			break;
		case 'h':
			timeout->tv_sec = 3600 * val;
			break;
		case 'd':
			timeout->tv_sec = 24 * 3600 * val;
			break;
		default:
			fprintf(stderr, "Uknown number %s\n", str);
			return 1;
	}

	return 0;
}

size_t read_size(const char *str)
{
	char *end;
	size_t val = strtoul(str, &end, 10);

	switch (*end) {
		case 'K':
		case 'k':
			val <<= 10;
			break;
		case 'M':
			val <<= 20;
			break;
		case 'G':
			val <<= 30;
			break;
	}

	return val;
}

size_t read_snapshot(int in_fd, char *buff, size_t buffsize, int use_us)
{
	size_t size;
	struct timeval now;

	gettimeofday(&now, NULL);
	size = snprintf(buff, buffsize, "=S %lu", now.tv_sec);
	if (use_us)
		size += snprintf(buff + size, buffsize - size, ".%lu", now.tv_usec);
	size += snprintf(buff + size, buffsize - size, "\n");
	lseek(in_fd, 0, SEEK_SET);
	size += read(in_fd, buff + size, buffsize - size);
	size += snprintf(buff + size, buffsize - size, "E=");
	if (use_us) {
		gettimeofday(&now, NULL);
		size += snprintf(buff + size, buffsize - size, " %lu.%lu", now.tv_sec, now.tv_usec);
	}
	size += snprintf(buff + size, buffsize - size, "\n");

	return size;
}

size_t snapshot_size_est(int in_fd, int use_us)
{
	char buff[BUFER_SIZE];

	return read_snapshot(in_fd, buff, sizeof(buff), use_us);
}

size_t dump_str(char *buffer, size_t buffer_size, size_t pos, const char *in, size_t size)
{
	size_t i;
	for (i = 0; i < size; i++) {
		buffer[pos] = in[i];
		pos = (pos + 1) % buffer_size;
	}

	return pos;
}

/* buffer == NULL -> stdout */
int __collect_logs(int in_fd, const struct timespec *timeout, char *buffer, size_t buffer_size, int use_wraparound, int use_us)
{
	char buff[BUFER_SIZE]; /* dump to the file automatically */
	time_t before, after;
	size_t out_pos = 0;
	size_t in_pos = 0;
	size_t size = 0;
	int estimate = 0;

	/* lock everything in */
	if (mlockall(MCL_CURRENT) == -1) {
		perror("mlockall. Continuing anyway");
	}

	while (1) {
		size_t record_size;
		before = time(NULL);

		record_size = read_snapshot(in_fd, buff, sizeof(buff), use_us);
		size += record_size;
		if (buffer && !estimate) {
			printf("Estimated %d entries fit to the buffer\n", buffer_size/size);
			estimate = 1;
		}
		if (!use_wraparound && (size > buffer_size))
			break;

		/* Dump to stdout */
		if (!buffer) {
			printf("%s", buff);
		} else {
			size_t pos;
			/* TODO align to the size of the record to prevent from partial overwrites */
			pos = dump_str(buffer, buffer_size, out_pos, buff, record_size);
			if (pos < out_pos)
				fprintf(stderr, "%lu: Buffer wrapped\n", before);
			out_pos = pos;
		}

		after = time(NULL);

		if (after - before > 2) {
			fprintf(stderr, "%d: Snapshoting took %d!!!\n", before, after-before);
		}
		if (nanosleep(timeout, NULL) == -1)
			if (errno == EINTR)
				return 0;
		/* kick in the flushing */
		if (buffer)
			msync(buffer, buffer_size, MS_ASYNC);
	}

	return 0;
}

int collect_logs(int in_fd, int out_fd, const struct timespec *timeout, size_t buffer_size, int use_wraparound, int use_us)
{
	unsigned char *buffer = NULL;

	if (out_fd != -1) {
		size_t buffer_psize = PAGE_ALIGN(buffer_size);

		if (ftruncate(out_fd, buffer_psize) == -1) {
			perror("ftruncate");
			return 1;
		}

		if (fallocate(out_fd, 0, 0, buffer_psize) && errno != EOPNOTSUPP) {
			perror("fallocate");
			return 1;
		}

		/* commit it to the disk */
		sync();

		buffer = mmap(NULL, buffer_psize, PROT_READ | PROT_WRITE,
				MAP_SHARED | MAP_POPULATE, out_fd, 0);
		if (buffer == MAP_FAILED) {
			perror("mmap");
			return 1;
		}
	}

	return __collect_logs(in_fd, timeout, buffer, buffer_size, use_wraparound, use_us);
}

int main(int argc, char **argv)
{
	struct timespec timeout = {.tv_sec = 1};
	const char *out_file;
	struct timespec time_period;
	int use_time_period = 0;
	int use_wraparound = 0;
	int use_us = 0;
	int in_fd = -1, out_fd = -1;
	size_t buffer_size = 10UL<<20;
	size_t snapshot_size;
	int opt;

	while ((opt = getopt(argc, argv, "i:o:t:T:s:wph")) != -1) {
		switch (opt) {
			case 'i': /* input file */
				in_fd = open(optarg, O_RDONLY);
				if (in_fd == -1) {
					perror("open log_file:");
					return 1;
				}
				break;
			case 'o': /* output file */
				out_file = optarg;
				out_fd = open_file(optarg);
				if (out_fd == -1)
					return 1;
				break;
			case 't': /* timeout */
				if (read_timeout(optarg, &timeout))
					return 1;
				break;
			case 'T': /* time period */
				use_time_period = 1;
				if (read_timeout(optarg, &time_period))
					return 1;
				break;
			case 's': /* output file size */
				buffer_size = read_size(optarg);
				if (buffer_size == -1UL)
					return 1;
				break;
			case 'w': /* wraparound mode */
				use_wraparound = 1;
				break;
			case 'p': /* use precision (us) timestamps */
				use_us = 1;
				break;
			case 'h':
				usage();
				return 0;
			default:
				usage();
				return 1;
		}
	}

	if (in_fd == -1) {
		fprintf(stderr, "Input file missing\n");
		return 1;
	}

	snapshot_size = snapshot_size_est(in_fd, use_us);
	if (use_time_period) {
		size_t entries;

		entries = (time_period.tv_sec * NS_PER_SEC + time_period.tv_nsec) / (timeout.tv_sec * NS_PER_SEC + timeout.tv_nsec);
		buffer_size = snapshot_size * (entries + 1);
	} else if (out_fd == -1) {
		/* no limit on stdout */
		buffer_size = -1UL;
	}

	printf("file:%s timeout:%lu.%lus buffer_size:%llu\n",
			(out_fd == -1)? "stdout" : out_file,
			timeout.tv_sec, timeout.tv_nsec / NS_PER_MS,
			buffer_size);

	return collect_logs(in_fd, out_fd, &timeout, buffer_size, use_wraparound, use_us);
}
openSUSE Build Service is sponsored by