1 /*
  2  * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  *
 23  */
 24 
 25 #include "precompiled.hpp"
 26 #include "cds/methodProfiler.hpp"
 27 #include "code/codeBlob.hpp"
 28 #include "code/nmethod.hpp"
 29 #include "logging/log.hpp"
 30 #include "runtime/frame.inline.hpp"
 31 #include "runtime/handshake.hpp"
 32 #include "runtime/javaThread.hpp"
 33 #include "runtime/registerMap.hpp"
 34 #include "runtime/task.hpp"
 35 
 36 volatile uint64_t MethodProfiler::_num_samples = 0;
 37 class MethodProfilerTask : public PeriodicTask {
 38 public:
 39   MethodProfilerTask()
 40     : PeriodicTask(RecordOptCompilationOrderInterval) {}
 41 
 42   void task() {
 43     MethodProfiler::tick();
 44   }
 45 };
 46 
 47 static MethodProfilerTask* _task;
 48 
 49 void MethodProfiler::initialize() {
 50   if (RecordOptCompilationOrder) {
 51     _task = new MethodProfilerTask();
 52     _task->enroll();
 53   }
 54 }
 55 
 56 class MethodProfilerClosure : public HandshakeClosure {
 57 public:
 58 MethodProfilerClosure()
 59     : HandshakeClosure("MethodProfiler") {}
 60 
 61   void do_thread(Thread* thread) {
 62     ResourceMark rm;
 63 
 64     if (thread != Thread::current()) {
 65       // Run by the VM thread - implication is that the target
 66       // thread was blocked or in native, i.e. not executing
 67       // Java code.
 68       return;
 69     }
 70 
 71     JavaThread* jt = JavaThread::cast(thread);
 72     if (!jt->has_last_Java_frame()) {
 73       return;
 74     }
 75 
 76     frame fr = jt->last_frame();
 77     if (fr.is_safepoint_blob_frame()) {
 78       RegisterMap rm(jt,
 79                      RegisterMap::UpdateMap::skip,
 80                      RegisterMap::ProcessFrames::skip,
 81                      RegisterMap::WalkContinuation::skip);
 82       fr = fr.sender(&rm);
 83     }
 84 
 85     if (!fr.is_compiled_frame()) {
 86       return;
 87     }
 88 
 89     nmethod* nm = fr.cb()->as_nmethod();
 90     if (!nm->is_compiled_by_c2()) {
 91       return;
 92     }
 93 
 94     log_trace(cds, profiling)("%s sampled %s::%s: " UINT64_FORMAT,
 95                               thread->name(),
 96                               nm->method()->method_holder()->name()->as_C_string(),
 97                               nm->method()->name()->as_C_string(),
 98                               nm->method_profiling_count());
 99 
100     // Found a C2 top frame that was just executing - sample it
101     nm->inc_method_profiling_count();
102     Atomic::inc(&MethodProfiler::_num_samples);
103   }
104 };
105 
106 void MethodProfiler::tick() {
107   MethodProfilerClosure cl;
108   Handshake::execute(&cl);
109 }
110 
111 static int method_hotness_compare(nmethod** a, nmethod** b) {
112   return (*b)->method_profiling_count() - (*a)->method_profiling_count();
113 }
114 
115 
116 GrowableArrayCHeap<nmethod*, mtClassShared>* MethodProfiler::sampled_nmethods() {
117   GrowableArrayCHeap<nmethod*, mtClassShared>* nmethods = new GrowableArrayCHeap<nmethod*, mtClassShared>();
118 
119   {
120     MutexLocker mu(CodeCache_lock, Mutex::_no_safepoint_check_flag);
121     NMethodIterator iter(NMethodIterator::not_unloading);
122     while(iter.next()) {
123       nmethod* nm = iter.method();
124       if (nm->is_compiled_by_c2() || nm->is_compiled_by_jvmci()) {
125         nmethods->append(nm);
126       }
127     }
128   }
129 
130   nmethods->sort(&method_hotness_compare);
131 
132   return nmethods;
133 }
134 
135 double MethodProfiler::hotness(nmethod* nm) {
136   return double(nm->method_profiling_count()) / double(_num_samples) * 100.0;
137 }
138 
139 uint64_t MethodProfiler::num_samples() {
140   return _num_samples;
141 }
142 
143 void MethodProfiler::process_method_hotness() {
144   if (_num_samples == 0) {
145     return;
146   }
147 
148   _task->disenroll();
149 
150   LogTarget(Debug, cds, profiling) lt;
151   if (lt.is_enabled()) {
152     ResourceMark rm;
153     double accumulated_sample_percent = 0.0;
154     int i = 0;
155     GrowableArrayCHeap<nmethod*, mtClassShared>* nmethods = sampled_nmethods();
156 
157     for (auto it = nmethods->begin(); it != nmethods->end(); ++it) {
158       ++i;
159       nmethod* nm = *it;
160       if (nm->method_profiling_count() == 0) {
161         break;
162       }
163       double sample_percent = hotness(nm);
164       accumulated_sample_percent += sample_percent;
165       log_info(cds, profiling)("%d (%.2f). %s::%s: " UINT64_FORMAT " (%.2f%%, %.2f%% accumulated)",
166                                i,
167                                double(i) / double(nmethods->length()) * 100.0,
168                                nm->method()->method_holder()->name()->as_C_string(),
169                                nm->method()->name()->as_C_string(),
170                                nm->method_profiling_count(),
171                                sample_percent,
172                                accumulated_sample_percent);
173     }
174 
175     delete nmethods;
176   }
177 }