Browse Source

Added Octane to the frameworks. (#2416)

* Added Octane to the frameworks.

* Renamed libchannels to octane.

* Adding the techempower_benchmark source code and build files.
Simon Guindon 8 years ago
parent
commit
14cbe90fd4

+ 1 - 0
.travis.yml

@@ -16,6 +16,7 @@ env:
     - "TESTDIR=C/haywire"
     - "TESTDIR=C/haywire"
     - "TESTDIR=C/onion"
     - "TESTDIR=C/onion"
     - "TESTDIR=C/h2o"
     - "TESTDIR=C/h2o"
+    - "TESTDIR=C/octane"
     - "TESTDIR=CSharp/aspnet"
     - "TESTDIR=CSharp/aspnet"
     - "TESTDIR=CSharp/aspnetcore"
     - "TESTDIR=CSharp/aspnetcore"
     ## - "TESTDIR=CSharp/aspnet-stripped"
     ## - "TESTDIR=CSharp/aspnet-stripped"

+ 36 - 0
frameworks/C/octane/CMakeLists.txt

@@ -0,0 +1,36 @@
+cmake_minimum_required(VERSION 2.6)
+include(CMakeToolsHelpers OPTIONAL)
+include("common.cmake")
+
+# ----------------------------------------
+# Benchmark service
+# ----------------------------------------
+file(GLOB_RECURSE TECHEMPOWER_BENCHMARKS_SOURCES
+    ${CMAKE_CURRENT_SOURCE_DIR}/lib/octane/lib/sds/*.h
+    ${CMAKE_CURRENT_SOURCE_DIR}/lib/octane/lib/sds/*.c
+    ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h
+    ${CMAKE_CURRENT_SOURCE_DIR}/src/*.hpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c
+    ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
+
+list(SORT TECHEMPOWER_BENCHMARKS_SOURCES)
+create_source_group("Source Files" "${CMAKE_CURRENT_SOURCE_DIR}/src" ${TECHEMPOWER_BENCHMARKS_SOURCES})
+include_directories(${CMAKE_SOURCE_DIR}/lib/octane/lib/libuv/include)
+include_directories(${CMAKE_SOURCE_DIR}/lib/octane/lib/sds)
+include_directories(${CMAKE_SOURCE_DIR}/lib/octane/include)
+include_directories(${CMAKE_SOURCE_DIR}/include)
+
+find_package(Threads REQUIRED)
+
+add_executable (techempower_benchmarks
+    ${TECHEMPOWER_BENCHMARKS_SOURCES})
+
+# Libraries to link in reverse order because that's what ld requires.
+target_link_libraries (techempower_benchmarks
+    ${CMAKE_SOURCE_DIR}/lib/octane/build/liboctane.a
+    ${CMAKE_SOURCE_DIR}/lib/octane/lib/libuv/.libs/libuv.a
+    ${CMAKE_THREAD_LIBS_INIT})
+
+if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
+    target_link_libraries (hello_world rt)
+endif (${CMAKE_SYSTEM_NAME} MATCHES "Linux")

+ 59 - 0
frameworks/C/octane/Makefile

@@ -0,0 +1,59 @@
+projectpath = ${CURDIR}
+octane_path = ${projectpath}/lib/octane
+
+ifeq ($(OS),Windows_NT)
+		OPERATING_SYSTEM = WINDOWS
+    CCFLAGS += -D WIN32
+    ifeq ($(PROCESSOR_ARCHITEW6432),AMD64)
+        CCFLAGS += -D AMD64
+    else
+        ifeq ($(PROCESSOR_ARCHITECTURE),AMD64)
+            CCFLAGS += -D AMD64
+        endif
+        ifeq ($(PROCESSOR_ARCHITECTURE),x86)
+            CCFLAGS += -D IA32
+        endif
+    endif
+else
+    UNAME_S := $(shell uname -s)
+    ifeq ($(UNAME_S),Linux)
+				OPERATING_SYSTEM = LINUX
+				PLATFORM_TARGET = linux
+        CCFLAGS += -D LINUX
+    endif
+    ifeq ($(UNAME_S),Darwin)
+				OPERATING_SYSTEM = OSX
+				PLATFORM_TARGET = osx
+        CCFLAGS += -D OSX
+    endif
+    UNAME_P := $(shell uname -p)
+    ifeq ($(UNAME_P),x86_64)
+        CCFLAGS += -D AMD64
+    endif
+    ifneq ($(filter %86,$(UNAME_P)),)
+        CCFLAGS += -D IA32
+    endif
+    ifneq ($(filter arm%,$(UNAME_P)),)
+        CCFLAGS += -D ARM
+    endif
+endif
+
+all: deps $(PLATFORM_TARGET)
+
+target: all
+
+osx: deps
+	if [ ! -d "build" ]; then mkdir -p build; fi
+	if [ ! -d "build/Makefile" ]; then cd build;cmake -DCMAKE_OSX_ARCHITECTURES=x86_64 ..; fi
+	cmake --build ./build --target all --config Debug -- -j 10
+
+xcode: deps
+	if [ ! -d "build" ]; then mkdir -p build; fi
+	if [ ! -d "build/techempower_benchmarks.xcodeproj" ]; then cd build;cmake -DCMAKE_OSX_ARCHITECTURES=x86_64 -G Xcode ..; fi
+	cd build;xcodebuild -project techempower_benchmarks.xcodeproj/
+
+$(octane_path)/build/liboctane.a:
+	if [ ! -d "$(octane_path)" ]; then git clone https://github.com/simongui/octane.git $(octane_path); fi
+	cd $(octane_path);make
+
+deps: $(octane_path)/build/liboctane.a

+ 3 - 0
frameworks/C/octane/README.md

@@ -0,0 +1,3 @@
+# OCTANE
+
+Benchmarks for the [octane](http://github.com/simongui/octane) library.

+ 23 - 0
frameworks/C/octane/benchmark_config.json

@@ -0,0 +1,23 @@
+{
+  "framework": "octane",
+  "tests": [{
+    "default": {
+      "setup_file": "setup",
+      "plaintext_url": "/",
+      "port": 8000,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "database": "None",
+      "framework": "None",
+      "language": "C",
+      "flavor": "None",
+      "orm": "Raw",
+      "platform": "None",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "notes": "",
+      "versus": "octane"
+    }
+  }]
+}

+ 56 - 0
frameworks/C/octane/common.cmake

@@ -0,0 +1,56 @@
+cmake_minimum_required(VERSION 2.6)
+
+# create_source_group(relativeSourcePath sourceGroupName files)
+#
+# Creates a source group with the specified name relative to the relative path
+# specified.
+#
+# Parameters:
+#    - sourceGroupName: Name of the source group to create.
+#    - relativeSourcePath: Relative path to the files.
+#    - sourceFiles: Files to add to the source group.
+#
+# For example if you have the following directory structure:
+#
+#    - ExampleApplication
+#        - include
+#            - Main.h
+#                - Window
+#                    Window.h
+#        - source
+#            - Main.cpp
+#                - Window
+#                    Window.cpp
+#
+# You can get your list of files and call create_source_group the following way
+#
+#    file(GLOB_RECURSE my_source_files ${CMAKE_CURRENT_SOURCE_DIR}/source/*)
+#    create_source_group("Source Files" "${CMAKE_CURRENT_SOURCE_DIR}/source" ${my_source_files})
+#    file(GLOB_RECURSE my_header_files ${CMAKE_CURRENT_SOURCE_DIR}/include/*)
+#    create_source_group("Header Files" "${CMAKE_CURRENT_SOURCE_DIR}/include" ${my_header_files})
+#    add_executable(ExampleApplication ${my_source_files} ${my_header_files})
+#
+# Then the generated solution would look like this
+#
+#    - ExampleApplication (project)
+#        - Header Files
+#            - Main.h
+#                - Window
+#                    Window.h
+#        - Source Files
+#            - Main.cpp
+#                - Window
+#                    Window.cpp
+#
+function(create_source_group sourceGroupName relativeSourcePath)
+    FOREACH(currentSourceFile ${ARGN})
+        FILE(RELATIVE_PATH folder ${relativeSourcePath} ${currentSourceFile})
+        get_filename_component(filename ${folder} NAME)
+        string(REPLACE ${filename} "" folder ${folder})
+        if(NOT folder STREQUAL "")
+            string(REGEX REPLACE "/+$" "" folderlast ${folder})
+            string(REPLACE "/" "\\" folderlast ${folderlast})
+            SOURCE_GROUP("${sourceGroupName}\\${folderlast}" FILES ${currentSourceFile})
+        endif(NOT folder STREQUAL "")
+    ENDFOREACH(currentSourceFile ${ARGN})
+endfunction(create_source_group)

+ 5 - 0
frameworks/C/octane/setup.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+fw_depends octane
+
+hello_world 8000 40 &

+ 5 - 0
frameworks/C/octane/src/common.h

@@ -0,0 +1,5 @@
+#pragma once
+
+#define memory_error(fmt, ...) do { \
+        fprintf(stderr, "%s: %s (%d): not enough memory: " fmt "\n", __FILE__, __FUNCTION__, __LINE__, ## __VA_ARGS__); \
+} while (0)

+ 16 - 0
frameworks/C/octane/src/connection.c

@@ -0,0 +1,16 @@
+#include <stdlib.h>
+#include <octane.h>
+#include "connection.h"
+
+connection* create_connection()
+{
+    connection* conn = calloc(1, sizeof(connection));
+    conn->bytes_remaining = 0;
+    conn->request_length = 0;
+    conn->keep_alive = true;
+    return conn;
+}
+
+void free_connection(connection* conn) {
+    free(conn);
+}

+ 25 - 0
frameworks/C/octane/src/connection.h

@@ -0,0 +1,25 @@
+#pragma once
+
+#include <stdbool.h>
+
+typedef struct
+{
+    uv_tcp_t stream;
+    enum {OPEN, CLOSING, CLOSED} state;
+    void* data;
+    bool keep_alive;
+    int bytes_remaining;
+    int request_length;
+} connection;
+
+typedef enum {
+    OK,
+    SIZE_EXCEEDED,
+    BAD_REQUEST,
+    INTERNAL_ERROR
+} request_state;
+
+connection* create_connection();
+void free_connection(connection* conn);
+
+char* current_time;

+ 171 - 0
frameworks/C/octane/src/program.c

@@ -0,0 +1,171 @@
+#include <stdio.h>
+#include <uv.h>
+#include <stdlib.h>
+#include <octane.h>
+#include <time.h>
+#include "common.h"
+#include "connection.h"
+#include "responders/static_responder.h"
+#include "responders/sds_responder.h"
+#include "responders/nobuffer_responder.h"
+
+typedef struct {
+  uv_write_t req;
+  uv_buf_t buf;
+} write_req_t;
+
+void stream_on_connect(uv_stream_t* stream, int status);
+void stream_on_alloc(uv_handle_t* client, size_t suggested_size, uv_buf_t* buf);
+void stream_on_read(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* buf);
+void timer_callback(uv_timer_t* timer);
+
+void (*stream_on_read_func)(connection* conn, size_t requests, uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf);
+
+uv_timer_t timer;
+
+int main(int args, char **argsv) {
+    //stream_on_read_func = &stream_on_read_static;
+    stream_on_read_func = &stream_on_read_sds;
+    //stream_on_read_func = &stream_on_read_nobuffer;
+
+    uv_async_t* service_handle = 0;
+    uv_loop_t* loop = uv_default_loop();
+
+    uv_timer_init(loop, &timer);
+    uv_timer_start(&timer, timer_callback, 0, 500);
+
+    uv_multi_listen("0.0.0.0", 8000, false, 40, DISPATCH_TYPE_REUSEPORT, loop, 128, stream_on_connect);
+}
+
+void send_error_response(connection* conn, request_state state) {
+
+}
+
+void stream_on_close(uv_handle_t* handle)
+{
+    uv_handle_t* stream = handle;
+    connection* conn = stream->data;
+
+    if (conn->state != CLOSED) {
+        conn->state = CLOSED;
+        connection* conn = (connection*)handle->data;
+        free_connection(conn);
+    }
+}
+
+void stream_close_connection(connection* conn) {
+    if (conn->state == OPEN) {
+        conn->state = CLOSING;
+        uv_close(&conn->stream, stream_on_close);
+    }
+}
+
+void handle_request_error(connection* conn, request_state state)
+{
+    uv_handle_t* stream = &conn->stream;
+
+    if (conn->state == OPEN) {
+        uv_read_stop(stream);
+    }
+
+    conn->keep_alive = false;
+
+    if (conn->state == OPEN) {
+        /* Send the error message back. */
+        send_error_response(conn, state);
+    } else {
+        stream_close_connection(conn);
+    }
+}
+
+void handle_bad_request(connection* conn)
+{
+    handle_request_error(conn, BAD_REQUEST);
+}
+
+void handle_buffer_exceeded_error(connection* conn)
+{
+    handle_request_error(conn, SIZE_EXCEEDED);
+}
+
+void handle_internal_error(connection* conn)
+{
+    handle_request_error(conn, INTERNAL_ERROR);
+}
+
+void stream_on_shutdown(uv_shutdown_t* req, int status)
+{
+    connection* conn = req->data;
+    uv_handle_t* stream = &conn->stream;
+    if (conn->state == OPEN) {
+        stream_close_connection(conn);
+    }
+    free(req);
+}
+
+void stream_on_connect(uv_stream_t* server_stream, int status) {
+    /* TODO: Use the return values from uv_accept() and uv_read_start() */
+    int rc = 0;
+
+    connection* conn = create_connection();
+    rc = uv_tcp_init(server_stream->loop, (uv_stream_t*)&conn->stream);
+    conn->bytes_remaining = 0;
+    conn->request_length = 0;
+    conn->stream.data = conn;
+
+    rc = uv_accept(server_stream, (uv_stream_t*)&conn->stream);
+    uv_read_start((uv_stream_t*)&conn->stream, stream_on_alloc, stream_on_read);
+}
+
+void stream_on_alloc(uv_handle_t* client, size_t suggested_size, uv_buf_t* buf) {
+    char* buffer;
+    if(!(buffer = malloc(suggested_size))){
+        memory_error("Unable to allocate buffer of size %d", suggested_size);
+    }
+    *buf = uv_buf_init(buffer, suggested_size);
+}
+
+void stream_on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
+    connection* conn = stream->data;
+
+    if (nread > 0) {
+        if (conn->request_length == 0) {
+            // We need to seek the first request to find out how many characters each request is.
+            for (int i = 1; i < nread; i++) {
+                if (buf->base[i] == '\r' && buf->base[i - 1] == '\n') {
+                    conn->request_length = i + 2;
+                    break;
+                }
+            }
+        }
+
+        ssize_t requests = (nread + conn->bytes_remaining) / conn->request_length;
+        conn->bytes_remaining = conn->bytes_remaining + (nread % conn->request_length);
+
+        stream_on_read_func(conn, requests, stream, nread, buf);
+    }
+    else if (nread == UV_ENOBUFS) {
+        handle_buffer_exceeded_error(conn);
+    }
+    else if (nread == UV_EOF){
+        uv_shutdown_t* req = malloc(sizeof(uv_shutdown_t));
+        req->data = conn;
+        uv_shutdown(req, &conn->stream, stream_on_shutdown);
+    }
+    else if (nread == UV_ECONNRESET || nread == UV_ECONNABORTED) {
+        /* Let's close the connection as the other peer just disappeared */
+        stream_close_connection(conn);
+    } else {
+        /* We didn't see this coming, but an unexpected UV error code was passed in, so we'll
+         * respond with a blanket 500 error if we can */
+        handle_internal_error(conn);
+    }
+    free(buf->base);
+}
+
+void timer_callback(uv_timer_t* timer) {
+    time_t curtime;
+    time(&curtime);
+    char* time = ctime(&curtime);
+    current_time = time;
+}

