Usage

camisole exposes an HTTP API with JSON or MessagePack serialization. Once the HTTP server of camisole is running, you can query it with any HTTP client using a POST request.

The body of your request should contain an object describing the request you want to execute. camisole will return the response as JSON (or MessagePack, see Binary payloads), always including a “success” boolean that indicates whether the request was successful or not.

Simple request

The main endpoint of camisole used to compile and execute code is /run. You always have to provide two mandatory parameters:

  • lang: the language of your input
  • source: the source code to compile and execute.

Example with an interpreted language:

{
    "lang": "python",
    "source": "print(42)"
}

Result:

{
    "success": true,
    "tests": [
        {
            "exitcode": 0,
            "meta": {
                "cg-mem": 2664,
                "csw-forced": 4,
                "csw-voluntary": 3,
                "exitcode": 0,
                "exitsig": 0,
                "exitsig-message": null,
                "killed": false,
                "max-rss": 7416,
                "message": null,
                "status": "OK",
                "time": 0.017,
                "wall-time": 0.069
            },
            "name": "test000",
            "stderr": "",
            "stdout": "42\n"
        }
    ]
}

Adding limits and quotas

The compilation and execution of the program are sandboxed by default, which means the process won’t be able to access to any of the files in the root filesystem except /usr, /bin and /lib in readonly, it won’t be able to interact with any other process on the system or to use the network devices.

That said, the resources of the process aren’t limited by default, which means if you don’t add limits and quotas, the process can consume all your memory or your disk space and will never stop running.

You can put global resource limitations in the compile and execute bloc for the compilation and the execution, respectively.

There are a lot of ways you can limit the resources of both the compilation and the execution of your program:

  • time: limit the user time of the program (seconds)
  • wall-time: limit the wall time of the program (seconds)
  • extra-time: grace period before killing a program after it exceeded a time limit (seconds)
  • mem: limit the available memory of each process (kilobytes)
  • virt-mem: limit the address space of each process (kilobytes)
  • fsize: limit the size of files created by the program (kilobytes)
  • processes: limit the number of processes and/or threads
  • quota: limit the disk quota to a number of blocks and inodes (separate both numbers by a comma, eg. 10,30)
  • stack: limit the stack size of each process (kilobytes)

This example demonstrates the use of resource limitations for both the compilation and execution:

{
    "lang": "ocaml",
    "source": "print_string \"Hello, world!\\\\n\"",
    "compile": {
        "wall-time": 10,
        "fsize": 4096,
        "mem": 100000
    },
    "execute": {
        "time": 2,
        "wall-time": 5,
        "processes": 1,
        "quota": "50,3",
        "fsize": 256,
        "stack": 9000,
        "mem": 100000
    }
}

To get more information about the different options, visit the documentation of isolate, the isolation backend, where they are described in detail.

Sending a test suite

If you want to execute your program on multiple inputs, you shouldn’t send one request per input, as this would recompile the source code every time. You can send a list of tests to camisole, and the compiled program will get executed once per test.

To send a test suite, add a tests field in your input that contains a list of tests. A test is a (possibly empty) object that can have the following attributes:

  • name: the name of the test (defaults to an autoincremetal testXXX)
  • stdin: the input that will be given to the program during this test
  • Any additional test-specific resource limit. These resource limits, when specified, will override the global ones specified in the execute bloc.
  • fatal: a boolean indicating whether the test is fatal or not. If the test is fatal and its execution fails, the remaining tests won’t be executed.

Note that you can also add the boolean all_fatal in your main request if you want the tests to always be fatal.

Example input:

{
    "lang": "python",
    "source": "print(int(input()) * 2)",
    "tests": [{"name": "test_h2g2", "stdin": "42", "mem": 200000},
              {"name": "test_notfound", "stdin": "404"},
              {"name": "test_leet", "stdin": "1337"},
              {"name": "test_666", "stdin": "27972", "time": 1}]
}

Output:

{
    "success": true,
    "tests": [
        {
            "exitcode": 0,
            "meta": {
                "cg-mem": 2644,
                "csw-forced": 6,
                "csw-voluntary": 3,
                "exitcode": 0,
                "exitsig": 0,
                "exitsig-message": null,
                "killed": false,
                "max-rss": 7612,
                "message": null,
                "status": "OK",
                "time": 0.016,
                "wall-time": 0.05
            },
            "name": "test_h2g2",
            "stderr": "",
            "stdout": "84\n"
        },
        {
            "exitcode": 0,
            "meta": {
                "cg-mem": 2520,
                "csw-forced": 24,
                "csw-voluntary": 2,
                "exitcode": 0,
                "exitsig": 0,
                "exitsig-message": null,
                "killed": false,
                "max-rss": 7448,
                "message": null,
                "status": "OK",
                "time": 0.026,
                "wall-time": 0.072
            },
            "name": "test_notfound",
            "stderr": "",
            "stdout": "808\n"
        },
        {
            "exitcode": 0,
            "meta": {
                "cg-mem": 2520,
                "csw-forced": 4,
                "csw-voluntary": 3,
                "exitcode": 0,
                "exitsig": 0,
                "exitsig-message": null,
                "killed": false,
                "max-rss": 7428,
                "message": null,
                "status": "OK",
                "time": 0.026,
                "wall-time": 0.083
            },
            "name": "test_leet",
            "stderr": "",
            "stdout": "2674\n"
        },
        {
            "exitcode": 0,
            "meta": {
                "cg-mem": 2644,
                "csw-forced": 34,
                "csw-voluntary": 3,
                "exitcode": 0,
                "exitsig": 0,
                "exitsig-message": null,
                "killed": false,
                "max-rss": 7736,
                "message": null,
                "status": "OK",
                "time": 0.029,
                "wall-time": 0.092
            },
            "name": "test_666",
            "stderr": "",
            "stdout": "55944\n"
        }
    ]
}

