/*
 * Copyright The async-profiler authors
 * SPDX-License-Identifier: Apache-2.0
 */

#include <assert.h>
#include <map>
#include <string>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>
#include "flightRecorder.h"
#include "incbin.h"
#include "jfrMetadata.h"
#include "lookup.h"
#include "os.h"
#include "processSampler.h"
#include "profiler.h"
#include "spinLock.h"
#include "symbols.h"
#include "threadFilter.h"
#include "threadLocalData.h"
#include "tsc.h"
#include "userEvents.h"
#include "vmStructs.h"


INCLUDE_HELPER_CLASS(JFR_SYNC_NAME, JFR_SYNC_CLASS, "one/profiler/JfrSync")

static void JNICALL JfrSync_stopProfiler(JNIEnv* env, jclass cls) {
    Profiler::instance()->stop();
}


const int SMALL_BUFFER_SIZE = 1024;
const int SMALL_BUFFER_LIMIT = SMALL_BUFFER_SIZE - 128;
const int RECORDING_BUFFER_SIZE = 65536;
const int RECORDING_BUFFER_LIMIT = RECORDING_BUFFER_SIZE - 4096;
const int MAX_STRING_LENGTH = 8191;
const u64 MAX_JLONG = 0x7fffffffffffffffULL;
const u64 MIN_JLONG = 0x8000000000000000ULL;

enum GCWhen {
    BEFORE_GC,
    AFTER_GC
};


static SpinLock _rec_lock(1);

static jclass _jfr_sync_class = NULL;
static jmethodID _start_method;
static jmethodID _stop_method;
static jmethodID _box_method;

static const char* const SETTING_CSTACK[] = {NULL, "no", "fp", "dwarf", "lbr", "vm"};


struct CpuTime {
    u64 real;
    u64 user;
    u64 system;
};

struct CpuTimes {
    CpuTime proc;
    CpuTime total;
};

class Buffer {
  private:
    int _offset;
    char _data[0];

  protected:
    Buffer() : _offset(0) {
    }

  public:
    const char* data() const {
        return _data;
    }

    int offset() const {
        return _offset;
    }

    int skip(int delta) {
        int offset = _offset;
        _offset = offset + delta;
        return offset;
    }

    void reset() {
        _offset = 0;
    }

    void put(const char* v, u32 len) {
        memcpy(_data + _offset, v, len);
        _offset += (int)len;
    }

    void put8(char v) {
        _data[_offset++] = v;
    }

    void put16(short v) {
        *(short*)(_data + _offset) = htons(v);
        _offset += 2;
    }

    void put32(int v) {
        *(int*)(_data + _offset) = htonl(v);
        _offset += 4;
    }

    void put64(u64 v) {
        *(u64*)(_data + _offset) = OS::hton64(v);
        _offset += 8;
    }

    void putFloat(float v) {
        union {
            float f;
            int i;
        } u;

        u.f = v;
        put32(u.i);
    }

    void putVar32(u32 v) {
        while (v > 0x7f) {
            _data[_offset++] = (char)v | 0x80;
            v >>= 7;
        }
        _data[_offset++] = (char)v;
    }

    void putVar64(u64 v) {
        int iter = 0;
        while (v > 0x1fffff) {
            _data[_offset++] = (char)v | 0x80; v >>= 7;
            _data[_offset++] = (char)v | 0x80; v >>= 7;
            _data[_offset++] = (char)v | 0x80; v >>= 7;
            if (++iter == 3) return;
        }
        while (v > 0x7f) {
            _data[_offset++] = (char)v | 0x80;
            v >>= 7;
        }
        _data[_offset++] = (char)v;
    }

    void putUtf8(const char* v) {
        if (v == NULL) {
            put8(0);
        } else {
            size_t len = strlen(v);
            putUtf8(v, len < MAX_STRING_LENGTH ? len : MAX_STRING_LENGTH);
        }
    }

    void putUtf8(const char* v, u32 len) {
        put8(3);
        putVar32(len);
        put(v, len);
    }

    void putByteString(const char* v, u32 len) {
        put8(5); // STRING_ENCODING_LATIN1_BYTE_ARRAY
        putVar32(len);
        put(v, len);
    }

    void putByteString(const char* v) {
        if (v == NULL) {
            put8(0);
        } else {
            size_t len = strlen(v);
            putByteString(v, len < MAX_STRING_LENGTH ? len : MAX_STRING_LENGTH);
        }
    }

    void put8(int offset, char v) {
        _data[offset] = v;
    }

    void putVar32(int offset, u32 v) {
        _data[offset] = v | 0x80;
        _data[offset + 1] = (v >> 7) | 0x80;
        _data[offset + 2] = (v >> 14) | 0x80;
        _data[offset + 3] = (v >> 21) | 0x80;
        _data[offset + 4] = (v >> 28);
    }
};

class SmallBuffer : public Buffer {
  private:
    char _buf[SMALL_BUFFER_SIZE - sizeof(Buffer)];

  public:
    SmallBuffer() : Buffer() {
    }
};

class RecordingBuffer : public Buffer {
  private:
    char _buf[RECORDING_BUFFER_SIZE - sizeof(Buffer)];

  public:
    RecordingBuffer() : Buffer() {
    }
};


class Recording {
  private:
    static char* _agent_properties;
    static char* _jvm_args;
    static char* _jvm_flags;
    static char* _java_command;

    RecordingBuffer _buf[CONCURRENCY_LEVEL];
    int _fd;
    int _memfd;
    char* _master_recording_file;
    off_t _chunk_start;
    ThreadFilter _thread_set;
    MethodMap _method_map;

    u64 _start_time;
    u64 _start_ticks;
    u64 _stop_time;
    u64 _stop_ticks;

    u64 _base_id;
    u64 _bytes_written;
    u64 _chunk_size;
    u64 _chunk_time;

    int _available_processors;
    int _recorded_lib_count;

    bool _in_memory;
    bool _cpu_monitor_enabled;
    bool _heap_monitor_enabled;
    u32 _last_gc_id;
    CpuTimes _last_times;
    SmallBuffer _monitor_buf;
    RecordingBuffer _proc_buf;
    ProcessSampler _process_sampler;

    static float ratio(float value) {
        return value < 0 ? 0 : value > 1 ? 1 : value;
    }

