/* EXERCISE 05 — REPAIRED.
 *
 * A crash-log scanner. It walks a log file line by line and tallies the lines
 * that contain a marker.
 *
 * The repair: read in BUFFER-sized gulps and split lines in user space. The
 * file is unchanged, the disk does the same work, and the answer is identical.
 * What changes is the number of user-to-kernel transitions: one per 64 KiB
 * instead of one per byte.
 *
 * This is not "caching" and it does not make the storage faster. The page
 * cache was already holding the data; the cost being removed is the system
 * call itself.
 *
 * Build and run:
 *   clang -O2 -g -Wall -Wextra logscan.c -o /tmp/logscan_fixed
 *   /tmp/logscan_fixed
 *
 * Every measurement is printed as key=value so a script can assert on it.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/time.h>
#include <sys/stat.h>

#define BLOCKS      96          /* log is built in 1024-line blocks      */
#define LINES       (BLOCKS * 1024)  /* lines in the generated log       */
#define LINE_BYTES  96          /* bytes per line, including the newline */
#define WATCHDOG_S  300

static double now_ms(void) {
    struct timeval t; gettimeofday(&t, NULL);
    return t.tv_sec * 1000.0 + t.tv_usec / 1000.0;
}

static void *watchdog(void *unused) {
    (void)unused;
    sleep(WATCHDOG_S);
    const char m[] = "WATCHDOG: no progress within budget - forcing _exit(75).\n";
    ssize_t ignored = write(2, m, sizeof m - 1); (void)ignored;
    _exit(75);
}

/* Build the log in a temporary file we own and delete. */
static int make_log(char *path, size_t pathsz, size_t *bytes_out) {
    snprintf(path, pathsz, "%s/logscan.%d.log",
             getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp", (int)getpid());
    int fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
    if (fd < 0) { perror("open"); return -1; }
    char *chunk = malloc((size_t)LINE_BYTES * 1024);
    size_t total = 0;
    for (int block = 0; block < BLOCKS; block++) {
        char *p = chunk;
        for (int i = 0; i < 1024; i++) {
            int seq = block * 1024 + i;
            memset(p, 'a' + (seq % 26), LINE_BYTES - 1);
            if (seq % 97 == 0) memcpy(p + 8, "MARKER", 6);
            p[LINE_BYTES - 1] = '\n';
            p += LINE_BYTES;
        }
        ssize_t w = write(fd, chunk, (size_t)LINE_BYTES * 1024);
        if (w < 0) { perror("write"); free(chunk); close(fd); return -1; }
        total += (size_t)w;
    }
    free(chunk);
    fcntl(fd, F_FULLFSYNC, 0);
    close(fd);
    *bytes_out = total;
    return 0;
}

int main(void) {
    pthread_t wd;
    pthread_create(&wd, NULL, watchdog, NULL);
    pthread_detach(wd);

    char path[512]; size_t filebytes = 0;
    if (make_log(path, sizeof path, &filebytes) != 0) return 1;

    int fd = open(path, O_RDONLY);
    if (fd < 0) { perror("open"); return 1; }

    char line[512];
    size_t len = 0;
    long lines = 0, markers = 0, syscalls = 0;
    unsigned long long checksum = 0;

    /* 64 KiB is comfortably past the knee of the size/throughput curve on
     * every machine this has been run on, and it is small enough to stay off
     * the heap's large-allocation path and out of cache-hostile territory. */
    enum { BUFFER = 64 * 1024 };
    char *buf = malloc(BUFFER);
    if (!buf) { perror("malloc"); close(fd); unlink(path); return 1; }

    double t0 = now_ms();
    for (;;) {
        /* One system call per BUFFER bytes. The line splitting that used to
         * happen between system calls now happens between memory accesses. */
        ssize_t n = read(fd, buf, BUFFER);
        syscalls++;
        if (n <= 0) break;
        for (ssize_t i = 0; i < n; i++) {
            char c = buf[i];
            if (c == '\n') {
                line[len < sizeof line - 1 ? len : sizeof line - 1] = '\0';
                lines++;
                if (strstr(line, "MARKER")) markers++;
                checksum += (unsigned long long)len;
                len = 0;
            } else if (len < sizeof line - 1) {
                line[len++] = c;
            }
        }
    }
    double ms = now_ms() - t0;
    free(buf);
    close(fd);
    unlink(path);

    printf("fileBytes=%zu\n", filebytes);
    printf("lines=%ld\n", lines);
    printf("markers=%ld\n", markers);
    printf("checksum=%llu\n", checksum);
    printf("readSyscalls=%ld\n", syscalls);
    printf("bytesPerSyscall=%.1f\n", syscalls ? (double)filebytes / (double)syscalls : 0.0);
    printf("elapsedMs=%.1f\n", ms);
    printf("throughputMBps=%.1f\n", (filebytes / 1048576.0) / (ms / 1000.0));
    return lines == LINES ? 0 : 1;
}