+ 54 - 0
frameworks/C/octane/src/responders/nobuffer_responder.c

@@ -0,0 +1,54 @@
+#include <stdlib.h>
+#include <uv.h>
+#include "nobuffer_responder.h"
+#include "../write_batch.h"
+
+void create_response_nobuffer(write_batch* batch) {
+    batch->buffers[batch->number_of_used_buffers].base = "HTTP/1.1 200 OK\r\n";
+    batch->buffers[batch->number_of_used_buffers].len = 17;
+    batch->number_of_used_buffers++;
+
+    batch->buffers[batch->number_of_used_buffers].base = "Server: octane\r\n";
+    batch->buffers[batch->number_of_used_buffers].len = 28;
+    batch->number_of_used_buffers++;
+
+    batch->buffers[batch->number_of_used_buffers].base = "Content-Type: text/plain\r\n";
+    batch->buffers[batch->number_of_used_buffers].len = 26;
+    batch->number_of_used_buffers++;
+
+    batch->buffers[batch->number_of_used_buffers].base = "Content-Length: 15\r\n";
+    batch->buffers[batch->number_of_used_buffers].len = 20;
+    batch->number_of_used_buffers++;
+
+    batch->buffers[batch->number_of_used_buffers].base = "Date: Mon Dec 12 00:00:00 2016\r\n";
+    batch->buffers[batch->number_of_used_buffers].len = 32;
+    batch->number_of_used_buffers++;
+
+    batch->buffers[batch->number_of_used_buffers].base = "\r\n";
+    batch->buffers[batch->number_of_used_buffers].len = 2;
+    batch->number_of_used_buffers++;
+
+    batch->buffers[batch->number_of_used_buffers].base = "Hello, World!\n\n";
+    batch->buffers[batch->number_of_used_buffers].len = 15;
+    batch->number_of_used_buffers++;
+}
+
+void stream_on_read_nobuffer(connection* conn, size_t requests, uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
+    uv_write_t *write_req = create_write_with_batch(requests * 7);
+    write_batch* batch = write_req->data;
+
+    for (int i=0; i<requests; i++) {
+        create_response_nobuffer(batch);
+    }
+
+    if (uv_is_writable(stream)) {
+        // TODO: Use the return values from uv_write()
+        int rc = uv_write(write_req, stream, batch->buffers, batch->number_of_used_buffers, after_write_nobuffer);
+    } else {
+        // TODO: Handle closing the stream.
+    }
+}
+
+void after_write_nobuffer(uv_write_t* req, int status) {
+    free(req);
+}