  public:
    Recording(int fd, const char* master_recording_file, Arguments& args) : _fd(fd), _thread_set(), _method_map() {
        _master_recording_file = master_recording_file == NULL ? NULL : strdup(master_recording_file);
        _chunk_start = lseek(_fd, 0, SEEK_END);
        _start_time = OS::micros();
        _start_ticks = TSC::ticks();
        _base_id = 0;
        _bytes_written = 0;
        _memfd = -1;
        _in_memory = false;

        _chunk_size = args._chunk_size <= 0 ? MAX_JLONG : (args._chunk_size < 262144 ? 262144 : args._chunk_size);
        _chunk_time = args._chunk_time <= 0 ? MAX_JLONG : (args._chunk_time < 5 ? 5 : args._chunk_time) * 1000000ULL;

        _available_processors = OS::getCpuCount();

        writeHeader(_buf);
        writeMetadata(_buf);
        writeRecordingInfo(_buf);
        writeSettings(_buf, args);
        if (!args.hasOption(NO_SYSTEM_INFO)) {
            writeOsCpuInfo(_buf);
            writeJvmInfo(_buf);
        }
        if (!args.hasOption(NO_SYSTEM_PROPS)) {
            writeSystemProperties(_buf);
        }
        if (!args.hasOption(NO_NATIVE_LIBS)) {
            _recorded_lib_count = 0;
            writeNativeLibraries(_buf);
        } else {
            _recorded_lib_count = -1;
        }
        flush(_buf);

        if (args.hasOption(IN_MEMORY) && (_memfd = OS::createMemoryFile("async-profiler-recording")) >= 0) {
            _in_memory = true;
        }

        _cpu_monitor_enabled = !args.hasOption(NO_CPU_LOAD);
        if (_cpu_monitor_enabled) {
            _last_times.proc.real = OS::getProcessCpuTime(&_last_times.proc.user, &_last_times.proc.system);
            _last_times.total.real = OS::getTotalCpuTime(&_last_times.total.user, &_last_times.total.system);
        }

        _heap_monitor_enabled = !args.hasOption(NO_HEAP_SUMMARY) && VM::_totalMemory != NULL && VM::_freeMemory != NULL;
        _last_gc_id = 0;

        if (args._proc > 0) {
            _process_sampler.enable(args._proc * 1000000);
        }
    }

    ~Recording() {
        off_t chunk_end = finishChunk();

        if (_memfd >= 0) {
            close(_memfd);
        }

        if (_master_recording_file != NULL) {
            appendRecording(_master_recording_file, chunk_end);
            free(_master_recording_file);
        }

        close(_fd);
    }

    off_t finishChunk() {
        flush(&_monitor_buf);
        flush(&_proc_buf);

        writeNativeLibraries(_buf);

        for (int i = 0; i < CONCURRENCY_LEVEL; i++) {
            flush(&_buf[i]);
        }

        _stop_time = OS::micros();
        _stop_ticks = TSC::ticks();

        if (_memfd >= 0) {
            OS::copyFile(_memfd, _fd, 0, lseek(_memfd, 0, SEEK_CUR));
            _in_memory = false;
        }

        off_t cpool_offset = lseek(_fd, 0, SEEK_CUR);
        writeCpool(_buf);
        flush(_buf);

        off_t chunk_end = lseek(_fd, 0, SEEK_CUR);

        // Patch cpool size field
        _buf->putVar32(0, chunk_end - cpool_offset);
        ssize_t result = pwrite(_fd, _buf->data(), 5, cpool_offset);
        (void)result;

        // Workaround for JDK-8191415: compute actual TSC frequency, in case JFR is wrong
        u64 tsc_frequency;
        if (TSC::enabled()) {
            tsc_frequency = (u64)(double(_stop_ticks - _start_ticks) / double(_stop_time - _start_time) * 1000000);
        } else {
            // TSC is not enabled, so frequency can be used to get the constant clock tick frequency
            tsc_frequency = TSC::frequency();
        }

        // Patch chunk header
        _buf->put64(chunk_end - _chunk_start);
        _buf->put64(cpool_offset - _chunk_start);
        _buf->put64(68);
        _buf->put64(_start_time * 1000);
        _buf->put64((_stop_time - _start_time) * 1000);
        _buf->put64(_start_ticks);
        _buf->put64(tsc_frequency);
        result = pwrite(_fd, _buf->data(), 56, _chunk_start + 8);
        (void)result;

        OS::freePageCache(_fd, _chunk_start);

        _buf->reset();
        return chunk_end;
    }

    void switchChunk() {
        _chunk_start = finishChunk();
        _start_time = _stop_time;
        _start_ticks = _stop_ticks;
        _base_id += 0x1000000;
        _bytes_written = 0;

        writeHeader(_buf);
        writeMetadata(_buf);
        writeRecordingInfo(_buf);
        flush(_buf);

        if (_memfd >= 0) {
            while (ftruncate(_memfd, 0) < 0 && errno == EINTR);  // restart if interrupted
            _in_memory = true;
        }
    }

    bool needSwitchChunk(u64 wall_time) {
        return loadAcquire(_bytes_written) >= _chunk_size || wall_time - _start_time >= _chunk_time;
    }

    size_t usedMemory() {
        return _method_map.usedMemory() + _thread_set.usedMemory() +
               (_memfd >= 0 ? lseek(_memfd, 0, SEEK_CUR) : 0);
    }

    void cpuMonitorCycle() {
        if (!_cpu_monitor_enabled) return;

        CpuTimes times;
        times.proc.real = OS::getProcessCpuTime(&times.proc.user, &times.proc.system);
        times.total.real = OS::getTotalCpuTime(&times.total.user, &times.total.system);

        float proc_user = 0, proc_system = 0, machine_total = 0;

        if (times.proc.real != (u64)-1 && times.proc.real > _last_times.proc.real) {
            float delta = (times.proc.real - _last_times.proc.real) * _available_processors;
            proc_user = ratio((times.proc.user - _last_times.proc.user) / delta);
            proc_system = ratio((times.proc.system - _last_times.proc.system) / delta);
        }

        if (times.total.real != (u64)-1 && times.total.real > _last_times.total.real) {
            float delta = times.total.real - _last_times.total.real;
            machine_total = ratio(((times.total.user + times.total.system) -
                                   (_last_times.total.user + _last_times.total.system)) / delta);
            if (machine_total < proc_user + proc_system) {
                machine_total = ratio(proc_user + proc_system);
            }
        }

        recordCpuLoad(&_monitor_buf, proc_user, proc_system, machine_total);
        flushIfNeeded(&_monitor_buf, SMALL_BUFFER_LIMIT);

        _last_times = times;
    }

    void heapMonitorCycle(u32 gc_id) {
        if (!_heap_monitor_enabled || gc_id == _last_gc_id) return;

        recordHeapSummary(&_monitor_buf, gc_id, AFTER_GC, VM::_totalMemory(), VM::_freeMemory());
        flushIfNeeded(&_monitor_buf, SMALL_BUFFER_LIMIT);

        _last_gc_id = gc_id;
    }

