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.

5
  • 5
    Side notes: (1) #include <bits/stdc++.h>: stackoverflow.com/questions/31816095/…, (2) using namespace std;: stackoverflow.com/questions/1452721/… Commented Jan 14, 2023 at 16:01
  • why in c++ rather than say in python or bash? Commented Jan 14, 2023 at 16:25
  • @NeilButterworth Can this be done in bash? Commented Jan 14, 2023 at 16:34
  • 1
    sure, why not? python would probably be nicer, though. Commented Jan 14, 2023 at 16:38
  • Use popen() to run another application and catch the standard output in a stream. man popen Commented Jan 14, 2023 at 18:14

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Could you write how to do it using std::system? (external libraries cannot be used in competitions)
@Darkolin Added example without boost

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.