+ 9 - 0
frameworks/C/octane/src/responders/nobuffer_responder.h

@@ -0,0 +1,9 @@
+#pragma once
+
+#include <uv.h>
+#include "../connection.h"
+#include "../write_batch.h"
+
+void create_response_nobuffer(write_batch* batch);
+void stream_on_read_nobuffer(connection* conn, size_t requests, uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf);
+void after_write_nobuffer(uv_write_t* req, int status);

+ 47 - 0
frameworks/C/octane/src/responders/sds_responder.c

@@ -0,0 +1,47 @@
+#include <uv.h>
+#include <sds.h>
+#include "sds_responder.h"
+#include "../connection.h"
+#include "../write_batch.h"
+
+void create_response_sds(write_batch* batch) {
+    //sds response_buffer = sdsnew("HTTP/1.1 200 OK\r\nServer: octane/master\r\nContent-Type: text/plain\r\nContent-Length: 15\r\n");
+    //response_buffer = sdscat(response_buffer, "Date: Mon Dec 12 00:00:00 2016\r\n");
+    //response_buffer = sdscat(response_buffer, "\r\nHello, World!\n\n");
+
+    sds response_buffer = sdsnew("HTTP/1.1 200 OK\r\n");
+    response_buffer = sdscat(response_buffer, "Server: octane\r\n");
+    response_buffer = sdscat(response_buffer, "Content-Type: text/plain\r\n");
+    response_buffer = sdscat(response_buffer, "Content-Length: 15\r\n");
+    response_buffer = sdscatprintf(response_buffer, "Date: %s", current_time);
+    response_buffer = sdscat(response_buffer, "\r\nHello, World!\n\n");
+
+    batch->buffers[batch->number_of_used_buffers].base = response_buffer;
+    batch->buffers[batch->number_of_used_buffers].len = sdslen(response_buffer);
+    batch->number_of_used_buffers++;
+}
+
+void stream_on_read_sds(connection* conn, size_t requests, uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
+    uv_write_t *write_req = create_write_with_batch(requests);
+    write_batch* batch = write_req->data;
+
+    for (int i=0; i<requests; i++) {
+        create_response_sds(batch);
+    }
+
+    if (uv_is_writable(stream)) {
+        // TODO: Use the return values from uv_write()
+        int rc = uv_write(write_req, stream, batch->buffers, batch->number_of_used_buffers, after_write_sds);
+    } else {
+        // TODO: Handle closing the stream.
+    }
+}
+
+void after_write_sds(uv_write_t* req, int status) {
+    write_batch* batch = get_write_batch(req);
+    for (int i=0; i<batch->number_of_used_buffers; i++) {
+        sds buffer = batch->buffers[i].base;
+        sdsfree(buffer);
+    }
+    free(req);
+}