    void processMonitorCycle(const u64 wall_time) {
        if (!_process_sampler.shouldSample(wall_time)) return;

        const u64 deadline_ns = OS::nanotime() + MAX_TIME_NS;
        const int process_count = _process_sampler.sample(wall_time);
        for (int pid_index = 0; pid_index < process_count; pid_index++) {
            const u64 current_time = OS::nanotime();
            if (current_time > deadline_ns) {
                Log::debug("Sampled %d of %d processes due to time limit", pid_index, process_count);
                break;
            }

            ProcessInfo info;
            if (_process_sampler.getProcessInfo(pid_index, current_time, info)) {
                flushIfNeeded(&_proc_buf, RECORDING_BUFFER_LIMIT - MAX_PROCESS_SAMPLE_JFR_EVENT_LENGTH);
                recordProcessSample(&_proc_buf, &info);
            }
        }
    }

    bool hasMasterRecording() const {
        return _master_recording_file != NULL;
    }

    void appendRecording(const char* target_file, size_t size) {
        int append_fd = open(target_file, O_WRONLY);
        if (append_fd >= 0) {
            lseek(append_fd, 0, SEEK_END);
            OS::copyFile(_fd, append_fd, 0, size);
            close(append_fd);
        } else {
            Log::warn("Failed to open JFR recording at %s: %s", target_file, strerror(errno));
        }
    }

    Buffer* buffer(int lock_index) {
        return &_buf[lock_index];
    }

    bool parseAgentProperties() {
        JNIEnv* env = VM::jni();
        jclass vm_support = env->FindClass("jdk/internal/vm/VMSupport");
        if (vm_support == NULL) {
            env->ExceptionClear();
            vm_support = env->FindClass("sun/misc/VMSupport");
        }
        if (vm_support != NULL) {
            jmethodID get_agent_props = env->GetStaticMethodID(vm_support, "getAgentProperties", "()Ljava/util/Properties;");
            jmethodID to_string = env->GetMethodID(env->FindClass("java/lang/Object"), "toString", "()Ljava/lang/String;");
            if (get_agent_props != NULL && to_string != NULL) {
                jobject props = env->CallStaticObjectMethod(vm_support, get_agent_props);
                if (props != NULL && !env->ExceptionCheck()) {
                    jstring str = (jstring)env->CallObjectMethod(props, to_string);
                    if (str != NULL && !env->ExceptionCheck()) {
                        _agent_properties = (char*)env->GetStringUTFChars(str, NULL);
                    }
                }
            }
        }
        env->ExceptionClear();

        if (_agent_properties == NULL) {
            return false;
        }

        char* p = _agent_properties + 1;
        p[strlen(p) - 1] = 0;

        while (*p) {
            if (strncmp(p, "sun.jvm.args=", 13) == 0) {
                _jvm_args = p + 13;
            } else if (strncmp(p, "sun.jvm.flags=", 14) == 0) {
                _jvm_flags = p + 14;
            } else if (strncmp(p, "sun.java.command=", 17) == 0) {
                _java_command = p + 17;
            }

            if ((p = strstr(p, ", ")) == NULL) {
                break;
            }

            *p = 0;
            p += 2;
        }

        return true;
    }

    static const char* getFeaturesString(char* str, size_t size, StackWalkFeatures& f) {
        snprintf(str, size, "%s %s %s %s %s %s %s %s %s %s %s %s %s",
                 f.unknown_java  ? "unknown_java"  : "-",
                 f.unwind_stub   ? "unwind_stub"   : "-",
                 f.unwind_comp   ? "unwind_comp"   : "-",
                 f.unwind_native ? "unwind_native" : "-",
                 f.java_anchor   ? "java_anchor"   : "-",
                 f.gc_traces     ? "gc_traces"     : "-",
                 f.stats         ? "stats"         : "-",
                 f.jnienv        ? "jnienv"        : "-",
                 f.probe_sp      ? "probesp"       : "-",
                 f.mixed         ? "mixed"         : "-",
                 f.vtable_target ? "vtable"        : "-",
                 f.comp_task     ? "comptask"      : "-",
                 f.pc_addr       ? "pcaddr"        : "-");
        return str;
    }

    void flush(Buffer* buf) {
        ssize_t result = write(_in_memory ? _memfd : _fd, buf->data(), buf->offset());
        if (result > 0) {
            atomicInc(_bytes_written, result);
        }
        buf->reset();
    }

    void flushIfNeeded(Buffer* buf, int limit = RECORDING_BUFFER_LIMIT) {
        if (buf->offset() >= limit) {
            flush(buf);
        }
    }

    void writeHeader(Buffer* buf) {
        buf->put("FLR\0", 4);            // magic
        buf->put16(2);                   // major
        buf->put16(0);                   // minor
        buf->put64(1024 * 1024 * 1024);  // chunk size (initially large, for JMC to skip incomplete chunk)
        buf->put64(0);                   // cpool offset
        buf->put64(0);                   // meta offset
        buf->put64(_start_time * 1000);  // start time, ns
        buf->put64(0);                   // duration, ns
        buf->put64(_start_ticks);        // start ticks
        // A frequency here may be inaccurate when using TSC clock.
        // It will be overwritten with a correct value in finishChunk later.
        buf->put64(TSC::frequency());    // ticks per sec
        buf->put32(1);                   // features
    }

    void writeMetadata(Buffer* buf) {
        int metadata_start = buf->skip(5);  // size will be patched later
        buf->putVar32(T_METADATA);
        buf->putVar64(_start_ticks);
        buf->putVar32(0);
        buf->putVar32(0x7fffffff);  // must not clash with JFR metadata ID, or 'jfr print' will break

        const Index& strings = JfrMetadata::strings();
        buf->putVar32(strings.size());
        strings.forEachOrdered([&] (size_t idx, const std::string& s) {
            buf->putUtf8(s.c_str());
        });

        writeElement(buf, JfrMetadata::root());

        buf->putVar32(metadata_start, buf->offset() - metadata_start);
    }

    void writeElement(Buffer* buf, const Element* e) {
        buf->putVar32(e->_name);

        buf->putVar32(e->_attributes.size());
        for (int i = 0; i < e->_attributes.size(); i++) {
            buf->putVar32(e->_attributes[i]._key);
            buf->putVar32(e->_attributes[i]._value);
        }

        buf->putVar32(e->_children.size());
        for (int i = 0; i < e->_children.size(); i++) {
            writeElement(buf, e->_children[i]);
        }
    }

    void writeRecordingInfo(Buffer* buf) {
        int start = buf->skip(1);
        buf->put8(T_ACTIVE_RECORDING);
        buf->putVar64(_start_ticks);
        buf->putVar32(1);
        buf->putUtf8("async-profiler " PROFILER_VERSION);
        buf->putUtf8("async-profiler.jfr");
        buf->putVar64(MAX_JLONG);
        buf->putVar32(0);
        buf->putVar64(_start_time / 1000);
        buf->putVar64(MAX_JLONG);
        buf->put8(start, buf->offset() - start);
    }

