diff --git a/src/asprof.cpp b/src/asprof.cpp index 4005d0f4..a458d00b 100644 --- a/src/asprof.cpp +++ b/src/asprof.cpp @@ -3,8 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include -#include #include "asprof.h" #include "hooks.h" #include "profiler.h" @@ -33,22 +31,17 @@ DLLEXPORT asprof_error_t asprof_execute(const char* command, asprof_writer_t out Log::open(args); if (!args.hasOutputFile()) { - // FIXME: get rid of stream - std::ostringstream out; + CallbackWriter out(output_callback); error = Profiler::instance()->runInternal(args, out); if (!error) { - if (output_callback != NULL) { - output_callback(out.str().data(), out.str().size()); - } return NULL; } } else { - std::ofstream out(args.file(), std::ios::out | std::ios::trunc); + FileWriter out(args.file()); if (!out.is_open()) { return asprof_error("Could not open output file"); } error = Profiler::instance()->runInternal(args, out); - out.close(); if (!error) { return NULL; } diff --git a/src/flameGraph.cpp b/src/flameGraph.cpp index d689c6b7..94e9f982 100644 --- a/src/flameGraph.cpp +++ b/src/flameGraph.cpp @@ -111,7 +111,7 @@ Trie* FlameGraph::addChild(Trie* f, const char* name, FrameTypeId type, u64 valu } } -void FlameGraph::dump(std::ostream& out, bool tree) { +void FlameGraph::dump(Writer& out, bool tree) { _name_order = new u32[_cpool.size() + 1](); _mintotal = _minwidth == 0 && tree ? _root._total / 1000 : (u64)(_root._total * _minwidth / 100); int depth = _root.depth(_mintotal, _name_order); @@ -167,7 +167,7 @@ void FlameGraph::dump(std::ostream& out, bool tree) { delete[] _name_order; } -void FlameGraph::printFrame(std::ostream& out, u32 key, const Trie& f, int level, u64 x) { +void FlameGraph::printFrame(Writer& out, u32 key, const Trie& f, int level, u64 x) { u32 name_and_type = _name_order[f.nameIndex(key)] << 3 | f.type(key); bool has_extra_types = (f._inlined | f._c1_compiled | f._interpreted) && f._inlined < f._total && f._interpreted < f._total; @@ -217,7 +217,7 @@ void FlameGraph::printFrame(std::ostream& out, u32 key, const Trie& f, int level } } -void FlameGraph::printTreeFrame(std::ostream& out, const Trie& f, int level, const char** names) { +void FlameGraph::printTreeFrame(Writer& out, const Trie& f, int level, const char** names) { std::vector children; children.reserve(f._children.size()); for (std::map::const_iterator it = f._children.begin(); it != f._children.end(); ++it) { @@ -264,7 +264,7 @@ void FlameGraph::printTreeFrame(std::ostream& out, const Trie& f, int level, con } } -void FlameGraph::printCpool(std::ostream& out) { +void FlameGraph::printCpool(Writer& out) { out << "'all'"; std::string prev; @@ -282,7 +282,9 @@ void FlameGraph::printCpool(std::ostream& out) { StringUtils::replace(s, '\\', "\\\\", 2); StringUtils::replace(s, '\'', "\\'", 2); - out << ",\n'" << s << "'"; + out << ",\n'"; + out.write(s.data(), s.size()); + out << "'"; } } @@ -290,7 +292,7 @@ void FlameGraph::printCpool(std::ostream& out) { _cpool = std::map(); } -const char* FlameGraph::printTill(std::ostream& out, const char* data, const char* till) { +const char* FlameGraph::printTill(Writer& out, const char* data, const char* till) { const char* pos = strstr(data, till); out.write(data, pos - data); return pos + strlen(till); diff --git a/src/flameGraph.h b/src/flameGraph.h index 7aa60464..5e0e3c8c 100644 --- a/src/flameGraph.h +++ b/src/flameGraph.h @@ -6,12 +6,12 @@ #ifndef _FLAMEGRAPH_H #define _FLAMEGRAPH_H -#include #include #include #include "arch.h" #include "arguments.h" #include "vmEntry.h" +#include "writer.h" class Trie { @@ -75,10 +75,10 @@ class FlameGraph { u64 _last_x; u64 _last_total; - void printFrame(std::ostream& out, u32 key, const Trie& f, int level, u64 x); - void printTreeFrame(std::ostream& out, const Trie& f, int level, const char** names); - void printCpool(std::ostream& out); - const char* printTill(std::ostream& out, const char* data, const char* till); + void printFrame(Writer& out, u32 key, const Trie& f, int level, u64 x); + void printTreeFrame(Writer& out, const Trie& f, int level, const char** names); + void printCpool(Writer& out); + const char* printTill(Writer& out, const char* data, const char* till); public: FlameGraph(const char* title, Counter counter, double minwidth, bool reverse) : @@ -100,7 +100,7 @@ class FlameGraph { Trie* addChild(Trie* f, const char* name, FrameTypeId type, u64 value); - void dump(std::ostream& out, bool tree); + void dump(Writer& out, bool tree); }; #endif // _FLAMEGRAPH_H diff --git a/src/javaApi.cpp b/src/javaApi.cpp index 63315093..a2bc24fd 100644 --- a/src/javaApi.cpp +++ b/src/javaApi.cpp @@ -3,8 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include -#include #include #include #include "asprof.h" @@ -71,23 +69,23 @@ Java_one_profiler_AsyncProfiler_execute0(JNIEnv* env, jobject unused, jstring co Log::open(args); if (!args.hasOutputFile()) { - std::ostringstream out; + BufferWriter out; error = Profiler::instance()->runInternal(args, out); if (!error) { - if (out.tellp() >= 0x3fffffff) { + out << '\0'; + if (out.size() >= 0x3fffffff) { throwNew(env, "java/lang/IllegalStateException", "Output exceeds string size limit"); return NULL; } - return env->NewStringUTF(out.str().c_str()); + return env->NewStringUTF(out.buf()); } } else { - std::ofstream out(args.file(), std::ios::out | std::ios::trunc); + FileWriter out(args.file()); if (!out.is_open()) { throwNew(env, "java/io/IOException", strerror(errno)); return NULL; } error = Profiler::instance()->runInternal(args, out); - out.close(); if (!error) { return env->NewStringUTF("OK"); } diff --git a/src/profiler.cpp b/src/profiler.cpp index a089e7a3..b0263255 100644 --- a/src/profiler.cpp +++ b/src/profiler.cpp @@ -4,7 +4,6 @@ */ #include -#include #include #include #include @@ -1295,7 +1294,7 @@ Error Profiler::flushJfr() { return Error::OK; } -Error Profiler::dump(std::ostream& out, Arguments& args) { +Error Profiler::dump(Writer& out, Arguments& args) { MutexLocker ml(_state_lock); if (_state != IDLE && _state != RUNNING) { return Error("Profiler has not started"); @@ -1333,7 +1332,7 @@ Error Profiler::dump(std::ostream& out, Arguments& args) { return Error::OK; } -void Profiler::printUsedMemory(std::ostream& out) { +void Profiler::printUsedMemory(Writer& out) { size_t call_trace_storage = _call_trace_storage.usedMemory(); size_t dictionaries = _class_map.usedMemory() + _symbol_map.usedMemory() + _thread_filter.usedMemory() + _jfr.usedMemory(); @@ -1379,7 +1378,7 @@ void Profiler::switchThreadEvents(jvmtiEventMode mode) { * * ;;...; */ -void Profiler::dumpCollapsed(std::ostream& out, Arguments& args) { +void Profiler::dumpCollapsed(Writer& out, Arguments& args) { FrameName fn(args, args._style | STYLE_NO_SEMICOLON, _epoch, _thread_names_lock, _thread_names); char buf[32]; @@ -1406,7 +1405,7 @@ void Profiler::dumpCollapsed(std::ostream& out, Arguments& args) { } } -void Profiler::dumpFlameGraph(std::ostream& out, Arguments& args, bool tree) { +void Profiler::dumpFlameGraph(Writer& out, Arguments& args, bool tree) { char title[64]; if (args._title == NULL) { Engine* active_engine = activeEngine(); @@ -1466,7 +1465,7 @@ void Profiler::dumpFlameGraph(std::ostream& out, Arguments& args, bool tree) { flamegraph.dump(out, tree); } -void Profiler::dumpText(std::ostream& out, Arguments& args) { +void Profiler::dumpText(Writer& out, Arguments& args) { FrameName fn(args, args._style | STYLE_DOTTED, _epoch, _thread_names_lock, _thread_names); char buf[1024] = {0}; @@ -1656,7 +1655,7 @@ void Profiler::timerLoop(void* timer_id) { } } -Error Profiler::runInternal(Arguments& args, std::ostream& out) { +Error Profiler::runInternal(Arguments& args, Writer& out) { switch (args._action) { case ACTION_START: case ACTION_RESUME: { @@ -1734,7 +1733,6 @@ Error Profiler::runInternal(Arguments& args, std::ostream& out) { } case ACTION_VERSION: out << PROFILER_VERSION; - out.flush(); break; default: break; @@ -1744,17 +1742,16 @@ Error Profiler::runInternal(Arguments& args, std::ostream& out) { Error Profiler::run(Arguments& args) { if (!args.hasOutputFile()) { - return runInternal(args, std::cout); + FileWriter out(STDOUT_FILENO); + return runInternal(args, out); } else { // Open output file under the lock to avoid races with background timer MutexLocker ml(_state_lock); - std::ofstream out(args.file(), std::ios::out | std::ios::trunc); + FileWriter out(args.file()); if (!out.is_open()) { return Error("Could not open output file"); } - Error error = runInternal(args, out); - out.close(); - return error; + return runInternal(args, out); } } @@ -1767,12 +1764,11 @@ Error Profiler::restart(Arguments& args) { } if (args._file != NULL && args._output != OUTPUT_NONE && args._output != OUTPUT_JFR) { - std::ofstream out(args.file(), std::ios::out | std::ios::trunc); + FileWriter out(args.file()); if (!out.is_open()) { return Error("Could not open output file"); } error = dump(out, args); - out.close(); if (error) { return error; } diff --git a/src/profiler.h b/src/profiler.h index 70631875..cf639001 100644 --- a/src/profiler.h +++ b/src/profiler.h @@ -6,8 +6,8 @@ #ifndef _PROFILER_H #define _PROFILER_H -#include #include +#include #include #include "arch.h" #include "arguments.h" @@ -23,6 +23,7 @@ #include "threadFilter.h" #include "trap.h" #include "vmEntry.h" +#include "writer.h" const int MAX_NATIVE_FRAMES = 128; @@ -145,9 +146,9 @@ class Profiler { void lockAll(); void unlockAll(); - void dumpCollapsed(std::ostream& out, Arguments& args); - void dumpFlameGraph(std::ostream& out, Arguments& args, bool tree); - void dumpText(std::ostream& out, Arguments& args); + void dumpCollapsed(Writer& out, Arguments& args); + void dumpFlameGraph(Writer& out, Arguments& args, bool tree); + void dumpText(Writer& out, Arguments& args); static Profiler* const _instance; @@ -189,15 +190,15 @@ class Profiler { CodeCacheArray* nativeLibs() { return &_native_libs; } Error run(Arguments& args); - Error runInternal(Arguments& args, std::ostream& out); + Error runInternal(Arguments& args, Writer& out); Error restart(Arguments& args); void shutdown(Arguments& args); Error check(Arguments& args); Error start(Arguments& args, bool reset); Error stop(bool restart = false); Error flushJfr(); - Error dump(std::ostream& out, Arguments& args); - void printUsedMemory(std::ostream& out); + Error dump(Writer& out, Arguments& args); + void printUsedMemory(Writer& out); void switchThreadEvents(jvmtiEventMode mode); int convertNativeTrace(int native_frames, const void** callchain, ASGCT_CallFrame* frames); u64 recordSample(void* ucontext, u64 counter, EventType event_type, Event* event); diff --git a/src/writer.cpp b/src/writer.cpp new file mode 100644 index 00000000..d3cd0cac --- /dev/null +++ b/src/writer.cpp @@ -0,0 +1,101 @@ +/* + * Copyright The async-profiler authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include "writer.h" + + +Writer& Writer::operator<<(char c) { + write(&c, 1); + return *this; +} + +Writer& Writer::operator<<(const char* s) { + write(s, strlen(s)); + return *this; +} + +Writer& Writer::operator<<(int n) { + char buf[16]; + write(buf, snprintf(buf, sizeof(buf), "%d", n)); + return *this; +} + +Writer& Writer::operator<<(long n) { + char buf[24]; + write(buf, snprintf(buf, sizeof(buf), "%ld", n)); + return *this; +} + +FileWriter::FileWriter(const char* file_name) : _size(0) { + _fd = open(file_name, O_WRONLY | O_TRUNC | O_CREAT, 0644); + _buf = (char*)malloc(BUF_SIZE); +} + +FileWriter::FileWriter(int fd) : _fd(fd), _size(0) { + _buf = (char*)malloc(BUF_SIZE); +} + +FileWriter::~FileWriter() { + flush(_buf, _size); + free(_buf); + if (_fd > STDERR_FILENO) { + close(_fd); + } +} + +void FileWriter::flush(const char* data, size_t len) { + while (len > 0) { + ssize_t bytes = ::write(_fd, data, len); + if (bytes < 0) { + _err = errno; + break; + } + data += bytes; + len -= (size_t)bytes; + } +} + +void FileWriter::write(const char* data, size_t len) { + if (_size + len > BUF_SIZE) { + flush(_buf, _size); + _size = 0; + if (len > BUF_SIZE) { + flush(data, len); + return; + } + } + memcpy(_buf + _size, data, len); + _size += len; +} + +BufferWriter::BufferWriter(size_t capacity) : _size(0), _capacity(capacity) { + _buf = (char*)malloc(capacity); +} + +BufferWriter::~BufferWriter() { + free(_buf); +} + +void BufferWriter::write(const char* data, size_t len) { + size_t new_size = _size + len; + if (new_size > _capacity) { + _capacity = new_size >= _capacity * 2 ? new_size : _capacity * 2; + _buf = (char*)realloc(_buf, _capacity); + } + memcpy(_buf + _size, data, len); + _size = new_size; +} + +void CallbackWriter::write(const char* data, size_t len) { + if (_output_callback != NULL) { + _output_callback(data, len); + } +} diff --git a/src/writer.h b/src/writer.h new file mode 100644 index 00000000..5bf466bb --- /dev/null +++ b/src/writer.h @@ -0,0 +1,86 @@ +/* + * Copyright The async-profiler authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _WRITER_H +#define _WRITER_H + +#include "asprof.h" + + +class Writer { + protected: + int _err; + + Writer() : _err(0) { + } + + public: + Writer& operator<<(char c); + Writer& operator<<(const char* s); + Writer& operator<<(int n); + Writer& operator<<(long n); + + bool good() const { + return _err == 0; + } + + virtual void write(const char* data, size_t len) = 0; +}; + +class FileWriter : public Writer { + private: + int _fd; + char* _buf; + size_t _size; + + enum { BUF_SIZE = 8192 }; + + void flush(const char* data, size_t len); + + public: + FileWriter(const char* file_name); + FileWriter(int fd); + ~FileWriter(); + + bool is_open() const { + return _fd >= 0; + } + + virtual void write(const char* data, size_t len); +}; + +class BufferWriter : public Writer { + private: + char* _buf; + size_t _size; + size_t _capacity; + + public: + BufferWriter(size_t capacity = 256); + ~BufferWriter(); + + char* buf() const { + return _buf; + } + + size_t size() const { + return _size; + } + + virtual void write(const char* data, size_t len); +}; + +class CallbackWriter : public Writer { + private: + asprof_writer_t _output_callback; + + public: + CallbackWriter(asprof_writer_t output_callback) : _output_callback(output_callback) { + } + + virtual void write(const char* data, size_t len); +}; + +#endif // _WRITER_H