// memtouch.c — presión de memoria: reserva N MB y los recorre varias veces,
// escribiendo un byte en cada página. Por cada pasada imprime el tiempo, la
// velocidad y cuántos fallos de página hubo (menores: sin disco; mayores:
// hubo que leer la página del disco, por ejemplo desde el swap).
//
// Uso:   ./memtouch MB [pasadas] [huge]
//   pasadas  cuántas veces se recorre la memoria (por defecto 4)
//   huge     permitir páginas grandes (THP); sin él se piden páginas de
//            4 KB para que cada página cuente como un fallo
//
// Para verlo hacer swap, dentro de un contenedor con poca memoria:
//   docker run --rm -it --memory=256m -v "$PWD":/work -w /work sistemas-operativos bash
//   ./memtouch 200 & vmstat 1      # cabe: sin si/so
//   ./memtouch 400 & vmstat 1      # no cabe: si/so distintos de 0
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <time.h>
#include <unistd.h>

static double now_s(void) {
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec + ts.tv_nsec / 1e9;
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "uso: %s MB [pasadas] [huge]\n", argv[0]);
        return 1;
    }
    long mb = atol(argv[1]);
    int passes = (argc > 2) ? atoi(argv[2]) : 4;
    int huge = (argc > 3) && strcmp(argv[3], "huge") == 0;
    long page = sysconf(_SC_PAGESIZE);
    size_t size = (size_t)mb << 20;
    if (mb <= 0 || passes <= 0) {
        fprintf(stderr, "MB y pasadas deben ser positivos\n");
        return 1;
    }

    // mmap (lo mismo que hace malloc con bloques grandes) solo reserva
    // direcciones: la memoria física llega con el primer acceso a cada
    // página, en un fallo de página.
    char *buf = mmap(NULL, size,
        PROT_READ | PROT_WRITE,
        MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (buf == MAP_FAILED) {
        perror("mmap");
        return 1;
    }
    madvise(buf, size,
            huge ? MADV_HUGEPAGE : MADV_NOHUGEPAGE);
    printf("PID %d: %ld MB, páginas de %ld bytes, %zu páginas\n", getpid(),
           mb, page, size / page);
    printf("%7s %9s %9s %10s %10s\n", "pasada", "segundos", "MB/s",
           "f.menores", "f.mayores");
    fflush(stdout);

    struct rusage before, after;
    for (int p = 1; p <= passes; p++) {
        getrusage(RUSAGE_SELF, &before);
        double t0 = now_s();
        for (size_t i = 0; i < size; i += page)
            buf[i] = (char)p; // queda sucia
        double dt = now_s() - t0;
        getrusage(RUSAGE_SELF, &after);
        printf("%7d %9.3f %9.0f %10ld %10ld\n", p, dt, mb / dt,
               after.ru_minflt - before.ru_minflt,
               after.ru_majflt - before.ru_majflt);
        fflush(stdout);
    }
    munmap(buf, size);
    return 0;
}
