* Improve dump output code.
Proposed changes provide more flexible design for dump output
code in order to implement more output modules in future.
* Revise fprintf().
Use incremental buffer.
Setup errno, since heaptrack use it in writeError().
* Add error output in outStreamFILE constructor.
* Add more comments.
* Provide proper errno in Putc() and Puts().
* Cosmetic changes.
* Fix misspell.
* Revise fprintf().
add_library(heaptrack_preload MODULE
heaptrack_preload.cpp
libheaptrack.cpp
+ outstream/outstream.cpp
+ outstream/outstream_file.cpp
)
target_compile_options(heaptrack_preload PRIVATE "-ftls-model=initial-exec")
add_library(heaptrack_inject MODULE
heaptrack_inject.cpp
libheaptrack.cpp
+ outstream/outstream.cpp
+ outstream/outstream_file.cpp
)
target_link_libraries(heaptrack_inject LINK_PRIVATE
#include "libheaptrack.h"
#include "util/config.h"
+#include "outstream/outstream.h"
#include <cstdlib>
#include <cstring>
isExiting = true;
});
- heaptrack_init(outputFileName, []() { overwrite_symbols(); }, [](FILE* out) { fprintf(out, "A\n"); },
+ heaptrack_init(outputFileName, []() { overwrite_symbols(); }, [](outStream* out) { fprintf(out, "A\n"); },
[]() {
bool do_shutdown = true;
dl_iterate_phdr(&iterate_phdrs, &do_shutdown);
#include "objectgraph.h"
#include "util/config.h"
#include "util/libunwind_config.h"
+#include "outstream/outstream_file.h"
/**
* uncomment this to get extended debug code for known pointers
*/
atomic<bool> s_forceCleanup{false};
-void writeVersion(FILE* out)
+void writeVersion(outStream* out)
{
fprintf(out, "v %x %x\n", HEAPTRACK_VERSION, HEAPTRACK_FILE_FORMAT_VERSION);
}
-void writeExe(FILE* out)
+void writeExe(outStream* out)
{
const int BUF_SIZE = 1023;
char buf[BUF_SIZE + 1];
}
}
-void writeCommandLine(FILE* out)
+void writeCommandLine(outStream* out)
{
fputc('X', out);
const int BUF_SIZE = 4096;
fputc('\n', out);
}
-void writeSystemInfo(FILE* out)
+void writeSystemInfo(outStream* out)
{
fprintf(out, "I %lx %lx\n", sysconf(_SC_PAGESIZE), sysconf(_SC_PHYS_PAGES));
}
-FILE* createFile(const char* fileName)
+outStream* createFile(const char* fileName)
{
string outputFileName;
if (fileName) {
if (outputFileName == "-" || outputFileName == "stdout") {
debugLog<VerboseOutput>("%s", "will write to stdout");
- return stdout;
+ return OpenStream<outStreamFILE, FILE*>(stdout);
} else if (outputFileName == "stderr") {
debugLog<VerboseOutput>("%s", "will write to stderr");
- return stderr;
+ return OpenStream<outStreamFILE, FILE*>(stderr);
}
if (outputFileName.empty()) {
boost::replace_all(outputFileName, "$$", to_string(getpid()));
- auto out = fopen(outputFileName.c_str(), "w");
+ auto out = OpenStream<outStreamFILE, const char*>(outputFileName.c_str());
debugLog<VerboseOutput>("will write to %s/%p\n", outputFileName.c_str(), out);
- // we do our own locking, this speeds up the writing significantly
- __fsetlocking(out, FSETLOCKING_BYCALLER);
return out;
}
});
});
- FILE* out = createFile(fileName);
+ outStream* out = createFile(fileName);
if (!out) {
fprintf(stderr, "ERROR: Failed to open heaptrack output file: %s\n", fileName);
struct LockedData
{
- LockedData(FILE* out, heaptrack_callback_t stopCallback)
+ LockedData(outStream* out, heaptrack_callback_t stopCallback)
: out(out)
, stopCallback(stopCallback)
{
}
if (out) {
- fclose(out);
+ delete out;
}
if (procSmaps) {
* Esp. in multi-threaded environments this is much faster
* to produce non-per-line-interleaved output.
*/
- FILE* out = nullptr;
+ outStream* out = nullptr;
/// /proc/self/smaps file stream to read address range data from
FILE* procSmaps = nullptr;
#endif
typedef void (*heaptrack_callback_t)();
-typedef void (*heaptrack_callback_initialized_t)(FILE*);
+class outStream;
+typedef void (*heaptrack_callback_initialized_t)(outStream*);
void heaptrack_init(const char* outputFileName, heaptrack_callback_t initCallbackBefore,
heaptrack_callback_initialized_t initCallbackAfter, heaptrack_callback_t stopCallback);
void *classId;
bool visited;
- void print(size_t gcCounter, FILE *out) {
+ void print(size_t gcCounter, outStream *out) {
// To make things more compact, if the node was already visited, don't
// traverse its children. It's enough to note that it is there.
if (visited) {
keyIt->second.children.push_back(&(valIt->second));
}
- void print(size_t gcCounter, FILE* out) {
+ void print(size_t gcCounter, outStream* out) {
auto it = m_graph.find(nullptr);
if (it == m_graph.end()) {
return;
--- /dev/null
+#include <cstdarg>
+#include <cstdio>
+#include <memory>
+#include <cassert>
+
+#include "outstream.h"
+
+int fprintf(outStream *stream, const char* format, ...) noexcept
+{
+ assert(stream && format);
+
+ // heaptrack creates only one outStream instance and provide its own locking, we are safe here
+ static std::unique_ptr<char[]> Buf;
+ static size_t BufSize = 0;
+ int tmpStrSize = 0;
+ va_list argptr;
+
+ for (;;) {
+ va_start(argptr, format);
+ tmpStrSize = std::vsnprintf(Buf.get(), BufSize, format, argptr);
+ va_end(argptr);
+ if (tmpStrSize > -1
+ && static_cast<size_t>(tmpStrSize) < BufSize) {
+ break;
+ } else if (tmpStrSize > -1) {
+ size_t needBufSize = tmpStrSize + sizeof('\0');
+ Buf.reset(new (std::nothrow) char[needBufSize]);
+ if (!Buf.get()) {
+ BufSize = 0;
+ errno = ENOMEM;
+ return -1;
+ }
+ BufSize = needBufSize;
+ } else {
+ errno = EIO;
+ return -1;
+ }
+ }
+
+ int ret = stream->Puts(Buf.get());
+ if (ret > 0) {
+ // make proper return code, since it different from fputs()
+ ret = tmpStrSize;
+ }
+
+ return ret;
+}
--- /dev/null
+#ifndef OUTSTREAM_H
+#define OUTSTREAM_H
+
+// Note, since heaptrack IO was designed in C-style, make sure,
+// that fprintf(), fputc() and fputs() don't throw exceptions
+// and return same value behavior as stdoi's fprintf(), fputc()
+// and fputs() respectively.
+
+// Make sure, that errno provide proper error code in Putc() and Puts(),
+// since heaptrack use it in writeError().
+
+class outStream {
+public:
+ outStream() = default;
+ virtual ~outStream() = default;
+ outStream(const outStream &) = delete;
+ outStream &operator = (const outStream &) = delete;
+
+ virtual int Putc(int Char) noexcept = 0;
+ virtual int Puts(const char *String) noexcept = 0;
+};
+
+template <class Implementation, class Initialization>
+outStream *OpenStream(Initialization InitValue) noexcept
+{
+ outStream *Result = nullptr;
+ try {
+ Result = new Implementation(InitValue);
+ } catch (...) {
+ Result = nullptr;
+ }
+ return Result;
+}
+
+int fprintf(outStream *stream, const char* format, ...) noexcept;
+
+inline int fputc(int ch, outStream *stream) noexcept
+{
+ return stream->Putc(ch);
+}
+
+inline int fputs(const char *str, outStream *stream) noexcept
+{
+ return stream->Puts(str);
+}
+
+#endif // OUTSTREAM_H
--- /dev/null
+#include <cassert>\r
+#include <stdexcept>\r
+#include <cstdio>\r
+#include <cstring>\r
+#include <stdio_ext.h>\r
+\r
+#include "outstream_file.h"\r
+\r
+outStreamFILE::outStreamFILE(const char *FileName) :\r
+ Stream_(nullptr),\r
+ Owner_(false)\r
+{\r
+ assert(FileName);\r
+\r
+ Stream_ = fopen(FileName, "w");\r
+ if (!Stream_) {\r
+ fprintf(stderr, "WARNING! Failed to open file %s: %s\n", FileName, strerror(errno));\r
+ throw std::runtime_error("Unable to open stream");\r
+ }\r
+\r
+ Owner_ = true;\r
+ // from heaptrack code: we do our own locking, this speeds up the writing significantly\r
+ __fsetlocking(Stream_, FSETLOCKING_BYCALLER);\r
+}\r
+\r
+outStreamFILE::outStreamFILE(FILE *FileStream) :\r
+ Stream_(nullptr),\r
+ Owner_(false)\r
+{\r
+ assert(FileStream);\r
+ Stream_ = FileStream;\r
+}\r
+\r
+outStreamFILE::~outStreamFILE()\r
+{\r
+ if (Owner_ && Stream_) {\r
+ fclose(Stream_);\r
+ }\r
+}\r
+\r
+int outStreamFILE::Putc(int Char) noexcept\r
+{\r
+ if (!Stream_) {\r
+ errno = EIO;\r
+ return -1;\r
+ }\r
+ return fputc(Char, Stream_);\r
+}\r
+\r
+int outStreamFILE::Puts(const char *String) noexcept\r
+{\r
+ if (!Stream_) {\r
+ errno = EIO;\r
+ return -1;\r
+ } else if (!String) {\r
+ errno = EINVAL;\r
+ return -1;\r
+ }\r
+ return fputs(String, Stream_);\r
+}\r
--- /dev/null
+#ifndef OUTSTREAMFILE_H
+#define OUTSTREAMFILE_H
+
+#include "outstream.h"
+
+class outStreamFILE final : public outStream
+{
+public:
+ outStreamFILE() = delete;
+ explicit outStreamFILE(const char *FileName);
+ explicit outStreamFILE(FILE *FileStream);
+ ~outStreamFILE();
+
+ outStreamFILE(const outStreamFILE &) = delete;
+ outStreamFILE &operator = (const outStreamFILE &) = delete;
+
+ outStreamFILE(outStreamFILE &&other) :
+ Stream_(other.Stream_),
+ Owner_(other.Owner_)
+ {
+ other.Stream_ = nullptr;
+ other.Owner_ = false;
+ }
+ outStreamFILE &operator = (outStreamFILE &&other) {
+ Stream_ = other.Stream_;
+ Owner_ = other.Owner_;
+ other.Stream_ = nullptr;
+ other.Owner_ = false;
+ return *this;
+ }
+
+ int Putc(int Char) noexcept override;
+ int Puts(const char *String) noexcept override;
+
+private:
+ FILE *Stream_;
+ bool Owner_;
+};
+
+#endif // OUTSTREAMFILE_H
#include <unordered_set>
#include "trace.h"
+#include "outstream/outstream.h"
#include "../profiler/src/stackentry.h"
*
* Unknown instruction pointers will be printed to @p out.
*/
- uint32_t index(const Trace& trace, FILE* out)
+ uint32_t index(const Trace& trace, outStream* out)
{
uint32_t index = 0;
TraceEdge* parent = &m_root;