If you don’t specify a test suite, camisole will only execute a single test named test000 with an empty input.

Response format

Here is an example response:

{
    "compile": {
        "exitcode": 0,
        "meta": {
            "cg-mem": 14984,
            "csw-forced": 53,
            "csw-voluntary": 14,
            "exitcode": 0,
            "exitsig": 0,
            "exitsig-message": null,
            "killed": false,
            "max-rss": 15276,
            "message": null,
            "status": "OK",
            "time": 0.122,
            "wall-time": 0.344
        },
        "stderr": "",
        "stdout": ""
    },
    "success": true,
    "tests": [
        {
            "exitcode": 0,
            "meta": {
                "cg-mem": 504,
                "csw-forced": 4,
                "csw-voluntary": 3,
                "exitcode": 0,
                "exitsig": 0,
                "exitsig-message": null,
                "killed": false,
                "max-rss": 1820,
                "message": null,
                "status": "OK",
                "time": 0.002,
                "wall-time": 0.058
            },
            "name": "test000",
            "stderr": "",
            "stdout": "Hello, world!\\n"
        }
    ]
}

The three fields of the response are:

  • the success flag indicating whether the request was performed successfully or if an exception occurred.
  • the compile object containing the compilation report
  • the tests list containing all the executed tests, their names and reports.

A report is an object containing three fields:

  • stdout: the standard output of the program
  • stderr: the standard error output of the program
  • exitcode: the exit code of isolate. This is not the exit code of the program, but it is advised to use it as if it was the case. The difference is that if the program returns 0 but is killed or violates a resource limitation policy, or if isolate crashes, the exitcode flag will report an error even if the program in itself executed fine.
  • meta: the execution metadata.

Execution metadata

The meta object contains metadata about the execution of the program and reports information about what happened to it. The fields are:

  • cg-mem: Memory used by the control group (kilobytes)
  • csw-forced: Number of context switches forced by the kernel
  • csw-voluntary: Number of context switches caused by the process
  • exitcode: The actual exit code of the program
  • exitsig: The code of the fatal signal received by the process
  • killed: True if the program was killed by the sandbox
  • max-rss: Maximum resident size of the process (kilobytes)
  • message: Status message
  • status: Status code
  • time: User time of the process (seconds)
  • wall-time: Wall time of the process (seconds)

The status code can be one of the following:

  • OK: the program ran and exited successfully
  • RUNTIME_ERROR: the program exited with a non-zero exit code
  • TIMED_OUT: the program timed out
  • SIGNALED: the program received a fatal signal
  • INTERNAL_ERROR: the sandbox crashed because of an internal error

Once again, to get more information about the meta object, visit the documentation of isolate.

Versions and options

There is a way to retrieve some information about the languages enabled in camisole: the endpoint /languages give you information about the versions of the compilers, interpreters and runtimes and their options.

{
    "languages": {
        "c": {
            "name": "C",
            "programs": {
                "gcc": {
                    "opts": [
                        "-std=c11",
                        "-Wall",
                        "-Wextra",
                        "-O2",
                        "-lm"
                    ],
                    "version": "7.2.0"
                }
            }
        },
        "python": {
            "name": "Python",
            "programs": {
                "python3": {
                    "opts": [
                        "-S"
                    ],
                    "version": "3.6.2"
                }
            }
        }
    },
    "success": true
}

System information

You can also get information about the system where camisole is running by doing a request to the /system endpoint.

{
    "success": true,
    "system": {
        "arch": "x86_64",
        "byte_order": "little",
        "cpu_cache_L1d": 32768,
        "cpu_cache_L1i": 32768,
        "cpu_cache_L2": 262144,
        "cpu_cache_L3": 6291456,
        "cpu_count": 1,
        "cpu_mhz": 2494.214,
        "cpu_name": "Intel(R) Core(TM) i7-4710HQ CPU @ 2.50GHz",
        "kernel": "Linux",
        "kernel_release": "4.10.9-1-ARCH",
        "kernel_version": "#1 SMP PREEMPT Sat Apr 8 12:39:59 CEST 2017",
        "memory": 2102362112
    }
}

Binary payloads

camisole was primarily designed for text (UTF-8 encodable) inputs and outputs, for both the program source and inputs/outputs. It works fine in most situations, but in case your program outputs binary data or you source is binary (eg. a Piet program), you can not use the JSON serializer to communicate with the camisole server as JSON can only encode and decode Unicode.

camisole automatically fallbacks to MessagePack serialization if the response cannot be JSON-encoded. You must be prepared to use the proper deserialization method depending on the Content-Type header sent by the server.

You can also encode your requests in MessagePack, if needed. Just send the correct Content-Type: application/msgpack header.

Note

The server will only send content-types that the client accepts, depending on the Accept header you send.

Send Accept: */* to allow the server to send either JSON or MessagePack if JSON isn’t suitable.

Send Accept: application/messagepack to enforce MessagePack responses.

Send Accept: application/json to enforce JSON responses. Responses containing binary data will fail with a Not Acceptable HTTP error.