    void writeSettings(Buffer* buf, Arguments& args) {
        assert(args._cstack < sizeof(SETTING_CSTACK) / sizeof(char*));
        writeStringSetting(buf, T_ACTIVE_RECORDING, "version", PROFILER_VERSION);
        writeStringSetting(buf, T_ACTIVE_RECORDING, "engine", Profiler::instance()->_engine->type());
        writeStringSetting(buf, T_ACTIVE_RECORDING, "cstack", SETTING_CSTACK[args._cstack]);
        writeStringSetting(buf, T_ACTIVE_RECORDING, "clock", TSC::enabled() ? "tsc" : "monotonic");
        writeStringSetting(buf, T_ACTIVE_RECORDING, "event", args._event);
        writeStringSetting(buf, T_ACTIVE_RECORDING, "filter", args._filter);
        writeStringSetting(buf, T_ACTIVE_RECORDING, "begin", args._begin);
        writeStringSetting(buf, T_ACTIVE_RECORDING, "end", args._end);
        writeListSetting(buf, T_ACTIVE_RECORDING, "include", args._include);
        writeListSetting(buf, T_ACTIVE_RECORDING, "exclude", args._exclude);
        writeIntSetting(buf, T_ACTIVE_RECORDING, "jstackdepth", args._jstackdepth);
        writeIntSetting(buf, T_ACTIVE_RECORDING, "jfropts", args._jfr_options);
        writeIntSetting(buf, T_ACTIVE_RECORDING, "chunksize", args._chunk_size);
        writeIntSetting(buf, T_ACTIVE_RECORDING, "chunktime", args._chunk_time);

        char str[256];
        writeStringSetting(buf, T_ACTIVE_RECORDING, "features", getFeaturesString(str, sizeof(str), args._features));

        writeBoolSetting(buf, T_EXECUTION_SAMPLE, "enabled", args._event != NULL);
        if (args._event != NULL) {
            writeIntSetting(buf, T_EXECUTION_SAMPLE, "interval", args._interval);
            writeBoolSetting(buf, T_EXECUTION_SAMPLE, "alluser", args._alluser);
        }
        if (args._wall >= 0) {
            writeIntSetting(buf, T_EXECUTION_SAMPLE, "wall", args._wall);
            writeBoolSetting(buf, T_EXECUTION_SAMPLE, "nobatch", args._nobatch);
        }
        if (args._nativemem >= 0) {
            writeIntSetting(buf, T_MALLOC, "nativemem", args._nativemem);
        }

        writeBoolSetting(buf, T_ALLOC_IN_NEW_TLAB, "enabled", args._alloc >= 0);
        writeBoolSetting(buf, T_ALLOC_OUTSIDE_TLAB, "enabled", args._alloc >= 0);
        if (args._alloc >= 0) {
            writeIntSetting(buf, T_ALLOC_IN_NEW_TLAB, "alloc", args._alloc);
            writeBoolSetting(buf, T_ALLOC_IN_NEW_TLAB, "live", args._live);
        }

        writeBoolSetting(buf, T_MONITOR_ENTER, "enabled", args._lock >= 0);
        writeBoolSetting(buf, T_THREAD_PARK, "enabled", args._lock >= 0);
        if (args._lock >= 0) {
            writeIntSetting(buf, T_MONITOR_ENTER, "lock", args._lock);
        }

        writeBoolSetting(buf, T_METHOD_TRACE, "enabled", !args._trace.empty());
        writeListSetting(buf, T_METHOD_TRACE, "trace", args._trace);

        writeBoolSetting(buf, T_PROCESS_SAMPLE, "enabled", args._proc > 0);
        if (args._proc > 0) {
            writeIntSetting(buf, T_PROCESS_SAMPLE, "proc", args._proc);
        }

        writeBoolSetting(buf, T_ACTIVE_RECORDING, "debugSymbols", VM::loaded() && VMStructs::libjvm()->hasDebugSymbols());
        writeBoolSetting(buf, T_ACTIVE_RECORDING, "kernelSymbols", Symbols::haveKernelSymbols());
    }

    void writeStringSetting(Buffer* buf, int category, const char* key, const char* value) {
        flushIfNeeded(buf, RECORDING_BUFFER_LIMIT - MAX_STRING_LENGTH);
        int start = buf->skip(5);
        buf->put8(T_ACTIVE_SETTING);
        buf->putVar64(_start_ticks);
        buf->putVar32(category);
        buf->putUtf8(key);
        buf->putUtf8(value);
        buf->putVar32(start, buf->offset() - start);
    }

    void writeBoolSetting(Buffer* buf, int category, const char* key, bool value) {
        writeStringSetting(buf, category, key, value ? "true" : "false");
    }

    void writeIntSetting(Buffer* buf, int category, const char* key, long long value) {
        char str[32];
        snprintf(str, sizeof(str), "%lld", value);
        writeStringSetting(buf, category, key, str);
    }

    void writeListSetting(Buffer* buf, int category, const char* key, const std::vector<const char*>& list) {
        for (const char* s : list) {
            writeStringSetting(buf, category, key, s);
        }
    }

    void writeOsCpuInfo(Buffer* buf) {
        struct utsname u;
        if (uname(&u) != 0) {
            return;
        }

        char str[512];
        snprintf(str, sizeof(str) - 1, "uname: %s %s %s %s", u.sysname, u.release, u.version, u.machine);
        str[sizeof(str) - 1] = 0;

        int start = buf->skip(5);
        buf->put8(T_OS_INFORMATION);
        buf->putVar64(_start_ticks);
        buf->putUtf8(str);
        buf->putVar32(start, buf->offset() - start);

        start = buf->skip(5);
        buf->put8(T_CPU_INFORMATION);
        buf->putVar64(_start_ticks);
        buf->putUtf8(u.machine);
        buf->putUtf8(OS::getCpuDescription(str, sizeof(str) - 1) ? str : "");
        buf->putVar32(1);
        buf->putVar32(_available_processors);
        buf->putVar32(_available_processors);
        buf->putVar32(start, buf->offset() - start);
    }

    void writeJvmInfo(Buffer* buf) {
        if (_agent_properties == NULL && !(VM::loaded() && parseAgentProperties())) {
            return;
        }

        char* jvm_name = NULL;
        char* jvm_version = NULL;

        jvmtiEnv* jvmti = VM::jvmti();
        jvmti->GetSystemProperty("java.vm.name", &jvm_name);
        jvmti->GetSystemProperty("java.vm.version", &jvm_version);

        flushIfNeeded(buf, RECORDING_BUFFER_LIMIT - 5 * MAX_STRING_LENGTH);
        int start = buf->skip(5);
        buf->put8(T_JVM_INFORMATION);
        buf->putVar64(_start_ticks);
        buf->putUtf8(jvm_name);
        buf->putUtf8(jvm_version);
        buf->putUtf8(_jvm_args);
        buf->putUtf8(_jvm_flags);
        buf->putUtf8(_java_command);
        buf->putVar64(OS::processStartTime());
        buf->putVar32(OS::processId());
        buf->putVar32(start, buf->offset() - start);

        jvmti->Deallocate((unsigned char*)jvm_version);
        jvmti->Deallocate((unsigned char*)jvm_name);
    }

