1 // Copyright (C) 2018 The Android Open Source Project 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 16 #include <android-base/logging.h> 17 18 #include <atomic> 19 #include <fstream> 20 #include <iostream> 21 #include <istream> 22 #include <iomanip> 23 #include <jni.h> 24 #include <jvmti.h> 25 #include <limits> 26 #include <map> 27 #include <memory> 28 #include <mutex> 29 #include <string> 30 #include <sstream> 31 #include <vector> 32 33 namespace tifast { 34 35 namespace { 36 37 // Special art ti-version number. We will use this as a fallback if we cannot get a regular JVMTI 38 // env. 39 static constexpr jint kArtTiVersion = JVMTI_VERSION_1_2 | 0x40000000; 40 41 // jthread is a typedef of jobject so we use this to allow the templates to distinguish them. 42 struct jthreadContainer { jthread thread; }; 43 // jlocation is a typedef of jlong so use this to distinguish the less common jlong. 44 struct jlongContainer { jlong val; }; 45 46 static void DeleteLocalRef(JNIEnv* env, jobject obj) { 47 if (obj != nullptr && env != nullptr) { 48 env->DeleteLocalRef(obj); 49 } 50 } 51 52 class ScopedThreadInfo { 53 public: 54 ScopedThreadInfo(jvmtiEnv* jvmtienv, JNIEnv* env, jthread thread) 55 : jvmtienv_(jvmtienv), env_(env), free_name_(false) { 56 if (thread == nullptr) { 57 info_.name = const_cast<char*>("<NULLPTR>"); 58 } else if (jvmtienv->GetThreadInfo(thread, &info_) != JVMTI_ERROR_NONE) { 59 info_.name = const_cast<char*>("<UNKNOWN THREAD>"); 60 } else { 61 free_name_ = true; 62 } 63 } 64 65 ~ScopedThreadInfo() { 66 if (free_name_) { 67 jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(info_.name)); 68 } 69 DeleteLocalRef(env_, info_.thread_group); 70 DeleteLocalRef(env_, info_.context_class_loader); 71 } 72 73 const char* GetName() const { 74 return info_.name; 75 } 76 77 private: 78 jvmtiEnv* jvmtienv_; 79 JNIEnv* env_; 80 bool free_name_; 81 jvmtiThreadInfo info_{}; 82 }; 83 84 class ScopedClassInfo { 85 public: 86 ScopedClassInfo(jvmtiEnv* jvmtienv, jclass c) : jvmtienv_(jvmtienv), class_(c) {} 87 88 ~ScopedClassInfo() { 89 if (class_ != nullptr) { 90 jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_)); 91 jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(generic_)); 92 jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(file_)); 93 jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(debug_ext_)); 94 } 95 } 96 97 bool Init(bool get_generic = true) { 98 if (class_ == nullptr) { 99 name_ = const_cast<char*>("<NONE>"); 100 generic_ = const_cast<char*>("<NONE>"); 101 return true; 102 } else { 103 jvmtiError ret1 = jvmtienv_->GetSourceFileName(class_, &file_); 104 jvmtiError ret2 = jvmtienv_->GetSourceDebugExtension(class_, &debug_ext_); 105 char** gen_ptr = &generic_; 106 if (!get_generic) { 107 generic_ = nullptr; 108 gen_ptr = nullptr; 109 } 110 return jvmtienv_->GetClassSignature(class_, &name_, gen_ptr) == JVMTI_ERROR_NONE && 111 ret1 != JVMTI_ERROR_MUST_POSSESS_CAPABILITY && 112 ret1 != JVMTI_ERROR_INVALID_CLASS && 113 ret2 != JVMTI_ERROR_MUST_POSSESS_CAPABILITY && 114 ret2 != JVMTI_ERROR_INVALID_CLASS; 115 } 116 } 117 118 jclass GetClass() const { 119 return class_; 120 } 121 122 const char* GetName() const { 123 return name_; 124 } 125 126 const char* GetGeneric() const { 127 return generic_; 128 } 129 130 const char* GetSourceDebugExtension() const { 131 if (debug_ext_ == nullptr) { 132 return "<UNKNOWN_SOURCE_DEBUG_EXTENSION>"; 133 } else { 134 return debug_ext_; 135 } 136 } 137 const char* GetSourceFileName() const { 138 if (file_ == nullptr) { 139 return "<UNKNOWN_FILE>"; 140 } else { 141 return file_; 142 } 143 } 144 145 private: 146 jvmtiEnv* jvmtienv_; 147 jclass class_; 148 char* name_ = nullptr; 149 char* generic_ = nullptr; 150 char* file_ = nullptr; 151 char* debug_ext_ = nullptr; 152 153 friend std::ostream& operator<<(std::ostream &os, ScopedClassInfo const& m); 154 }; 155 156 class ScopedMethodInfo { 157 public: 158 ScopedMethodInfo(jvmtiEnv* jvmtienv, JNIEnv* env, jmethodID m) 159 : jvmtienv_(jvmtienv), env_(env), method_(m) {} 160 161 ~ScopedMethodInfo() { 162 DeleteLocalRef(env_, declaring_class_); 163 jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_)); 164 jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(signature_)); 165 jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(generic_)); 166 } 167 168 bool Init(bool get_generic = true) { 169 if (jvmtienv_->GetMethodDeclaringClass(method_, &declaring_class_) != JVMTI_ERROR_NONE) { 170 return false; 171 } 172 class_info_.reset(new ScopedClassInfo(jvmtienv_, declaring_class_)); 173 jint nlines; 174 jvmtiLineNumberEntry* lines; 175 jvmtiError err = jvmtienv_->GetLineNumberTable(method_, &nlines, &lines); 176 if (err == JVMTI_ERROR_NONE) { 177 if (nlines > 0) { 178 first_line_ = lines[0].line_number; 179 } 180 jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(lines)); 181 } else if (err != JVMTI_ERROR_ABSENT_INFORMATION && 182 err != JVMTI_ERROR_NATIVE_METHOD) { 183 return false; 184 } 185 return class_info_->Init(get_generic) && 186 (jvmtienv_->GetMethodName(method_, &name_, &signature_, &generic_) == JVMTI_ERROR_NONE); 187 } 188 189 const ScopedClassInfo& GetDeclaringClassInfo() const { 190 return *class_info_; 191 } 192 193 jclass GetDeclaringClass() const { 194 return declaring_class_; 195 } 196 197 const char* GetName() const { 198 return name_; 199 } 200 201 const char* GetSignature() const { 202 return signature_; 203 } 204 205 const char* GetGeneric() const { 206 return generic_; 207 } 208 209 jint GetFirstLine() const { 210 return first_line_; 211 } 212 213 private: 214 jvmtiEnv* jvmtienv_; 215 JNIEnv* env_; 216 jmethodID method_; 217 jclass declaring_class_ = nullptr; 218 std::unique_ptr<ScopedClassInfo> class_info_; 219 char* name_ = nullptr; 220 char* signature_ = nullptr; 221 char* generic_ = nullptr; 222 jint first_line_ = -1; 223 }; 224 225 std::ostream& operator<<(std::ostream &os, ScopedClassInfo const& c) { 226 const char* generic = c.GetGeneric(); 227 if (generic != nullptr) { 228 return os << c.GetName() << "<" << generic << ">" << " file: " << c.GetSourceFileName(); 229 } else { 230 return os << c.GetName() << " file: " << c.GetSourceFileName(); 231 } 232 } 233 234 class LockedStream { 235 public: 236 explicit LockedStream(const std::string& filepath) { 237 stream_.open(filepath, std::ofstream::out); 238 if (!stream_.is_open()) { 239 LOG(ERROR) << "====== JVMTI FAILED TO OPEN LOG FILE"; 240 } 241 } 242 ~LockedStream() { 243 stream_.close(); 244 } 245 void Write(const std::string& str) { 246 stream_ << str; 247 stream_.flush(); 248 } 249 private: 250 std::ofstream stream_; 251 }; 252 253 static LockedStream* stream = nullptr; 254 255 class UniqueStringTable { 256 public: 257 UniqueStringTable() = default; 258 ~UniqueStringTable() = default; 259 std::string Intern(const std::string& header, const std::string& key) { 260 if (map_.find(key) == map_.end()) { 261 map_[key] = next_index_; 262 // Emit definition line. E.g., =123,string 263 stream->Write(header + std::to_string(next_index_) + "," + key + "\n"); 264 ++next_index_; 265 } 266 return std::to_string(map_[key]); 267 } 268 private: 269 int32_t next_index_; 270 std::map<std::string, int32_t> map_; 271 }; 272 273 static UniqueStringTable* string_table = nullptr; 274 275 // Formatter for the thread, type, and size of an allocation. 276 static std::string formatAllocation(jvmtiEnv* jvmti, 277 JNIEnv* jni, 278 jthreadContainer thr, 279 jclass klass, 280 jlongContainer size) { 281 ScopedThreadInfo sti(jvmti, jni, thr.thread); 282 std::ostringstream allocation; 283 allocation << "jthread[" << sti.GetName() << "]"; 284 ScopedClassInfo sci(jvmti, klass); 285 if (sci.Init(/*get_generic=*/false)) { 286 allocation << ", jclass[" << sci << "]"; 287 } else { 288 allocation << ", jclass[TYPE UNKNOWN]"; 289 } 290 allocation << ", size[" << size.val << ", hex: 0x" << std::hex << size.val << "]"; 291 return string_table->Intern("+", allocation.str()); 292 } 293 294 // Formatter for a method entry on a call stack. 295 static std::string formatMethod(jvmtiEnv* jvmti, JNIEnv* jni, jmethodID method_id) { 296 ScopedMethodInfo smi(jvmti, jni, method_id); 297 std::string method; 298 if (smi.Init(/*get_generic=*/false)) { 299 method = std::string(smi.GetDeclaringClassInfo().GetName()) + 300 "::" + smi.GetName() + smi.GetSignature(); 301 } else { 302 method = "ERROR"; 303 } 304 return string_table->Intern("+", method); 305 } 306 307 static int sampling_rate; 308 static int stack_depth_limit; 309 310 static void JNICALL logVMObjectAlloc(jvmtiEnv* jvmti, 311 JNIEnv* jni, 312 jthread thread, 313 jobject obj ATTRIBUTE_UNUSED, 314 jclass klass, 315 jlong size) { 316 // Sample only once out of sampling_rate tries, and prevent recursive allocation tracking, 317 static thread_local int sample_countdown = sampling_rate; 318 --sample_countdown; 319 if (sample_countdown != 0) { 320 return; 321 } 322 323 // Guard accesses to string table and emission. 324 static std::mutex mutex; 325 std::lock_guard<std::mutex> lg(mutex); 326 327 std::string record = 328 formatAllocation(jvmti, 329 jni, 330 jthreadContainer{.thread = thread}, 331 klass, 332 jlongContainer{.val = size}); 333 334 std::unique_ptr<jvmtiFrameInfo[]> stack_frames(new jvmtiFrameInfo[stack_depth_limit]); 335 jint stack_depth; 336 jvmtiError err = jvmti->GetStackTrace(thread, 337 0, 338 stack_depth_limit, 339 stack_frames.get(), 340 &stack_depth); 341 if (err == JVMTI_ERROR_NONE) { 342 // Emit stack frames in order from deepest in the stack to most recent. 343 // This simplifies post-collection processing. 344 for (int i = stack_depth - 1; i >= 0; --i) { 345 record += ";" + formatMethod(jvmti, jni, stack_frames[i].method); 346 } 347 } 348 stream->Write(string_table->Intern("=", record) + "\n"); 349 350 sample_countdown = sampling_rate; 351 } 352 353 static jvmtiEventCallbacks kLogCallbacks { 354 .VMObjectAlloc = logVMObjectAlloc, 355 }; 356 357 static jint SetupJvmtiEnv(JavaVM* vm, jvmtiEnv** jvmti) { 358 jint res = vm->GetEnv(reinterpret_cast<void**>(jvmti), JVMTI_VERSION_1_1); 359 if (res != JNI_OK || *jvmti == nullptr) { 360 LOG(ERROR) << "Unable to access JVMTI, error code " << res; 361 return vm->GetEnv(reinterpret_cast<void**>(jvmti), kArtTiVersion); 362 } 363 return res; 364 } 365 366 } // namespace 367 368 static jvmtiError SetupCapabilities(jvmtiEnv* jvmti) { 369 jvmtiCapabilities caps{}; 370 caps.can_generate_vm_object_alloc_events = 1; 371 caps.can_get_line_numbers = 1; 372 caps.can_get_source_file_name = 1; 373 caps.can_get_source_debug_extension = 1; 374 return jvmti->AddCapabilities(&caps); 375 } 376 377 static bool ProcessOptions(std::string options) { 378 std::string output_file_path; 379 if (options.empty()) { 380 static constexpr int kDefaultSamplingRate = 10; 381 static constexpr int kDefaultStackDepthLimit = 50; 382 static constexpr const char* kDefaultOutputFilePath = "/data/local/tmp/logstream.txt"; 383 384 sampling_rate = kDefaultSamplingRate; 385 stack_depth_limit = kDefaultStackDepthLimit; 386 output_file_path = kDefaultOutputFilePath; 387 } else { 388 // options string should contain "sampling_rate,stack_depth_limit,output_file_path". 389 size_t comma_pos = options.find(','); 390 if (comma_pos == std::string::npos) { 391 return false; 392 } 393 sampling_rate = std::stoi(options.substr(0, comma_pos)); 394 options = options.substr(comma_pos + 1); 395 comma_pos = options.find(','); 396 if (comma_pos == std::string::npos) { 397 return false; 398 } 399 stack_depth_limit = std::stoi(options.substr(0, comma_pos)); 400 output_file_path = options.substr(comma_pos + 1); 401 } 402 LOG(INFO) << "Starting allocation tracing: sampling_rate=" << sampling_rate 403 << ", stack_depth_limit=" << stack_depth_limit 404 << ", output_file_path=" << output_file_path; 405 stream = new LockedStream(output_file_path); 406 407 return true; 408 } 409 410 static jint AgentStart(JavaVM* vm, 411 char* options, 412 void* reserved ATTRIBUTE_UNUSED) { 413 // Handle the sampling rate, depth limit, and output path, if set. 414 if (!ProcessOptions(options)) { 415 return JNI_ERR; 416 } 417 418 // Create the environment. 419 jvmtiEnv* jvmti = nullptr; 420 if (SetupJvmtiEnv(vm, &jvmti) != JNI_OK) { 421 LOG(ERROR) << "Could not get JVMTI env or ArtTiEnv!"; 422 return JNI_ERR; 423 } 424 425 jvmtiError error = SetupCapabilities(jvmti); 426 if (error != JVMTI_ERROR_NONE) { 427 LOG(ERROR) << "Unable to set caps"; 428 return JNI_ERR; 429 } 430 431 // Add callbacks and notification. 432 error = jvmti->SetEventCallbacks(&kLogCallbacks, static_cast<jint>(sizeof(kLogCallbacks))); 433 if (error != JVMTI_ERROR_NONE) { 434 LOG(ERROR) << "Unable to set event callbacks."; 435 return JNI_ERR; 436 } 437 error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, 438 JVMTI_EVENT_VM_OBJECT_ALLOC, 439 nullptr /* all threads */); 440 if (error != JVMTI_ERROR_NONE) { 441 LOG(ERROR) << "Unable to enable event " << JVMTI_EVENT_VM_OBJECT_ALLOC; 442 return JNI_ERR; 443 } 444 445 string_table = new UniqueStringTable(); 446 447 return JNI_OK; 448 } 449 450 // Late attachment (e.g. 'am attach-agent'). 451 extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char* options, void* reserved) { 452 return AgentStart(vm, options, reserved); 453 } 454 455 // Early attachment 456 extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) { 457 return AgentStart(jvm, options, reserved); 458 } 459 460 } // namespace tifast 461 462