0

I would like to measure the maximum memory usage of abc.exe on random tests generated by gen.exe. How could I do that?

My code that runs abc.exe on tests from gen.exe looks like this:

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int i = 0;
    while (true)
    {
        string si = to_string(i);
        cout << i << "\n";
        if (system(("echo " + si + "| ./gen.exe > test.in").c_str())) // gen.exe is test generator
        {
            cout << "gen error\n";
            break;
        }
        if (system(("./abc.exe < test.in > a.out"))) // abc.exe is the program I want to test
        {
            cout << "abc error\n";
            break;
        }
        i++;
    }
}

I know that i can use time -v ./abc.exe but then the used memory is printed in the terminal but I'd like to be able to save it to a variable.

Darkolin
  • 71
  • 5

1 Answers1

1

You can use getrusage( RUSAGE_CHILDREN, ... ) to obtain the maximum resident memory. Note that this call will return the maximum memory used by the biggest child at that point in time.

In the example below I used boost::process because it gives better control but it's up to you to use std::system or not, works the same way.

#include <string>
#include <cstdint>
#include <string.h>
#include <iostream>
#include <boost/process/child.hpp>
#include <sys/resource.h>

namespace bp = boost::process;

int parent( const std::string& exename )
{
    // Loop from 0 to 10 megabytes
    for ( int j=0; j<10; ++j )
    {
        // Command name is the name of this executable plus one argument with size
        std::string gencmd = exename + " " + std::to_string(j);

        // Start process
        bp::child child( gencmd );

        // Wait for it to allocate memory
        sleep(1);

        // Query the memory usage at this point in time
        struct rusage ru;
        getrusage( RUSAGE_CHILDREN, &ru );
        std::cerr << "Loop:" << j << " mem:"<< ru.ru_maxrss/1024. << " MB" << std::endl;

        // Wait for process to quit
        child.wait();
        if ( child.exit_code()!=0 )
        {
            std::cerr << "Error executing child:" << child.exit_code() << std::endl;
            return 1;
        }
    }
    return 0;
}

int child( int size ) {
    // Allocated "size" megabites explicitly
    size_t memsize = size*1024*1024;
    uint8_t* ptr = (uint8_t*)malloc( memsize );
    memset( ptr, size, memsize );

    // Wait for the parent to sample our memory usage
    sleep( 2 );

    // Free memory
    free( ptr );

    return 0;
}

int main( int argc, char* argv[] )
{
    // Without arguments, it is the parent. 
    // Pass the name of the binary 
    if ( argc==1 ) return parent( argv[0] );
    return child( std::atoi( argv[1] ) );
}

It prints

$ ./env_test 
Loop:0 mem:0 MB
Loop:1 mem:3.5625 MB
Loop:2 mem:4.01953 MB
Loop:3 mem:5.05469 MB
Loop:4 mem:6.04688 MB
Loop:5 mem:7.05078 MB
Loop:6 mem:7.78516 MB
Loop:7 mem:8.97266 MB
Loop:8 mem:9.82031 MB
Loop:9 mem:10.8867 MB

If you cannot use boost libraries, you'd got to work a little more but it is still feasible.

If you just want to know the maximum size ever of your children processes then the following works with std::system:

#include <cstdio>
#include <string>
#include <iostream>
#include <sstream>

#include <string.h>
#include <unistd.h>
#include <sys/resource.h>

int main(int argc, char* argv[]) {
    if (argc > 1) {
        size_t size = ::atol(argv[1]);
        size_t memsize = size * 1024 * 1024;
        void* ptr = ::malloc(memsize);
        memset(ptr, 0, memsize);
        ::sleep(2);
        ::free(ptr);
        return 0;
    }

    for (int j = 0; j < 10; ++j) {
        std::ostringstream cmd;
        cmd << argv[0] << " " << j;
        int res = std::system(cmd.str().c_str());
        if (res < 0) {
            fprintf(stderr, "ERROR system: %s\n", strerror(errno));
            break;
        }
        struct rusage ru;
        res = getrusage(RUSAGE_CHILDREN, &ru);
        size_t maxmem = ru.ru_maxrss;
        fprintf(stderr, "Loop:%d MaxMem:%ld\n", j, maxmem);
    }
    return 0;
}

It prints

Loop:0 MaxMem:3552
Loop:1 MaxMem:4192
Loop:2 MaxMem:5148
Loop:3 MaxMem:6228
Loop:4 MaxMem:7364
Loop:5 MaxMem:8456
Loop:6 MaxMem:9120
Loop:7 MaxMem:10188
Loop:8 MaxMem:11324
Loop:9 MaxMem:12256

However if you want to keep track of the memory usage during the child process execution you cannot use std::system(). First, you need to call fork() to spawn a new process and then execv() to execute a bash command.

#include <string>
#include <cstdint>
#include <string.h>
#include <unistd.h>
#include <iostream>
#include <sys/resource.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <vector>

int parent(const std::string& exename) {
    // Loop from 0 to 10 megabytes
    for (int j = 0; j < 10; ++j) {
        // Command name is the name of this executable plus one argument with size
        std::string gencmd = exename + " " + std::to_string(j);

        // Start process
        pid_t pid = fork();
        if (pid == 0) {  // child
            const char* args[] = {"/bin/bash", "-c", gencmd.c_str(), (char*)0};
            int res = execv("/bin/bash", (char**)args);
            // Should never return
            std::cerr << "execv error: " << strerror(errno) << std::endl;
            return 1;
        }

        // parent
        long maxmem = 0;
        while (true) {
            int status;
            pid_t rid = ::waitpid(pid, &status, WNOHANG);
            if (rid < 0) {
                if (errno != ECHILD) {
                    std::cerr << "waitpid:" << strerror(errno) << std::endl;
                    return 2;
                }
                break;
            }
            if (rid == pid) {
                if (WIFEXITED(pid)) {
                    break;
                }
            }

            // Wait for it to allocate memory
            usleep(10000);

            // Query the memory usage at this point in time
            struct rusage ru;
            int res = getrusage(RUSAGE_CHILDREN, &ru);
            if (res != 0) {
                if (errno != ECHILD) {
                    std::cerr << "getrusage:" << errno << strerror(errno) << std::endl;
                }
                break;
            }
            if (maxmem < ru.ru_maxrss) {
                maxmem = ru.ru_maxrss;
            }
        }
        std::cerr << "Loop:" << j << " mem:" << maxmem / 1024. << " MB" << std::endl;
    }
    return 0;
}

int child(int size) {
    // Allocated "size" megabites explicitly
    size_t memsize = size * 1024 * 1024;
    uint8_t* ptr = (uint8_t*)malloc(memsize);
    memset(ptr, size, memsize);

    // Wait for the parent to sample our memory usage
    sleep(2);

    // Free memory
    free(ptr);

    return 0;
}

int main(int argc, char* argv[]) {
    // Without arguments, it is the parent.
    // Pass the name of the binary
    if (argc == 1) return parent(argv[0]);
    return child(std::atoi(argv[1]));
}

The result on my machine is:

$ ./fork_test 
Loop:0 mem:3.22656 MB
Loop:1 mem:3.69922 MB
Loop:2 mem:4.80859 MB
Loop:3 mem:5.92578 MB
Loop:4 mem:6.87109 MB
Loop:5 mem:8.05469 MB
Loop:6 mem:8.77344 MB
Loop:7 mem:9.71875 MB
Loop:8 mem:10.7422 MB
Loop:9 mem:11.6797 MB

There is a video about this post.

Something Something
  • 3,999
  • 1
  • 6
  • 21