    void writeSystemProperties(Buffer* buf) {
        jvmtiEnv* jvmti = VM::jvmti();
        jint count;
        char** keys;
        if (!VM::loaded() || jvmti->GetSystemProperties(&count, &keys) != 0) {
            return;
        }

        for (int i = 0; i < count; i++) {
            char* key = keys[i];
            char* value = NULL;
            if (jvmti->GetSystemProperty(key, &value) == 0) {
                flushIfNeeded(buf, RECORDING_BUFFER_LIMIT - 2 * MAX_STRING_LENGTH);
                int start = buf->skip(5);
                buf->put8(T_INITIAL_SYSTEM_PROPERTY);
                buf->putVar64(_start_ticks);
                buf->putUtf8(key);
                buf->putUtf8(value);
                buf->putVar32(start, buf->offset() - start);
                jvmti->Deallocate((unsigned char*)value);
            }
            jvmti->Deallocate((unsigned char*)key);
        }

        jvmti->Deallocate((unsigned char*)keys);
    }

    void writeNativeLibraries(Buffer* buf) {
        if (_recorded_lib_count < 0) return;

        Profiler* profiler = Profiler::instance();
        CodeCacheArray& native_libs = profiler->_native_libs;
        int native_lib_count = native_libs.count();

        for (int i = _recorded_lib_count; i < native_lib_count; i++) {
            flushIfNeeded(buf, RECORDING_BUFFER_LIMIT - MAX_STRING_LENGTH);
            int start = buf->skip(5);
            buf->put8(T_NATIVE_LIBRARY);
            buf->putVar64(_start_ticks);
            buf->putUtf8(native_libs[i]->name());
            buf->putVar64((uintptr_t) native_libs[i]->minAddress());
            buf->putVar64((uintptr_t) native_libs[i]->maxAddress());
            buf->putVar32(start, buf->offset() - start);
        }

        _recorded_lib_count = native_lib_count;
    }

    void writeCpool(Buffer* buf) {
        buf->skip(5);  // size will be patched later
        buf->putVar32(T_CPOOL);
        buf->putVar64(_start_ticks);
        buf->putVar32(0);
        buf->putVar32(0);
        buf->putVar32(1);

        buf->putVar32(11);

        Index packages(1);
        Index symbols(1);
        Lookup lookup(&_method_map, Profiler::instance()->classMap(), &packages, &symbols, OUTPUT_JFR);
        writeFrameTypes(buf);
        writeThreadStates(buf);
        writeGCWhen(buf);
        writeThreads(buf);
        writeStackTraces(buf, &lookup);
        writeMethods(buf, &lookup);
        writeClasses(buf, &lookup);
        writePackages(buf, &lookup);
        writeSymbols(buf, &lookup);
        writeUserEventTypes(buf);
        // Write log levels last. The order does not affect the JFR's validity,
        // but log levels have an easily-visible format that makes it easy
        // to see if a JFR file has been accidentally truncated.
        writeLogLevels(buf);
    }

    void writePoolHeader(Buffer* buf, JfrType type, u32 size) {
        if (size > 0) {
            buf->putVar32(type);
            buf->putVar32(size);
        } else {
            // JDK's built-in JFR reader does not support empty pools.
            // Write a dummy String pool of 1 element instead.
            buf->putVar32(T_STRING);
            buf->putVar32(1);
            buf->putVar32(1);  // key
            buf->put8(0);      // null string
        }
    }

    void writeFrameTypes(Buffer* buf) {
        buf->putVar32(T_FRAME_TYPE);
        buf->putVar32(7);
        buf->putVar32(FRAME_INTERPRETED);  buf->putUtf8("Interpreted");
        buf->putVar32(FRAME_JIT_COMPILED); buf->putUtf8("JIT compiled");
        buf->putVar32(FRAME_INLINED);      buf->putUtf8("Inlined");
        buf->putVar32(FRAME_NATIVE);       buf->putUtf8("Native");
        buf->putVar32(FRAME_CPP);          buf->putUtf8("C++");
        buf->putVar32(FRAME_KERNEL);       buf->putUtf8("Kernel");
        buf->putVar32(FRAME_C1_COMPILED);  buf->putUtf8("C1 compiled");
    }

    void writeThreadStates(Buffer* buf) {
        buf->putVar32(T_THREAD_STATE);
        buf->putVar32(3);
        buf->putVar32(THREAD_UNKNOWN);     buf->putUtf8("STATE_DEFAULT");
        buf->putVar32(THREAD_RUNNING);     buf->putUtf8("STATE_RUNNABLE");
        buf->putVar32(THREAD_SLEEPING);    buf->putUtf8("STATE_SLEEPING");
    }

    void writeGCWhen(Buffer* buf) {
        buf->putVar32(T_GC_WHEN);
        buf->putVar32(2);
        buf->putVar32(BEFORE_GC);          buf->putUtf8("Before GC");
        buf->putVar32(AFTER_GC);           buf->putUtf8("After GC");
    }

    void writeThreads(Buffer* buf) {
        std::vector<int> threads;
        _thread_set.collect(threads);
        _thread_set.clear();

        Profiler* profiler = Profiler::instance();
        MutexLocker ml(profiler->_thread_names_lock);
        std::map<int, std::string>& thread_names = profiler->_thread_names;
        std::map<int, jlong>& thread_ids = profiler->_thread_ids;
        char name_buf[32];

        writePoolHeader(buf, T_THREAD, threads.size());
        for (int i = 0; i < threads.size(); i++) {
            const char* thread_name;
            jlong thread_id;
            std::map<int, std::string>::const_iterator it = thread_names.find(threads[i]);
            if (it != thread_names.end()) {
                thread_name = it->second.c_str();
                thread_id = thread_ids[threads[i]];
            } else {
                snprintf(name_buf, sizeof(name_buf), "[tid=%d]", threads[i]);
                thread_name = name_buf;
                thread_id = 0;
            }

            flushIfNeeded(buf, RECORDING_BUFFER_LIMIT - 2 * MAX_STRING_LENGTH);
            buf->putVar32(threads[i]);
            buf->putUtf8(thread_name);
            buf->putVar32(threads[i]);
            if (thread_id == 0) {
                buf->put8(0);
            } else {
                buf->putUtf8(thread_name);
            }
            buf->putVar64(thread_id);
        }
    }

