Skip to content

Commit

Permalink
8314021: HeapDump: Optimize segmented heap file merging phase
Browse files Browse the repository at this point in the history
Reviewed-by: amenkov, kevinw
  • Loading branch information
y1yang0 committed Sep 19, 2023
1 parent f226ceb commit 3760a04
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 8 deletions.
8 changes: 8 additions & 0 deletions src/hotspot/os/linux/os_linux.cpp
Expand Up @@ -89,6 +89,7 @@
# include <sys/mman.h>
# include <sys/stat.h>
# include <sys/select.h>
# include <sys/sendfile.h>
# include <pthread.h>
# include <signal.h>
# include <endian.h>
Expand Down Expand Up @@ -4368,6 +4369,13 @@ jlong os::Linux::fast_thread_cpu_time(clockid_t clockid) {
return (tp.tv_sec * NANOSECS_PER_SEC) + tp.tv_nsec;
}

// copy data between two file descriptor within the kernel
// the number of bytes written to out_fd is returned if transfer was successful
// otherwise, returns -1 that implies an error
jlong os::Linux::sendfile(int out_fd, int in_fd, jlong* offset, jlong count) {
return sendfile64(out_fd, in_fd, (off64_t*)offset, (size_t)count);
}

// Determine if the vmid is the parent pid for a child in a PID namespace.
// Return the namespace pid if so, otherwise -1.
int os::Linux::get_namespace_pid(int vmid) {
Expand Down
2 changes: 2 additions & 0 deletions src/hotspot/os/linux/os_linux.hpp
Expand Up @@ -167,6 +167,8 @@ class os::Linux {

static jlong fast_thread_cpu_time(clockid_t clockid);

static jlong sendfile(int out_fd, int in_fd, jlong* offset, jlong count);

// Determine if the vmid is the parent pid for a child in a PID namespace.
// Return the namespace pid if so, otherwise -1.
static int get_namespace_pid(int vmid);
Expand Down
72 changes: 64 additions & 8 deletions src/hotspot/share/services/heapDumper.cpp
Expand Up @@ -62,6 +62,9 @@
#include "utilities/checkedCast.hpp"
#include "utilities/macros.hpp"
#include "utilities/ostream.hpp"
#ifdef LINUX
#include "os_linux.hpp"
#endif

/*
* HPROF binary format - description copied from:
Expand Down Expand Up @@ -630,6 +633,7 @@ class DumpWriter : public AbstractDumpWriter {
AbstractCompressor* compressor() { return _compressor; }
void set_compressor(AbstractCompressor* p) { _compressor = p; }
bool is_overwrite() const { return _writer->is_overwrite(); }
int get_fd() const { return _writer->get_fd(); }

void flush() override;
};
Expand Down Expand Up @@ -1533,6 +1537,7 @@ class DumpMerger : public StackObj {
private:
void merge_file(char* path);
void merge_done();
void set_error(const char* msg);

public:
DumpMerger(const char* path, DumpWriter* writer, int dump_seq) :
Expand All @@ -1553,15 +1558,62 @@ void DumpMerger::merge_done() {
_dump_seq = 0; //reset
}

void DumpMerger::set_error(const char* msg) {
assert(msg != nullptr, "sanity check");
log_error(heapdump)("%s (file: %s)", msg, _path);
_writer->set_error(msg);
_has_error = true;
}

#ifdef LINUX
// Merge segmented heap files via sendfile, it's more efficient than the
// read+write combination, which would require transferring data to and from
// user space.
void DumpMerger::merge_file(char* path) {
assert(!SafepointSynchronize::is_at_safepoint(), "merging happens outside safepoint");
TraceTime timer("Merge segmented heap file directly", TRACETIME_LOG(Info, heapdump));

int segment_fd = os::open(path, O_RDONLY, 0);
if (segment_fd == -1) {
set_error("Can not open segmented heap file during merging");
return;
}

struct stat st;
if (os::stat(path, &st) != 0) {
::close(segment_fd);
set_error("Can not get segmented heap file size during merging");
return;
}

// A successful call to sendfile may write fewer bytes than requested; the
// caller should be prepared to retry the call if there were unsent bytes.
jlong offset = 0;
while (offset < st.st_size) {
int ret = os::Linux::sendfile(_writer->get_fd(), segment_fd, &offset, st.st_size);
if (ret == -1) {
::close(segment_fd);
set_error("Failed to merge segmented heap file");
return;
}
}

// As sendfile variant does not call the write method of the global writer,
// bytes_written is also incorrect for this variant, we need to explicitly
// accumulate bytes_written for the global writer in this case
julong accum = _writer->bytes_written() + st.st_size;
_writer->set_bytes_written(accum);
::close(segment_fd);
}
#else
// Generic implementation using read+write
void DumpMerger::merge_file(char* path) {
assert(!SafepointSynchronize::is_at_safepoint(), "merging happens outside safepoint");
TraceTime timer("Merge segmented heap file", TRACETIME_LOG(Info, heapdump));

fileStream segment_fs(path, "rb");
if (!segment_fs.is_open()) {
log_error(heapdump)("Can not open segmented heap file %s during merging", path);
_writer->set_error("Can not open segmented heap file during merging");
_has_error = true;
set_error("Can not open segmented heap file during merging");
return;
}

Expand All @@ -1575,12 +1627,10 @@ void DumpMerger::merge_file(char* path) {

_writer->flush();
if (segment_fs.fileSize() != total) {
log_error(heapdump)("Merged heap dump %s is incomplete, expect %ld but read " JLONG_FORMAT " bytes",
path, segment_fs.fileSize(), total);
_writer->set_error("Merged heap dump is incomplete");
_has_error = true;
set_error("Merged heap dump is incomplete");
}
}
#endif

void DumpMerger::do_merge() {
assert(!SafepointSynchronize::is_at_safepoint(), "merging happens outside safepoint");
Expand All @@ -1591,14 +1641,16 @@ void DumpMerger::do_merge() {
AbstractCompressor* saved_compressor = _writer->compressor();
_writer->set_compressor(nullptr);

// merge segmented heap file and remove it anyway
// Merge the content of the remaining files into base file. Regardless of whether
// the merge process is successful or not, these segmented files will be deleted.
char path[JVM_MAXPATHLEN];
for (int i = 0; i < _dump_seq; i++) {
memset(path, 0, JVM_MAXPATHLEN);
os::snprintf(path, JVM_MAXPATHLEN, "%s.p%d", _path, i);
if (!_has_error) {
merge_file(path);
}
// Delete selected segmented heap file nevertheless
remove(path);
}

Expand Down Expand Up @@ -2012,6 +2064,7 @@ DumpWriter* VM_HeapDumper::create_local_writer() {

// generate segmented heap file path
const char* base_path = writer()->get_file_path();
// share global compressor, local DumpWriter is not responsible for its life cycle
AbstractCompressor* compressor = writer()->compressor();
int seq = Atomic::fetch_then_add(&_dump_seq, 1);
os::snprintf(path, JVM_MAXPATHLEN, "%s.p%d", base_path, seq);
Expand Down Expand Up @@ -2256,6 +2309,9 @@ int HeapDumper::dump(const char* path, outputStream* out, int compression, bool
}
}

if (compressor != nullptr) {
delete compressor;
}
return (writer.error() == nullptr) ? 0 : -1;
}

Expand Down
2 changes: 2 additions & 0 deletions src/hotspot/share/services/heapDumperCompression.hpp
Expand Up @@ -78,6 +78,8 @@ class FileWriter : public AbstractWriter {
const char* get_file_path() { return _path; }

bool is_overwrite() const { return _overwrite; }

int get_fd() const {return _fd; }
};


Expand Down

1 comment on commit 3760a04

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.