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 "cds/methodProfiler.hpp"
 26 #include "code/codeBlob.hpp"
 27 #include "code/nmethod.hpp"
 28 #include "logging/log.hpp"
 29 #include "runtime/frame.inline.hpp"
 30 #include "runtime/handshake.hpp"
 31 #include "runtime/javaThread.hpp"
 32 #include "runtime/registerMap.hpp"
 33 #include "runtime/task.hpp"
 34 
 35 volatile uint64_t MethodProfiler::_num_samples = 0;
 36 class MethodProfilerTask : public PeriodicTask {
 37 public:
 38   MethodProfilerTask()
 39     : PeriodicTask(RecordOptCompilationOrderInterval) {}
 40 
 41   void task() {
 42     MethodProfiler::tick();
 43   }
 44 };
 45 
 46 static MethodProfilerTask* _task;
 47 
 48 void MethodProfiler::initialize() {
 49   if (RecordOptCompilationOrder) {
 50     _task = new MethodProfilerTask();
 51     _task->enroll();
 52   }
 53 }
 54 
 55 class MethodProfilerClosure : public HandshakeClosure {
 56 public:
 57 MethodProfilerClosure()
 58     : HandshakeClosure("MethodProfiler") {}
 59 
 60   void do_thread(Thread* thread) {
 61     ResourceMark rm;
 62 
 63     if (thread != Thread::current()) {
 64       // Run by the VM thread - implication is that the target
 65       // thread was blocked or in native, i.e. not executing
 66       // Java code.
 67       return;
 68     }
 69 
 70     JavaThread* jt = JavaThread::cast(thread);
 71     if (!jt->has_last_Java_frame()) {
 72       return;
 73     }
 74 
 75     frame fr = jt->last_frame();
 76     if (fr.is_safepoint_blob_frame()) {
 77       RegisterMap rm(jt,
 78                      RegisterMap::UpdateMap::skip,
 79                      RegisterMap::ProcessFrames::skip,
 80                      RegisterMap::WalkContinuation::skip);
 81       fr = fr.sender(&rm);
 82     }
 83 
 84     if (!fr.is_compiled_frame()) {
 85       return;
 86     }
 87 
 88     nmethod* nm = fr.cb()->as_nmethod();
 89     if (!nm->is_compiled_by_c2()) {
 90       return;
 91     }
 92 
 93     log_trace(cds, profiling)("%s sampled %s::%s: " UINT64_FORMAT,
 94                               thread->name(),
 95                               nm->method()->method_holder()->name()->as_C_string(),
 96                               nm->method()->name()->as_C_string(),
 97                               nm->method_profiling_count());
 98 
 99     // Found a C2 top frame that was just executing - sample it
100     nm->inc_method_profiling_count();
101     Atomic::inc(&MethodProfiler::_num_samples);
102   }
103 };
104 
105 void MethodProfiler::tick() {
106   MethodProfilerClosure cl;
107   Handshake::execute(&cl);
108 }
109 
110 static int method_hotness_compare(nmethod** a, nmethod** b) {
111   return (*b)->method_profiling_count() - (*a)->method_profiling_count();
112 }
113 
114 
115 GrowableArrayCHeap<nmethod*, mtClassShared>* MethodProfiler::sampled_nmethods() {
116   GrowableArrayCHeap<nmethod*, mtClassShared>* nmethods = new GrowableArrayCHeap<nmethod*, mtClassShared>();
117 
118   {
119     MutexLocker mu(CodeCache_lock, Mutex::_no_safepoint_check_flag);
120     NMethodIterator iter(NMethodIterator::not_unloading);
121     while(iter.next()) {
122       nmethod* nm = iter.method();
123       if (nm->is_compiled_by_c2() || nm->is_compiled_by_jvmci()) {
124         nmethods->append(nm);
125       }
126     }
127   }
128 
129   nmethods->sort(&method_hotness_compare);
130 
131   return nmethods;
132 }
133 
134 double MethodProfiler::hotness(nmethod* nm) {
135   return double(nm->method_profiling_count()) / double(_num_samples) * 100.0;
136 }
137 
138 uint64_t MethodProfiler::num_samples() {
139   return _num_samples;
140 }
141 
142 void MethodProfiler::process_method_hotness() {
143   if (_num_samples == 0) {
144     return;
145   }
146 
147   _task->disenroll();
148 
149   LogTarget(Debug, cds, profiling) lt;
150   if (lt.is_enabled()) {
151     ResourceMark rm;
152     double accumulated_sample_percent = 0.0;
153     int i = 0;
154     GrowableArrayCHeap<nmethod*, mtClassShared>* nmethods = sampled_nmethods();
155 
156     for (auto it = nmethods->begin(); it != nmethods->end(); ++it) {
157       ++i;
158       nmethod* nm = *it;
159       if (nm->method_profiling_count() == 0) {
160         break;
161       }
162       double sample_percent = hotness(nm);
163       accumulated_sample_percent += sample_percent;
164       log_info(cds, profiling)("%d (%.2f). %s::%s: " UINT64_FORMAT " (%.2f%%, %.2f%% accumulated)",
165                                i,
166                                double(i) / double(nmethods->length()) * 100.0,
167                                nm->method()->method_holder()->name()->as_C_string(),
168                                nm->method()->name()->as_C_string(),
169                                nm->method_profiling_count(),
170                                sample_percent,
171                                accumulated_sample_percent);
172     }
173 
174     delete nmethods;
175   }
176 }