+ 9 - 0
frameworks/C/octane/src/responders/sds_responder.h

@@ -0,0 +1,9 @@
+#pragma once
+
+#include <uv.h>
+#include "../connection.h"
+#include "../write_batch.h"
+
+void create_response_sds(write_batch* batch);
+void stream_on_read_sds(connection* conn, size_t requests, uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf);
+void after_write_sds(uv_write_t* req, int status);

+ 30 - 0
frameworks/C/octane/src/responders/static_responder.c

@@ -0,0 +1,30 @@
+#include <stdlib.h>
+#include <uv.h>
+#include "static_responder.h"
+
+void create_response_static(write_batch* batch) {
+    batch->buffers[batch->number_of_used_buffers].base = RESPONSE;
+    batch->buffers[batch->number_of_used_buffers].len = sizeof(RESPONSE);
+    batch->number_of_used_buffers++;
+}
+
+void stream_on_read_static(connection* conn, size_t requests, uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
+    uv_write_t *write_req = create_write_with_batch(requests);
+    write_batch* batch = write_req->data;
+
+    for (int i=0; i<requests; i++) {
+        create_response_static(batch);
+    }
+
+    if (uv_is_writable(stream)) {
+        // TODO: Use the return values from uv_write()
+        int rc = uv_write(write_req, stream, batch->buffers, batch->number_of_used_buffers, after_write_static);
+    } else {
+        // TODO: Handle closing the stream.
+    }
+}
+
+void after_write_static(uv_write_t* req, int status) {
+    free(req);
+}
+