    void writeStackTraces(Buffer* buf, Lookup* lookup) {
        std::map<u32, CallTrace*> traces;
        Profiler::instance()->_call_trace_storage.collectTraces(traces);

        writePoolHeader(buf, T_STACK_TRACE, traces.size());
        for (std::map<u32, CallTrace*>::const_iterator it = traces.begin(); it != traces.end(); ++it) {
            CallTrace* trace = it->second;
            buf->putVar32(it->first);
            buf->putVar32(0);  // truncated
            buf->putVar32(trace->num_frames);
            for (int i = 0; i < trace->num_frames; i++) {
                MethodInfo* mi = lookup->resolveMethod(trace->frames[i]);
                buf->putVar32(mi->_key);
                if (mi->_type == FRAME_INTERPRETED) {
                    jint bci = trace->frames[i].bci;
                    FrameTypeId type = FrameType::decode(bci);
                    bci = (bci & 0x10000) ? 0 : (bci & 0xffff);
                    buf->putVar32(mi->getLineNumber(bci));
                    buf->putVar32(bci);
                    buf->put8(type);
                } else {
                    buf->put8(0);
                    buf->put8(0);
                    buf->put8(mi->_type);
                }
                flushIfNeeded(buf);
            }
            flushIfNeeded(buf);
        }
    }

    void writeMethods(Buffer* buf, Lookup* lookup) {
        MethodMap* method_map = lookup->_method_map;

        u32 marked_count = 0;
        for (MethodMap::const_iterator it = method_map->begin(); it != method_map->end(); ++it) {
            if (it->second._mark) {
                marked_count++;
            }
        }

        writePoolHeader(buf, T_METHOD, marked_count);
        for (MethodMap::iterator it = method_map->begin(); it != method_map->end(); ++it) {
            MethodInfo& mi = it->second;
            if (mi._mark) {
                mi._mark = false;
                buf->putVar32(mi._key);
                buf->putVar32(mi._class);
                buf->putVar64(mi._name | _base_id);
                buf->putVar64(mi._sig | _base_id);
                buf->putVar32(mi._modifiers);
                buf->putVar32(0);  // hidden
                flushIfNeeded(buf);
            }
        }
    }

    void writeClasses(Buffer* buf, Lookup* lookup) {
        std::map<u32, const char*> classes;
        lookup->_classes->collect(classes);

        writePoolHeader(buf, T_CLASS, classes.size());
        for (std::map<u32, const char*>::const_iterator it = classes.begin(); it != classes.end(); ++it) {
            buf->putVar32(it->first);
            buf->putVar32(0);  // classLoader
            buf->putVar64(lookup->_symbols->indexOf(it->second) | _base_id);
            buf->putVar64(lookup->getPackage(it->second) | _base_id);
            buf->putVar32(0);  // access flags
            flushIfNeeded(buf);
        }
    }

    void writePackages(Buffer* buf, Lookup* lookup) {
        writePoolHeader(buf, T_PACKAGE, lookup->_packages->size());
        lookup->_packages->forEachOrdered([&] (size_t idx, const std::string& s) {
            buf->putVar64(idx | _base_id);
            buf->putVar64(lookup->_symbols->indexOf(s) | _base_id);
            flushIfNeeded(buf);
        });
    }

    void writeSymbols(Buffer* buf, Lookup* lookup) {
        writePoolHeader(buf, T_SYMBOL, lookup->_symbols->size());
        lookup->_symbols->forEachOrdered([&] (size_t idx, const std::string& s) {
            flushIfNeeded(buf, RECORDING_BUFFER_LIMIT - MAX_STRING_LENGTH);
            buf->putVar64(idx | _base_id);
            buf->putUtf8(s.c_str());
        });
    }

    void writeLogLevels(Buffer* buf) {
        buf->putVar32(T_LOG_LEVEL);
        buf->putVar32(LOG_ERROR - LOG_TRACE + 1);
        for (int i = LOG_TRACE; i <= LOG_ERROR; i++) {
            buf->putVar32(i);
            buf->putUtf8(Log::LEVEL_NAME[i]);
        }
    }

    void writeUserEventTypes(Buffer* buf) {
        std::map<u32, const char*> events;
        UserEvents::collect(events);

        writePoolHeader(buf, T_USER_EVENT_TYPE, events.size());
        for (std::map<u32, const char*>::const_iterator it = events.begin(); it != events.end(); ++it) {
            flushIfNeeded(buf, RECORDING_BUFFER_LIMIT - MAX_STRING_LENGTH);
            buf->putVar32(it->first);
            buf->putUtf8(it->second);
        }
    }

    void recordExecutionSample(Buffer* buf, int tid, u32 call_trace_id, ExecutionEvent* event) {
        int start = buf->skip(1);
        buf->put8(T_EXECUTION_SAMPLE);
        buf->putVar64(event->_start_time);
        buf->putVar32(tid);
        buf->putVar32(call_trace_id);
        buf->putVar32(event->_thread_state);
        buf->put8(start, buf->offset() - start);
    }

    void recordMethodTrace(Buffer* buf, int tid, u32 call_trace_id, MethodTraceEvent* event) {
        int start = buf->skip(1);
        buf->put8(T_METHOD_TRACE);
        buf->putVar64(event->_start_time);
        buf->putVar64(event->_duration);
        buf->putVar32(tid);
        buf->putVar32(call_trace_id);
        buf->putVar32(0); // TODO: method
        buf->put8(start, buf->offset() - start);
    }

    void recordWallClockSample(Buffer* buf, int tid, u32 call_trace_id, WallClockEvent* event) {
        int start = buf->skip(1);
        buf->put8(T_WALL_CLOCK_SAMPLE);
        buf->putVar64(event->_start_time);
        buf->putVar32(tid);
        buf->putVar32(call_trace_id);
        buf->putVar32(event->_thread_state);
        buf->putVar32(event->_samples);
        buf->put8(start, buf->offset() - start);
    }

    void recordAllocationInNewTLAB(Buffer* buf, int tid, u32 call_trace_id, AllocEvent* event) {
        int start = buf->skip(1);
        buf->put8(T_ALLOC_IN_NEW_TLAB);
        buf->putVar64(event->_start_time);
        buf->putVar32(tid);
        buf->putVar32(call_trace_id);
        buf->putVar32(event->_class_id);
        buf->putVar64(event->_instance_size);
        buf->putVar64(event->_total_size);
        buf->put8(start, buf->offset() - start);
    }

    void recordAllocationOutsideTLAB(Buffer* buf, int tid, u32 call_trace_id, AllocEvent* event) {
        int start = buf->skip(1);
        buf->put8(T_ALLOC_OUTSIDE_TLAB);
        buf->putVar64(event->_start_time);
        buf->putVar32(tid);
        buf->putVar32(call_trace_id);
        buf->putVar32(event->_class_id);
        buf->putVar64(event->_total_size);
        buf->put8(start, buf->offset() - start);
    }

    void recordMallocSample(Buffer* buf, int tid, u32 call_trace_id, MallocEvent* event) {
        int start = buf->skip(1);
        buf->put8(event->_size != 0 ? T_MALLOC : T_FREE);
        buf->putVar64(event->_start_time);
        buf->putVar32(tid);
        buf->putVar32(call_trace_id);
        buf->putVar64(event->_address);
        if (event->_size != 0) {
            buf->putVar64(event->_size);
        }
        buf->put8(start, buf->offset() - start);
    }

