Skip to content

8359110: Log accumulated GC and process CPU time upon VM exit #25779

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/hotspot/os/posix/os_posix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
#include <sys/resource.h>
#include <sys/socket.h>
#include <spawn.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/times.h>
#include <sys/types.h>
Expand Down Expand Up @@ -1601,6 +1602,17 @@ jlong os::elapsed_frequency() {

bool os::supports_vtime() { return true; }

double os::elapsed_process_vtime() {
struct rusage usage;
int retval = getrusage(RUSAGE_SELF, &usage);
if (retval == 0) {
return usage.ru_utime.tv_sec + usage.ru_stime.tv_sec +
(usage.ru_utime.tv_usec + usage.ru_stime.tv_usec) / (1000.0 * 1000.0);
} else {
return -1;
}
}

// Return the real, user, and system times in seconds from an
// arbitrary fixed point in the past.
bool os::getTimesSecs(double* process_real_time,
Expand Down
32 changes: 32 additions & 0 deletions src/hotspot/os/windows/os_windows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1209,6 +1209,38 @@ double os::elapsedVTime() {
}
}

double os::elapsed_process_vtime() {
FILETIME create;
FILETIME exit;
FILETIME kernel;
FILETIME user;

if (GetProcessTimes(GetCurrentProcess(), &create, &exit, &kernel, &user) == 0) {
return -1;
}

SYSTEMTIME user_total;
if (FileTimeToSystemTime(&user, &user_total) == 0) {
return -1;
}

SYSTEMTIME kernel_total;
if (FileTimeToSystemTime(&kernel, &kernel_total) == 0) {
return -1;
}

double user_seconds =
double(user_total.wHour) * 3600.0 + double(user_total.wMinute) * 60.0 +
double(user_total.wSecond) + double(user_total.wMilliseconds) / 1000.0;

double kernel_seconds = double(kernel_total.wHour) * 3600.0 +
double(kernel_total.wMinute) * 60.0 +
double(kernel_total.wSecond) +
double(kernel_total.wMilliseconds) / 1000.0;

return user_seconds + kernel_seconds;
}

jlong os::javaTimeMillis() {
FILETIME wt;
GetSystemTimeAsFileTime(&wt);
Expand Down
2 changes: 2 additions & 0 deletions src/hotspot/share/gc/g1/g1CollectedHeap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1469,6 +1469,8 @@ bool G1CollectedHeap::concurrent_mark_is_terminating() const {
}

void G1CollectedHeap::stop() {
CollectedHeap::stop();

// Stop all concurrent threads. We do this to make sure these threads
// do not continue to execute and access resources (e.g. logging)
// that are destroyed during shutdown.
Expand Down
46 changes: 46 additions & 0 deletions src/hotspot/share/gc/shared/collectedHeap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "gc/shared/gcWhen.hpp"
#include "gc/shared/memAllocator.hpp"
#include "gc/shared/stringdedup/stringDedup.hpp"
#include "gc/shared/stringdedup/stringDedupProcessor.hpp"
#include "gc/shared/tlab_globals.hpp"
#include "logging/log.hpp"
#include "logging/logStream.hpp"
Expand Down Expand Up @@ -66,6 +67,7 @@ size_t CollectedHeap::_lab_alignment_reserve = SIZE_MAX;
Klass* CollectedHeap::_filler_object_klass = nullptr;
size_t CollectedHeap::_filler_array_max_size = 0;
size_t CollectedHeap::_stack_chunk_max_size = 0;
jlong CollectedHeap::_vm_vtime = 0;

class GCLogMessage : public FormatBuffer<512> {};

Expand Down Expand Up @@ -200,6 +202,23 @@ void CollectedHeap::print_relative_to_gc(GCWhen::Type when) const {
}
}

class VCPUThreadClosure : public ThreadClosure {
private:
volatile jlong _vtime = 0;

public:
virtual void do_thread(Thread* thread) {
Atomic::add(&_vtime, os::thread_cpu_time(thread));
}
jlong vtime() { return _vtime; };
};

double CollectedHeap::elapsed_gc_vtime() const {
VCPUThreadClosure cl;
gc_threads_do(&cl);
return (double)(cl.vtime() + _vm_vtime) / NANOSECS_PER_SEC;
}

void CollectedHeap::print_before_gc() const {
print_relative_to_gc(GCWhen::BeforeGC);
}
Expand Down Expand Up @@ -603,6 +622,33 @@ void CollectedHeap::post_initialize() {
initialize_serviceability();
}

void CollectedHeap::log_gc_vtime() const {
LogTarget(Info, gc) out;
if (os::is_thread_cpu_time_supported() && out.is_enabled()) {
double process_vtime = os::elapsed_process_vtime();
double gc_vtime = elapsed_gc_vtime();
double string_dedup_vtime = UseStringDeduplication ? os::thread_cpu_time((Thread*)StringDedup::_processor->_thread) / NANOSECS_PER_SEC : 0;

if (process_vtime == -1 || gc_vtime == -1 || string_dedup_vtime == -1) {
return;
}

double usage;
if (gc_vtime > process_vtime || process_vtime == 0 || gc_vtime == 0) {
// This can happen e.g. for short running processes with
// low CPU utilization
usage = 0;
} else {
usage = 100 * (gc_vtime + string_dedup_vtime) / process_vtime;
}
out.print("GC CPU usage: %.2f%%", usage);
}
}

void CollectedHeap::stop() {
log_gc_vtime();
}

#ifndef PRODUCT

bool CollectedHeap::promotion_should_fail(volatile size_t* count) {
Expand Down
10 changes: 9 additions & 1 deletion src/hotspot/share/gc/shared/collectedHeap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ class CollectedHeap : public CHeapObj<mtGC> {
NOT_PRODUCT(volatile size_t _promotion_failure_alot_count;)
NOT_PRODUCT(volatile size_t _promotion_failure_alot_gc_number;)

static jlong _vm_vtime;

// Reason for current garbage collection. Should be set to
// a value reflecting no collection between collections.
GCCause::Cause _gc_cause;
Expand Down Expand Up @@ -240,12 +242,15 @@ class CollectedHeap : public CHeapObj<mtGC> {
virtual void post_initialize();

// Stop any onging concurrent work and prepare for exit.
virtual void stop() {}
virtual void stop();
Copy link
Member

Choose a reason for hiding this comment

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

I find it a bit odd to call log_gc_vtime inside this method, given the comment and its name. I wonder if print_tracing_info can be used instead, which is invoked before exit as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it makes sense. We want to log the total GC CPU time before exiting. The latest point we can do that is right before we terminate threads, which we do in when we call ConcurrentGCThread::stop. I am open for any suggestions to rename log_gc_vtime in case that would help.

Copy link
Member

Choose a reason for hiding this comment

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

I'd also prefer to see a small adjustment here. With this change all GCs have to add a back call to CollectedHeap::stop(). I think it makes the could a tiny bit less clean.

I wonder if an alternative could be to split CollectedHeap::stop() into two functions:

virtual void CollectedHeap::stop() = 0; // Now pure virtual

void CollectedHeap::before_exit() {
  // Log GC CPU usage.
  log_gc_vtime();

  // Stop any on-going concurrent work and prepare for exit.
  stop();
}

And revert the added calls to CollectedHeap::stop().


// Stop and resume concurrent GC threads interfering with safepoint operations
virtual void safepoint_synchronize_begin() {}
virtual void safepoint_synchronize_end() {}

static jlong vm_vtime();
static void add_vm_vtime(jlong time);

void initialize_reserved_region(const ReservedHeapSpace& rs);

virtual size_t capacity() const = 0;
Expand Down Expand Up @@ -455,6 +460,9 @@ class CollectedHeap : public CHeapObj<mtGC> {
// Default implementation does nothing.
virtual void print_tracing_info() const = 0;

double elapsed_gc_vtime() const;
void log_gc_vtime() const;

void print_before_gc() const;
void print_after_gc() const;

Expand Down
4 changes: 4 additions & 0 deletions src/hotspot/share/gc/shared/collectedHeap.inline.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,8 @@ inline oop CollectedHeap::class_allocate(Klass* klass, size_t size, TRAPS) {
return allocator.allocate();
}

inline void CollectedHeap::add_vm_vtime(jlong time) {
_vm_vtime += time;
}

#endif // SHARE_GC_SHARED_COLLECTEDHEAP_INLINE_HPP
3 changes: 3 additions & 0 deletions src/hotspot/share/gc/shared/stringdedup/stringDedup.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,13 @@
// For additional information on string deduplication, please see JEP 192,
// https://openjdk.org/jeps/192

#include "gc/shared/collectedHeap.hpp"
#include "memory/allocation.hpp"
#include "memory/allStatic.hpp"
#include "oops/oopsHierarchy.hpp"
#include "utilities/globalDefinitions.hpp"

class CollectedHeap;
class Klass;
class StringDedupThread;
class ThreadClosure;
Expand All @@ -115,6 +117,7 @@ class ThreadClosure;
// feature. Other functions in the StringDedup class are called where
// needed, without requiring GC-specific code.
class StringDedup : public AllStatic {
friend class CollectedHeap;
friend class StringDedupThread;

class Config;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
#ifndef SHARE_GC_SHARED_STRINGDEDUP_STRINGDEDUPPROCESSOR_HPP
#define SHARE_GC_SHARED_STRINGDEDUP_STRINGDEDUPPROCESSOR_HPP

#include "gc/shared/collectedHeap.hpp"
#include "gc/shared/stringdedup/stringDedup.hpp"
#include "memory/allocation.hpp"
#include "utilities/macros.hpp"

class CollectedHeap;
class JavaThread;
class OopStorage;

Expand All @@ -42,6 +44,8 @@ class OopStorage;
// incremental operations for resizing and for removing dead entries, so
// safepoint checks can be performed between steps in those operations.
class StringDedup::Processor : public CHeapObj<mtGC> {
friend class CollectedHeap;

Processor();
~Processor() = default;

Expand Down
44 changes: 44 additions & 0 deletions src/hotspot/share/gc/shared/vtimeScope.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/

#ifndef SHARE_GC_SHARED_VTIMESCOPE_HPP
#define SHARE_GC_SHARED_VTIMESCOPE_HPP

#include "memory/allocation.hpp"

class VMThread;

class VTimeScope : public StackObj {
private:
jlong _start;
bool _enabled;
bool _is_gc_operation;
Thread* _thread;

public:
VTimeScope(VMThread* thread, bool is_gc_operation);
~VTimeScope();
};

#endif // SHARE_GC_SHARED_VTIMESCOPE_HPP
56 changes: 56 additions & 0 deletions src/hotspot/share/gc/shared/vtimeScope.inline.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/

#include "gc/shared/vtimeScope.hpp"

#include "gc/shared/collectedHeap.inline.hpp"
#include "memory/universe.hpp"
#include "runtime/cpuTimeCounters.hpp"
#include "runtime/os.hpp"
#include "runtime/vmThread.hpp"

inline VTimeScope::VTimeScope(VMThread* thread, bool is_gc_operation)
: _start(0),
_enabled(os::is_thread_cpu_time_supported()),
_is_gc_operation(is_gc_operation),
_thread(thread) {
if (_is_gc_operation && _enabled) {
_start = os::thread_cpu_time(_thread);
}
}

inline VTimeScope::~VTimeScope() {
if (!_enabled) return;

jlong end = (_is_gc_operation || UsePerfData) ? os::thread_cpu_time(_thread) : 0;

if (_is_gc_operation) {
jlong duration = end > _start ? end - _start : 0;
Universe::heap()->add_vm_vtime(duration);
}

if (UsePerfData) {
CPUTimeCounters::get_instance()->update_counter(CPUTimeGroups::CPUTimeType::vm, end);
}
}
2 changes: 2 additions & 0 deletions src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2202,6 +2202,8 @@ uint ShenandoahHeap::max_workers() {
}

void ShenandoahHeap::stop() {
CollectedHeap::stop();

// The shutdown sequence should be able to terminate when GC is running.

// Step 0. Notify policy to disable event recording and prevent visiting gc threads during shutdown
Expand Down
3 changes: 3 additions & 0 deletions src/hotspot/share/gc/z/zCollectedHeap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
*/

#include "classfile/classLoaderData.hpp"
#include "gc/shared/collectedHeap.inline.hpp"
#include "gc/shared/gcHeapSummary.hpp"
#include "gc/shared/gcLogPrecious.hpp"
#include "gc/shared/suspendibleThreadSet.hpp"
Expand Down Expand Up @@ -104,6 +105,8 @@ class ZStopConcurrentGCThreadClosure : public ThreadClosure {
};

void ZCollectedHeap::stop() {
CollectedHeap::stop();

log_info_p(gc, exit)("Stopping ZGC");
ZAbort::abort();
ZStopConcurrentGCThreadClosure cl;
Expand Down
4 changes: 4 additions & 0 deletions src/hotspot/share/runtime/cpuTimeCounters.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
#include "runtime/perfData.hpp"
#include "runtime/perfDataTypes.hpp"

class VTimeScope;

class CPUTimeGroups : public AllStatic {
public:
enum class CPUTimeType {
Expand All @@ -51,6 +53,8 @@ class CPUTimeGroups : public AllStatic {
};

class CPUTimeCounters: public CHeapObj<mtServiceability> {
friend class VTimeScope;

private:
// CPUTimeCounters is a singleton instance.
CPUTimeCounters();
Expand Down
1 change: 1 addition & 0 deletions src/hotspot/share/runtime/os.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ class os: AllStatic {
// returns the elapsed virtual time for the current thread.
static bool supports_vtime();
static double elapsedVTime();
static double elapsed_process_vtime();

// Return current local time in a string (YYYY-MM-DD HH:MM:SS).
// It is MT safe, but not async-safe, as reading time zone
Expand Down
Loading