+ 18 - 0
frameworks/C/octane/src/responders/static_responder.h

@@ -0,0 +1,18 @@
+#pragma once
+
+#include <uv.h>
+#include "../connection.h"
+#include "../write_batch.h"
+
+#define RESPONSE \
+    "HTTP/1.1 200 OK\r\n" \
+    "Server: octane\r\n" \
+    "Date: Mon Dec 12 00:00:00 2016\r\n" \
+    "Content-Type: text/plain\r\n" \
+    "Content-Length: 15\r\n" \
+    "\r\n" \
+    "Hello, World!\n"
+
+void create_response_static(write_batch* batch);
+void stream_on_read_static(connection* conn, size_t requests, uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf);
+void after_write_static(uv_write_t* req, int status);

+ 28 - 0
frameworks/C/octane/src/write_batch.c

@@ -0,0 +1,28 @@
+#include <stdlib.h>
+#include "write_batch.h"
+#include "common.h"
+
+uv_write_t* create_write_with_batch(size_t number_of_total_buffers) {
+    /*
+     * Allocate the 3 structures and array of buffers in one malloc call
+     * to reduce malloc() contention across CPU cores.
+     */
+    uv_write_t *write_req;
+    size_t bytes = sizeof(*write_req) + sizeof(write_batch) + (sizeof(uv_buf_t) * number_of_total_buffers);
+    if(!(write_req = (uv_write_t *) malloc(bytes))){
+        memory_error("Unable to allocate buffer of size %d", bytes);
+    }
+
+    write_batch* batch = get_write_batch(write_req);
+    batch->buffers = (uv_buf_t*)(batch + 1);
+    batch->number_of_used_buffers = 0;
+    batch->number_of_total_buffers = number_of_total_buffers;
+    write_req->data = batch;
+
+    return write_req;
+}
+
+write_batch* get_write_batch(uv_write_t* write_req) {
+    write_batch* batch = (write_batch*)(write_req + 1);
+    return batch;
+}

+ 12 - 0
frameworks/C/octane/src/write_batch.h

@@ -0,0 +1,12 @@
+#pragma once
+#include <stdlib.h>
+#include <uv.h>
+
+typedef struct {
+    uv_buf_t* buffers;
+    size_t number_of_used_buffers;
+    size_t number_of_total_buffers;
+}write_batch;
+
+uv_write_t* create_write_with_batch(size_t number_of_total_buffers);
+write_batch* get_write_batch(uv_write_t* write_req);

+ 18 - 0
toolset/setup/linux/frameworks/octane.sh

@@ -0,0 +1,18 @@
+#!/bin/bash
+
+RETCODE=$(fw_exists ${IROOT}/octane.installed)
+[ ! "$RETCODE" == 0 ] || { \
+  # Load environment variables
+  source $IROOT/octane.installed
+  return 0; }
+
+OCTANE_HOME=$IROOT/octane
+
+git clone https://github.com/simongui/octane.git
+cd $OCTANE_HOME
+make
+
+echo "export OCTANE_HOME=${OCTANE_HOME}" >> $IROOT/octane.installed
+echo -e "export PATH=\$OCTANE_HOME/build/:\$PATH" >> $IROOT/octane.installed
+
+source $IROOT/octane.installed