    void recordUserEvent(Buffer* buf, int tid, UserEvent* event) {
        // estimate of size of non-string fields of this event
        const size_t event_non_string_size_limit = 64;
        // When calling recordUserEvent, the buffer can be up to RECORDING_BUFFER_LIMIT bytes full.
        // Check that the buffer is not exceeded.
        static_assert(RECORDING_BUFFER_LIMIT + event_non_string_size_limit + ASPROF_MAX_JFR_EVENT_LENGTH
            <= RECORDING_BUFFER_SIZE, "output must fit within recording buffer");

        int start = buf->skip(5);
        buf->put8(T_USER_EVENT);
        buf->putVar64(event->_start_time);
        buf->putVar32(tid);
        buf->putVar32(event->_type);
        buf->putByteString((const char*)event->_data,
            event->_len > ASPROF_MAX_JFR_EVENT_LENGTH ? ASPROF_MAX_JFR_EVENT_LENGTH : event->_len);
        buf->putVar32(start, buf->offset() - start);
    }

    void recordProcessSample(Buffer* buf, const ProcessInfo* info) {
        int start = buf->skip(5);
        buf->put8(T_PROCESS_SAMPLE);
        buf->putVar64(TSC::ticks());

        buf->putVar32(info->pid);
        buf->putVar32(info->ppid);
        buf->putByteString(info->name);
        buf->putByteString(info->cmdline);

        buf->putVar32(info->uid);
        buf->put8(info->state);

        buf->putVar64(info->start_time);
        buf->putFloat(info->cpu_user);
        buf->putFloat(info->cpu_system);
        buf->putFloat(info->cpu_percent);
        buf->putVar32(info->threads);

        buf->putVar64(info->vm_size);
        buf->putVar64(info->vm_rss);

        buf->putVar64(info->rss_anon);
        buf->putVar64(info->rss_files);
        buf->putVar64(info->rss_shmem);

        buf->putVar64(info->minor_faults);
        buf->putVar64(info->major_faults);

        buf->putVar64(info->io_read);
        buf->putVar64(info->io_write);

        buf->putVar32(start, buf->offset() - start);
    }

    void recordLiveObject(Buffer* buf, int tid, u32 call_trace_id, LiveObject* event) {
        int start = buf->skip(1);
        buf->put8(T_LIVE_OBJECT);
        buf->putVar64(event->_start_time);
        buf->putVar32(tid);
        buf->putVar32(call_trace_id);
        buf->putVar32(event->_class_id);
        buf->putVar64(event->_alloc_size);
        buf->putVar64(event->_alloc_time);
        buf->put8(start, buf->offset() - start);
    }

    void recordMonitorBlocked(Buffer* buf, int tid, u32 call_trace_id, LockEvent* event) {
        int start = buf->skip(1);
        buf->put8(T_MONITOR_ENTER);
        buf->putVar64(event->_start_time);
        buf->putVar64(event->_end_time - event->_start_time);
        buf->putVar32(tid);
        buf->putVar32(call_trace_id);
        buf->putVar32(event->_class_id);
        buf->put8(0);
        buf->putVar64(event->_address);
        buf->put8(start, buf->offset() - start);
    }

    void recordThreadPark(Buffer* buf, int tid, u32 call_trace_id, LockEvent* event) {
        int start = buf->skip(1);
        buf->put8(T_THREAD_PARK);
        buf->putVar64(event->_start_time);
        buf->putVar64(event->_end_time - event->_start_time);
        buf->putVar32(tid);
        buf->putVar32(call_trace_id);
        buf->putVar32(event->_class_id);
        buf->putVar64(event->_timeout);
        buf->putVar64(MIN_JLONG);
        buf->putVar64(event->_address);
        buf->put8(start, buf->offset() - start);
    }

    void recordWindow(Buffer* buf, int tid, ProfilingWindow* event) {
        int start = buf->skip(1);
        buf->put8(T_WINDOW);
        buf->putVar64(event->_start_time);
        buf->putVar64(event->_end_time - event->_start_time);
        buf->putVar32(tid);
        buf->put8(start, buf->offset() - start);
    }

    void recordCpuLoad(Buffer* buf, float proc_user, float proc_system, float machine_total) {
        int start = buf->skip(1);
        buf->put8(T_CPU_LOAD);
        buf->putVar64(TSC::ticks());
        buf->putFloat(proc_user);
        buf->putFloat(proc_system);
        buf->putFloat(machine_total);
        buf->put8(start, buf->offset() - start);
    }

    void recordHeapSummary(Buffer* buf, u32 id, GCWhen when, u64 total_memory, u64 free_memory) {
        CollectedHeap* heap = CollectedHeap::heap();
        u64 heap_start = heap != NULL ? heap->start() : 0;
        u64 heap_size = heap != NULL ? heap->size() : total_memory;

        int start = buf->skip(1);
        buf->put8(T_GC_HEAP_SUMMARY);
        buf->putVar64(TSC::ticks());
        buf->putVar32(id);
        buf->putVar32(when);
        buf->putVar64(heap_start);
        buf->putVar64(heap_start + total_memory);
        buf->putVar64(total_memory);
        buf->putVar64(heap_start + heap_size);
        buf->putVar64(heap_size);
        buf->putVar64(total_memory - free_memory);
        buf->put8(start, buf->offset() - start);
    }

    void addThread(int tid) {
        if (!_thread_set.accept(tid)) {
            _thread_set.add(tid);
        }
    }
};

char* Recording::_agent_properties = NULL;
char* Recording::_jvm_args = NULL;
char* Recording::_jvm_flags = NULL;
char* Recording::_java_command = NULL;


Error FlightRecorder::start(Arguments& args, bool reset) {
    const char* filename = args.file();
    if (filename == NULL || filename[0] == 0) {
        return Error("Flight Recorder output file is not specified");
    }

    char* filename_tmp = NULL;
    const char* master_recording_file = NULL;
    if (args._jfr_sync != NULL) {
        Error error = startMasterRecording(args, master_recording_file = filename);
        if (error) {
            return error;
        }

        size_t len = strlen(filename);
        filename_tmp = (char*)malloc(len + 16);
        snprintf(filename_tmp, len + 16, "%s.%d~", filename, OS::processId());
        filename = filename_tmp;
    }

    TSC::enable(args._clock);

    int fd = open(filename, O_CREAT | O_RDWR | (reset ? O_TRUNC : 0), 0644);
    if (fd == -1) {
        free(filename_tmp);
        return Error("Could not open Flight Recorder output file");
    }

    if (args._jfr_sync != NULL) {
        unlink(filename_tmp);
        free(filename_tmp);
    }

    _rec = new Recording(fd, master_recording_file, args);
    _rec_lock.unlock();
    return Error::OK;
}

void FlightRecorder::stop() {
    if (_rec != NULL) {
        _rec_lock.lock();

        if (_rec->hasMasterRecording()) {
            stopMasterRecording();
        }

        delete _rec;
        _rec = NULL;
    }
}

void FlightRecorder::flush() {
    if (_rec != NULL) {
        _rec_lock.lock();
        _rec->switchChunk();
        _rec_lock.unlock();
    }
}

size_t FlightRecorder::usedMemory() {
    size_t bytes = 0;
    if (_rec != NULL) {
        _rec_lock.lock();
        bytes = _rec->usedMemory();
        _rec_lock.unlock();
    }
    return bytes;
}

bool FlightRecorder::timerTick(u64 wall_time, u32 gc_id) {
    if (!_rec_lock.tryLockShared()) {
        // No active recording
        return false;
    }

    _rec->cpuMonitorCycle();
    _rec->heapMonitorCycle(gc_id);
    _rec->processMonitorCycle(wall_time);

    bool need_switch_chunk = _rec->needSwitchChunk(wall_time);

    _rec_lock.unlockShared();
    return need_switch_chunk;
}

Error FlightRecorder::startMasterRecording(Arguments& args, const char* filename) {
    JNIEnv* env = VM::jni();

    if (_jfr_sync_class == NULL) {
        if (env->FindClass("jdk/jfr/FlightRecorderListener") == NULL) {
            env->ExceptionClear();
            return Error("JDK Flight Recorder is not available");
        }

        const JNINativeMethod native_method = {(char*)"stopProfiler", (char*)"()V", (void*)JfrSync_stopProfiler};

        jclass cls = env->DefineClass(JFR_SYNC_NAME, NULL, (const jbyte*)JFR_SYNC_CLASS, INCBIN_SIZEOF(JFR_SYNC_CLASS));
        if (cls == NULL || env->RegisterNatives(cls, &native_method, 1) != 0
                || (_start_method = env->GetStaticMethodID(cls, "start", "(Ljava/lang/String;Ljava/lang/String;I)V")) == NULL
                || (_stop_method = env->GetStaticMethodID(cls, "stop", "()V")) == NULL
                || (_box_method = env->GetStaticMethodID(cls, "box", "(I)Ljava/lang/Integer;")) == NULL
                || (_jfr_sync_class = (jclass)env->NewGlobalRef(cls)) == NULL) {
            env->ExceptionDescribe();
            return Error("Failed to initialize JfrSync class");
        }
    }

    jclass options_class = env->FindClass("jdk/jfr/internal/Options");
    if (options_class != NULL) {
        if (args._chunk_size > 0) {
            jmethodID method = env->GetStaticMethodID(options_class, "setMaxChunkSize", "(J)V");
            if (method != NULL) {
                env->CallStaticVoidMethod(options_class, method, args._chunk_size < 1024 * 1024 ? 1024 * 1024 : args._chunk_size);
                env->ExceptionClear();
            }
        }

        if (args._jstackdepth > 0) {
            jmethodID method = env->GetStaticMethodID(options_class, "setStackDepth", "(Ljava/lang/Integer;)V");
            if (method != NULL) {
                jobject value = env->CallStaticObjectMethod(_jfr_sync_class, _box_method, args._jstackdepth);
                if (value != NULL && !env->ExceptionCheck()) {
                    env->CallStaticVoidMethod(options_class, method, value);
                }
            }
        }
    }
    env->ExceptionClear();

    jobject jfilename = env->NewStringUTF(filename);
    jobject jsettings = args._jfr_sync == NULL ? NULL : env->NewStringUTF(args._jfr_sync);

    int event_mask = args.eventMask() |
                     ((args._jfr_options ^ JFR_SYNC_OPTS) << EVENT_MASK_SIZE);

    env->CallStaticVoidMethod(_jfr_sync_class, _start_method, jfilename, jsettings, event_mask);

    if (env->ExceptionCheck()) {
        env->ExceptionDescribe();
        return Error("Could not start master JFR recording");
    }

    return Error::OK;
}

void FlightRecorder::stopMasterRecording() {
    JNIEnv* env = VM::jni();
    env->CallStaticVoidMethod(_jfr_sync_class, _stop_method);
    env->ExceptionClear();
}

void FlightRecorder::recordEvent(int lock_index, int tid, u32 call_trace_id,
                                 EventType event_type, Event* event) {
    if (_rec != NULL) {
        // Recording an event, increment the sample counter to allow
        // user code to attach metadata.
        ThreadLocalData::incrementSampleCounter();

        Buffer* buf = _rec->buffer(lock_index);
        switch (event_type) {
            case PERF_SAMPLE:
            case EXECUTION_SAMPLE:
            case INSTRUMENTED_METHOD:
                _rec->recordExecutionSample(buf, tid, call_trace_id, (ExecutionEvent*)event);
                break;
            case METHOD_TRACE:
                _rec->recordMethodTrace(buf, tid, call_trace_id, (MethodTraceEvent*)event);
                break;
            case WALL_CLOCK_SAMPLE:
                _rec->recordWallClockSample(buf, tid, call_trace_id, (WallClockEvent*)event);
                break;
            case MALLOC_SAMPLE:
                _rec->recordMallocSample(buf, tid, call_trace_id, (MallocEvent*)event);
                break;
            case ALLOC_SAMPLE:
                _rec->recordAllocationInNewTLAB(buf, tid, call_trace_id, (AllocEvent*)event);
                break;
            case ALLOC_OUTSIDE_TLAB:
                _rec->recordAllocationOutsideTLAB(buf, tid, call_trace_id, (AllocEvent*)event);
                break;
            case LIVE_OBJECT:
                _rec->recordLiveObject(buf, tid, call_trace_id, (LiveObject*)event);
                break;
            case LOCK_SAMPLE:
                _rec->recordMonitorBlocked(buf, tid, call_trace_id, (LockEvent*)event);
                break;
            case PARK_SAMPLE:
                _rec->recordThreadPark(buf, tid, call_trace_id, (LockEvent*)event);
                break;
            case PROFILING_WINDOW:
                _rec->recordWindow(buf, tid, (ProfilingWindow*)event);
                break;
            case USER_EVENT:
                _rec->recordUserEvent(buf, tid, (UserEvent*)event);
                break;
        }
        _rec->flushIfNeeded(buf);
        _rec->addThread(tid);
    }
}

void FlightRecorder::recordLog(LogLevel level, const char* message, size_t len) {
    if (!_rec_lock.tryLockShared()) {
        // No active recording
        return;
    }

    if (len > MAX_STRING_LENGTH) len = MAX_STRING_LENGTH;
    Buffer* buf = (Buffer*)alloca(len + 40);
    buf->reset();

    int start = buf->skip(5);
    buf->put8(T_LOG);
    buf->putVar64(TSC::ticks());
    buf->put8(level);
    buf->putUtf8(message, len);
    buf->putVar32(start, buf->offset() - start);
    _rec->flush(buf);

    _rec_lock.unlockShared();
}
